Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.evernote.android.state.StateProcessor Maven / Gradle / Ivy
package com.evernote.android.state;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import com.squareup.javapoet.WildcardTypeName;
import java.io.Serializable;
import java.io.Writer;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List ;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
@AutoService(Processor.class)
public class StateProcessor extends AbstractProcessor {
private static final Map TYPE_MAPPING = new HashMap() {{
put("boolean" , "Boolean" );
put("boolean[]" , "BooleanArray" );
put("java.lang.Boolean" , "BoxedBoolean" );
put("byte" , "Byte" );
put("byte[]" , "ByteArray" );
put("java.lang.Byte" , "BoxedByte" );
put("char" , "Char" );
put("char[]" , "CharArray" );
put("java.lang.Character" , "BoxedChar" );
put("double" , "Double" );
put("double[]" , "DoubleArray" );
put("java.lang.Double" , "BoxedDouble" );
put("float" , "Float" );
put("float[]" , "FloatArray" );
put("java.lang.Float" , "BoxedFloat" );
put("int" , "Int" );
put("int[]" , "IntArray" );
put("java.lang.Integer" , "BoxedInt" );
put("long" , "Long" );
put("long[]" , "LongArray" );
put("java.lang.Long" , "BoxedLong" );
put("short" , "Short" );
put("short[]" , "ShortArray" );
put("java.lang.Short" , "BoxedShort" );
put("java.lang.CharSequence" , "CharSequence" );
put("java.lang.CharSequence[]" , "CharSequenceArray" );
put("java.lang.String" , "String" );
put("java.lang.String[]" , "StringArray" );
put("java.util.ArrayList " , "CharSequenceArrayList" );
put("java.util.ArrayList " , "IntegerArrayList" );
put("java.util.ArrayList " , "StringArrayList" );
put("android.os.Bundle" , "Bundle" );
put("android.os.Parcelable[]" , "ParcelableArray" );
}};
private static final Comparator COMPARATOR = new Comparator() {
@Override
public int compare(Element o1, Element o2) {
return o1.getSimpleName().toString().compareTo(o2.getSimpleName().toString());
}
};
static final String STATE_SAVER_SUFFIX = "$$StateSaver" ;
private static final String STATE_CLASS_NAME = "com.evernote.android.state.State" ;
private static final String STATE_REFLECTION_CLASS_NAME = "com.evernote.android.state.StateReflection" ;
private static final String INJECTOR_VIEW_CLASS_NAME = "com.evernote.android.state.Injector.View" ;
private static final String INJECTOR_OBJECT_CLASS_NAME = "com.evernote.android.state.Injector.Object" ;
private static final String INJECTION_HELPER_CLASS_NAME = "com.evernote.android.state.InjectionHelper" ;
private static final String OBJECT_CLASS_NAME = Object.class.getName();
private static final String PARCELABLE_CLASS_NAME = "android.os.Parcelable" ;
private static final String PARCELABLE_ARRAY_CLASS_NAME = "android.os.Parcelable[]" ;
private static final String BUNDLE_CLASS_NAME = "android.os.Bundle" ;
private static final String SPARSE_ARRAY_CLASS_NAME = "android.util.SparseArray" ;
private static final String VIEW_CLASS_NAME = "android.view.View" ;
private static final String BUNDLER_CLASS_NAME = "com.evernote.android.state.Bundler" ;
private static final String SERIALIZABLE_CLASS_NAME = Serializable.class.getName();
private static final String ARRAY_LIST_CLASS_NAME = ArrayList.class.getName();
private static final Set GENERIC_SUPER_TYPES = Collections.unmodifiableSet(new HashSet() {{
add(PARCELABLE_CLASS_NAME);
add(SERIALIZABLE_CLASS_NAME);
}});
private static final Set GENERIC_SUPER_TYPE_SERIALIZABLE = Collections.singleton(SERIALIZABLE_CLASS_NAME);
private static final Set IGNORED_TYPE_DECLARATIONS = Collections.unmodifiableSet(new HashSet() {{
add(BUNDLE_CLASS_NAME);
add(String.class.getName());
add(Byte.class.getName());
add(Short.class.getName());
add(Integer.class.getName());
add(Long.class.getName());
add(Float.class.getName());
add(Double.class.getName());
add(Character.class.getName());
add(Boolean.class.getName());
}});
private Types mTypeUtils;
private Elements mElementUtils;
private Filer mFiler;
private Messager mMessager;
private HashMap> mMapGeneratedFileToOriginatingElements = new LinkedHashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mTypeUtils = processingEnv.getTypeUtils();
mElementUtils = processingEnv.getElementUtils();
mFiler = processingEnv.getFiler();
mMessager = processingEnv.getMessager();
}
@Override
public Set getSupportedAnnotationTypes() {
Set annotations = new HashSet<>();
annotations.add(STATE_CLASS_NAME);
annotations.add(STATE_REFLECTION_CLASS_NAME);
return Collections.unmodifiableSet(annotations);
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set annotations, RoundEnvironment env) {
final Set annotatedFields = new HashSet<>();
annotatedFields.addAll(env.getElementsAnnotatedWith(mElementUtils.getTypeElement(STATE_CLASS_NAME)));
annotatedFields.addAll(env.getElementsAnnotatedWith(mElementUtils.getTypeElement(STATE_REFLECTION_CLASS_NAME)));
final Map bundlers = new HashMap<>();
for (Element field : annotatedFields) {
if (field.getModifiers().contains(Modifier.STATIC )) {
mMessager.printMessage(Diagnostic.Kind.ERROR, "Field must not be static" , field);
return true ;
}
if (field.getModifiers().contains(Modifier.FINAL )) {
mMessager.printMessage(Diagnostic.Kind.ERROR, "Field must not be final" , field);
return true ;
}
BundlerWrapper bundlerWrapper = getBundlerWrapper(field);
if (bundlerWrapper == null && TYPE_MAPPING.get(field.asType().toString()) == null ) {
CompatibilityType compatibilityType = getCompatibilityType(field);
if (compatibilityType == null ) {
mMessager.printMessage(Diagnostic.Kind.ERROR, "Don't know how to put " + field.asType() + " into a bundle" , field);
return true ;
}
TYPE_MAPPING.put(field.asType().toString(), compatibilityType.mMapping);
}
if (bundlerWrapper != null ) {
Element privateClass = getPrivateClass(mElementUtils.getTypeElement(bundlerWrapper.mGenericName.toString()));
if (privateClass != null ) {
mMessager.printMessage(Diagnostic.Kind.ERROR, "Class must not be private" , privateClass);
return true ;
}
privateClass = getPrivateClass(mElementUtils.getTypeElement(bundlerWrapper.mBundlerName.toString()));
if (privateClass != null ) {
mMessager.printMessage(Diagnostic.Kind.ERROR, "Class must not be private" , privateClass);
return true ;
}
bundlers.put(field, bundlerWrapper);
}
}
Map> classes = getClassElements(annotatedFields);
Set allClassElements = classes.keySet();
for (Element classElement : allClassElements) {
Element privateClass = getPrivateClass(classElement);
if (privateClass != null ) {
mMessager.printMessage(Diagnostic.Kind.ERROR, "Class must not be private" , privateClass);
return true ;
}
}
for (Element classElement : allClassElements) {
final List fields = sorted(classes.get(classElement));
final Element packageElement = findElement(classElement, ElementKind.PACKAGE);
String packageName = packageElement.asType().toString();
if ("package" .equals(packageName)) {
String packageJackCompiler = findPackageJackCompiler(classElement);
packageName = packageJackCompiler == null ? packageName : packageJackCompiler;
}
final String className = getClassName(classElement);
final boolean isView = isAssignable(classElement, VIEW_CLASS_NAME);
final TypeVariableName genericType = TypeVariableName.get("T" , TypeName.get(eraseGenericIfNecessary(classElement.asType())));
final TypeName superTypeName;
final TypeMirror superType = getSuperType(classElement.asType(), allClassElements);
if (superType == null ) {
superTypeName = ParameterizedTypeName.get(ClassName.bestGuess(isView ? INJECTOR_VIEW_CLASS_NAME : INJECTOR_OBJECT_CLASS_NAME), genericType);
} else {
ClassName rawType = ClassName.bestGuess(eraseGenericIfNecessary(superType).toString() + STATE_SAVER_SUFFIX);
if (!rawType.toString().equals(rawType.reflectionName())) {
rawType = ClassName.bestGuess(rawType.reflectionName());
}
superTypeName = ParameterizedTypeName.get(rawType, genericType);
}
MethodSpec.Builder saveMethodBuilder = MethodSpec.methodBuilder("save" )
.addAnnotation(Override.class)
.addAnnotation(
AnnotationSpec
.builder(SuppressWarnings.class)
.addMember("value" , "$S" , "unchecked" )
.build()
)
.addModifiers(Modifier.PUBLIC )
.addParameter(genericType, "target" );
MethodSpec.Builder restoreMethodBuilder = MethodSpec.methodBuilder("restore" )
.addAnnotation(Override.class)
.addAnnotation(
AnnotationSpec
.builder(SuppressWarnings.class)
.addMember("value" , "$S" , "unchecked" )
.build()
)
.addModifiers(Modifier.PUBLIC )
.addParameter(genericType, "target" );
TypeName bundleTypeName = getTypeName(BUNDLE_CLASS_NAME);
if (isView) {
TypeName parcelableTypeName = getTypeName(PARCELABLE_CLASS_NAME);
saveMethodBuilder = saveMethodBuilder.returns(parcelableTypeName).addParameter(parcelableTypeName, "p" );
restoreMethodBuilder = restoreMethodBuilder.returns(parcelableTypeName).addParameter(parcelableTypeName, "p" );
if (superType != null ) {
saveMethodBuilder = saveMethodBuilder.addStatement("$T state = HELPER.putParent(super.save(target, p))" , bundleTypeName);
} else {
saveMethodBuilder = saveMethodBuilder.addStatement("$T state = HELPER.putParent(p)" , bundleTypeName);
}
restoreMethodBuilder = restoreMethodBuilder.addStatement("$T state = ($T) p" , bundleTypeName, bundleTypeName);
} else {
saveMethodBuilder = saveMethodBuilder.returns(void.class).addParameter(bundleTypeName, "state" );
restoreMethodBuilder = restoreMethodBuilder.returns(void.class).addParameter(bundleTypeName, "state" );
if (superType != null ) {
saveMethodBuilder = saveMethodBuilder.addStatement("super.save(target, state)" );
restoreMethodBuilder = restoreMethodBuilder.addStatement("super.restore(target, state)" );
}
}
CodeBlock.Builder staticInitBlock = CodeBlock.builder();
for (Element field : fields) {
String fieldTypeString = field.asType().toString();
String mapping = TYPE_MAPPING.get(fieldTypeString);
FieldType fieldType = getFieldType(field, false );
if (fieldType == FieldType.NOT_SUPPORTED && isHungarianNotation(field)) {
fieldType = getFieldType(field, true );
}
String fieldName = fieldType.getFieldName(field);
String castedType = null ;
CompatibilityType compatibilityType = getCompatibilityType(field);
if (compatibilityType == CompatibilityType.PARCELABLE_ARRAY && !PARCELABLE_ARRAY_CLASS_NAME.equals(fieldTypeString)) {
mMessager.printMessage(Diagnostic.Kind.NOTE, "Field " + fieldTypeString + " " + PARCELABLE_ARRAY_CLASS_NAME);
castedType = fieldTypeString;
}
BundlerWrapper bundler = bundlers.get(field);
if (bundler != null ) {
staticInitBlock = staticInitBlock.addStatement("BUNDLERS.put($S, new $T())" , fieldName, bundler.mBundlerName);
mapping = "WithBundler" ;
}
switch (fieldType) {
case FIELD:
saveMethodBuilder = saveMethodBuilder.addStatement("HELPER.put$N(state, $S, target.$N)" , mapping, fieldName, fieldName);
if (castedType != null ) {
restoreMethodBuilder = restoreMethodBuilder.addStatement("target.$N = ($N) HELPER.get$N(state, $S)" , fieldName, castedType, mapping, fieldName);
} else {
restoreMethodBuilder = restoreMethodBuilder.addStatement("target.$N = HELPER.get$N(state, $S)" , fieldName, mapping, fieldName);
}
break ;
case BOOLEAN_PROPERTY:
case BOOLEAN_PROPERTY_KOTLIN:
case PROPERTY_HUNGARIAN:
case PROPERTY:
if (fieldType == FieldType.BOOLEAN_PROPERTY || fieldType == FieldType.BOOLEAN_PROPERTY_KOTLIN) {
saveMethodBuilder.addStatement("HELPER.put$N(state, $S, target.$N$N())" , mapping, fieldName, "is" , fieldName);
} else {
saveMethodBuilder.addStatement("HELPER.put$N(state, $S, target.$N$N())" , mapping, fieldName, "get" , fieldName);
}
if (bundler != null ) {
TypeName genericName = bundler.mGenericName;
if (fieldType == FieldType.PROPERTY) {
TypeMirror parameterType = findParameterType(field);
if (parameterType != null ) {
genericName = ClassName.get(parameterType);
}
}
restoreMethodBuilder = restoreMethodBuilder.addStatement("target.set$N(HELPER.<$T>get$N(state, $S))" , fieldName,
genericName, mapping, fieldName);
} else {
InsertedTypeResult insertedType = getInsertedType(field, true );
if (insertedType != null ) {
restoreMethodBuilder = restoreMethodBuilder.addStatement("target.set$N(HELPER.<$T>get$N(state, $S))" ,
fieldName, ClassName.get(insertedType.mTypeMirror), mapping, fieldName);
} else if (castedType != null ) {
restoreMethodBuilder = restoreMethodBuilder.addStatement("target.set$N(($N) HELPER.get$N(state, $S))" ,
fieldName, castedType, mapping, fieldName);
} else {
restoreMethodBuilder = restoreMethodBuilder.addStatement("target.set$N(HELPER.get$N(state, $S))" ,
fieldName, mapping, fieldName);
}
}
break ;
case FIELD_REFLECTION:
String reflectionMapping = isPrimitiveMapping(mapping) ? mapping : "" ;
InsertedTypeResult insertedType = getInsertedType(field, true );
if (compatibilityType != null && insertedType != null ) {
fieldTypeString = compatibilityType.mClass;
}
saveMethodBuilder = saveMethodBuilder
.beginControlFlow("try" )
.addStatement("$T field = target.getClass().getDeclaredField($S)" , Field.class, fieldName)
.addStatement("boolean accessible = field.isAccessible()" )
.beginControlFlow("if (!accessible)" )
.addStatement("field.setAccessible(true)" )
.endControlFlow()
.addStatement("HELPER.put$N(state, $S, ($N) field.get$N(target))" , mapping, fieldName, fieldTypeString, reflectionMapping)
.beginControlFlow("if (!accessible)" )
.addStatement("field.setAccessible(false)" )
.endControlFlow()
.nextControlFlow("catch (Exception e)" )
.addStatement("throw new $T(e)" , RuntimeException.class)
.endControlFlow();
restoreMethodBuilder = restoreMethodBuilder
.beginControlFlow("try" )
.addStatement("$T field = target.getClass().getDeclaredField($S)" , Field.class, fieldName)
.addStatement("boolean accessible = field.isAccessible()" )
.beginControlFlow("if (!accessible)" )
.addStatement("field.setAccessible(true)" )
.endControlFlow()
.addStatement("field.set$N(target, HELPER.get$N(state, $S))" , reflectionMapping, mapping, fieldName)
.beginControlFlow("if (!accessible)" )
.addStatement("field.setAccessible(false)" )
.endControlFlow()
.nextControlFlow("catch (Exception e)" )
.addStatement("throw new $T(e)" , RuntimeException.class)
.endControlFlow();
break ;
case NOT_SUPPORTED:
default :
mMessager.printMessage(Diagnostic.Kind.ERROR, "Field " + field.getSimpleName()
+ " must be either non-private or provide a getter and setter method" , field);
return true ;
}
}
if (isView) {
saveMethodBuilder = saveMethodBuilder.addStatement("return state" );
if (superType != null ) {
restoreMethodBuilder = restoreMethodBuilder.addStatement("return super.restore(target, HELPER.getParent(state))" );
} else {
restoreMethodBuilder = restoreMethodBuilder.addStatement("return HELPER.getParent(state)" );
}
}
TypeName bundlerType = ParameterizedTypeName.get(ClassName.bestGuess(BUNDLER_CLASS_NAME), WildcardTypeName.subtypeOf(Object.class));
TypeName bundlerMap = ParameterizedTypeName.get(ClassName.get(HashMap.class), ClassName.get(String.class), bundlerType);
TypeSpec classBuilder = TypeSpec.classBuilder(className + STATE_SAVER_SUFFIX)
.addModifiers(Modifier.PUBLIC )
.superclass(superTypeName)
.addTypeVariable(genericType)
.addField(
FieldSpec.builder(bundlerMap, "BUNDLERS" )
.addModifiers(Modifier.PRIVATE , Modifier.STATIC , Modifier.FINAL )
.initializer("new $T()" , bundlerMap)
.build()
)
.addStaticBlock(staticInitBlock.build())
.addField(
FieldSpec.builder(getTypeName(INJECTION_HELPER_CLASS_NAME), "HELPER" )
.addModifiers(Modifier.FINAL , Modifier.STATIC , Modifier.PRIVATE )
.initializer("new $T($S, $N)" , getTypeName(INJECTION_HELPER_CLASS_NAME), packageName + '.' + className + STATE_SAVER_SUFFIX, "BUNDLERS" )
.build()
)
.addMethod(saveMethodBuilder.build())
.addMethod(restoreMethodBuilder.build())
.addOriginatingElement(classElement)
.build();
JavaFile javaFile = JavaFile.builder(packageName, classBuilder).build();
if (!writeJavaFile(javaFile)) {
return true ;
}
mMapGeneratedFileToOriginatingElements.put(javaFile.toJavaFileObject().getName(), javaFile.typeSpec.originatingElements);
}
return true ;
}
private boolean writeJavaFile(JavaFile javaFile) {
StringBuilder builder = new StringBuilder();
JavaFileObject filerSourceFile = null ;
try {
builder.append(LICENSE_HEADER);
javaFile.writeTo(builder);
String fileName = javaFile.packageName.isEmpty() ? javaFile.typeSpec.name : javaFile.packageName + "." + javaFile.typeSpec.name;
List originatingElements = javaFile.typeSpec.originatingElements;
filerSourceFile = mFiler.createSourceFile(fileName, originatingElements.toArray(new Element[0 ]));
try (Writer writer = filerSourceFile.openWriter()) {
writer.write(builder.toString());
}
return true ;
} catch (Exception e) {
mMessager.printMessage(Diagnostic.Kind.ERROR, "Couldn't generate classes " + javaFile.packageName + '.' + javaFile.typeSpec.name);
e.printStackTrace();
if (filerSourceFile != null ) {
filerSourceFile.delete();
}
return false ;
}
}
HashMap> getMapGeneratedFileToOriginatingElements() {
return mMapGeneratedFileToOriginatingElements;
}
private Map> getClassElements(Collection annotatedFields) {
Map> result = new HashMap<>();
for (Element field : annotatedFields) {
Element classElement = findElement(field, ElementKind.CLASS);
Set elements = result.get(classElement);
if (elements == null ) {
elements = new HashSet<>();
}
elements.add(field);
result.put(classElement, elements);
}
return result;
}
private Element findElement(Element element, ElementKind kind) {
Element enclosingElement = element.getEnclosingElement();
if (enclosingElement == null ) {
return element;
}
return element.getKind() == kind ? element : findElement(enclosingElement, kind);
}
private String findPackageJackCompiler(Element element) {
Element enclosingElement = element.getEnclosingElement();
if (enclosingElement != null && enclosingElement.getKind() == ElementKind.PACKAGE && element.getKind() == ElementKind.CLASS) {
String className = element.asType().toString();
int index = className.lastIndexOf('.' );
if (index <= 0 ) {
mMessager.printMessage(Diagnostic.Kind.WARNING, "Couldn't find package name with Jack compiler" );
return null ;
}
return className.substring(0 , index);
}
return enclosingElement != null ? findPackageJackCompiler(enclosingElement) : null ;
}
private Element getPrivateClass(Element classElement) {
if (classElement == null || classElement.getKind() != ElementKind.CLASS) {
return null ;
} else if (classElement.getModifiers().contains(Modifier.PRIVATE )) {
return classElement;
} else {
return getPrivateClass(classElement.getEnclosingElement());
}
}
private String getClassName(Element classElement) {
StringBuilder className = new StringBuilder(classElement.getSimpleName().toString());
Element enclosingElement = classElement.getEnclosingElement();
while (enclosingElement != null && enclosingElement.getKind() == ElementKind.CLASS) {
className.insert(0 , enclosingElement.getSimpleName() + "$" );
enclosingElement = enclosingElement.getEnclosingElement();
}
return className.toString();
}
private static boolean isHungarianNotation(Element field) {
String fieldName = field.getSimpleName().toString();
return fieldName.length() >= 2 && fieldName.startsWith("m" ) && Character.isUpperCase(fieldName.charAt(1 ));
}
private static String getPropertyFieldName(Element field, boolean ignoreHungarianNotation) {
String fieldName = field.getSimpleName().toString();
if (!ignoreHungarianNotation && isHungarianNotation(field)) {
fieldName = fieldName.substring(1 );
}
return Character.toUpperCase(fieldName.charAt(0 )) + (fieldName.length() > 1 ? fieldName.substring(1 ) : "" );
}
private TypeMirror findParameterType(Element field) {
TypeMirror result = findParameterType(field, true );
return result != null ? result : findParameterType(field, false );
}
private TypeMirror findParameterType(Element field, boolean ignoreHungarianNotation) {
String fieldName = getPropertyFieldName(field, ignoreHungarianNotation);
String setterName = "set" + fieldName;
List elements = field.getEnclosingElement().getEnclosedElements();
for (Element element : elements) {
if (element.getKind() != ElementKind.METHOD || !(element instanceof ExecutableElement)) {
continue ;
}
String elementName = element.getSimpleName().toString();
if (setterName.equals(elementName) && !element.getModifiers().contains(Modifier.PRIVATE )) {
List parameters = ((ExecutableElement) element).getParameters();
if (parameters == null || parameters.isEmpty()) {
continue ;
}
VariableElement variableElement = parameters.get(0 );
return variableElement.asType();
}
}
return null ;
}
private FieldType getFieldType(Element field, boolean ignoreHungarianNotation) {
if (!field.getModifiers().contains(Modifier.PRIVATE )) {
return FieldType.FIELD;
}
if (useReflection(field)) {
return FieldType.FIELD_REFLECTION;
}
String fieldName = getPropertyFieldName(field, ignoreHungarianNotation);
String getterName = "get" + fieldName;
String isGetterName = "is" + fieldName;
String setterName = "set" + fieldName;
String isGetterNameKotlin = null ;
String setterNameKotlin = null ;
if (fieldName.length() > 2 && fieldName.startsWith("Is" ) && Character.isUpperCase(fieldName.charAt(2 ))) {
String substring = fieldName.substring(2 );
isGetterNameKotlin = "is" + substring;
setterNameKotlin = "set" + substring;
}
List elements = field.getEnclosingElement().getEnclosedElements();
boolean hasGetter = false ;
boolean hasIsGetter = false ;
boolean hasSetter = false ;
boolean isKotlinBooleanProperty = false ;
for (Element element : elements) {
if (element.getKind() != ElementKind.METHOD) {
continue ;
}
String elementName = element.getSimpleName().toString();
if (!hasGetter && getterName.equals(elementName) && !element.getModifiers().contains(Modifier.PRIVATE )) {
hasGetter = true ;
if (hasSetter) {
break ;
}
}
if (!hasIsGetter && isGetterName.equals(elementName) && !element.getModifiers().contains(Modifier.PRIVATE )) {
hasIsGetter = true ;
if (hasSetter) {
break ;
}
}
if (!hasGetter && isGetterNameKotlin != null && isGetterNameKotlin.equals(elementName) && !element.getModifiers().contains(Modifier.PRIVATE )) {
hasIsGetter = true ;
isKotlinBooleanProperty = true ;
if (hasSetter) {
break ;
}
}
if (!hasSetter && setterName.equals(elementName) && !element.getModifiers().contains(Modifier.PRIVATE )) {
hasSetter = true ;
if (hasGetter) {
break ;
}
}
if (!hasSetter && setterNameKotlin != null && setterNameKotlin.equals(elementName) && !element.getModifiers().contains(Modifier.PRIVATE )) {
hasSetter = true ;
if (hasGetter) {
break ;
}
}
}
if (isKotlinBooleanProperty && hasSetter) {
return FieldType.BOOLEAN_PROPERTY_KOTLIN;
} else if (hasIsGetter && hasSetter) {
return FieldType.BOOLEAN_PROPERTY;
} else if (hasGetter && hasSetter) {
return ignoreHungarianNotation ? FieldType.PROPERTY_HUNGARIAN : FieldType.PROPERTY;
} else {
return FieldType.NOT_SUPPORTED;
}
}
private enum FieldType {
FIELD, FIELD_REFLECTION, PROPERTY, BOOLEAN_PROPERTY, BOOLEAN_PROPERTY_KOTLIN, PROPERTY_HUNGARIAN, NOT_SUPPORTED;
public String getFieldName(Element field) {
switch (this) {
case FIELD:
case FIELD_REFLECTION:
return field.getSimpleName().toString();
case PROPERTY:
case BOOLEAN_PROPERTY:
return getPropertyFieldName(field, false );
case PROPERTY_HUNGARIAN:
return getPropertyFieldName(field, true );
case BOOLEAN_PROPERTY_KOTLIN:
return field.getSimpleName().toString().substring(2 );
case NOT_SUPPORTED:
default :
return null ;
}
}
}
private BundlerWrapper getBundlerWrapper(Element field) {
for (AnnotationMirror annotationMirror : field.getAnnotationMirrors()) {
if (!isStateAnnotation(annotationMirror)) {
continue ;
}
Map elementValues = annotationMirror.getElementValues();
for (ExecutableElement executableElement : elementValues.keySet()) {
if ("value" .equals(executableElement.getSimpleName().toString())) {
Object value = elementValues.get(executableElement).getValue();
if (value == null ) {
continue ;
}
TypeName bundlerName = ClassName.get(mElementUtils.getTypeElement(value.toString()));
TypeName genericName = null ;
try {
List interfaces = mElementUtils.getTypeElement(value.toString()).getInterfaces();
for (TypeMirror anInterface : interfaces) {
if (isAssignable(mTypeUtils.erasure(anInterface), BUNDLER_CLASS_NAME)) {
List typeArguments = ((DeclaredType) anInterface).getTypeArguments();
if (typeArguments != null && typeArguments.size() >= 1 ) {
TypeMirror genericTypeMirror = typeArguments.get(0 );
String genericString = genericTypeMirror.toString();
if (genericString.contains(" for parcelable list bundler
if (PARCELABLE_CLASS_NAME.equals(innerType)) {
InsertedTypeResult insertedType = getInsertedType(field, true);
if (insertedType != null) {
innerType = insertedType.mTypeMirror.toString();
}
}
ClassName erasureClassName = ClassName.bestGuess(mTypeUtils.erasure(genericTypeMirror).toString());
genericName = ParameterizedTypeName.get(erasureClassName, ClassName.bestGuess(innerType));
} else {
genericName = ClassName.get(genericTypeMirror);
}
}
}
}
} catch (Exception ignored) {
}
return new BundlerWrapper(bundlerName, genericName == null ? ClassName.get(Object.class) : genericName);
}
}
}
return null;
}
private boolean useReflection(Element field) {
for (AnnotationMirror annotationMirror : field.getAnnotationMirrors()) {
if (STATE_REFLECTION_CLASS_NAME.equals(annotationMirror.getAnnotationType().toString())) {
return true;
}
}
return false;
}
private boolean isPrimitiveMapping(String mapping) {
switch (mapping) {
case " Int":
case " Short":
case " Byte":
case " Long":
case " Char":
case " Boolean":
case " Double":
case " Float":
return true;
default:
return false;
}
}
private static final class BundlerWrapper {
final TypeName mBundlerName;
final TypeName mGenericName;
private BundlerWrapper(TypeName bundlerName, TypeName genericName) {
mBundlerName = bundlerName;
mGenericName = genericName;
}
}
@SuppressWarnings(" unused")
private enum CompatibilityType {
PARCELABLE(" Parcelable", PARCELABLE_CLASS_NAME, null),
PARCELABLE_ARRAY(" ParcelableArray", PARCELABLE_ARRAY_CLASS_NAME, null),
PARCELABLE_LIST(" ParcelableArrayList", ARRAY_LIST_CLASS_NAME, PARCELABLE_CLASS_NAME),
SPARSE_PARCELABLE_ARRAY(" SparseParcelableArray", SPARSE_ARRAY_CLASS_NAME, PARCELABLE_CLASS_NAME),
SERIALIZABLE(" Serializable", SERIALIZABLE_CLASS_NAME, null);
final String mMapping;
final String mClass;
final String mGenericClass;
CompatibilityType(String mapping, String clazz, String genericClass) {
mMapping = mapping;
mClass = clazz;
mGenericClass = genericClass;
}
}
private CompatibilityType getCompatibilityType(Element field) {
TypeMirror typeMirror = field.asType();
for (CompatibilityType compatibilityType : CompatibilityType.values()) {
if (compatibilityType == CompatibilityType.PARCELABLE_ARRAY) {
TypeMirror arrayType = getArrayType(field);
if (arrayType != null && isAssignable(arrayType, PARCELABLE_CLASS_NAME)) {
return CompatibilityType.PARCELABLE_ARRAY;
}
} else if (compatibilityType.mGenericClass == null) {
if (isAssignable(typeMirror, compatibilityType.mClass)) {
return compatibilityType;
}
} else if (typeMirror.getKind() != TypeKind.WILDCARD) {
if (isAssignable(mTypeUtils.erasure(typeMirror), compatibilityType.mClass)) {
List typeArguments = ((DeclaredType) typeMirror).getTypeArguments();
if (typeArguments != null && typeArguments.size() >= 1 && isAssignable(typeArguments.get(0), compatibilityType.mGenericClass)) {
return compatibilityType;
}
}
}
}
return null;
}
private TypeMirror getArrayType(Element field) {
TypeMirror typeMirror = field.asType();
if (typeMirror instanceof ArrayType) {
return ((ArrayType) typeMirror).getComponentType();
} else {
return null;
}
}
@SuppressWarnings(" SameParameterValue")
private boolean isAssignable(Element element, String className) {
return isAssignable(element.asType(), className);
}
private boolean isAssignable(TypeMirror typeMirror, String className) {
return mTypeUtils.isAssignable(typeMirror, mElementUtils.getTypeElement(className).asType());
}
private static List sorted(Collection collection) {
List result = new ArrayList<>(collection);
Collections.sort(result, COMPARATOR);
return result;
}
private TypeMirror eraseGenericIfNecessary(TypeMirror typeMirror) {
// is there a better way to detect a generic type?
String[] split = typeMirror.toString().split(" \\.");
boolean hasGeneric = false;
for (String className : split) {
if (className.endsWith(" >")) {
hasGeneric = true;
break;
}
}
return hasGeneric ? mTypeUtils.erasure(typeMirror) : typeMirror;
}
private TypeMirror eraseCovarianceAndInvariance(TypeMirror typeMirror) {
String string = typeMirror.toString();
if (string.startsWith(" ? extends") || string.startsWith(" ? super")) {
return mTypeUtils.erasure(typeMirror);
} else {
return typeMirror;
}
}
private TypeMirror getSuperType(TypeMirror classElement, Set allClassElements) {
List typeMirrors = mTypeUtils.directSupertypes(classElement);
while (typeMirrors != null && !typeMirrors.isEmpty()) {
TypeMirror superClass = typeMirrors.get(0); // interfaces are at the end
if (OBJECT_CLASS_NAME.equals(superClass.toString())) {
break;
}
if (allClassElements.contains(mTypeUtils.asElement(superClass))) {
return superClass;
}
typeMirrors = mTypeUtils.directSupertypes(superClass);
}
return null;
}
private InsertedTypeResult getInsertedType(Element field, @SuppressWarnings(" SameParameterValue") boolean checkIgnoredTypes) {
if (field == null) {
return null;
}
return getInsertedType(field.asType(), checkIgnoredTypes);
}
private InsertedTypeResult getInsertedType(TypeMirror fieldType, boolean checkIgnoredTypes) {
fieldType = eraseCovarianceAndInvariance(fieldType);
TypeElement classElement = mElementUtils.getTypeElement(eraseGenericIfNecessary(fieldType).toString());
List superTypes = classElement == null ? null : mTypeUtils.directSupertypes(classElement.asType());
if (fieldType instanceof DeclaredType) {
List typeArguments = ((DeclaredType) fieldType).getTypeArguments();
if (typeArguments != null && !typeArguments.isEmpty()) {
// generic type, return the generic value
if (superTypes != null && isSuperType(superTypes, GENERIC_SUPER_TYPES) && !fieldType.toString().startsWith(ArrayList.class.getName())) {
// if this is generic Parcelable or Serializable, then use the type, ignore ArrayList, which also implements Serializable
return new InsertedTypeResult(fieldType, isSuperType(superTypes, GENERIC_SUPER_TYPE_SERIALIZABLE));
}
InsertedTypeResult insertedType = getInsertedType(typeArguments.get(0), false);
if (insertedType != null && insertedType.mSerializable && fieldType.toString().startsWith(ArrayList.class.getName())) {
// because ArrayList was excluded above we need to check again, if it's an ArrayList containing Serializable, then
// just serialize the ArrayList
return new InsertedTypeResult(fieldType, true);
}
return insertedType;
}
}
if (classElement == null || OBJECT_CLASS_NAME.equals(classElement.toString())) {
return null;
}
if (checkIgnoredTypes && IGNORED_TYPE_DECLARATIONS.contains(classElement.toString())) {
return null;
}
if (superTypes != null) {
if (isSuperType(superTypes, GENERIC_SUPER_TYPES)) {
// either instance of Serializable or Parcelable
return new InsertedTypeResult(fieldType, isSuperType(superTypes, GENERIC_SUPER_TYPE_SERIALIZABLE));
}
for (TypeMirror superType : superTypes) {
InsertedTypeResult result = getInsertedType(eraseGenericIfNecessary(superType), checkIgnoredTypes);
if (result != null) {
// always return the passed in type and not any super type
return new InsertedTypeResult(fieldType, result.mSerializable);
}
}
}
return null;
}
private boolean isSuperType(List typeMirrors, Collection superTypes) {
for (TypeMirror superType : typeMirrors) {
if (superTypes.contains(superType.toString())) {
return true;
}
}
return false;
}
private boolean isStateAnnotation(AnnotationMirror annotationMirror) {
String string = annotationMirror.getAnnotationType().toString();
return STATE_CLASS_NAME.equals(string) || STATE_REFLECTION_CLASS_NAME.equals(string);
}
private TypeName getTypeName(String className) {
return TypeName.get(mElementUtils.getTypeElement(className).asType());
}
private static final class InsertedTypeResult {
private final TypeMirror mTypeMirror;
private final boolean mSerializable;
private InsertedTypeResult(TypeMirror typeMirror, boolean serializable) {
mTypeMirror = typeMirror;
mSerializable = serializable;
}
}
private static final String LICENSE_HEADER = " \n";
}