package io.github.kobakei.spot; import android.content.Context; 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.TypeName; import com.squareup.javapoet.TypeSpec; import io.github.kobakei.spot.annotation.Pref; import io.github.kobakei.spot.annotation.PrefField; import io.github.kobakei.spot.internal.PreferencesUtil; 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.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.MirroredTypeException; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.tools.Diagnostic; import java.io.IOException; import java.util.HashSet; import java.util.Set; @AutoService(Processor.class) public class SpotCompiler extends AbstractProcessor { private static final boolean LOGGABLE = false; private Filer filer; private Messager messager; private Elements elements; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); this.messager = processingEnv.getMessager(); this.filer = processingEnv.getFiler(); this.elements = processingEnv.getElementUtils(); } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override public Set<String> getSupportedAnnotationTypes() { Set<String> set = new HashSet<>(); set.add(Pref.class.getCanonicalName()); return set; } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { log("*** process START ***"); Class<Pref> tableClass = Pref.class; for (Element element : roundEnv.getElementsAnnotatedWith(tableClass)) { ElementKind kind = element.getKind(); if (kind == ElementKind.CLASS) { try { log("*** Found table. Generating repository ***"); generateRepositoryClass(element); } catch (IOException e) { logError("IO error"); } } else { logError("Type error"); } } log("*** process END ***"); return true; } /** * Generate YourModel$$Repository class * @param element * @throws IOException */ private void generateRepositoryClass(Element element) throws IOException { String packageName = elements.getPackageOf(element).getQualifiedName().toString(); ClassName entityClass = ClassName.get(packageName, element.getSimpleName().toString()); ClassName stringClass = ClassName.get(String.class); ClassName contextClass = ClassName.get(Context.class); ClassName utilClass = ClassName.get(PreferencesUtil.class); Pref prefAnnotation = element.getAnnotation(Pref.class); String tableName = prefAnnotation.name(); MethodSpec constructorSpec = MethodSpec.constructorBuilder() .addModifiers(Modifier.PRIVATE) .build(); MethodSpec getNameSpec = MethodSpec.methodBuilder("getName") .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC) .returns(stringClass) .addStatement("return $S", tableName) .build(); MethodSpec.Builder getEntitySpecBuilder = MethodSpec.methodBuilder("getEntity") .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC) .addParameter(contextClass, "context") .returns(entityClass) .addStatement("$T entity = new $T()", entityClass, entityClass); MethodSpec.Builder putEntitySpecBuilder = MethodSpec.methodBuilder("putEntity") .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC) .addParameter(contextClass, "context") .addParameter(entityClass, "entity"); MethodSpec.Builder clearSpecBuilder = MethodSpec.methodBuilder("clear") .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC) .addParameter(contextClass, "context") .addStatement("$T.clear(context, getName())", utilClass); for (Element element1 : element.getEnclosedElements()) { if (element1.getAnnotation(PrefField.class) != null) { handlePrefField(element1, getEntitySpecBuilder, putEntitySpecBuilder); } } getEntitySpecBuilder.addStatement("return entity"); String className = element.getSimpleName() + "SpotRepository"; TypeSpec repository = TypeSpec.classBuilder(className) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addMethod(constructorSpec) .addMethod(getNameSpec) .addMethod(getEntitySpecBuilder.build()) .addMethod(putEntitySpecBuilder.build()) .addMethod(clearSpecBuilder.build()) .build(); JavaFile.builder(packageName, repository) .build() .writeTo(filer); } private void handlePrefField(Element element1, MethodSpec.Builder getEntitySpecBuilder, MethodSpec.Builder putEntitySpecBuilder) { PrefField pref = element1.getAnnotation(PrefField.class); // Convert class TypeName convertClass = getConvertClass(pref); String className = convertClass.toString(); if ("java.lang.Void".equals(className)) { className = element1.asType().toString(); } if ("int".equals(className) || "java.lang.Integer".equals(className)) { handlePrefInt(element1, getEntitySpecBuilder, putEntitySpecBuilder); } else if ("long".equals(className) || "java.lang.Long".equals(className)) { handlePrefLong(element1, getEntitySpecBuilder, putEntitySpecBuilder); } else if ("float".equals(className) || "java.lang.Float".equals(className)) { handlePrefFloat(element1, getEntitySpecBuilder, putEntitySpecBuilder); } else if ("boolean".equals(className) || "java.lang.Boolean".equals(className)) { handlePrefBoolean(element1, getEntitySpecBuilder, putEntitySpecBuilder); } else if ("java.lang.String".equals(className)) { handlePrefString(element1, getEntitySpecBuilder, putEntitySpecBuilder); } else if ("java.util.Set<java.lang.String>".equals(className)) { handlePrefStringSet(element1, getEntitySpecBuilder, putEntitySpecBuilder); } } private void handlePrefInt(Element element1, MethodSpec.Builder getEntitySpecBuilder, MethodSpec.Builder putEntitySpecBuilder) { ClassName utilClass = ClassName.get(PreferencesUtil.class); PrefField pref = element1.getAnnotation(PrefField.class); TypeName converterClass = getConverterClass(pref); getEntitySpecBuilder.beginControlFlow("if ($T.contains(context, getName(), $S))", utilClass, pref.name()); if (pref.useSetter()) { String setterName = getSetterName(element1.getSimpleName().toString()); getEntitySpecBuilder.addStatement( "entity.$L ( new $T().convertFromSupportedType( $T.getInt(context, getName(), $S, $L) ) )", setterName, converterClass, utilClass, pref.name(), 0); } else { getEntitySpecBuilder.addStatement( "entity.$N = new $T().convertFromSupportedType( $T.getInt(context, getName(), $S, $L) )", element1.getSimpleName(), converterClass, utilClass, pref.name(), 0); } getEntitySpecBuilder.endControlFlow(); if (pref.useGetter()) { String getterName = getGetterName(element1.getSimpleName().toString()); putEntitySpecBuilder.addStatement( "$T.putInt(context, getName(), $S, new $T().convertToSupportedType(entity.$N()))", utilClass, pref.name(), converterClass, getterName); } else { putEntitySpecBuilder.addStatement( "$T.putInt(context, getName(), $S, new $T().convertToSupportedType(entity.$N))", utilClass, pref.name(), converterClass, element1.getSimpleName()); } } private void handlePrefLong(Element element1, MethodSpec.Builder getEntitySpecBuilder, MethodSpec.Builder putEntitySpecBuilder) { ClassName utilClass = ClassName.get(PreferencesUtil.class); PrefField pref = element1.getAnnotation(PrefField.class); TypeName converterClass = getConverterClass(pref); getEntitySpecBuilder.beginControlFlow("if ($T.contains(context, getName(), $S))", utilClass, pref.name()); if (pref.useSetter()) { String setterName = getSetterName(element1.getSimpleName().toString()); getEntitySpecBuilder.addStatement( "entity.$L ( new $T().convertFromSupportedType( $T.getLong(context, getName(), $S, $L) ) )", setterName, converterClass, utilClass, pref.name(), 0L); } else { getEntitySpecBuilder.addStatement( "entity.$N = new $T().convertFromSupportedType( $T.getLong(context, getName(), $S, $L) )", element1.getSimpleName(), converterClass, utilClass, pref.name(), 0L); } getEntitySpecBuilder.endControlFlow(); if (pref.useGetter()) { String getterName = getGetterName(element1.getSimpleName().toString()); putEntitySpecBuilder.addStatement( "$T.putLong(context, getName(), $S, new $T().convertToSupportedType(entity.$N()))", utilClass, pref.name(), converterClass, getterName); } else { putEntitySpecBuilder.addStatement( "$T.putLong(context, getName(), $S, new $T().convertToSupportedType(entity.$N))", utilClass, pref.name(), converterClass, element1.getSimpleName()); } } private void handlePrefFloat(Element element1, MethodSpec.Builder getEntitySpecBuilder, MethodSpec.Builder putEntitySpecBuilder) { ClassName utilClass = ClassName.get(PreferencesUtil.class); PrefField pref = element1.getAnnotation(PrefField.class); TypeName converterClass = getConverterClass(pref); getEntitySpecBuilder.beginControlFlow("if ($T.contains(context, getName(), $S))", utilClass, pref.name()); if (pref.useSetter()) { String setterName = getSetterName(element1.getSimpleName().toString()); getEntitySpecBuilder.addStatement( "entity.$L ( new $T().convertFromSupportedType( $T.getFloat(context, getName(), $S, $Lf) ) )", setterName, converterClass, utilClass, pref.name(), 0.0f); } else { getEntitySpecBuilder.addStatement( "entity.$N = new $T().convertFromSupportedType( $T.getFloat(context, getName(), $S, $Lf) )", element1.getSimpleName(), converterClass, utilClass, pref.name(), 0.0f); } getEntitySpecBuilder.endControlFlow(); if (pref.useGetter()) { String getterName = getGetterName(element1.getSimpleName().toString()); putEntitySpecBuilder.addStatement( "$T.putFloat(context, getName(), $S, new $T().convertToSupportedType(entity.$N()))", utilClass, pref.name(), converterClass, getterName); } else { putEntitySpecBuilder.addStatement( "$T.putFloat(context, getName(), $S, new $T().convertToSupportedType(entity.$N))", utilClass, pref.name(), converterClass, element1.getSimpleName()); } } private void handlePrefBoolean(Element element1, MethodSpec.Builder getEntitySpecBuilder, MethodSpec.Builder putEntitySpecBuilder) { ClassName utilClass = ClassName.get(PreferencesUtil.class); PrefField pref = element1.getAnnotation(PrefField.class); TypeName converterClass = getConverterClass(pref); getEntitySpecBuilder.beginControlFlow("if ($T.contains(context, getName(), $S))", utilClass, pref.name()); if (pref.useSetter()) { String setterName = getSetterName( removeBooleanFieldPrefix(element1.getSimpleName().toString())); getEntitySpecBuilder.addStatement( "entity.$L ( new $T().convertFromSupportedType( $T.getBoolean(context, getName(), $S, $L) ) )", setterName, converterClass, utilClass, pref.name(), false); } else { getEntitySpecBuilder.addStatement( "entity.$N = new $T().convertFromSupportedType( $T.getBoolean(context, getName(), $S, $L) )", element1.getSimpleName(), converterClass, utilClass, pref.name(), false); } getEntitySpecBuilder.endControlFlow(); if (pref.useGetter()) { String getterName = getBooleanGetterName(element1.getSimpleName().toString()); putEntitySpecBuilder.addStatement( "$T.putBoolean(context, getName(), $S, new $T().convertToSupportedType(entity.$N()) )", utilClass, pref.name(), converterClass, getterName); } else { putEntitySpecBuilder.addStatement( "$T.putBoolean(context, getName(), $S, new $T().convertToSupportedType(entity.$N) )", utilClass, pref.name(), converterClass, element1.getSimpleName()); } } private void handlePrefString(Element element1, MethodSpec.Builder getEntitySpecBuilder, MethodSpec.Builder putEntitySpecBuilder) { ClassName utilClass = ClassName.get(PreferencesUtil.class); PrefField pref = element1.getAnnotation(PrefField.class); TypeName converterClass = getConverterClass(pref); getEntitySpecBuilder.beginControlFlow("if ($T.contains(context, getName(), $S))", utilClass, pref.name()); if (pref.useSetter()) { String setterName = getSetterName(element1.getSimpleName().toString()); getEntitySpecBuilder.addStatement( "entity.$L( new $T().convertFromSupportedType( $T.getString(context, getName(), $S, $S) ) )", setterName, converterClass, utilClass, pref.name(), null); } else { getEntitySpecBuilder.addStatement( "entity.$N = new $T().convertFromSupportedType( $T.getString(context, getName(), $S, $S) )", element1.getSimpleName(), converterClass, utilClass, pref.name(), null); } getEntitySpecBuilder.endControlFlow(); if (pref.useGetter()) { String getterName = getGetterName(element1.getSimpleName().toString()); putEntitySpecBuilder.addStatement( "$T.putString(context, getName(), $S, new $T().convertToSupportedType(entity.$N()) )", utilClass, pref.name(), converterClass, getterName); } else { putEntitySpecBuilder.addStatement( "$T.putString(context, getName(), $S, new $T().convertToSupportedType(entity.$N) )", utilClass, pref.name(), converterClass, element1.getSimpleName()); } } private void handlePrefStringSet(Element element1, MethodSpec.Builder getEntitySpecBuilder, MethodSpec.Builder putEntitySpecBuilder) { ClassName utilClass = ClassName.get(PreferencesUtil.class); PrefField pref = element1.getAnnotation(PrefField.class); TypeName converterClass = getConverterClass(pref); getEntitySpecBuilder.beginControlFlow("if ($T.contains(context, getName(), $S))", utilClass, pref.name()); if (pref.useSetter()) { String setterName = getSetterName(element1.getSimpleName().toString()); getEntitySpecBuilder.addStatement( "entity.$L ( new $T().convertFromSupportedType( $T.getStringSet(context, getName(), $S, null) ) )", setterName, converterClass, utilClass, pref.name()); } else { getEntitySpecBuilder.addStatement( "entity.$N = new $T().convertFromSupportedType( $T.getStringSet(context, getName(), $S, null) )", element1.getSimpleName(), converterClass, utilClass, pref.name()); } getEntitySpecBuilder.endControlFlow(); if (pref.useGetter()) { String getterName = getGetterName(element1.getSimpleName().toString()); putEntitySpecBuilder.addStatement( "$T.putStringSet(context, getName(), $S, new $T().convertToSupportedType(entity.$N()))", utilClass, pref.name(), converterClass, getterName); } else { putEntitySpecBuilder.addStatement( "$T.putStringSet(context, getName(), $S, new $T().convertToSupportedType(entity.$N))", utilClass, pref.name(), converterClass, element1.getSimpleName()); } } private String removeBooleanFieldPrefix(String field) { if (field.length() > 0 && field.startsWith("is")) { if (Character.isUpperCase(field.charAt(2))) { return field.substring(2, field.length()); } } return field; } private String getBooleanGetterName(String field) { if (field.length() > 0 && field.startsWith("is") && Character.isUpperCase(field.charAt(2))) { return field; } String setter = "get"; if (field.length() > 0) { setter += field.substring(0, 1).toUpperCase(); if (field.length() > 1) { setter += field.substring(1); } } return setter; } private String getGetterName(String field) { String setter = "get"; if (field.length() > 0) { setter += field.substring(0, 1).toUpperCase(); if (field.length() > 1) { setter += field.substring(1); } } return setter; } private String getSetterName(String field) { String setter = "set"; if (field.length() > 0) { setter += field.substring(0, 1).toUpperCase(); if (field.length() > 1) { setter += field.substring(1); } } return setter; } private TypeName getConverterClass(PrefField prefField) { TypeMirror typeMirror = null; try { prefField.converter(); } catch (MirroredTypeException e) { typeMirror = e.getTypeMirror(); } return ClassName.bestGuess(typeMirror.toString()); } private TypeName getConvertClass(PrefField prefField) { TypeElement typeElement = null; try { prefField.converter(); } catch (MirroredTypeException e) { DeclaredType typeMirror = (DeclaredType) e.getTypeMirror(); typeElement = (TypeElement) typeMirror.asElement(); } if (typeElement == null) { throw new IllegalArgumentException("TypeConverter may be wrong"); } TypeMirror superType = typeElement.getSuperclass(); TypeMirror arg = ((DeclaredType) superType).getTypeArguments().get(1); return ClassName.get(arg); } private void log(String msg) { if (LOGGABLE) { this.messager.printMessage(Diagnostic.Kind.OTHER, msg); } } private void logError(String msg) { this.messager.printMessage(Diagnostic.Kind.ERROR, msg); } }