package com.sora.util.akatsuki; import static com.sora.util.akatsuki.SourceUtils.T; import static com.sora.util.akatsuki.SourceUtils.type; import java.util.EnumMap; import java.util.EnumSet; import java.util.List; import java.util.Optional; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import javax.lang.model.element.Modifier; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic.Kind; import com.sora.util.akatsuki.BundleContext.SimpleBundleContext; import com.sora.util.akatsuki.analyzers.CascadingTypeAnalyzer; import com.sora.util.akatsuki.analyzers.CascadingTypeAnalyzer.Analysis; import com.sora.util.akatsuki.analyzers.CascadingTypeAnalyzer.InvocationType; import com.sora.util.akatsuki.analyzers.Element; import com.sora.util.akatsuki.models.BaseModel; import com.sora.util.akatsuki.models.ClassInfo; import com.sora.util.akatsuki.models.FieldModel; import com.sora.util.akatsuki.models.SourceClassModel; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.MethodSpec.Builder; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; public class BundleRetainerClassBuilder extends BaseModel { enum Direction { SAVE("save", InvocationType.SAVE), RESTORE("restore", InvocationType.RESTORE); final String methodName; final InvocationType type; Direction(String methodName, InvocationType type) { this.methodName = methodName; this.type = type; } } private final SourceClassModel classModel; private final EnumSet<Direction> directions; private final Function<ClassInfo, ClassInfo> classInfoFunction; private final Function<ClassInfo, ClassInfo> superClassInfoFunction; private Optional<Predicate<FieldModel>> fieldModelPredicate = Optional.empty(); private Optional<AnalysisTransformation> analysisTransformation = Optional.empty(); private Optional<BundleContext> bundleContext = Optional.empty(); BundleRetainerClassBuilder(ProcessorContext context, SourceClassModel classModel, EnumSet<Direction> direction, Function<ClassInfo, ClassInfo> classInfoFunction, Function<ClassInfo, ClassInfo> superClassInfoFunction) { super(context); this.classModel = classModel; this.directions = direction; this.classInfoFunction = classInfoFunction; this.superClassInfoFunction = superClassInfoFunction; } public BundleRetainerClassBuilder withFieldPredicate(Predicate<FieldModel> modelPredicate) { this.fieldModelPredicate = Optional.of(modelPredicate); return this; } public BundleRetainerClassBuilder withAnalysisTransformation( AnalysisTransformation transformation) { this.analysisTransformation = Optional.of(transformation); return this; } public BundleRetainerClassBuilder withBundleContext(BundleContext context) { this.bundleContext = Optional.of(context); return this; } // public ClassInfo classInfo() { // return classInfo; // } public TypeSpec.Builder build() { BundleContext bundleContext = this.bundleContext .orElse(new SimpleBundleContext("source", "bundle")); final ClassName sourceClassName = ClassName.get(classModel.originatingElement()); TypeVariableName actualClassCapture = SourceUtils.T_extends(sourceClassName); final ParameterSpec sourceSpec = ParameterSpec .builder(actualClassCapture, bundleContext.sourceObjectName(), Modifier.FINAL) .build(); final ParameterSpec bundleSpec = ParameterSpec .builder(ClassName.get(AndroidTypes.Bundle.asMirror(context)), bundleContext.bundleObjectName(), Modifier.FINAL) .build(); EnumMap<Direction, Builder> actionBuilderMap = new EnumMap<>(Direction.class); // we implement the interface here, all methods action must actually be // there for (Direction direction : Direction.values()) { final Builder saveMethodBuilder = MethodSpec.methodBuilder(direction.methodName) .addModifiers(Modifier.PUBLIC).returns(void.class).addParameter(sourceSpec) .addParameter(bundleSpec); actionBuilderMap.put(direction, saveMethodBuilder); if (classModel.directSuperModel().isPresent()) { String superInvocation = "super.$L($L, $L)"; saveMethodBuilder.addStatement(superInvocation, direction.methodName, bundleContext.sourceObjectName(), bundleContext.bundleObjectName()); } } List<Element<TypeMirror>> elements = classModel.fields().stream().map(Element::new) .collect(Collectors.toList()); EnumSet<Direction> emptyDirections = EnumSet.complementOf(this.directions); for (Direction direction : emptyDirections) { actionBuilderMap.get(direction).addCode("throw new $T($S);", AssertionError.class, "Unused action, should not be called at all"); } for (Element<TypeMirror> element : elements) { if (!fieldModelPredicate.orElseGet(() -> fm -> true).test(element.model())) continue; final CascadingTypeAnalyzer<?, ?, ?> strategy = context.resolver().resolve(element); if (strategy == null) { context.messager().printMessage(Kind.ERROR, "unsupported field, reflected type is " + element.refinedMirror() + " representing class is " + element.refinedMirror().getClass(), element.originatingElement()); } else { try { for (Direction direction : directions) { Analysis analysis = strategy.transform(bundleContext, element, direction.type); analysisTransformation.ifPresent( ft -> ft.transform(context, direction, element, analysis)); actionBuilderMap.get(direction) .addCode(JavaPoetUtils.escapeStatement(analysis.preEmitOnce() + analysis.emit() + analysis.postEmitOnce())); } } catch (Exception | Error e) { context.messager().printMessage(Kind.ERROR, "An exception/error occurred", element.originatingElement()); throw new RuntimeException(e); } } } TypeSpec.Builder typeSpecBuilder = TypeSpec .classBuilder(classInfoFunction.apply(classModel.asClassInfo()).className) .addModifiers(Modifier.PUBLIC).addTypeVariable(actualClassCapture); for (Builder builder : actionBuilderMap.values()) { typeSpecBuilder.addMethod(builder.build()); } Optional<SourceClassModel> superModel = classModel.directSuperModel(); if (superModel.isPresent()) { ClassName className = superClassInfoFunction.apply(superModel.get().asClassInfo()) .toClassName(); typeSpecBuilder.superclass(type(className, T)); } else { final ParameterizedTypeName interfaceName = type(BundleRetainer.class, T); typeSpecBuilder.addSuperinterface(interfaceName); } return typeSpecBuilder; } public interface AnalysisTransformation { void transform(ProcessorContext context, Direction direction, Element<?> element, Analysis analysis); } }