package com.arellomobile.mvp.compiler; import com.arellomobile.mvp.GenerateViewState; import com.arellomobile.mvp.InjectViewState; import com.arellomobile.mvp.RegisterMoxyReflectorPackages; import com.arellomobile.mvp.presenter.InjectPresenter; import com.google.auto.service.AutoService; import java.io.IOException; import java.io.Writer; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.processing.AbstractProcessor; 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.util.Elements; import javax.lang.model.util.Types; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; import static javax.lang.model.SourceVersion.latestSupported; /** * Date: 12.12.2015 * Time: 15:35 * * @author Yuri Shmakov */ @SuppressWarnings("unused") @AutoService(Processor.class) public class MvpCompiler extends AbstractProcessor { private static Messager sMessager; private static Types sTypeUtils; private static Elements sElementUtils; private static Map<String, String> sOptions; public static Messager getMessager() { return sMessager; } public static Types getTypeUtils() { return sTypeUtils; } public static Elements getElementUtils() { return sElementUtils; } @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); sMessager = processingEnv.getMessager(); sTypeUtils = processingEnv.getTypeUtils(); sElementUtils = processingEnv.getElementUtils(); sOptions = processingEnv.getOptions(); } @Override public Set<String> getSupportedAnnotationTypes() { Set<String> supportedAnnotationTypes = new HashSet<>(); Collections.addAll(supportedAnnotationTypes, InjectPresenter.class.getCanonicalName(), InjectViewState.class.getCanonicalName(), RegisterMoxyReflectorPackages.class.getCanonicalName(), GenerateViewState.class.getCanonicalName()); return supportedAnnotationTypes; } @Override public SourceVersion getSupportedSourceVersion() { return latestSupported(); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { if (annotations.isEmpty()) { return false; } try { return throwableProcess(roundEnv); } catch (RuntimeException e) { getMessager().printMessage(Diagnostic.Kind.OTHER, "Moxy compilation failed. Could you copy stack trace above and write us (or make issue on Githhub)?"); e.printStackTrace(); getMessager().printMessage(Diagnostic.Kind.ERROR, "Moxy compilation failed; see the compiler error output for details (" + e + ")"); } return true; } private boolean throwableProcess(RoundEnvironment roundEnv) { checkInjectors(roundEnv, InjectPresenter.class, new PresenterInjectorRules(ElementKind.FIELD, Modifier.PUBLIC, Modifier.DEFAULT)); ViewStateProviderClassGenerator viewStateProviderClassGenerator = new ViewStateProviderClassGenerator(); PresenterBinderClassGenerator presenterBinderClassGenerator = new PresenterBinderClassGenerator(); processInjectors(roundEnv, InjectViewState.class, ElementKind.CLASS, viewStateProviderClassGenerator); processInjectors(roundEnv, InjectPresenter.class, ElementKind.FIELD, presenterBinderClassGenerator); ViewStateClassGenerator viewStateClassGenerator = new ViewStateClassGenerator(); Set<TypeElement> usedViews = viewStateProviderClassGenerator.getUsedViews(); for (TypeElement usedView : usedViews) { generateCode(ElementKind.INTERFACE, viewStateClassGenerator, usedView); } String moxyReflectorPackage = sOptions.get("moxyReflectorPackage"); if (moxyReflectorPackage == null) { moxyReflectorPackage = "com.arellomobile.mvp"; } List<String> additionalMoxyReflectorPackages = getAdditionalMoxyReflectorPackages(roundEnv); String moxyReflector = MoxyReflectorGenerator.generate( moxyReflectorPackage, viewStateProviderClassGenerator.getPresenterClassNames(), presenterBinderClassGenerator.getPresentersContainers(), viewStateClassGenerator.getStrategyClasses(), additionalMoxyReflectorPackages); ClassGeneratingParams classGeneratingParams = new ClassGeneratingParams(); classGeneratingParams.setName(moxyReflectorPackage + ".MoxyReflector"); classGeneratingParams.setBody(moxyReflector); createSourceFile(classGeneratingParams); return true; } private List<String> getAdditionalMoxyReflectorPackages(RoundEnvironment roundEnv) { List<String> result = new ArrayList<>(); for (Element element : roundEnv.getElementsAnnotatedWith(RegisterMoxyReflectorPackages.class)) { if (element.getKind() != ElementKind.CLASS) { getMessager().printMessage(Diagnostic.Kind.ERROR, element + " must be " + ElementKind.CLASS.name() + ", or not mark it as @" + RegisterMoxyReflectorPackages.class.getSimpleName()); } String[] packages = element.getAnnotation(RegisterMoxyReflectorPackages.class).value(); Collections.addAll(result, packages); } return result; } private void checkInjectors(final RoundEnvironment roundEnv, Class<? extends Annotation> clazz, AnnotationRule annotationRule) { for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(clazz)) { annotationRule.checkAnnotation(annotatedElement); } String errorStack = annotationRule.getErrorStack(); if (errorStack != null && errorStack.length() > 0) { getMessager().printMessage(Diagnostic.Kind.ERROR, errorStack); } } private void processInjectors(final RoundEnvironment roundEnv, Class<? extends Annotation> clazz, ElementKind kind, ClassGenerator classGenerator) { for (Element annotatedElements : roundEnv.getElementsAnnotatedWith(clazz)) { if (annotatedElements.getKind() != kind) { getMessager().printMessage(Diagnostic.Kind.ERROR, annotatedElements + " must be " + kind.name() + ", or not mark it as @" + clazz.getSimpleName()); } generateCode(kind, classGenerator, annotatedElements); } } private void generateCode(ElementKind kind, ClassGenerator classGenerator, Element element) { if (element.getKind() != kind) { getMessager().printMessage(Diagnostic.Kind.ERROR, element + " must be " + kind.name()); } List<ClassGeneratingParams> classGeneratingParamsList = new ArrayList<>(); //noinspection unchecked final boolean generated = classGenerator.generate(element, classGeneratingParamsList); if (!generated) { return; } for (ClassGeneratingParams classGeneratingParams : classGeneratingParamsList) { createSourceFile(classGeneratingParams); } } private void createSourceFile(ClassGeneratingParams classGeneratingParams) { try { JavaFileObject f = processingEnv.getFiler().createSourceFile(classGeneratingParams.getName()); Writer w = f.openWriter(); w.write(classGeneratingParams.getBody()); w.flush(); w.close(); } catch (IOException e) { e.printStackTrace(); } } }