package pocketknife.internal.codegen.binding; import android.os.Build; import pocketknife.BindArgument; import pocketknife.BundleSerializer; import pocketknife.NotRequired; import pocketknife.PocketKnifeBundleSerializer; import pocketknife.SaveState; import pocketknife.internal.codegen.Access; import pocketknife.internal.codegen.BundleFieldBinding; import pocketknife.internal.codegen.InvalidTypeException; import pocketknife.internal.codegen.KeySpec; import javax.annotation.processing.Messager; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import java.io.PrintWriter; import java.io.StringWriter; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import static pocketknife.internal.GeneratedAdapters.BUNDLE_ADAPTER_SUFFIX; import static pocketknife.internal.codegen.BundleFieldBinding.AnnotationType.SAVE_STATE; import static pocketknife.internal.codegen.BundleFieldBinding.SAVE_STATE_KEY_PREFIX; public class BundleBindingProcessor extends BindingProcessor { public BundleBindingProcessor(Messager messager, Elements elements, Types types) { super(messager, elements, types); } public Map<TypeElement, BundleBindingAdapterGenerator> findAndParseTargets(RoundEnvironment env) { Map<TypeElement, BundleBindingAdapterGenerator> targetClassMap = new LinkedHashMap<TypeElement, BundleBindingAdapterGenerator>(); Set<String> erasedTargetNames = new LinkedHashSet<String>(); // used for parent lookup. // Process each @SaveState for (Element element : env.getElementsAnnotatedWith(SaveState.class)) { SaveState annotation = element.getAnnotation(SaveState.class); if (annotation == null) { continue; } try { parseSaveState(element, targetClassMap, erasedTargetNames); } catch (Exception e) { StringWriter stackTrace = new StringWriter(); e.printStackTrace(new PrintWriter(stackTrace)); error(element, "Unable to generate bundle adapter for @SaveState.\n\n%s", stackTrace); } } // Process each @BindArgument Annotation for (Element element : env.getElementsAnnotatedWith(BindArgument.class)) { BindArgument annotation = element.getAnnotation(BindArgument.class); if (annotation == null) { continue; } try { parseBindAnnotation(element, targetClassMap, erasedTargetNames); } catch (Exception e) { StringWriter stackTrace = new StringWriter(); e.printStackTrace(new PrintWriter(stackTrace)); error(element, "Unable to generate bundle adapter for @BindAnnotation.\n\n%s", stackTrace); } } // Try to find a parent adapter for each adapter for (Map.Entry<TypeElement, BundleBindingAdapterGenerator> entry : targetClassMap.entrySet()) { TypeElement parentElement = findParent(entry.getKey(), erasedTargetNames); if (parentElement != null) { entry.getValue().setParentAdapter(getPackageName(parentElement), parentElement.getSimpleName() + BUNDLE_ADAPTER_SUFFIX); } } return targetClassMap; } private void parseSaveState(Element element, Map<TypeElement, BundleBindingAdapterGenerator> targetClassMap, Set<String> erasedTargetNames) throws ClassNotFoundException, InvalidTypeException { TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Verify that the target has all the appropriate information for type TypeMirror type = element.asType(); if (type instanceof TypeVariable) { TypeVariable typeVariable = (TypeVariable) type; type = typeVariable.getUpperBound(); } validateNotRequiredArguments(element); validateForCodeGeneration(SaveState.class, element); validateBindingPackage(SaveState.class, element); Access access = getAccess(SaveState.class, element, enclosingElement); TypeMirror bundleSerializer = getAnnotationElementClass(element, BundleSerializer.class); validateSerializer(element, BundleSerializer.class, bundleSerializer, PocketKnifeBundleSerializer.class); // Assemble information on the bind point. String name = element.getSimpleName().toString(); String bundleType = null; NotRequired notRequired = element.getAnnotation(NotRequired.class); boolean required = notRequired == null; int minSdk = Build.VERSION_CODES.FROYO; if (!required) { minSdk = notRequired.value(); } boolean canHaveDefault = false; boolean needsToBeCast = false; if (bundleSerializer == null) { bundleType = typeUtil.getBundleType(type); canHaveDefault = !required && canHaveDefault(type, minSdk); needsToBeCast = typeUtil.needToCastBundleType(type); } BundleBindingAdapterGenerator bundleBindingAdapterGenerator = getOrCreateTargetClass(targetClassMap, enclosingElement); BundleFieldBinding binding = new BundleFieldBinding(SAVE_STATE, name, access, type, bundleType, new KeySpec(null, generateKey(SAVE_STATE_KEY_PREFIX, name)), needsToBeCast, canHaveDefault, required, bundleSerializer); bundleBindingAdapterGenerator.addField(binding); // Add the type-erased version to the valid targets set. erasedTargetNames.add(enclosingElement.toString()); } private void parseBindAnnotation(Element element, Map<TypeElement, BundleBindingAdapterGenerator> targetClassMap, Set<String> erasedTargetNames) throws InvalidTypeException { TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Verify that the target has all the appropriate information for type TypeMirror type = element.asType(); if (element instanceof TypeVariable) { TypeVariable typeVariable = (TypeVariable) type; type = typeVariable.getUpperBound(); } validateNotRequiredArguments(element); validateBindingPackage(BindArgument.class, element); validateForCodeGeneration(BindArgument.class, element); Access access = getAccess(BindArgument.class, element, enclosingElement); TypeMirror bundleSerializer = getAnnotationElementClass(element, BundleSerializer.class); validateSerializer(element, BundleSerializer.class, bundleSerializer, PocketKnifeBundleSerializer.class); // Assemble information on the bind point String name = element.getSimpleName().toString(); String bundleType = null; KeySpec key = getKey(element); boolean canHaveDefault = false; boolean needsToBeCast = false; NotRequired notRequired = element.getAnnotation(NotRequired.class); boolean required = notRequired == null; int minSdk = Build.VERSION_CODES.FROYO; if (!required) { minSdk = notRequired.value(); } if (bundleSerializer == null) { bundleType = typeUtil.getBundleType(type); canHaveDefault = !required && canHaveDefault(type, minSdk); needsToBeCast = typeUtil.needToCastBundleType(type); } BundleBindingAdapterGenerator bundleBindingAdapterGenerator = getOrCreateTargetClass(targetClassMap, enclosingElement); BundleFieldBinding binding = new BundleFieldBinding(BundleFieldBinding.AnnotationType.ARGUMENT, name, access, type, bundleType, key, needsToBeCast, canHaveDefault, required, bundleSerializer); bundleBindingAdapterGenerator.orRequired(required); bundleBindingAdapterGenerator.addField(binding); // Add the type-erased version to the valid targets set. erasedTargetNames.add(enclosingElement.toString()); } private KeySpec getKey(Element element) { if (isDefaultAnnotationElement(element, BindArgument.class.getName(), "value")) { return new KeySpec(null, generateKey(BundleFieldBinding.ARGUMENT_KEY_PREFIX, element.getSimpleName().toString())); } return new KeySpec(null, element.getAnnotation(BindArgument.class).value()); } private boolean canHaveDefault(TypeMirror type, int minSdk) { return typeUtil.isPrimitive(type) || minSdk >= Build.VERSION_CODES.HONEYCOMB_MR1 && types.isAssignable(type, typeUtil.charSequenceType); } private BundleBindingAdapterGenerator getOrCreateTargetClass(Map<TypeElement, BundleBindingAdapterGenerator> targetClassMap, TypeElement enclosingElement) { BundleBindingAdapterGenerator bundleBindingAdapterGenerator = targetClassMap.get(enclosingElement); if (bundleBindingAdapterGenerator == null) { TypeMirror targetType = enclosingElement.asType(); String classPackage = getPackageName(enclosingElement); String className = getClassName(enclosingElement, classPackage) + BUNDLE_ADAPTER_SUFFIX; bundleBindingAdapterGenerator = new BundleBindingAdapterGenerator(classPackage, className, targetType, typeUtil); targetClassMap.put(enclosingElement, bundleBindingAdapterGenerator); } return bundleBindingAdapterGenerator; } }