package com.sora.util.akatsuki; import java.io.IOException; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedOptions; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.tools.Diagnostic.Kind; import com.google.auto.service.AutoService; import com.google.common.collect.ImmutableSet; import com.sora.util.akatsuki.AkatsukiConfig.Flags; import com.sora.util.akatsuki.AkatsukiConfig.OptFlags; import com.sora.util.akatsuki.Utils.Defaults; import com.sora.util.akatsuki.Utils.Values; import com.sora.util.akatsuki.models.SourceTreeModel; import com.sora.util.akatsuki.models.SourceTreeModel.Arrangement; @AutoService(Processor.class) @SupportedSourceVersion(SourceVersion.RELEASE_8) @SupportedOptions({ "akatsuki.loggingLevel", "akatsuki.allowTransient", "akatsuki.allowVolatile", "akatsuki.optFlags", "akatsuki.flags" }) public class AkatsukiProcessor extends AbstractProcessor { private ProcessorContext context; private Map<String, String> options; private static final Set<Class<? extends Annotation>> FIELD_ANNOTATIONS = ImmutableSet .of(With.class, Retained.class, Arg.class); private static final Set<Class<? extends Annotation>> SUPPORTED_ANNOTATIONS = ImmutableSet.of( TransformationTemplate.class, IncludeClasses.class, DeclaredConverter.class, TypeFilter.class, TypeConstraint.class, RetainConfig.class, ArgConfig.class, AkatsukiConfig.class); @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); processingEnv.getMessager().printMessage(Kind.NOTE, "Processor started"); this.context = new ProcessorContext(processingEnv); Log.verbose(context, "Processor context created..."); Map<String, String> options = processingEnv.getOptions(); if (options != null && !options.isEmpty()) { this.options = options; Log.verbose(context, "Options received: " + options); } } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { // NOTE: process gets called multiple times if any other annotation // processor exists context.roundStarted(); Log.verbose(context, "Begin processing round, roots: " + roundEnv.getRootElements()); AkatsukiConfig config; final List<AkatsukiConfig> configs = findAnnotations( roundEnv.getElementsAnnotatedWith(AkatsukiConfig.class), AkatsukiConfig.class); if (configs.size() > 1) { context.messager().printMessage(Kind.ERROR, "Multiple @RetainConfig found, you can only have one config. Found:" + configs.toString()); context.roundFinished(); return false; } config = configs.isEmpty() ? Defaults.of(AkatsukiConfig.class) : configs.get(0); if (options != null) { config = Values.of(AkatsukiConfig.class, config, options, methodName -> "akatsuki." + methodName); } Configuration configuration = new Configuration(config); Log.verbose(context, "Verifying configuration..."); configuration.validate(context); // config is ready, we can log properly now Log.verbose(context, "Configuration loaded: " + configuration); context.setConfigForRound(configuration); // short circuit when compiler disabled if (context.config().flags().contains(Flags.DISABLE_COMPILER)) { Log.verbose(context, "DISABLE_COMPILER flag found, compiler disabled."); context.roundFinished(); return false; } Log.verbose(context, "Building source tree..."); SourceTreeModel model = SourceTreeModel.fromRound(context, roundEnv, FIELD_ANNOTATIONS); if (model == null) { context.messager().printMessage(Kind.ERROR, "Source tree verification failed"); context.roundFinished(); return true; } if (model.classModels(Arrangement.HEAD).isEmpty()) { Log.verbose(context, "Round has no elements, classes possibly originated from another annotation processor"); context.roundFinished(); return false; } Log.verbose(context, "Source tree built"); Log.verbose(context, "Tree = " + model.toString()); Log.verbose(context, "Resolving @TransformationTemplate..."); List<TransformationTemplate> templates = findTransformationTemplates(roundEnv); Log.verbose(context, "Found " + templates.size()); Log.verbose(context, "Resolving @DeclaredConverter..."); List<DeclaredConverterModel> declaredConverters = findDeclaredConverters(roundEnv); Log.verbose(context, "Found " + declaredConverters.size()); final TypeAnalyzerResolver resolver = new TypeAnalyzerResolver(templates, declaredConverters, model, context); Log.verbose(context, "TypeAnalyzerResolver created..."); context.setBundleTypeResolverForRound(resolver); // bundle retainer first Log.verbose(context, "Generating classes for @Retained..."); List<RetainedStateModel> retainedStateModels = model.classModels(Arrangement.HEAD).stream() .filter(m -> m.containsAnyAnnotation(Retained.class)) .map(cm -> new RetainedStateModel(context, cm, model)).collect(Collectors.toList()); Log.verbose(context, "Generated " + retainedStateModels.size() + ":"); retainedStateModels.forEach(m -> Log.verbose(context, "\t" + m.classInfo().className)); for (RetainedStateModel stateModel : retainedStateModels) { try { stateModel.writeToFile(processingEnv.getFiler()); } catch (IOException e) { context.messager().printMessage(Kind.ERROR, "An error occurred while writing file:" + stateModel.classInfo()); throw new RuntimeException(e); } } if (context.config().optFlags().contains(OptFlags.CLASS_LUT) && !retainedStateModels.isEmpty()) { Log.verbose(context, "Generating additional classes for OptFlags.CLASS_LUT..."); try { new RetainerLUTModel(context, retainedStateModels, roundEnv.getRootElements()) .writeToFile(processingEnv.getFiler()); } catch (IOException e) { context.messager().printMessage(Kind.ERROR, "An error occurred while writing cache class, " + "try disabling OptFlags.VECTORIZE_INHERITANCE"); throw new RuntimeException(e); } } Log.verbose(context, "Generating classes for @Arg..."); List<ArgumentBuilderModel> argumentBuilderModels = model.classModels(Arrangement.FLATTENED) .stream() // .filter(m -> m.containsAnyAnnotation(Arg.class) || m.directSuperModel() // .map(mm -> mm.containsAnyAnnotation(Arg.class)).orElse(false)) .map(cm -> new ArgumentBuilderModel(context, cm, model, Optional.of(Internal.BUILDER_CLASS_NAME))) .collect(Collectors.toList()); Log.verbose(context, "Generated " + argumentBuilderModels.size() + ":"); argumentBuilderModels.forEach(m -> Log.verbose(context, "\t" + m.classInfo().className)); try { ArgumentBuildersModel argumentBuildersModel = new ArgumentBuildersModel(context, argumentBuilderModels); argumentBuildersModel.writeToFile(processingEnv.getFiler()); } catch (IOException e) { context.messager().printMessage(Kind.ERROR, "An error occurred while writing argument builder"); throw new RuntimeException(e); } context.roundFinished(); return true; } private <T extends Annotation> List<T> findAnnotations(Set<? extends Element> elements, Class<T> clazz) { return elements.stream().map(e -> e.getAnnotation(clazz)).collect(Collectors.toList()); } private List<TransformationTemplate> findTransformationTemplates(RoundEnvironment roundEnv) { final List<TransformationTemplate> templates = new ArrayList<>(); // find all included final List<IncludeClasses> includeClasses = findAnnotations( roundEnv.getElementsAnnotatedWith(IncludeClasses.class), IncludeClasses.class); for (IncludeClasses classes : includeClasses) { context.utils().getClassArrayFromAnnotationMethod(classes::value) .forEach(t -> templates.addAll(Arrays.asList( t.asElement().getAnnotationsByType(TransformationTemplate.class)))); } // find all directly annotated templates.addAll( findAnnotations(roundEnv.getElementsAnnotatedWith(TransformationTemplate.class), TransformationTemplate.class)); return templates; } private List<DeclaredConverterModel> findDeclaredConverters(RoundEnvironment roundEnv) { final Set<? extends Element> elements = roundEnv .getElementsAnnotatedWith(DeclaredConverter.class); for (Element element : elements) { if (!context.utils().isAssignable(element.asType(), context.utils().of(TypeConverter.class), true)) { context.messager().printMessage(Kind.ERROR, "@DeclaredConverter can only be used on types that implement TypeConverter", element); } } return elements.stream() .map(e -> new DeclaredConverterModel((DeclaredType) e.asType(), e.getAnnotation(DeclaredConverter.class).value())) .collect(Collectors.toList()); } @Override public Set<String> getSupportedAnnotationTypes() { HashSet<Class<? extends Annotation>> classes = new HashSet<>(); classes.addAll(FIELD_ANNOTATIONS); classes.addAll(SUPPORTED_ANNOTATIONS); return classes.stream().map(Class::getName).collect(Collectors.toSet()); } }