package fit.compiler; import com.google.auto.common.SuperficialValidation; import com.google.auto.service.AutoService; import com.squareup.javapoet.ClassName; 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 fit.PreferenceIgnore; import fit.SharedPreferenceAble; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.util.Collections; import java.util.HashSet; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Filer; 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.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Modifier; import javax.lang.model.element.Name; import javax.lang.model.element.NestingKind; import javax.lang.model.element.TypeElement; import javax.lang.model.type.ExecutableType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.tools.Diagnostic; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PUBLIC; @AutoService(Processor.class) public class FitProcessor extends AbstractProcessor { private static final ClassName MM = ClassName.get("fit", "MM"); private static final ClassName CONTEXT = ClassName.get("android.content", "Context"); private static final ClassName SHARED_PREFERENCES = ClassName.get("android.content", "SharedPreferences"); private static final ClassName SHARED_PREFERENCES_EDITOR = ClassName.get("android.content.SharedPreferences", "Editor"); private static final ClassName UTILS = ClassName.get("fit.internal", "Utils"); private static final ClassName FILE_OBJECT_UTIL = ClassName.get("fit.internal", "FileObjectUtil"); private static final ClassName STRING = ClassName.get("java.lang", "String"); //Set<String> TypeName stringTypeName = TypeName.get(String.class); ClassName set = ClassName.get("java.util", "Set"); ClassName hashSet = ClassName.get("java.util", "HashSet"); TypeName setOfHoverboards = ParameterizedTypeName.get(set, stringTypeName); TypeName hashSetOfHoverboards = ParameterizedTypeName.get(hashSet, stringTypeName); private static final String METHOD_GET_STRING = "getString"; private static final String METHOD_GET_Int = "getInt"; private Elements elementUtils; private Filer filer; @Override public synchronized void init(ProcessingEnvironment env) { super.init(env); elementUtils = env.getElementUtils(); //typeUtils = env.getTypeUtils(); filer = env.getFiler(); //try { // trees = Trees.instance(processingEnv); //} catch (IllegalArgumentException ignored) { //} } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { // Process each @SharedPreferenceAble element. for (Element element : roundEnv.getElementsAnnotatedWith(SharedPreferenceAble.class)) { if (!SuperficialValidation.validateElement(element)) continue; // we don't SuperficialValidation.validateElement(element) // so that an unresolved View type can be generated by later processing rounds TypeElement enclosingElement = (TypeElement) element; if (enclosingElement.getKind() != ElementKind.CLASS) { throw new RuntimeException("Fit only use class"); } //remove inner class if (enclosingElement.getNestingKind() != NestingKind.TOP_LEVEL) { throw new RuntimeException("Fit can't use Inner class"); } // Assemble information on the field. TypeName targetType = TypeName.get(enclosingElement.asType()); if (targetType instanceof ParameterizedTypeName) { targetType = ((ParameterizedTypeName) targetType).rawType; } String packageName = getPackageName(enclosingElement); try { String className = getClassName(enclosingElement, packageName); ClassName preferenceClassName = ClassName.get(packageName, className + "_Preference"); boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL); boolean hasNonParaConstructor = false; Set<Element> fieldElements = new HashSet<>(); Set<Element> privateFieldElements = new HashSet<>(); Set<Element> suspectedGetterElements = new HashSet<>(); Set<Element> suspectedSetterElements = new HashSet<>(); for (Element memberElement : elementUtils.getAllMembers(enclosingElement)) { Set<Modifier> modifiers = memberElement.getModifiers(); //add not static /private field final ElementKind kind = memberElement.getKind(); if (modifiers.contains(Modifier.STATIC)) { continue; } if (kind == ElementKind.FIELD && !modifiers.contains(Modifier.TRANSIENT)) { //ignore field if (null != memberElement.getAnnotation(PreferenceIgnore.class)) { continue; } if (modifiers.contains(Modifier.PRIVATE)) { privateFieldElements.add(memberElement); } else { fieldElements.add(memberElement); } continue; } else if (kind == ElementKind.METHOD && !modifiers.contains(Modifier.PRIVATE)) { Name methodName = memberElement.getSimpleName(); if ((methodName.contentEquals("getMetaClass") && memberElement.asType() .toString() .equals("()groovy.lang.MetaClass")) || methodName.contentEquals("getClass")) { continue; } if (isGetter(memberElement)) { suspectedGetterElements.add(memberElement); continue; } if (isSetter(memberElement)) { suspectedSetterElements.add(memberElement); continue; } } else if (memberElement.getKind() == ElementKind.CONSTRUCTOR && memberElement.toString() .equals(className + "()") && !modifiers.contains(Modifier.PRIVATE)) { hasNonParaConstructor = true; } } //移除重复属性 Set<Element> rep = new HashSet<>(); for (Element field : fieldElements) { if (privateFieldElements.isEmpty()) { break; } for (Element privateField : privateFieldElements) { if (privateField.getSimpleName().equals(field.getSimpleName())) { rep.add(field); break; } } } fieldElements.removeAll(rep); if (!hasNonParaConstructor) { throw new RuntimeException("Fit can't use no non-parameter constructor"); } Set<fit.compiler.PropertyDescriptor> getterPropertyDescriptors = new HashSet<>(); Set<fit.compiler.PropertyDescriptor> setterPropertyDescriptors = new HashSet<>(); //过滤getter Set<Element> getterElements = new HashSet<>(); for (Element method : suspectedGetterElements) { String methodName = method.getSimpleName().toString(); final String propertyName = methodName.startsWith("is") ? methodName.substring(2).toLowerCase() : methodName.substring(3).toLowerCase(); for (Element field : privateFieldElements) { if (field.getSimpleName().toString().equalsIgnoreCase(propertyName)) { getterElements.add(method); fit.compiler.PropertyDescriptor propertyDescriptor = new fit.compiler.PropertyDescriptor(); propertyDescriptor.setField(field); propertyDescriptor.setGetter(method); getterPropertyDescriptors.add(propertyDescriptor); break; } } } //过滤setter Set<Element> setterElements = new HashSet<>(); for (Element method : suspectedSetterElements) { String methodName = method.getSimpleName().toString(); final String propertyName = methodName.substring(3).toLowerCase(); for (Element field : privateFieldElements) { if (field.getSimpleName().toString().equalsIgnoreCase(propertyName)) { setterElements.add(method); fit.compiler.PropertyDescriptor propertyDescriptor = new fit.compiler.PropertyDescriptor(); propertyDescriptor.setField(field); propertyDescriptor.setSetter(method); setterPropertyDescriptors.add(propertyDescriptor); break; } } } JavaFile javaFile = JavaFile.builder(preferenceClassName.packageName(), createPreferenceClass(preferenceClassName, isFinal, targetType, fieldElements, getterPropertyDescriptors, setterPropertyDescriptors)) .addFileComment("Generated code from Fit. Do not modify!") .build(); javaFile.writeTo(filer); } catch (Exception e) { logParsingError(element, SharedPreferenceAble.class, e); } } return false; } private boolean isGetter(Element method) { Name methodName = method.getSimpleName(); if ((!methodName.toString().startsWith("get")) && !methodName.toString().startsWith("is")) { return false; } ExecutableType type = (ExecutableType) method.asType(); //返回值为void if (TypeKind.VOID.equals(type.getReturnType().getKind())) { return false; } //有参数 if (type.getParameterTypes().size() > 0) { return false; } if (methodName.length() < 4) { return false; } return true; } private boolean isSetter(Element method) { Name methodName = method.getSimpleName(); if (!methodName.toString().startsWith("set")) { return false; } ExecutableType type = (ExecutableType) method.asType(); //返回值不为void if (!TypeKind.VOID.equals(type.getReturnType().getKind())) { return false; } //有1个参数 if (type.getParameterTypes().size() != 1) { return false; } if (methodName.length() < 4) { return false; } return true; } private String getPackageName(TypeElement type) { return elementUtils.getPackageOf(type).getQualifiedName().toString(); } private static String getClassName(TypeElement type, String packageName) { int packageLen = packageName.length() + 1; return type.getQualifiedName().toString().substring(packageLen).replace('.', '$'); } private TypeSpec createPreferenceClass(ClassName preferenceClassName, boolean isFinal, TypeName targetTypeName, Set<Element> fieldElements, Set<fit.compiler.PropertyDescriptor> getterPropertyDescriptors, Set<fit.compiler.PropertyDescriptor> setterPropertyDescriptors) { TypeSpec.Builder result = TypeSpec.classBuilder(preferenceClassName.simpleName()).addModifiers(PUBLIC); if (isFinal) { result.addModifiers(FINAL); } ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(MM, targetTypeName); result.addSuperinterface(parameterizedTypeName); result.addMethod( createPreferenceSaveMethod(targetTypeName, fieldElements, getterPropertyDescriptors)); result.addMethod( createPreferenceGetMethod(targetTypeName, fieldElements, setterPropertyDescriptors)); result.addMethod(createPreferenceClearFieldsMethod(fieldElements, getterPropertyDescriptors)); return result.build(); } private MethodSpec createPreferenceSaveMethod(TypeName targetType, Set<Element> fieldElements, Set<fit.compiler.PropertyDescriptor> getterPropertyDescriptors) { MethodSpec.Builder result = MethodSpec.methodBuilder("save") .returns(SHARED_PREFERENCES_EDITOR) .addAnnotation(Override.class) .addModifiers(PUBLIC) .addParameter(CONTEXT, "context") .addParameter(STRING, "name") .addParameter(targetType, "obj") .addStatement( "SharedPreferences.Editor editor = $T.getSharedPreferenceEditor(context, name)", UTILS); //属性 for (Element element : fieldElements) { TypeName typeName = TypeName.get(element.asType()); String putMethod = genPutMethod(typeName); String valueL = element.getSimpleName().toString(); result = genSaveCode(result, typeName, putMethod, valueL, element.getSimpleName().toString()); } //getter for (fit.compiler.PropertyDescriptor propertyDescriptor : getterPropertyDescriptors) { Element method = propertyDescriptor.getGetter(); TypeMirror typeMirror = ((ExecutableType) method.asType()).getReturnType(); TypeName typeName = TypeName.get(typeMirror); String putMethod = genPutMethod(typeName); String valueL = method.toString(); result = genSaveCode(result, typeName, putMethod, valueL, propertyDescriptor.getField().getSimpleName().toString()); } result = result.addStatement("return editor"); return result.build(); } private String genPutMethod(TypeName fieldTypeName) { TypeName unboxFieldTypeName = unbox(fieldTypeName); String putMethod = ""; if (stringTypeName.equals(unboxFieldTypeName)) { putMethod = "putString"; } else if (TypeName.BOOLEAN.equals(unboxFieldTypeName)) { putMethod = "putBoolean"; } else if (TypeName.FLOAT.equals(unboxFieldTypeName)) { putMethod = "putFloat"; } else if (TypeName.INT.equals(unboxFieldTypeName) || TypeName.BYTE.equals(unboxFieldTypeName) || TypeName.SHORT.equals(unboxFieldTypeName) || TypeName.CHAR.equals(unboxFieldTypeName)) { putMethod = "putInt"; } else if (TypeName.LONG.equals(unboxFieldTypeName)) { putMethod = "putLong"; } else if (TypeName.DOUBLE.equals(unboxFieldTypeName)) { putMethod = "putLong"; } else if (setOfHoverboards.equals(unboxFieldTypeName) || hashSetOfHoverboards.equals( unboxFieldTypeName)) { putMethod = "putStringSet"; } return putMethod; } private MethodSpec.Builder genSaveCode(MethodSpec.Builder builder, TypeName typeName, String putMethod, String valueL, String propertyName) { TypeName unboxFieldTypeName = unbox(typeName); if (TypeName.DOUBLE.equals(typeName)) { valueL = "Double.doubleToLongBits( obj." + valueL + ")"; builder.addStatement("editor.$L($S, " + valueL + ")", putMethod, propertyName); return builder; } else if (setOfHoverboards.equals(unboxFieldTypeName) || hashSetOfHoverboards.equals( unboxFieldTypeName)) { builder.addStatement("$T.$L($L, $S, obj." + valueL + ")", UTILS, putMethod, "editor", propertyName); return builder; } if (typeName.isBoxedPrimitive()) { if (TypeName.DOUBLE.equals(unboxFieldTypeName)) { builder.addStatement( "editor.$L($S, Double.doubleToLongBits($T.checkNonNull(obj.$L) ? obj.$L : 0))", putMethod, propertyName, UTILS, propertyName, propertyName); } else if (TypeName.CHAR.equals(unboxFieldTypeName) || TypeName.BYTE.equals( unboxFieldTypeName) || TypeName.SHORT.equals(unboxFieldTypeName) || TypeName.INT.equals( unboxFieldTypeName) || TypeName.LONG.equals(unboxFieldTypeName) || TypeName.FLOAT.equals( unboxFieldTypeName)) { builder.addStatement( "editor.$L($S, $T.checkNonNull( obj." + valueL + ") ? obj." + valueL + " : 0)", putMethod, propertyName, UTILS); } else if (TypeName.BOOLEAN.equals(unboxFieldTypeName)) { builder.addStatement("editor.$L($S, $T.checkNonNull(obj.$L) ? obj.$L : false)", putMethod, propertyName, UTILS, propertyName, propertyName); } } else { if (stringTypeName.equals(unboxFieldTypeName) || typeName.isPrimitive()) { builder.addStatement("editor.$L($S, obj." + valueL + ")", putMethod, propertyName); } else { builder.addStatement("$T.writeObject(context, name + $S, obj.$L)", FILE_OBJECT_UTIL, "." + propertyName, valueL); } } return builder; } private MethodSpec createPreferenceGetMethod(TypeName targetType, Set<Element> fieldElements, Set<fit.compiler.PropertyDescriptor> setterPropertyDescriptors) { MethodSpec.Builder result = MethodSpec.methodBuilder("get") .addAnnotation(Override.class) .addModifiers(PUBLIC) .addParameter(CONTEXT, "context") .addParameter(STRING, "name"); result.addStatement( "$T sharedPreferences = context.getSharedPreferences(name, Context.MODE_PRIVATE)", SHARED_PREFERENCES); result.addStatement("$T obj = new $T()", targetType, targetType); for (Element element : fieldElements) { genGetCode(false, result, element.asType(), element.getSimpleName().toString(), "obj.$N"); } //setter for (fit.compiler.PropertyDescriptor propertyDescriptor : setterPropertyDescriptors) { Element method = propertyDescriptor.getSetter(); TypeMirror typeMirror = ((ExecutableType) method.asType()).getParameterTypes().get(0); genGetCode(true, result, typeMirror, propertyDescriptor.getField().getSimpleName().toString(), "obj." + method.getSimpleName() + "("); } result.addStatement("return obj").returns(targetType); return result.build(); } private MethodSpec.Builder genGetCode(boolean isSetter, MethodSpec.Builder builder, TypeMirror typeMirror, String propertyName, String assignment) { TypeName fieldTypeName = unbox(TypeName.get(typeMirror)); String method; String defaultValue = "0"; String cast = ""; String value = "$L sharedPreferences.$L($S, $L)"; if (stringTypeName.equals(fieldTypeName)) { method = METHOD_GET_STRING; defaultValue = null; } else if (TypeName.BOOLEAN.equals(fieldTypeName)) { method = "getBoolean"; defaultValue = "false"; } else if (TypeName.FLOAT.equals(fieldTypeName)) { method = "getFloat"; } else if (TypeName.INT.equals(fieldTypeName)) { method = METHOD_GET_Int; } else if (TypeName.BYTE.equals(fieldTypeName)) { method = METHOD_GET_Int; value = "($L) sharedPreferences.$L($S, $L)"; cast = "byte"; } else if (TypeName.SHORT.equals(fieldTypeName)) { method = METHOD_GET_Int; value = "($L) sharedPreferences.$L($S, $L)"; cast = "short"; } else if (TypeName.CHAR.equals(fieldTypeName)) { method = METHOD_GET_Int; value = "($L) sharedPreferences.$L($S, $L)"; cast = "char"; } else if (TypeName.LONG.equals(fieldTypeName)) { method = "getLong"; } else if (TypeName.DOUBLE.equals(fieldTypeName)) { method = "getLong"; value = "$L Double.longBitsToDouble(sharedPreferences.$L($S, $L))"; } else if (setOfHoverboards.equals(fieldTypeName) || hashSetOfHoverboards.equals( fieldTypeName)) { defaultValue = null; value = "($T) $T.getStringSet($L, $S, $L)"; if (isSetter) { return builder.addStatement(assignment + value + ")", hashSetOfHoverboards, UTILS, "sharedPreferences", propertyName, defaultValue); } return builder.addStatement(assignment + " = " + value, propertyName, hashSetOfHoverboards, UTILS, "sharedPreferences", propertyName, defaultValue); } else { if (isSetter) { builder.addStatement(assignment + "($T) $T.readObject(context, name + $S))", fieldTypeName, FILE_OBJECT_UTIL, "." + propertyName); } else { builder.addStatement(assignment + " = ($T) $T.readObject(context, name + $S)", propertyName, fieldTypeName, FILE_OBJECT_UTIL, "." + propertyName); } return builder; } if (isSetter) { return builder.addStatement(assignment + value + ")", cast, method, propertyName, defaultValue); } return builder.addStatement(assignment + " = " + value, propertyName, cast, method, propertyName, defaultValue); } private MethodSpec createPreferenceClearFieldsMethod(Set<Element> fieldElements, Set<fit.compiler.PropertyDescriptor> getterPropertyDescriptors) { MethodSpec.Builder result = MethodSpec.methodBuilder("clearFields") .addAnnotation(Override.class) .addModifiers(PUBLIC) .addParameter(CONTEXT, "context") .addParameter(STRING, "name"); //属性 for (Element element : fieldElements) { TypeName typeName = TypeName.get(element.asType()); if (isObject(typeName)) { result.addStatement("$T.deleteFile(context, name + $S)", FILE_OBJECT_UTIL, "." + element.getSimpleName().toString()); } } //getter for (fit.compiler.PropertyDescriptor propertyDescriptor : getterPropertyDescriptors) { Element element = propertyDescriptor.getGetter(); TypeMirror typeMirror = ((ExecutableType) element.asType()).getReturnType(); TypeName typeName = TypeName.get(typeMirror); String valueL = element.toString(); if (isObject(typeName)) { result.addStatement("$T.deleteFile(context, name + $S)", FILE_OBJECT_UTIL, "." + propertyDescriptor.getField().getSimpleName().toString()); } } return result.build(); } /** * @param typeName {@link Type} * @return object is true,otherwise false * @since 1.0.1 */ private boolean isObject(TypeName typeName) { typeName = typeName.box(); return !(typeName.isBoxedPrimitive() || stringTypeName.equals(typeName) || setOfHoverboards.equals(typeName) || hashSetOfHoverboards.equals(typeName)); } private TypeName unbox(TypeName typeName) { if (typeName.isBoxedPrimitive()) { return typeName.unbox(); } return typeName; } private void logParsingError(Element element, Class<? extends Annotation> annotation, Exception e) { StringWriter stackTrace = new StringWriter(); e.printStackTrace(new PrintWriter(stackTrace)); error(element, "Unable to parse @%s shared.\n\n%s", annotation.getSimpleName(), stackTrace); } private void error(Element element, String message, Object... args) { printMessage(Diagnostic.Kind.ERROR, element, message, args); } private void note(Element element, String message, Object... args) { printMessage(Diagnostic.Kind.NOTE, element, message, args); } private void printMessage(Diagnostic.Kind kind, Element element, String message, Object[] args) { if (args.length > 0) { message = String.format(message, args); } processingEnv.getMessager().printMessage(kind, message, element); } @Override public Set<String> getSupportedAnnotationTypes() { return Collections.singleton(SharedPreferenceAble.class.getCanonicalName()); } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } }