解析自定义注解代码

2020-11-10  本文已影响0人  菜鸟何时起飞

代码来源:https://github.com/bingoogolapple/BGABadgeView-Android

@AutoService(Processor.class)
public class BGABadgeProcessor extends AbstractProcessor {
    private static final String CLASS_JAVA_DOC = "Generated by BGABadgeView-Android. Do not edit it!\n";
    private static final String PACKAGE_NAME = "cn.bingoogolapple.badgeview";
    private static final String CLASS_PREFIX = "BGABadge";
    private Filer mFileUtils;
    private Elements mElementUtils;
    private Messager mMessager;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mFileUtils = processingEnv.getFiler();
        mElementUtils = processingEnv.getElementUtils();
        mMessager = processingEnv.getMessager();
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    /**
     * 告知 Processor 哪些注解需要处理
     *
     * @return 返回一个 Set 集合,集合内容为自定义注解的包名+类名
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        final Set<String> annotationTypes = new LinkedHashSet<>();
        annotationTypes.add(BGABadge.class.getCanonicalName());
        return annotationTypes;
    }

    /**
     * 所有的注解处理都是从这个方法开始的,当 APT 找到所有需要处理的注解后,会回调这个方法。当没有属于该 Processor 处理的注解被使用时,不会回调该方法
     *
     * @param set              所有的由该 Processor 处理,并待处理的 Annotations「属于该 Processor 处理的注解,但并未被使用,不存在与这个集合里」
     * @param roundEnvironment 表示当前或是之前的运行环境,可以通过该对象查找到注解
     * @return 表示这组 Annotation 是否被这个 Processor 消费,如果消费「返回 true」后续子的 Processor 不会再对这组 Annotation 进行处理
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BGABadge.class);
        if (elements == null || elements.isEmpty()) {
            return true;
        }
        mMessager.printMessage(Diagnostic.Kind.NOTE,
                "====================================== BGABadgeProcessor process START ======================================");
        Set<String> viewClassSet = new HashSet<>();
        parseParams(elements, viewClassSet);
        try {
            generate(viewClassSet);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IOException e) {
            mMessager.printMessage(Diagnostic.Kind.ERROR, "Exception occurred when generating class file.");
            e.printStackTrace();
        }
        mMessager.printMessage(Diagnostic.Kind.NOTE,
                "====================================== BGABadgeProcessor process END ======================================");
        return true;
    }

    private void parseParams(Set<? extends Element> elements, Set<String> viewClassSet) {
        for (Element element : elements) {
            checkAnnotationValid(element, BGABadge.class);
            TypeElement classElement = (TypeElement) element;
            // 获取该注解的值
            BGABadge badgeAnnotation = classElement.getAnnotation(BGABadge.class);
            try {
                badgeAnnotation.value();
            } catch (MirroredTypesException e) {
                List<? extends TypeMirror> typeMirrors = e.getTypeMirrors();
                for (TypeMirror typeMirror : typeMirrors) {
                    DeclaredType classTypeMirror = (DeclaredType) typeMirror;
                    TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement();
                    String qualifiedName = classTypeElement.getQualifiedName().toString();

                    viewClassSet.add(qualifiedName);
                }
            }
        }
    }

    private void generate(Set<String> viewClassSet) throws IllegalAccessException, IOException {
        mMessager.printMessage(Diagnostic.Kind.NOTE, "生成 " + viewClassSet.size() + " 个");
        for (String clazz : viewClassSet) {
            int lastDotIndex = clazz.lastIndexOf(".");
            String superPackageName = clazz.substring(0, lastDotIndex);
            String superClassName = clazz.substring(lastDotIndex + 1);
            String className = CLASS_PREFIX + superClassName;

            mMessager.printMessage(Diagnostic.Kind.NOTE, clazz + " ====> " + className);

            TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(className)
                    .addJavadoc(CLASS_JAVA_DOC)
                    .addModifiers(Modifier.PUBLIC)
                    .superclass(ClassName.get(superPackageName, superClassName))
                    .addSuperinterface(ClassName.get(PACKAGE_NAME, "BGABadgeable"))
                    .addField(ClassName.get(PACKAGE_NAME, "BGABadgeViewHelper"), "mBadgeViewHelper", Modifier.PRIVATE);

            generateMethod(typeBuilder, clazz);

            JavaFile javaFile = JavaFile.builder(PACKAGE_NAME, typeBuilder.build()).build();
            javaFile.writeTo(mFileUtils);
        }
    }

    private void generateMethod(TypeSpec.Builder typeBuilder, String clazz) {
        constructor(typeBuilder, clazz);
        onTouchEvent(typeBuilder);
        callSuperOnTouchEvent(typeBuilder);
        if (isAssignable(clazz, "android.view.ViewGroup")) {
            dispatchDraw(typeBuilder);
        } else {
            onDraw(typeBuilder);
        }
        showCirclePointBadge(typeBuilder);
        showTextBadge(typeBuilder);
        hiddenBadge(typeBuilder);
        showDrawableBadge(typeBuilder);
        setDragDismissDelegate(typeBuilder);
        isShowBadge(typeBuilder);
        isDraggable(typeBuilder);
        isDragging(typeBuilder);
        getBadgeViewHelper(typeBuilder);
    }

    private void constructor(TypeSpec.Builder typeBuilder, String clazz) {
        TypeName contextType = ClassName.get("android.content", "Context");
        TypeName attributeSetType = ClassName.get("android.util", "AttributeSet");
        MethodSpec constructorOne = MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .addParameter(contextType, "context")
                .addStatement("this(context, null)")
                .build();
        MethodSpec constructorTwo = MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .addParameter(contextType, "context")
                .addParameter(attributeSetType, "attrs")
                .addStatement("this(context, attrs, 0)")
                .build();
        MethodSpec.Builder constructorThreeBuilder = MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .addParameter(contextType, "context")
                .addParameter(attributeSetType, "attrs")
                .addParameter(int.class, "defStyleAttr")
                .addStatement("super(context, attrs, defStyleAttr)");
        if (isAssignable(clazz, "android.widget.ImageView") || isAssignable(clazz, "android.widget.RadioButton")) {
            constructorThreeBuilder.addStatement("mBadgeViewHelper = new BGABadgeViewHelper(this, context, attrs, BGABadgeViewHelper.BadgeGravity.RightTop)");
        } else {
            constructorThreeBuilder.addStatement(
                    "mBadgeViewHelper = new BGABadgeViewHelper(this, context, attrs, BGABadgeViewHelper.BadgeGravity.RightCenter)");
        }

        typeBuilder.addMethod(constructorOne)
                .addMethod(constructorTwo)
                .addMethod(constructorThreeBuilder.build());
    }

    private void onTouchEvent(TypeSpec.Builder typeBuilder) {
        MethodSpec methodSpec = MethodSpec.methodBuilder("onTouchEvent")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(ClassName.get("android.view", "MotionEvent"), "event")
                .addStatement("return mBadgeViewHelper.onTouchEvent(event)")
                .returns(boolean.class)
                .build();
        typeBuilder.addMethod(methodSpec);
    }

    private void callSuperOnTouchEvent(TypeSpec.Builder typeBuilder) {
        MethodSpec methodSpec = MethodSpec.methodBuilder("callSuperOnTouchEvent")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(ClassName.get("android.view", "MotionEvent"), "event")
                .addStatement("return super.onTouchEvent(event)")
                .returns(boolean.class)
                .build();
        typeBuilder.addMethod(methodSpec);
    }

    private void onDraw(TypeSpec.Builder typeBuilder) {
        MethodSpec methodSpec = MethodSpec.methodBuilder("onDraw")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(ClassName.get("android.graphics", "Canvas"), "canvas")
                .addStatement("super.onDraw(canvas)")
                .addStatement("mBadgeViewHelper.drawBadge(canvas)")
                .build();
        typeBuilder.addMethod(methodSpec);
    }

    private void dispatchDraw(TypeSpec.Builder typeBuilder) {
        MethodSpec methodSpec = MethodSpec.methodBuilder("dispatchDraw")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(ClassName.get("android.graphics", "Canvas"), "canvas")
                .addStatement("super.dispatchDraw(canvas)")
                .addStatement("mBadgeViewHelper.drawBadge(canvas)")
                .build();
        typeBuilder.addMethod(methodSpec);
    }

    private void showCirclePointBadge(TypeSpec.Builder typeBuilder) {
        MethodSpec methodSpec = MethodSpec.methodBuilder("showCirclePointBadge")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addStatement("mBadgeViewHelper.showCirclePointBadge()")
                .build();
        typeBuilder.addMethod(methodSpec);
    }

    private void showTextBadge(TypeSpec.Builder typeBuilder) {
        MethodSpec methodSpec = MethodSpec.methodBuilder("showTextBadge")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(String.class, "badgeText")
                .addStatement("mBadgeViewHelper.showTextBadge(badgeText)")
                .build();
        typeBuilder.addMethod(methodSpec);
    }

    private void hiddenBadge(TypeSpec.Builder typeBuilder) {
        MethodSpec methodSpec = MethodSpec.methodBuilder("hiddenBadge")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addStatement("mBadgeViewHelper.hiddenBadge()")
                .build();
        typeBuilder.addMethod(methodSpec);
    }

    private void showDrawableBadge(TypeSpec.Builder typeBuilder) {
        MethodSpec methodSpec = MethodSpec.methodBuilder("showDrawableBadge")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(ClassName.get("android.graphics", "Bitmap"), "bitmap")
                .addStatement("mBadgeViewHelper.showDrawable(bitmap)")
                .build();
        typeBuilder.addMethod(methodSpec);
    }

    private void setDragDismissDelegate(TypeSpec.Builder typeBuilder) {
        MethodSpec methodSpec = MethodSpec.methodBuilder("setDragDismissDelegate")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(ClassName.get(PACKAGE_NAME, "BGADragDismissDelegate"), "delegate")
                .addStatement("mBadgeViewHelper.setDragDismissDelegate(delegate)")
                .build();
        typeBuilder.addMethod(methodSpec);
    }

    private void isShowBadge(TypeSpec.Builder typeBuilder) {
        MethodSpec methodSpec = MethodSpec.methodBuilder("isShowBadge")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addStatement("return mBadgeViewHelper.isShowBadge()")
                .returns(boolean.class)
                .build();
        typeBuilder.addMethod(methodSpec);
    }

    private void isDraggable(TypeSpec.Builder typeBuilder) {
        MethodSpec methodSpec = MethodSpec.methodBuilder("isDraggable")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addStatement("return mBadgeViewHelper.isDraggable()")
                .returns(boolean.class)
                .build();
        typeBuilder.addMethod(methodSpec);
    }

    private void isDragging(TypeSpec.Builder typeBuilder) {
        MethodSpec methodSpec = MethodSpec.methodBuilder("isDragging")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addStatement("return mBadgeViewHelper.isDragging()")
                .returns(boolean.class)
                .build();
        typeBuilder.addMethod(methodSpec);
    }

    private void getBadgeViewHelper(TypeSpec.Builder typeBuilder) {
        MethodSpec methodSpec = MethodSpec.methodBuilder("getBadgeViewHelper")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addStatement("return mBadgeViewHelper")
                .returns(ClassName.get(PACKAGE_NAME, "BGABadgeViewHelper"))
                .build();
        typeBuilder.addMethod(methodSpec);
    }

    private boolean checkAnnotationValid(Element annotatedElement, Class clazz) {
        if (annotatedElement.getKind() != ElementKind.CLASS) {
            error(annotatedElement, "%s must be declared on class.", clazz.getSimpleName());
            return false;
        }

        if (annotatedElement.getModifiers().contains(Modifier.PRIVATE)) {
            error(annotatedElement, "%s must can not be private.", ((TypeElement) annotatedElement).getQualifiedName());
            return false;
        }
        return true;
    }

    private void error(Element element, String message, Object... args) {
        if (args.length > 0) {
            message = String.format(message, args);
        }
        mMessager.printMessage(Diagnostic.Kind.ERROR, message, element);
    }

    private boolean isAssignable(String childClazz, String superClazz) {
        return processingEnv.getTypeUtils().isAssignable(
                processingEnv.getElementUtils().getTypeElement(childClazz).asType(),
                processingEnv.getElementUtils().getTypeElement(superClazz).asType());

    }
}
上一篇 下一篇

猜你喜欢

热点阅读