package onactivityresult.compiler; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.NameAllocator; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TreeMap; import javax.annotation.Generated; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PUBLIC; final class ActivityResultClass { private static final String TARGET_VARIABLE_NAME = "t"; private static final String TYPE_VARIABLE_NAME = TARGET_VARIABLE_NAME.toUpperCase(Locale.US); private static final String REQUEST_CODE_PARAMETER_NAME = "requestCode"; private static final String ON_RESULT_METHOD_NAME = "onResult"; private static final String DID_HANDLE_VARIABLE = "didHandle"; private static final ClassName ACTIVITY_ON_RESULT = ClassName.get("onactivityresult.internal", "IOnActivityResult"); private static final ClassName INTENT = ClassName.get("android.content", "Intent"); private static final ClassName INTENT_HELPER = ClassName.get("onactivityresult", "IntentHelper"); private static final Comparator<MethodCall> METHOD_CALL_COMPARATOR = new Comparator<MethodCall>() { @Override public int compare(final MethodCall lhs, final MethodCall rhs) { return lhs.getResultCodes().compareTo(rhs.getResultCodes()); } }; private final ClassName generatedClassName; private final TypeName targetTypeName; private final String superActivityResultClass; private final boolean shouldAddGeneratedAnnotation; private final ActivityResultMethodCalls activityResultCalls = new ActivityResultMethodCalls(); ActivityResultClass(final ClassName generatedClassName, final TypeName targetTypeName, final String superActivityResultClass, final boolean shouldAddGeneratedAnnotation) { this.generatedClassName = generatedClassName; this.targetTypeName = targetTypeName; this.superActivityResultClass = superActivityResultClass; this.shouldAddGeneratedAnnotation = shouldAddGeneratedAnnotation; } JavaFile brewJava() { final TypeSpec.Builder result = TypeSpec.classBuilder(generatedClassName).addModifiers(PUBLIC); result.addTypeVariable(TypeVariableName.get(TYPE_VARIABLE_NAME, targetTypeName)); final TypeVariableName typeVariableName = TypeVariableName.get(TYPE_VARIABLE_NAME); if (superActivityResultClass != null) { result.superclass(ParameterizedTypeName.get(ClassName.bestGuess(superActivityResultClass), typeVariableName)); } else { result.addSuperinterface(ParameterizedTypeName.get(ACTIVITY_ON_RESULT, typeVariableName)); } result.addMethod(this.createOnResultMethod()); if (shouldAddGeneratedAnnotation) { result.addAnnotation(AnnotationSpec.builder(Generated.class).addMember("value", "$S", OnActivityResultProcessor.class.getCanonicalName()).build()); } return JavaFile.builder(generatedClassName.packageName(), result.build()).skipJavaLangImports(true).addFileComment("Generated code from OnActivityResult. Do not modify!").build(); } void add(final MethodCall element, final RequestCode requestCode) { activityResultCalls.add(element, requestCode); } private MethodSpec createOnResultMethod() { final MethodSpec.Builder result = MethodSpec.methodBuilder(ON_RESULT_METHOD_NAME).addAnnotation(Override.class).addModifiers(PUBLIC); result.addParameter(TypeVariableName.get(TYPE_VARIABLE_NAME), TARGET_VARIABLE_NAME, FINAL); result.addParameter(int.class, REQUEST_CODE_PARAMETER_NAME, FINAL); result.addParameter(int.class, Parameter.RESULT_CODE, FINAL).addParameter(INTENT, Parameter.INTENT, FINAL); result.returns(boolean.class); this.createOnResultMethodBody(result); return result.build(); } private void createOnResultMethodBody(final MethodSpec.Builder result) { this.addSuperCallIfNecessaryAndResultVariable(result); this.addRequestCodeControlFlows(result); } private void addSuperCallIfNecessaryAndResultVariable(final MethodSpec.Builder result) { if (superActivityResultClass != null) { result.addStatement("$T $L = super.$L($L, $L, $L, $L)", boolean.class, DID_HANDLE_VARIABLE, ON_RESULT_METHOD_NAME, TARGET_VARIABLE_NAME, REQUEST_CODE_PARAMETER_NAME, Parameter.RESULT_CODE, Parameter.INTENT); } else { result.addStatement("$T $L = false", boolean.class, DID_HANDLE_VARIABLE); } } private void addRequestCodeControlFlows(final MethodSpec.Builder result) { final RequestCode[] requestCodes = activityResultCalls.getRequestCodes(); for (int i = 0; i < requestCodes.length; i++) { final boolean isFirst = i == 0; final RequestCode requestCode = requestCodes[i]; if (isFirst) { result.beginControlFlow("if ($L == $L)", REQUEST_CODE_PARAMETER_NAME, requestCode.requestCode); } else { result.nextControlFlow("else if ($L == $L)", REQUEST_CODE_PARAMETER_NAME, requestCode.requestCode); } final Map<ResultCodes, List<MethodCall>> methodCallsForRequestCode = this.getSortedMethodCallsGroupedByResultCodesFor(requestCode); this.addMethodCallsForResultCodes(result, methodCallsForRequestCode); } result.endControlFlow(); result.addStatement("return $L", DID_HANDLE_VARIABLE); } private Map<ResultCodes, List<MethodCall>> getSortedMethodCallsGroupedByResultCodesFor(final RequestCode requestCode) { final List<MethodCall> methodCallsForRequestCode = activityResultCalls.getMethodCallsForRequestCode(requestCode); Collections.sort(methodCallsForRequestCode, METHOD_CALL_COMPARATOR); final Map<ResultCodes, List<MethodCall>> sortedMethodCallsGroupedByResultCodes = new TreeMap<>(); for (final MethodCall methodCall : methodCallsForRequestCode) { final ResultCodes resultCodes = methodCall.getResultCodes(); final List<MethodCall> methodCalls = sortedMethodCallsGroupedByResultCodes.get(resultCodes); if (methodCalls != null) { methodCalls.add(methodCall); } else { final List<MethodCall> methodCallList = new ArrayList<>(); methodCallList.add(methodCall); sortedMethodCallsGroupedByResultCodes.put(resultCodes, methodCallList); } } return sortedMethodCallsGroupedByResultCodes; } @SuppressWarnings("PMD.CyclomaticComplexity") private void addMethodCallsForResultCodes(final MethodSpec.Builder result, final Map<ResultCodes, List<MethodCall>> sortedMethodCallsGroupedByResultCodes) { final Set<Parameter> existingParameters = new HashSet<>(); boolean isFirstResultCodeIfStatement = true; final Set<Map.Entry<ResultCodes, List<MethodCall>>> entries = sortedMethodCallsGroupedByResultCodes.entrySet(); final Iterator<Map.Entry<ResultCodes, List<MethodCall>>> it = entries.iterator(); final boolean hasOnlyResultCodeFilters = this.hasOnlyResultCodeFilters(entries); while (it.hasNext()) { final Map.Entry<ResultCodes, List<MethodCall>> resultCodesListEntry = it.next(); final ResultCodes resultCodes = resultCodesListEntry.getKey(); final List<MethodCall> methodCalls = resultCodesListEntry.getValue(); final boolean hasResultCodeFilter = resultCodes.size() > 0; final boolean isMethodCallWithoutResultCodeAfterMethodCallWithResultCode = !hasResultCodeFilter && !isFirstResultCodeIfStatement; if (isMethodCallWithoutResultCodeAfterMethodCallWithResultCode) { result.endControlFlow(); existingParameters.clear(); } if (hasResultCodeFilter) { final String ifExpression = resultCodes.getIfExpression(); if (isFirstResultCodeIfStatement) { result.beginControlFlow("if ($L)", ifExpression); isFirstResultCodeIfStatement = false; } else { result.nextControlFlow("else if ($L)", ifExpression); } existingParameters.clear(); } this.addMethodCalls(result, existingParameters, methodCalls); if (hasOnlyResultCodeFilters || !hasResultCodeFilter) { result.addStatement("$L = true", DID_HANDLE_VARIABLE); } final boolean isLast = !it.hasNext(); if (isLast && !isMethodCallWithoutResultCodeAfterMethodCallWithResultCode && !isFirstResultCodeIfStatement) { result.endControlFlow(); } } } private boolean hasOnlyResultCodeFilters(final Set<Map.Entry<ResultCodes, List<MethodCall>>> entries) { for (final Map.Entry<ResultCodes, List<MethodCall>> resultCodesListEntry : entries) { final ResultCodes resultCodes = resultCodesListEntry.getKey(); if (resultCodes.size() == 0) { return false; } } return true; } private void addMethodCalls(final MethodSpec.Builder result, final Set<Parameter> existingParameters, final List<MethodCall> methodCalls) { final NameAllocator nameAllocator = new NameAllocator(); for (final MethodCall methodCall : methodCalls) { final ParameterList parameterList = methodCall.getParameterList(); this.addNecessaryParameterVariables(nameAllocator, result, existingParameters, parameterList); result.addStatement("$L.$L($L)", TARGET_VARIABLE_NAME, methodCall.getMethodName(), parameterList.toString(nameAllocator)); } } @SuppressWarnings("PMD.CyclomaticComplexity") private void addNecessaryParameterVariables(final NameAllocator nameAllocator, final MethodSpec.Builder result, final Set<Parameter> existingParameters, final ParameterList parameterList) { for (final Parameter parameter : parameterList) { final boolean isNotPresent = !existingParameters.contains(parameter); if (isNotPresent) { String parameterName; try { parameterName = nameAllocator.get(parameter.hashCode()); // The only way to know whether a name has already been generated for that hashCode or not } catch (final IllegalArgumentException ignore) { parameterName = nameAllocator.newName(parameter.getName(), parameter.hashCode()); } if (AnnotatedParameter.INTENT_DATA == parameter.annotatedParameter) { result.addStatement("final $T $L = $T.getIntentData$L($L)", AnnotatedParameter.INTENT_DATA.type, parameterName, INTENT_HELPER, parameter.preCondition.getSuffix(), Parameter.INTENT); existingParameters.add(parameter); } else if (AnnotatedParameter.BOOLEAN == parameter.annotatedParameter) { result.addStatement("final $T $L = $T.getExtraBoolean($L, $S, $L)", AnnotatedParameter.BOOLEAN.type, parameterName, INTENT_HELPER, Parameter.INTENT, parameter.getKey(), parameter.getDefaultValue()); existingParameters.add(parameter); } else if (AnnotatedParameter.BYTE == parameter.annotatedParameter) { result.addStatement("final $T $L = $T.getExtraByte($L, $S, (byte) $L)", AnnotatedParameter.BYTE.type, parameterName, INTENT_HELPER, Parameter.INTENT, parameter.getKey(), parameter.getDefaultValue()); existingParameters.add(parameter); } else if (AnnotatedParameter.CHAR == parameter.annotatedParameter) { result.addStatement("final $T $L = $T.getExtraChar($L, $S, (char) $L)", AnnotatedParameter.CHAR.type, parameterName, INTENT_HELPER, Parameter.INTENT, parameter.getKey(), parameter.getDefaultValue()); existingParameters.add(parameter); } else if (AnnotatedParameter.DOUBLE == parameter.annotatedParameter) { result.addStatement("final $T $L = $T.getExtraDouble($L, $S, $L)", AnnotatedParameter.DOUBLE.type, parameterName, INTENT_HELPER, Parameter.INTENT, parameter.getKey(), parameter.getDefaultValue()); existingParameters.add(parameter); } else if (AnnotatedParameter.FLOAT == parameter.annotatedParameter) { result.addStatement("final $T $L = $T.getExtraFloat($L, $S, $Lf)", AnnotatedParameter.FLOAT.type, parameterName, INTENT_HELPER, Parameter.INTENT, parameter.getKey(), parameter.getDefaultValue()); existingParameters.add(parameter); } else if (AnnotatedParameter.INT == parameter.annotatedParameter) { result.addStatement("final $T $L = $T.getExtraInt($L, $S, $L)", AnnotatedParameter.INT.type, parameterName, INTENT_HELPER, Parameter.INTENT, parameter.getKey(), parameter.getDefaultValue()); existingParameters.add(parameter); } else if (AnnotatedParameter.LONG == parameter.annotatedParameter) { result.addStatement("final $T $L = $T.getExtraLong($L, $S, $LL)", AnnotatedParameter.LONG.type, parameterName, INTENT_HELPER, Parameter.INTENT, parameter.getKey(), parameter.getDefaultValue()); existingParameters.add(parameter); } else if (AnnotatedParameter.SHORT == parameter.annotatedParameter) { result.addStatement("final $T $L = $T.getExtraShort($L, $S, (short) $L)", AnnotatedParameter.SHORT.type, parameterName, INTENT_HELPER, Parameter.INTENT, parameter.getKey(), parameter.getDefaultValue()); existingParameters.add(parameter); } else if (AnnotatedParameter.STRING == parameter.annotatedParameter) { result.addStatement("final $T $L = $T.getExtraString($L, $S, $S)", AnnotatedParameter.STRING.type, parameterName, INTENT_HELPER, Parameter.INTENT, parameter.getKey(), parameter.getDefaultValue()); existingParameters.add(parameter); } else if (AnnotatedParameter.CHAR_SEQUENCE == parameter.annotatedParameter) { result.addStatement("final $T $L = $T.getExtraCharSequence($L, $S, $L)", AnnotatedParameter.CHAR_SEQUENCE.type, parameterName, INTENT_HELPER, Parameter.INTENT, parameter.getKey(), parameter.getDefaultValue()); existingParameters.add(parameter); } else if (AnnotatedParameter.BUNDLE == parameter.annotatedParameter) { result.addStatement("final $T $L = $T.getExtraBundle($L, $S, $L)", AnnotatedParameter.BUNDLE.type, parameterName, INTENT_HELPER, Parameter.INTENT, parameter.getKey(), parameter.getDefaultValue()); existingParameters.add(parameter); } else if (AnnotatedParameter.SERIALIZABLE == parameter.annotatedParameter) { result.addStatement("final $T $L = $T.getExtraSerializable($L, $S, $L)", parameter.className, parameterName, INTENT_HELPER, Parameter.INTENT, parameter.getKey(), parameter.getDefaultValue()); existingParameters.add(parameter); } else if (AnnotatedParameter.PARCELABLE == parameter.annotatedParameter) { result.addStatement("final $T $L = $T.getExtraParcelable($L, $S, $L)", parameter.className, parameterName, INTENT_HELPER, Parameter.INTENT, parameter.getKey(), parameter.getDefaultValue()); existingParameters.add(parameter); } } } } }