All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.miracles.compiler.processor.UpgradeProcessor Maven / Gradle / Ivy

The newest version!
package com.miracles.compiler.processor;

import com.miracles.annotations.UpgradeInstance;
import com.miracles.annotations.VersionUpgrade;
import com.miracles.compiler.AnnotationProcessor;
import com.miracles.compiler.utils.Utils;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
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 java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic;

import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;

/**
 * Created by lxw
 */
public class UpgradeProcessor implements IAProcessor {
    private static final String CLASS_NAME_SUFFIX = "UpgradeManager";
    private static final String PACKAGE_NAME = "com.miracles.support.upgrade";

    @Override
    public void process(RoundEnvironment roundEnv, AnnotationProcessor mProcessor) {
        Set elements = ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(UpgradeInstance.class));
        if (elements.isEmpty()) {
            return;
        }
        try {
            parse(mProcessor, elements);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            mProcessor.mMessager.printMessage(Diagnostic.Kind.ERROR, "UpgradeProcessor error...");
        }
    }

    private void parse(AnnotationProcessor mProcessor, Set elements) throws Exception {
        Map> collection = collection(mProcessor, elements);
        for (Map.Entry> entry : collection.entrySet()) {
            String id = entry.getKey();
            List elementHolders = entry.getValue();
            generateCodeForAnnotationElements(mProcessor, id, elementHolders);
        }
    }

    private static class TypeElementHolder {
        private TypeElement typeElement;
        private ClassName typeArgClzName;

        private TypeElementHolder(TypeElement typeElement, ClassName typeArgClzName) {
            this.typeElement = typeElement;
            this.typeArgClzName = typeArgClzName;
        }
    }

    private Map> collection(AnnotationProcessor mProcessor, Set elements) {
        Map> idSortMapping = new HashMap<>();
        for (TypeElement element : elements) {
            UpgradeInstance upgradeInstance = element.getAnnotation(UpgradeInstance.class);
            ElementTypeArgHolder typeArgHolder = collectElementTypeArg(mProcessor, element);
            if (!typeArgHolder.implementVh) {
                mProcessor.mMessager.printMessage(Diagnostic.Kind.ERROR, "you must implements interface VersionHandler in " + element.getQualifiedName());
                break;
            }
            String id = upgradeInstance.id();
            List elementList = idSortMapping.get(id);
            if (elementList == null) {
                elementList = new ArrayList<>();
                idSortMapping.put(id, elementList);
            }
            elementList.add(new TypeElementHolder(element, typeArgHolder.typeArgClzName));
        }
        return idSortMapping;
    }

    private ElementTypeArgHolder collectElementTypeArg(AnnotationProcessor mProcessor, TypeElement element) {
        String vhName = "com.miracles.upgrade.VersionHandler";
        String objectName = "java.lang.Object";
        List interfaces = element.getInterfaces();
        ClassName typeArgClzName = ClassName.OBJECT;
        for (TypeMirror typeMirror : interfaces) {
            //mProcessor.mTypes.isSameType not support wildcard.
            boolean implementVh = vhName.equals(mProcessor.mTypes.erasure(typeMirror).toString());
            if (implementVh) {
                DeclaredType declaredType = (DeclaredType) typeMirror;
                List typeArguments = declaredType.getTypeArguments();
                if (!typeArguments.isEmpty()) {
                    typeArgClzName = Utils.getType(mProcessor.mTypes.erasure(typeArguments.get(0)).toString());
                }
                return new ElementTypeArgHolder(true, typeArgClzName);
            }
        }
        TypeMirror superTypeMirror = element.getSuperclass();
        if (!objectName.equals(superTypeMirror.toString())) {
            return collectElementTypeArg(mProcessor, (TypeElement) mProcessor.mTypes.asElement(superTypeMirror));
        } else {
            return new ElementTypeArgHolder(false, typeArgClzName);
        }
    }

    private static class ElementTypeArgHolder {
        private boolean implementVh;
        private ClassName typeArgClzName;

        private ElementTypeArgHolder(boolean implementVh, ClassName typeArgClzName) {
            this.implementVh = implementVh;
            this.typeArgClzName = typeArgClzName;
        }
    }

    private static class UpgradeMethodHolder {
        private TypeElement typeElement;
        private String inApplyParamName;

        private UpgradeMethodHolder(TypeElement typeElement, String inApplyParamName) {
            this.typeElement = typeElement;
            this.inApplyParamName = inApplyParamName;
        }
    }

    private static class MethodInvokeHolder implements Comparable {
        private String methodName;
        private int methodPriority;
        private String inApplyParamName;

        private MethodInvokeHolder(String methodName, int methodPriority, String inApplyParamName) {
            this.methodName = methodName;
            this.methodPriority = methodPriority;
            this.inApplyParamName = inApplyParamName;
        }

        @Override
        public int compareTo(MethodInvokeHolder other) {
            return other.methodPriority - this.methodPriority;
        }
    }

    private MethodSpec generateInvokeVersionHandlerMethod(AnnotationProcessor mProcessor, MethodSpec.Builder applyUpgradeMethod, List elementHolders) {
        String invokeVersionHandlerName = "invokeVersionHandler";
        List upgradeMethodHolderList = new ArrayList<>();
        Map> methodInvokeHolderMap = new HashMap<>();
        for (TypeElementHolder typeElementHolder : elementHolders) {
            TypeElement typeElement = typeElementHolder.typeElement;
            String inParamName = Utils.lowerFirstChar(typeElement.getSimpleName().toString());
            List enclosedElements = typeElement.getEnclosedElements();
            UpgradeMethodHolder upgradeMethodHolder = new UpgradeMethodHolder(typeElement, inParamName);
            for (Element element : enclosedElements) {
                VersionUpgrade annotation = element.getAnnotation(VersionUpgrade.class);
                if (annotation == null) {
                    continue;
                }
                int priority = annotation.priority();
                ExecutableElement executableElement = (ExecutableElement) element;
                List parameters = executableElement.getParameters();
                if (!parameters.isEmpty()) {
                    mProcessor.mMessager.printMessage(Diagnostic.Kind.ERROR, "you must implements upgrade method with no arguments in" + typeElement.getQualifiedName());
                    break;
                }
                String methodName = executableElement.getSimpleName().toString();
                String identifierName = versionProtocol(annotation.fromVersion(), annotation.toVersion());
                Set methodInvokeHolders = methodInvokeHolderMap.get(identifierName);
                if (methodInvokeHolders == null) {
                    methodInvokeHolders = new TreeSet<>();
                    methodInvokeHolderMap.put(identifierName, methodInvokeHolders);
                }
                methodInvokeHolders.add(new MethodInvokeHolder(methodName, priority, inParamName));
            }
            upgradeMethodHolderList.add(upgradeMethodHolder);
        }
        String versionParamName = "version";
        MethodSpec.Builder invokeVersionHandlerMethod = MethodSpec.methodBuilder(invokeVersionHandlerName)
                .returns(TypeName.VOID).addModifiers(PRIVATE);
        for (UpgradeMethodHolder holder : upgradeMethodHolderList) {
            invokeVersionHandlerMethod.addParameter(ClassName.get(holder.typeElement), holder.inApplyParamName);
        }
        invokeVersionHandlerMethod.addParameter(String.class, versionParamName);
        CodeBlock.Builder codeBuilder = CodeBlock.builder();
        codeBuilder.addStatement("if($L==null) return", versionParamName);
        codeBuilder.beginControlFlow("switch($L)", versionParamName);
        for (Map.Entry> entry : methodInvokeHolderMap.entrySet()) {
            codeBuilder.add("case $S:\n", entry.getKey());
            for (MethodInvokeHolder methodInvokeHolder : entry.getValue()) {
                codeBuilder.addStatement("$L.$L()", methodInvokeHolder.inApplyParamName, methodInvokeHolder.methodName);
            }
            codeBuilder.addStatement("break");
        }
        codeBuilder.add("default:\n");
        codeBuilder.addStatement("throw new $T ($S+$L)", RuntimeException.class, "you must implements upgrade method which identify is fromVersion->toVersion ", versionParamName);
        codeBuilder.endControlFlow();
        invokeVersionHandlerMethod.addCode(codeBuilder.build());
        return invokeVersionHandlerMethod.build();
    }

    private String versionProtocol(int fromVersion, int toVersion) {
        return fromVersion + "->" + toVersion;
    }

    private void generateCodeForAnnotationElements(AnnotationProcessor mProcessor, String id, List elementHolders) throws IOException {
        if (elementHolders == null || elementHolders.isEmpty()) {
            return;
        }
        String vhName = "com.miracles.upgrade.VersionHandler";
        String umName = "com.miracles.upgrade.UpgradeManager";
        String upExName = "com.miracles.upgrade.UpgradeException";
        String logName = "android.util.Log";

        ClassName vhClzName = Utils.getType(vhName);
        ClassName umClzName = Utils.getType(umName);
        ClassName upExClzName = Utils.getType(upExName);
        ClassName logClzName = Utils.getType(logName);

        String umOldVersionParamName = "oldVersion";
        String umNewVersionParamName = "newVersion";
        String clzName = Utils.upperFirstChar(id) + CLASS_NAME_SUFFIX;
        String mSeedFieldName = "mSeedInstance";
        ClassName universalTypeArgClzName = elementHolders.get(0).typeArgClzName;
        ParameterizedTypeName umPtName = ParameterizedTypeName.get(umClzName, universalTypeArgClzName);
        ParameterizedTypeName vhPtName = ParameterizedTypeName.get(vhClzName, universalTypeArgClzName);
        TypeSpec.Builder clzBuilder = TypeSpec.classBuilder(clzName)
                .addSuperinterface(umPtName)
                .addField(universalTypeArgClzName, mSeedFieldName, PRIVATE)
                .addModifiers(PUBLIC);
        FieldSpec.Builder tagFieldBuilder = FieldSpec.builder(String.class, "TAG", PRIVATE, STATIC, FINAL).initializer("$S", clzName);
        clzBuilder.addField(tagFieldBuilder.build());
        //logMethod
        MethodSpec.Builder logMethodBuilder = MethodSpec.methodBuilder("log")
                .returns(TypeName.VOID).addModifiers(PUBLIC)
                .addParameter(String.class, "message");
        logMethodBuilder.addStatement("$T.d($L,$L)", logClzName, "TAG", "message");
        MethodSpec logMethod = logMethodBuilder.build();
        clzBuilder.addMethod(logMethod);
        //setSeedInstance
        MethodSpec.Builder setSeedMethod = MethodSpec.methodBuilder("setSeedInstance")
                .returns(TypeName.VOID).addModifiers(PUBLIC)
                .addAnnotation(Override.class)
                .addParameter(universalTypeArgClzName, mSeedFieldName);
        setSeedMethod.addStatement("this.$L=$L", mSeedFieldName, mSeedFieldName);
        clzBuilder.addMethod(setSeedMethod.build());
        MethodSpec.Builder applyUpgradeMethod = MethodSpec.methodBuilder("applyUpgrade")
                .returns(TypeName.VOID).addModifiers(PUBLIC)
                .addAnnotation(Override.class)
                .addException(upExClzName);
        //invoke method
        MethodSpec invokeHandlerMethod = generateInvokeVersionHandlerMethod(mProcessor, applyUpgradeMethod, elementHolders);
        clzBuilder.addMethod(invokeHandlerMethod);
        //applyUpgrade;
        StringBuilder invokeParamSb = new StringBuilder();
        for (int i = 0; i < elementHolders.size(); i++) {
            TypeElementHolder typeElementHolder = elementHolders.get(i);
            TypeElement typeElement = typeElementHolder.typeElement;
            String inParamName = Utils.lowerFirstChar(typeElement.getSimpleName().toString());
            applyUpgradeMethod.addStatement("$T $L=new $T()", typeElement, inParamName, typeElement);
            applyUpgradeMethod.addStatement("$L.setSeedInstance($L)", inParamName, mSeedFieldName);
            invokeParamSb.append(inParamName);
            invokeParamSb.append(",");
            if (i == 0) {
                applyUpgradeMethod.addStatement("int oldVersion=$L.getVersion()", inParamName);
                applyUpgradeMethod.addStatement("int newVersion=$L.maxVersion()", inParamName);
            } else {
                //oldVersion
                applyUpgradeMethod.beginControlFlow("if($L.getVersion()newVersion)", inParamName);
                applyUpgradeMethod.addStatement("newVersion=$L.maxVersion()", inParamName);
                applyUpgradeMethod.endControlFlow();
            }
        }
        applyUpgradeMethod.addStatement("int fromVersion=$L", umOldVersionParamName);
        applyUpgradeMethod.addStatement("int toVersion=fromVersion+1");
        CodeBlock.Builder tryCode = CodeBlock.builder();
        tryCode.beginControlFlow("try");
        tryCode.beginControlFlow("while ($L >= toVersion)", umNewVersionParamName);
        //handle upgrade
        tryCode.addStatement("String compact=$L+$S+$L", "fromVersion", "->", "toVersion");
        tryCode.addStatement("$L($S+$L+$S+$L)", logMethod.name, "start upgrade fromVersion->toVersion:", "fromVersion", "->", "toVersion");
        tryCode.addStatement("$L($L$L)", invokeHandlerMethod.name, invokeParamSb.toString(), "compact");
        tryCode.addStatement("$L($S+$L+$S+$L)", logMethod.name, "upgrade success fromVersion->toVersion:", "fromVersion", "->", "toVersion");
        tryCode.addStatement("fromVersion=toVersion");
        tryCode.addStatement("++toVersion");
        tryCode.endControlFlow();
        tryCode.endControlFlow();
        tryCode.beginControlFlow("catch ($T throwable)", ClassName.get(Throwable.class));
        tryCode.addStatement("throw new $T(throwable.getMessage(), throwable, fromVersion, toVersion)", upExClzName);
        tryCode.endControlFlow();
        applyUpgradeMethod.addCode(tryCode.build());
        clzBuilder.addMethod(applyUpgradeMethod.build());
        JavaFile javaFile = JavaFile.builder(PACKAGE_NAME, clzBuilder.build()).build();// 生成源代码
        javaFile.writeTo(mProcessor.mFiler);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy