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

com.flyjingfish.android_aop_processor.AndroidAopProcessor Maven / Gradle / Ivy

Go to download

Lightweight Aop for Android platform, you deserve it, action is worse than your heartbeat

There is a newer version: 2.3.0
Show newest version
package com.flyjingfish.android_aop_processor;

import com.flyjingfish.android_aop_annotation.anno.AndroidAopCollectMethod;
import com.flyjingfish.android_aop_annotation.anno.AndroidAopReplaceNew;
import com.flyjingfish.android_aop_annotation.aop_anno.AopClass;
import com.flyjingfish.android_aop_annotation.aop_anno.AopCollectMethod;
import com.flyjingfish.android_aop_annotation.aop_anno.AopMatchClassMethod;
import com.flyjingfish.android_aop_annotation.anno.AndroidAopMatchClassMethod;
import com.flyjingfish.android_aop_annotation.aop_anno.AopPointCut;
import com.flyjingfish.android_aop_annotation.anno.AndroidAopPointCut;
import com.flyjingfish.android_aop_annotation.anno.AndroidAopReplaceClass;
import com.flyjingfish.android_aop_annotation.anno.AndroidAopModifyExtendsClass;
import com.flyjingfish.android_aop_annotation.anno.AndroidAopReplaceMethod;
import com.flyjingfish.android_aop_annotation.aop_anno.AopReplaceMethod;
import com.flyjingfish.android_aop_annotation.aop_anno.AopModifyExtendsClass;
import com.flyjingfish.android_aop_annotation.base.BasePointCut;
import com.flyjingfish.android_aop_annotation.base.BasePointCutCreator;
import com.flyjingfish.android_aop_annotation.base.MatchClassMethod;
import com.flyjingfish.android_aop_annotation.base.MatchClassMethodCreator;
import com.flyjingfish.android_aop_annotation.enums.MatchType;
import com.google.gson.Gson;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
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.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.MirroredTypeException;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;

public class AndroidAopProcessor extends AbstractProcessor {
    Filer mFiler;
//    private static final String packageName = "com.flyjingfish.android_aop_core.aop";
    private TypeMirror matchClassMethodType;
    private Types types;
    private static final String AOP_METHOD_NAME = "aopConfigMethod";
    private Elements elementUtils;
    private static final Gson mGson = new Gson();

    @Override
    public Set getSupportedAnnotationTypes() {
        Set set = new LinkedHashSet<>();
        set.add(AndroidAopPointCut.class.getCanonicalName());
        set.add(AndroidAopMatchClassMethod.class.getCanonicalName());
        set.add(AndroidAopReplaceClass.class.getCanonicalName());
        set.add(AndroidAopReplaceMethod.class.getCanonicalName());
        set.add(AndroidAopReplaceNew.class.getCanonicalName());
        set.add(AndroidAopModifyExtendsClass.class.getCanonicalName());
        set.add(AndroidAopCollectMethod.class.getCanonicalName());
        return set;
    }
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
    public static boolean isEmpty(final Map map) {
        return map == null || map.isEmpty();
    }
    public static boolean isEmpty(final Set set) {
        return set == null || set.isEmpty();
    }
    public static boolean isNotEmpty(final Map map) {
        return !isEmpty(map);
    }
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mFiler = processingEnv.getFiler();
        elementUtils = processingEnv.getElementUtils();
        matchClassMethodType = elementUtils.getTypeElement(MatchClassMethod.class.getName()).asType();
        types = processingEnv.getTypeUtils();
    }

    @Override
    public boolean process(Set set, RoundEnvironment roundEnvironment) {
        if (isEmpty(set)){
            return false;
        }

        processPointCut(set, roundEnvironment);
        processMatch(set, roundEnvironment);
        processReplaceMethod(set, roundEnvironment,AndroidAopReplaceMethod.class);
        processReplaceMethod(set, roundEnvironment,AndroidAopReplaceNew.class);
        processReplace(set, roundEnvironment);
        processModifyExtendsClass(set, roundEnvironment);
        processCollectMethod(set, roundEnvironment);
        return false;
    }



    public void processPointCut(Set set, RoundEnvironment roundEnvironment) {
        Set elements = roundEnvironment.getElementsAnnotatedWith(AndroidAopPointCut.class);
        for (Element element : elements) {
            Name name1 = element.getSimpleName();
            AndroidAopPointCut cut = element.getAnnotation(AndroidAopPointCut.class);
            Target target = element.getAnnotation(Target.class);
            Retention retention = element.getAnnotation(Retention.class);
            if (target == null){
                throw new IllegalArgumentException("注意:" + "请给 "+name1+" 设置 @Target 为ElementType.METHOD ");
            }
            ElementType[] elementTypes = target.value();
            if (elementTypes.length > 1){
                throw new IllegalArgumentException("注意:" + name1 + "只可以设置 @Target 为 ElementType.METHOD 这一种");
            }else if (elementTypes.length == 1){
                if (elementTypes[0] != ElementType.METHOD){
                    throw new IllegalArgumentException("注意:" + "请给 "+name1+" 设置 @Target 为 ElementType.METHOD ");
                }
            }else {
                throw new IllegalArgumentException("注意:" + "请给 "+name1+" 设置 @Target 为 ElementType.METHOD ");
            }
            if (retention == null){
                throw new IllegalArgumentException("注意:" + "请给 "+name1+" 设置 @Retention 为 RetentionPolicy.RUNTIME ");
            }
            RetentionPolicy retentionPolicy = retention.value();
            if (retentionPolicy == null){
                throw new IllegalArgumentException("注意:" + "请给 "+name1+" 设置 @Retention 为 RetentionPolicy.RUNTIME ");
            }else if (retentionPolicy != RetentionPolicy.RUNTIME){
                throw new IllegalArgumentException("注意:" + "必须给 "+name1+" 设置 @Retention 为 RetentionPolicy.RUNTIME ");
            }

            String className;
            try{
                className = cut.value().getName();
            }catch (MirroredTypeException mirroredTypeException){
                String errorMessage = mirroredTypeException.getLocalizedMessage();
                className = errorMessage.substring( errorMessage.lastIndexOf(" ")+1);
            }
            ClassName superinterface = ClassName.bestGuess(BasePointCutCreator.class.getName());
            TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(name1+"$$AndroidAopClass")
                    .addAnnotation(AopClass.class)
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addSuperinterface(superinterface);

            String elementClassName = element.toString();
            String packageName = elementClassName.substring(0, elementClassName.lastIndexOf("."));
            String cutClassPackageName = className.substring(0, className.lastIndexOf("."));

            TypeName implementClassName = ClassName.get(packageName, element.getSimpleName().toString());
            ClassName bindClassName = ClassName.bestGuess(BasePointCut.class.getName());
            ParameterizedTypeName returnType = ParameterizedTypeName.get(bindClassName,implementClassName);

            MethodSpec.Builder whatsMyName2 = whatsMyName("newInstance")
                    .addAnnotation(Override.class)
                    .addModifiers(Modifier.FINAL)
                    .addModifiers(Modifier.PUBLIC)
                    .returns(returnType)
                    .addStatement("return new $T()",ClassName.bestGuess(className))
                    .addAnnotation(AnnotationSpec.builder(AopPointCut.class)
                            .addMember("value", "$S", "@"+element)
                            .addMember("pointCutClassName", "$S", className)
                            .build());

            typeBuilder.addMethod(whatsMyName2.build());

            TypeSpec typeSpec = typeBuilder.build();

            JavaFile javaFile = JavaFile.builder(cutClassPackageName, typeSpec)
                    .build();
            try {
                javaFile.writeTo(mFiler);
            } catch (IOException e) {
//                throw new RuntimeException(e);
            }
        }
    }

    public void processMatch(Set set, RoundEnvironment roundEnvironment) {
        Set elements = roundEnvironment.getElementsAnnotatedWith(AndroidAopMatchClassMethod.class);
        for (Element element : elements) {
            if (!types.isSubtype(element.asType(), matchClassMethodType)) {
                throw new IllegalArgumentException("注意:" +element+ " 必须实现 MatchClassMethod 接口");
            }
            Name name1 = element.getSimpleName();
//                System.out.println("======"+element);
            AndroidAopMatchClassMethod cut = element.getAnnotation(AndroidAopMatchClassMethod.class);
            String className = cut.targetClassName();
            String[] methodNames = cut.methodName();
            String[] excludeClasses = cut.excludeClasses();
            MatchType matchType = cut.type();
            boolean overrideMethod = cut.overrideMethod();
            StringBuilder methodNamesBuilder = new StringBuilder();
            boolean hasMethodStar = false;
            for (int i = 0; i < methodNames.length; i++) {
                if ("*".equals(methodNames[i])){
                    hasMethodStar = true;
                }
                methodNamesBuilder.append(methodNames[i]);
                if (i != methodNames.length -1){
                    methodNamesBuilder.append("-");
                }
            }
            if (hasMethodStar && methodNames.length > 1){
                throw new IllegalArgumentException("注意:"+element+" 匹配所有方法时不可以再设置其他方法名了");
            }
            StringBuilder excludeClassesBuilder = new StringBuilder();
            for (int i = 0; i < excludeClasses.length; i++) {
                excludeClassesBuilder.append(excludeClasses[i]);
                if (i != excludeClasses.length -1){
                    excludeClassesBuilder.append("-");
                }
            }
            boolean matchAll = "*".equals(methodNamesBuilder.toString()) || className.contains("*");
            if (matchAll && overrideMethod){
                throw new IllegalArgumentException("注意:"+element+" 匹配所有方法或匹配包名时不可以设置 overrideMethod 为 true");
            }
            ClassName superinterface = ClassName.bestGuess(MatchClassMethodCreator.class.getName());

            TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(name1+"$$AndroidAopClass")
                    .addAnnotation(AopClass.class)
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addSuperinterface(superinterface);

            ClassName bindClassName = ClassName.bestGuess(MatchClassMethod.class.getName());

            MethodSpec.Builder whatsMyName2 = whatsMyName("newInstance")
                    .addModifiers(Modifier.FINAL)
                    .addModifiers(Modifier.PUBLIC)
                    .addAnnotation(Override.class)
                    .returns(bindClassName)
                    .addStatement("return new $T()",ClassName.bestGuess(element.toString()))
                    .addAnnotation(AnnotationSpec.builder(AopMatchClassMethod.class)
                            .addMember("baseClassName", "$S", className)
                            .addMember("methodNames", "$S", mGson.toJson(methodNames))
                            .addMember("pointCutClassName", "$S", element)
                            .addMember("matchType", "$S", matchType.name())
                            .addMember("excludeClasses", "$S", mGson.toJson(excludeClasses))
                            .addMember("overrideMethod", "$L", overrideMethod)
                            .build());

            typeBuilder.addMethod(whatsMyName2.build());

            TypeSpec typeSpec = typeBuilder.build();
            String elementClassName = element.toString();
            String packageName = elementClassName.substring(0, elementClassName.lastIndexOf("."));
            JavaFile javaFile = JavaFile.builder(packageName, typeSpec)
                    .build();
            try {
                javaFile.writeTo(mFiler);
            } catch (IOException e) {
//                throw new RuntimeException(e);
            }
        }
    }



    private void processReplace(Set set, RoundEnvironment roundEnvironment) {

        Set elements = roundEnvironment.getElementsAnnotatedWith(AndroidAopReplaceClass.class);
        for (Element element : elements) {
            Name name1 = element.getSimpleName();
//                System.out.println("======"+element);
            AndroidAopReplaceClass cut = element.getAnnotation(AndroidAopReplaceClass.class);
            String className = cut.value();
            String[] excludeClasses = cut.excludeClasses();
            MatchType matchType = cut.type();
            StringBuilder excludeClassesBuilder = new StringBuilder();
            for (int i = 0; i < excludeClasses.length; i++) {
                excludeClassesBuilder.append(excludeClasses[i]);
                if (i != excludeClasses.length -1){
                    excludeClassesBuilder.append("-");
                }
            }
            TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(name1+"$$AndroidAopClass")
                    .addAnnotation(AopClass.class)
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
            MethodSpec.Builder whatsMyName1 = whatsMyName(AOP_METHOD_NAME)
                    .addAnnotation(AnnotationSpec.builder(AopReplaceMethod.class)
                            .addMember("targetClassName", "$S", className)
                            .addMember("invokeClassName", "$S", element)
                            .addMember("matchType", "$S", matchType.name())
                            .addMember("excludeClasses", "$S", mGson.toJson(excludeClasses))
                            .build());

            typeBuilder.addMethod(whatsMyName1.build());

            TypeSpec typeSpec = typeBuilder.build();
            String elementClassName = element.toString();
            String packageName = elementClassName.substring(0, elementClassName.lastIndexOf("."));
            JavaFile javaFile = JavaFile.builder(packageName, typeSpec)
                    .build();
            try {
                javaFile.writeTo(mFiler);
            } catch (IOException e) {
//                throw new RuntimeException(e);
            }
        }
    }

    private void processModifyExtendsClass(Set set, RoundEnvironment roundEnvironment) {
        Set elements = roundEnvironment.getElementsAnnotatedWith(AndroidAopModifyExtendsClass.class);
        for (Element element : elements) {
            Name name1 = element.getSimpleName();
//                System.out.println("======"+element);
            AndroidAopModifyExtendsClass cut = element.getAnnotation(AndroidAopModifyExtendsClass.class);
            String className = cut.value();

            TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(name1+"$$AndroidAopClass")
                    .addAnnotation(AopClass.class)
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
            MethodSpec.Builder whatsMyName1 = whatsMyName(AOP_METHOD_NAME)
                    .addAnnotation(AnnotationSpec.builder(AopModifyExtendsClass.class)
                            .addMember("targetClassName", "$S", className)
                            .addMember("extendsClassName", "$S", element)
                            .build());

            typeBuilder.addMethod(whatsMyName1.build());

            TypeSpec typeSpec = typeBuilder.build();
            String elementClassName = element.toString();
            String packageName = elementClassName.substring(0, elementClassName.lastIndexOf("."));
            JavaFile javaFile = JavaFile.builder(packageName, typeSpec)
                    .build();
            try {
                javaFile.writeTo(mFiler);
            } catch (IOException e) {
//                throw new RuntimeException(e);
            }
        }
    }

    private void processReplaceMethod(Set set, RoundEnvironment roundEnvironment,Class var1) {
        Set elements = roundEnvironment.getElementsAnnotatedWith(var1);
        for (Element element : elements) {
            Name name1 = element.getSimpleName();
            boolean isStatic = false;
            Set modifiers = element.getModifiers();
            for (Modifier modifier : modifiers) {
                if ("static".equals(modifier.toString())){
                    isStatic = true;
                }
            }
            if (!isStatic){
                throw new IllegalArgumentException("注意:" + "方法 "+element.getEnclosingElement()+"."+name1+" 必须是静态方法 ");
            }
        }
    }

    private void processCollectMethod(Set set, RoundEnvironment roundEnvironment) {
        Set elements = roundEnvironment.getElementsAnnotatedWith(AndroidAopCollectMethod.class);

        Map> funMap = new HashMap<>();
        for (Element element : elements) {
            Name name1 = element.getSimpleName();

            AndroidAopCollectMethod cut = element.getAnnotation(AndroidAopCollectMethod.class);
            String regex = cut.regex();
            String collectType = cut.collectType().name();
            if (regex == null){
                regex = "";
            }

            boolean isStatic = false;
            boolean isPublic = false;
            Set modifiers = element.getModifiers();
            for (Modifier modifier : modifiers) {
                if ("static".equals(modifier.toString())){
                    isStatic = true;
                }
                if ("public".equals(modifier.toString())){
                    isPublic = true;
                }
            }
            String exceptionJavaHintPreText = "注意:" + "方法 "+element.getEnclosingElement()+"."+name1;
            if (!isPublic){
                throw new IllegalArgumentException(exceptionJavaHintPreText+" 必须是public公共方法 ");
            }
            if (!isStatic){
                throw new IllegalArgumentException(exceptionJavaHintPreText+" 必须是静态方法 ");
            }


            ExecutableElement executableElement = (ExecutableElement) element;
            String returnType = executableElement.getReturnType().toString();
            if (!"void".equals(returnType)){
                throw new IllegalArgumentException(exceptionJavaHintPreText+" 只可以设置 void 作为返回类型");
            }
            if (executableElement.getParameters().isEmpty()){
                throw new IllegalArgumentException(exceptionJavaHintPreText+" 必须设置您想收集的类作为参数");
            }else if (executableElement.getParameters().size() != 1){
                throw new IllegalArgumentException(exceptionJavaHintPreText+" 参数必须设置一个");
            }

            VariableElement variableElement = executableElement.getParameters().get(0);

            TypeMirror asType = variableElement.asType();
            Element typeElement = types.asElement(asType);
//            System.out.println("asType="+typeElement);
            if (typeElement == null){
                throw new IllegalArgumentException(exceptionJavaHintPreText+" 参数的类型不可以设置为"+asType);
            }
            String collectClassName = typeElement.toString();
            boolean isClazz = "java.lang.Class".equals(collectClassName);
            boolean regexIsEmpty = "".equals(regex);
            if (!regexIsEmpty && regex.replaceAll(" ","").isEmpty()){
                throw new IllegalArgumentException(exceptionJavaHintPreText+" 的 regex 必须包含字符,不可以只有空格");
            }
            if (isClazz){
                String checkType = variableElement.asType().toString();
                String type = getType(checkType);
                if (regexIsEmpty){
                    if (type == null){
                        throw new IllegalArgumentException(exceptionJavaHintPreText+" 参数的泛型设置的不对");
                    }else {
                        collectClassName = type;
                    }
                }else {
                    if (checkType(checkType)){
                        throw new IllegalArgumentException(exceptionJavaHintPreText+" 参数的泛型设置的不对");
                    }else {
                        if (type == null){
                            collectClassName = "java.lang.Object";
                        }else {
                            collectClassName = type;
                        }
                    }

                }

            }
            if ("kotlin.reflect.KClass".equals(collectClassName)){
                throw new IllegalArgumentException(exceptionJavaHintPreText+" 参数不可以设定为 "+collectClassName);
            }
            if ("kotlin.Any".equals(collectClassName) || "java.lang.Object".equals(collectClassName)){
                if (regexIsEmpty){
                    throw new IllegalArgumentException(exceptionJavaHintPreText+" 参数不可以设定为 "+collectClassName);
                }else{
                    collectClassName = "java.lang.Object";
                }
            }
//            System.out.println("collectClassName="+collectClassName);
            List list = funMap.computeIfAbsent(element.getEnclosingElement().toString(), k -> new ArrayList<>());

            list.add(new CollectMethod(collectClassName,element.getEnclosingElement().toString(),name1.toString(),isClazz+"",regex,collectType));


        }

        Set keySet= funMap.keySet();
        for (String s : keySet) {
            TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(getClassName(s)+"$$AndroidAopClass")
                    .addAnnotation(AopClass.class)
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
            List collectMethods = funMap.get(s);
            String elementClassName = null;
            for (int i = 0; i < collectMethods.size(); i++) {
                CollectMethod collectMethod = collectMethods.get(i);
                MethodSpec.Builder whatsMyName1 = whatsMyName(AOP_METHOD_NAME+i)
                        .addAnnotation(AnnotationSpec.builder(AopCollectMethod.class)
                                .addMember("collectClass", "$T.class", ClassName.bestGuess(collectMethod.collectClassName))
                                .addMember("invokeClass", "$T.class", ClassName.bestGuess(collectMethod.invokeClassName))
                                .addMember("invokeMethod", "$S", collectMethod.invokeMethod)
                                .addMember("isClazz", "$L", "true".equals(collectMethod.isClazz))
                                .addMember("regex", "$S", collectMethod.regex)
                                .addMember("collectType", "$S", collectMethod.collectType)
                                .build());

                typeBuilder.addMethod(whatsMyName1.build());
                elementClassName = collectMethod.invokeClassName;
            }

            if (elementClassName != null){
                TypeSpec typeSpec = typeBuilder.build();
                String packageName = elementClassName.substring(0, elementClassName.lastIndexOf("."));

                JavaFile javaFile = JavaFile.builder(packageName, typeSpec)
                        .build();
                try {
                    javaFile.writeTo(mFiler);
                } catch (IOException e) {
//                throw new RuntimeException(e);
                }
            }

        }
    }

    String getClassName(String str){
        if (str.contains(".")){
             return str.substring(str.lastIndexOf(".")+1);
        }else{
             return str;
        }
    }

    private static final Pattern classnamePattern = Pattern.compile("<\\? extends .*?>");
    private static final Pattern classnamePattern1 = Pattern.compile("<\\? extends .*?");
    private static final Pattern classnamePattern2 = Pattern.compile("<\\? super .*?>");

    public static String getType(String type) {
        Matcher matcher = classnamePattern.matcher(type);
        if (matcher.find()){
            String type2= matcher.group();
            Matcher matcher1 = classnamePattern1.matcher(type2);
            if (matcher1.find()){
                String realType = matcher1.replaceFirst("");
                Matcher realMatcher = classnamePattern.matcher(realType);
                String realTypeClass;
                if (realMatcher.find()){
                    realTypeClass = realMatcher.replaceFirst("");
                }else {
                    realTypeClass = realType.replaceAll(">","");
                }
                return realTypeClass;
            }
        }
        return null;
    }

    public static boolean checkType(String type) {
        Matcher matcher = classnamePattern2.matcher(type);
        return matcher.find();
    }

    private static String computeMD5(String message) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] digest = md.digest(message.getBytes());
        return bytesToHex(digest);
    }

    private static String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
        }
        return hexString.toString();
    }
    private static MethodSpec.Builder whatsMyName(String name) {
        return MethodSpec.methodBuilder(name)
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy