7. ARouter implementation

1. Mapping between Path and Group

There are app, order and personal. Take personal as an example to explain the relationship in detail

1. The teacher becomes a path

public class ARouter$$Path$$personal implements ARouterPath {
  @Override
  public Map<String, RouterBean> getPathMap(a) {
    Map<String, RouterBean> pathMap = new HashMap<>();
    pathMap.put("/personal/Personal_Main2Activity", RouterBean.create();
    pathMap.put("/personal/Personal_MainActivity", RouterBean.create());
    returnpathMap; }}Copy the code

2. Regenerate into groups

public class ARouter$$Group$$personal implements ARouterGroup {
  @Override
  public Map<String, Class<? extends ARouterPath>> getGroupMap() {
    Map<String, Class<? extends ARouterPath>> groupMap = new HashMap<>();
    groupMap.put("personal", ARouter$$Path$$personal.class);
    return groupMap;
  }
Copy the code

2. Manage Path generation

import com.google.auto.service.AutoService;
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 com.squareup.javapoet.WildcardTypeName;
import com.xiangxue.arouter_annotation.ARouter;
import com.xiangxue.arouter_annotation.bean.RouterBean;
import com.xiangxue.arouter_compiler.utils.ProcessorConfig;
import com.xiangxue.arouter_compiler.utils.ProcessorUtils;

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 javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
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.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;

/** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **

// AutoService is a fixed notation
// The @autoService annotation handler in auto-service can be automatically generated for registration
/ / used to generate the meta-inf/services/javax.mail annotation. Processing. The Processor file
@AutoService(Processor.class)

// Allow/support annotation types to be handled by the annotation processor
@SupportedAnnotationTypes({ProcessorConfig.AROUTER_PACKAGE})

// Specify the JDK build version
@SupportedSourceVersion(SourceVersion.RELEASE_7)

// annotate the parameters received by the processor
@SupportedOptions({ProcessorConfig.OPTIONS, ProcessorConfig.APT_PACKAGE})

public class ARouterProcessor extends AbstractProcessor {

    // The utility class that operates on Element (classes, functions, attributes, actually elements)
    private Elements elementTool;

    // Utility class for type(class information), which contains utility methods for manipulating TypeMirror
    private Types typeTool;

    // Message Displays log information
    private Messager messager;

    // File generators, class resources, etc., are the files that need Filer to be generated
    private Filer filer;

    private String options; // The module name passed by each module, for example, app Order personal
    private String aptPackage; // The directory passed from each module is used to uniformly store files generated by APT

    // Store one Path cache one
    // Map<"personal", List<RouterBean>>
    private Map<String, List<RouterBean>> mAllPathMap = new HashMap<>(); // It is currently one

    // Group cache 2
    // Map<"personal", "ARouter$$Path$$personal.class">
    private Map<String, String> mAllGroupMap = new HashMap<>();

    // Performs the same initialization as the onCreate function in the Activity
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);

        elementTool = processingEnvironment.getElementUtils();
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();
        typeTool = processingEnvironment.getTypeUtils();

        // Only by receiving the books passed from the App shell can we prove that our APT environment is completed
        options = processingEnvironment.getOptions().get(ProcessorConfig.OPTIONS);
        aptPackage = processingEnvironment.getOptions().get(ProcessorConfig.APT_PACKAGE);
        messager.printMessage(Diagnostic.Kind.NOTE, ">>>>>>>>>>>>>>>>>>>>>> options:" + options);
        messager.printMessage(Diagnostic.Kind.NOTE, ">>>>>>>>>>>>>>>>>>>>>> aptPackage:" + aptPackage);
        if(options ! =null&& aptPackage ! =null) {
            messager.printMessage(Diagnostic.Kind.NOTE, "APT environment setup completed....");
        } else {
            messager.printMessage(Diagnostic.Kind.NOTE, "APT environment problem, please check options and aptPackage null..."); }}/** * is equivalent to the main function, which starts processing annotations ** The core method of the annotation processor, processes the specific annotations, and generates Java files **@paramSet uses a collection of nodes * that support processing annotations@paramRoundEnvironment is the current or previous runtime environment that can be found through this object's annotations. *@returnTrue indicates that the subsequent processor will not process (it has already processed) */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (set.isEmpty()) {
            messager.printMessage(Diagnostic.Kind.NOTE, "Didn't find anything annotated by @arouter.");
            return false; // No chance to process
        }

        // Get the collection of all elements annotated by @arouter
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);

        // Get the Activity and Callback types from the Element utility class
        TypeElement activityType = elementTool.getTypeElement(ProcessorConfig.ACTIVITY_PACKAGE);
        Display class information (get annotated nodes, class nodes) this is also called a self-describing Mirror
        TypeMirror activityMirror = activityType.asType();

        // Iterate over all class nodes
        for (Element element : elements) {
            // Obtain class node, obtain package node (com.xiangxue.xxxxxx)
            // String packageName = elementTool.getPackageOf(element).getQualifiedName().toString();

            // Get a simple class name, such as MainActivity
            String className = element.getSimpleName().toString();
            messager.printMessage(Diagnostic.Kind.NOTE, "Classes annotated by @aretuer are:" + className); // Print APT to prove that there is no problem

            // Get the annotation
            ARouter aRouter = element.getAnnotation(ARouter.class);

            // Let's practice JavaPoet
            /** * package com.example.helloworld; * * public final class HelloWorld { * public static void main(String[] args) { * System.out.println("Hello, JavaPoet!" ); *} *} */
            / / 1. Methods
            /*MethodSpec mainMethod = MethodSpec.methodBuilder("main") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(void.class) .addParameter(String[].class, "args") .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!" ) .build(); Class TypeSpec helloWorld = TypeSpec. ClassBuilder (" helloWorld ").addModiFIERS (Modifier.PUBLIC, Modifier.FINAL) .addMethod(mainMethod) .build(); JavaFile packagef = javafile.builder ("com.derry.study", helloWorld).build(); // Generate try {packagef.writeto (filer); } catch (IOException e) { e.printStackTrace(); PrintMessage (diagnostic.kind. NOTE, "generation failed, please check code..." ); } * /

            // JavaPoet: JavaPoet: JavaPoet: JavaPoet: JavaPoet: JavaPoet: JavaPoet: JavaPoet
            /* package com.example.helloworld; public final class HelloWorld { public static void main(String[] args) { System.out.println("Hello, JavaPoet!" ); }} * /
            / / method
            /*MethodSpec mainMethod = MethodSpec.methodBuilder("main") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .returns(void.class) .addParameter(System[].class, AddStatement ("$t.ut.println ($S)", System. Class, "AAAAAAAAAAA!") ) .build(); // Class Testapp Testorder TypeSpec testClass = TypeSpec. ClassBuilder ("Test" + options).addMethod(mainMethod) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .build(); JavaFile packagef = javafile.builder ("com.xiangxue.test22", testClass).build(); try { packagef.writeTo(filer); } catch (IOException e) { e.printStackTrace(); PrintMessage (diagno.kind. NOTE, "failed to generate Test file, exception :" + LLDB etMessage()); } * /

            // TODO a series of checks
            // In the loop, encapsulate the "route object"
            RouterBean routerBean = new RouterBean.Builder()
                    .addGroup(aRouter.group())
                    .addPath(aRouter.path())
                    .addElement(element)
                    .build();

            // Classes annotated by ARouter must inherit from activities
            TypeMirror elementMirror = element.asType(); // The details of Main2Activity such as inheriting the Activity
            if (typeTool.isSubtype(elementMirror, activityMirror)) { // activityMirror Android.app. Activity Description
                routerBean.setTypeEnum(RouterBean.TypeEnum.ACTIVITY); // The final proof is the Activity
            } else { // Derry. Java's dry method throws an exception
                // Do not match the exception thrown, use caution here! Consider maintenance issues
                throw new RuntimeException("The @arouter annotation is currently only available on the Activity class");
            }

            if (checkRouterPath(routerBean)) {
                messager.printMessage(Diagnostic.Kind.NOTE, "RouterBean Check Success:" + routerBean.toString());

                // Assign mAllPathMap to the set
                List<RouterBean> routerBeans = mAllPathMap.get(routerBean.getGroup());

                GetGroup () = bean.getGroup() = bean.getGroup()
                if (ProcessorUtils.isEmpty(routerBeans)) { // There is nothing in the warehouse
                    routerBeans = new ArrayList<>();
                    routerBeans.add(routerBean);
                    mAllPathMap.put(routerBean.getGroup(), routerBeans);// Join warehouse 1
                } else{ routerBeans.add(routerBean); }}else { // ERROR An exception occurred during compilation
                messager.printMessage(Diagnostic.Kind.ERROR, "@arouter annotation not configured as specification, e.g. /app/MainActivity"); }}// TODO end for TODO end for TODO

        // mAllPathMap has values in it
        // The definition (generated class file implementation interface) has Path Group
        TypeElement pathType = elementTool.getTypeElement(ProcessorConfig.AROUTER_API_PATH); / / ARouterPath description
        TypeElement groupType = elementTool.getTypeElement(ProcessorConfig.AROUTER_API_GROUP); / / ARouterGroup description

        // TODO first step: series PATH
        try {
            createPathFile(pathType); // Generate the Path class
        } catch (IOException e) {
            e.printStackTrace();
            messager.printMessage(Diagnostic.Kind.NOTE, "Exception E: while generating PATH template" + e.getMessage());
        }

        // TODO step 2: group head
        try {
            createGroupFile(groupType, pathType);
        } catch (IOException e) {
            e.printStackTrace();
            messager.printMessage(Diagnostic.Kind.NOTE, "Exception E: while generating GROUP template" + e.getMessage());
        }

        return true; // pit: the return value must be written to indicate that processing of the @arouter annotation is complete
    }

    /** * Generate routing Group Group file, such as ARouter$$Group$$app *@paramGroupType ARouterLoadGroup Interface information *@paramPathType ARouterLoadPath Indicates the interface information */
    private void createGroupFile(TypeElement groupType, TypeElement pathType) throws IOException {
        // Cache 2 Determines whether there are class files that need to be generated
        if (ProcessorUtils.isEmpty(mAllGroupMap) || ProcessorUtils.isEmpty(mAllPathMap)) return;

        Map
      
       >
      ,>
        TypeName methodReturns = ParameterizedTypeName.get(
                ClassName.get(Map.class),        // Map
                ClassName.get(String.class),    // Map<String,

                // Class
       >
                ParameterizedTypeName.get(ClassName.get(Class.class),
                        // ? extends ARouterPath
                        WildcardTypeName.subtypeOf(ClassName.get(pathType))) // ? extends ARouterLoadPath
                        / / WildcardTypeName supertypeOf () doing the experiment? super

                // Final: Map
      
       >
      ,>
        );

        Public Map
      
       > getGroupMap() {
      ,>
        MethodSpec.Builder methodBuidler = MethodSpec.methodBuilder(ProcessorConfig.GROUP_METHOD_NAME) / / the method name
                .addAnnotation(Override.class) // Override annotation @override
                .addModifiers(Modifier.PUBLIC) // public modifier
                .returns(methodReturns); // The method returns a value

        // Map<String, Class<? extends ARouterPath>> groupMap = new HashMap<>();
        methodBuidler.addStatement("$T<$T, $T> $N = new $T<>()",
                ClassName.get(Map.class),
                ClassName.get(String.class),

                // Class
       difficulty
                ParameterizedTypeName.get(ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(pathType))), // ? extends ARouterPath
                        ProcessorConfig.GROUP_VAR1,
                        ClassName.get(HashMap.class));

        // groupMap.put("personal", ARouter$$Path$$personal.class);
        //	groupMap.put("order", ARouter$$Path$$order.class);
        for (Map.Entry<String, String> entry : mAllGroupMap.entrySet()) {
            methodBuidler.addStatement("$N.put($S, $T.class)",
                    ProcessorConfig.GROUP_VAR1, // groupMap.put
                    entry.getKey(), // order, personal ,app
                    ClassName.get(aptPackage, entry.getValue()));
        }

        // return groupMap;
        methodBuidler.addStatement("return $N", ProcessorConfig.GROUP_VAR1);

        ARouter$$Group$$+ personal
        String finalClassName = ProcessorConfig.GROUP_FILE_NAME + options;

        messager.printMessage(Diagnostic.Kind.NOTE, "APT generates routing Group class file:" +
                aptPackage + "." + finalClassName);

        // Generate class file: ARouter$$Group$$app
        JavaFile.builder(aptPackage, / / package name
                TypeSpec.classBuilder(finalClassName) / / the name of the class
                .addSuperinterface(ClassName.get(groupType)) // Implements the ARouterLoadGroup interface implements ARouterGroup
                .addModifiers(Modifier.PUBLIC) // public modifier
                .addMethod(methodBuidler.build()) // Method construction (method parameters + method body)
                .build()) // The class is built
                .build() // JavaFile is built
                .writeTo(filer); // The file generator starts generating class files
    }

    /** * series Path class generation work *@paramPathType ARouterPath High-level standard *@throws IOException
     */
    private void createPathFile(TypeElement pathType) throws IOException {
        // Check whether there are files to be generated in the map repository
        if (ProcessorUtils.isEmpty(mAllPathMap)) {
            return; // There are no values in the cache
        }

        // Generate code in reverse order

        // Any class type must be wrapped
        // Map<String, RouterBean>
        TypeName methodReturn = ParameterizedTypeName.get(
                  ClassName.get(Map.class),         // Map
                  ClassName.get(String.class),      // Map<String,
                  ClassName.get(RouterBean.class)   // Map<String, RouterBean>
        );

        // Go through the store app,order,personal
        for (Map.Entry<String, List<RouterBean>> entry : mAllPathMap.entrySet()) { // personal
            / / 1. Methods
            MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(ProcessorConfig.PATH_METHOD_NAME)
                    .addAnnotation(Override.class) // Add the @override annotation to the method
                    .addModifiers(Modifier.PUBLIC) // public modifier
                    .returns(methodReturn) // Add Map
      
        to the method
      ,>
                    ;

            // Map
      
        pathMap = new HashMap<>(); $N == {$N == {$N == {$N == {$N == {$N == {$N ==
      ,>
            methodBuilder.addStatement("$T<$T, $T> $N = new $T<>()",
                    ClassName.get(Map.class),           // Map
                    ClassName.get(String.class),        // Map<String,
                    ClassName.get(RouterBean.class),    // Map<String, RouterBean>
                    ProcessorConfig.PATH_VAR1,          // Map<String, RouterBean> pathMap
                    ClassName.get(HashMap.class)        // Map<String, RouterBean> pathMap = new HashMap<>();
                    );

            // It must loop because there are multiple
            // pathMap.put("/personal/Personal_Main2Activity", RouterBean.create(RouterBean.TypeEnum.ACTIVITY,
            // Personal_Main2Activity.class);
            // pathMap.put("/personal/Personal_MainActivity", RouterBean.create(RouterBean.TypeEnum.ACTIVITY));
            List<RouterBean> pathList = entry.getValue();
            /** $L == TypeEnum.ACTIVITY */
            // Personal details
            for (RouterBean bean : pathList) {
                methodBuilder.addStatement("$N.put($S, $T.create($T.$L, $T.class, $S, $S))",
                        ProcessorConfig.PATH_VAR1, // pathMap.put
                        bean.getPath(), // "/personal/Personal_Main2Activity"
                        ClassName.get(RouterBean.class), // RouterBean
                        ClassName.get(RouterBean.TypeEnum.class), // RouterBean.Type
                        bean.getTypeEnum(), // Enumeration type: ACTIVITY
                        ClassName.get((TypeElement) bean.getElement()), // MainActivity.class Main2Activity.class
                        bean.getPath(), / / the path name
                        bean.getGroup() / / group name
                        );
            } // TODO end for

            // return pathMap;
            methodBuilder.addStatement("return $N", ProcessorConfig.PATH_VAR1);

            // TODO note: not as before, 1. Method, 2. Class 3. Implements, so methods and classes need to be generated as one, which is a special case

            ARouter$$Path$$personal
            String finalClassName = ProcessorConfig.PATH_FILE_NAME + entry.getKey();

            messager.printMessage(Diagnostic.Kind.NOTE, "APT generates route Path class file:" +
                    aptPackage + "." + finalClassName);

            // Generate class file: ARouter$$Path$$personal
            JavaFile.builder(aptPackage, // Directory where package name APT is stored
                    TypeSpec.classBuilder(finalClassName) / / the name of the class
                            .addSuperinterface(ClassName.get(pathType)) // Implements the ARouterLoadPath interface implements ARouterPath==pathType
                            .addModifiers(Modifier.PUBLIC) // public modifier
                            .addMethod(methodBuilder.build()) // Method construction (method parameters + method body)
                            .build()) // The class is built
                    .build() // JavaFile is built
                    .writeTo(filer); // The file generator starts generating class files

            // Store 2 cache 2 is very important step, note: the PATH file is generated before assigning routing group mAllGroupMapmAllGroupMap.put(entry.getKey(), finalClassName); }}/ * * *@ARouterThe value of the annotation, if group is not specified, is truncated from the required path@paramBean routing details, and finally entity encapsulation class */
    private final boolean checkRouterPath(RouterBean bean) {
        String group = bean.getGroup(); // Remember: "app" "order" "personal"
        String path = bean.getPath();   // "/app/MainActivity" "/order/Order_MainActivity" "/personal/Personal_MainActivity"

        / / check
        // the path value in @arouter annotations must start with a/(mimics the Ali ARouter specification)
        if(ProcessorUtils.isEmpty(path) || ! path.startsWith("/")) {
            messager.printMessage(Diagnostic.Kind.ERROR, "The path value in the @arouter annotation must start with a /.");
            return false;
        }

        // If the developer code is: path = "/MainActivity", the last/must be at the first bit of the string
        if (path.lastIndexOf("/") = =0) {
            // Architects define specifications for developers to follow
            messager.printMessage(Diagnostic.Kind.ERROR, "@arouter annotation not configured as specification, e.g. /app/MainActivity");
            return false;
        }

        // From the first/to the second/in the middle, e.g. /app/MainActivity intercepts app,order,personal as group
        String finalGroup = path.substring(1, path.indexOf("/".1));

        // app,order,personal == options

        // the group in the @arouter annotation has an assignment
        if(! ProcessorUtils.isEmpty(group) && ! group.equals(options)) {// Architects define specifications for developers to follow
            messager.printMessage(Diagnostic.Kind.ERROR, "The group value in the @arouter annotation must match the submodule name!");
            return false;
        } else {
            bean.setGroup(finalGroup);
        }

        // If you do return ture RouterBean.group XXXXX assignment is successful, no problem
        return true; }}Copy the code

ProcessorConfig


public interface ProcessorConfig {

    // @arouter annotated package name + class name
    String AROUTER_PACKAGE =  "com.xiangxue.arouter_annotation.ARouter";

    // Receive the TAG of the parameter
    String OPTIONS = "moduleName"; // Students: The goal is to receive each module name
    String APT_PACKAGE = "packageNameForAPT"; // APT package name (APT package name)

    // String Full class name
    public static final String STRING_PACKAGE = "java.lang.String";

    // Name of the Activity class
    public static final String ACTIVITY_PACKAGE = "android.app.Activity";

    // ARouter API package name
    String AROUTER_API_PACKAGE = "com.xiangxue.arouter_api";

    // ARouterGroup high-level standard for the ARouter API
    String AROUTER_API_GROUP = AROUTER_API_PACKAGE + ".ARouterGroup";

    // ARouterPath high-level standard for the ARouter API
    String AROUTER_API_PATH = AROUTER_API_PACKAGE + ".ARouterPath";

    // Route group, the method name in Path
    String PATH_METHOD_NAME = "getPathMap";

    // The method name in the routing Group
    String GROUP_METHOD_NAME = "getGroupMap";

    // Routing group, the variable name in Path is 1
    String PATH_VAR1 = "pathMap";

    // Routing Group, the variable name in Group is 1
    String GROUP_VAR1 = "groupMap";

    // Routing group, PATH specifies the name of the file to be generated
    String PATH_FILE_NAME = "ARouter$$Path$$";

    // Routing GROUP, GROUP Specifies the name of the file to be generated
    String GROUP_FILE_NAME = "ARouter$$Group$$";
}

Copy the code

ProcessorUtils

import java.util.Collection;
import java.util.Map;

/** * string, set null tool */
public final class ProcessorUtils {


    public static boolean isEmpty(CharSequence cs) {
        return cs == null || cs.length() == 0;
    }

    public static boolean isEmpty(Collection
        coll) {
        return coll == null || coll.isEmpty();
    }

    public static boolean isEmpty(finalMap<? ,? > map) {
        return map == null|| map.isEmpty(); }}Copy the code

3.Group generation management

4. Communication transmission

5. Dynamically generate lazy loading parameters to receive

1. Arouter_annotation Adds the Parameter annotation

/** * <strong> </strong> * <ul> * <li>@Target(elementtype.type) // Interface, class, enumeration, annotation </li> * <li>@Target(elementtype.field) // Attributes, enumerated constants </li> * <li>@Target(ElementType.METHOD) // METHOD </li> * <li>@Target(elementtype.parameter) // Method PARAMETER </li> * <li>@Target(elementtype. CONSTRUCTOR) // CONSTRUCTOR </li> * <li>@Target(elementtype.local_variable) </li> * <li>@Target</li> * <li>@Target// PACKAGE </li> * <li>@Retention(retentionPolicy.runtime) <br> annotations exist in the class bytecode file and can be reflected by the JVM when it loads. SOURCE < CLASS < RUNTIME * 1, generally if you need to obtain the annotation information dynamically at RUNTIME, use RUNTIME annotations * 2, to do some pre-processing at compile time, such as ButterKnife, use CLASS annotations. Annotations exist in the class file, but are discarded at runtime@Override, annotated with SOURCE code. Annotations exist only at the source level and are discarded at compile time
@Target(ElementType.FIELD) // This annotation applies to fields above the class
@Retention(RetentionPolicy.CLASS) // To do some preprocessing at compile time, annotations will exist in the class file
public @interface Parameter {

    // If the annotation value of name is left blank, the attribute name is the key
    // Get the pass parameter value from the getIntent() method
    String name(a) default "";
}

Copy the code

Arouter_api Definition of ParameterGet standard rules

public interface ParameterGet {

    /** * Target object. Attribute name = getIntent(). Attribute type... Complete the assignment *@paramTargetParameter Target object: for example, those properties */ in MainActivity
    void getParameter(Object targetParameter);

}

Copy the code

3. Arouter_compiler ParameterProcessor Generates the template code you just parsed

import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.xiangxue.arouter_annotation.Parameter;
import com.xiangxue.arouter_compiler.utils.ProcessorConfig;
import com.xiangxue.arouter_compiler.utils.ProcessorUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
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;

@AutoService(Processor.class) / / open
@SupportedAnnotationTypes({ProcessorConfig.PARAMETER_PACKAGE}) // Notes on our services
@SupportedSourceVersion(SourceVersion.RELEASE_7) // Class: This is mandatory
public class ParameterProcessor extends AbstractProcessor {

    private Elements elementUtils; / / class information
    private Types typeUtils;  // Specific type
    private Messager messager; / / log
    private Filer filer;  / / generator

    // Temporary map store, used to store the set of attributes annotated by @parameter, traversed during class file generation
    // key: class node, value: set of attributes annotated by @parameter
    private Map<TypeElement, List<Element>> tempParameterMap = new HashMap<>();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        elementUtils = processingEnvironment.getElementUtils();
        typeUtils = processingEnvironment.getTypeUtils();
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        // Since false is returned, no code is needed
        /*if (set.isempty ()) {messager.printMessage(diagno.kind.note, "there is no place annotated by @arouter "); /* If (set.isempty ()) {messager.printMessage(diagno.kind.note," there is no place annotated by @arouter "); return false; // No chance to process}*/

        // When scanning, see where the @parameter annotation is used
        if(! ProcessorUtils.isEmpty(set)) {// Get the set of all elements annotated by @parameter
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Parameter.class);

            if(! ProcessorUtils.isEmpty(elements)) {// TODO stores related information to the warehouse
                for (Element element : elements) { // Element == name, sex, age

                    // The last node of the field node ==Key
                    // The annotation is at the top of the attribute. The parent of the attribute node is the class node
                    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

                    // enclosingElement == Personal_MainActivity == key

                    if (tempParameterMap.containsKey(enclosingElement)) {
                        tempParameterMap.get(enclosingElement).add(element);
                    } else { // There is no key Personal_MainActivity
                        List<Element> fields = new ArrayList<>();
                        fields.add(element);
                        tempParameterMap.put(enclosingElement, fields); // add cache}}// For end cache tempParameterMap has a value

                // TODO generates class files
                // Determine whether there is a class file that needs to be generated
                if (ProcessorUtils.isEmpty(tempParameterMap)) return true;

                TypeElement activityType = elementUtils.getTypeElement(ProcessorConfig.ACTIVITY_PACKAGE);
                TypeElement parameterType = elementUtils.getTypeElement(ProcessorConfig.AROUTER_AIP_PARAMETER_GET);

                // Generate method
                // Object targetParameter
                ParameterSpec parameterSpec = ParameterSpec.builder(TypeName.OBJECT, ProcessorConfig.PARAMETER_NAME).build();

                // Loop over the cache tempParameterMap
                // The @parameter annotation may be used in many places, so you need to traverse the repository
                for (Map.Entry<TypeElement, List<Element>> entry : tempParameterMap.entrySet()) {
                    / / key: Personal_MainActivity
                    Value: / / / the name, sex, age,
                    TypeElement typeElement = entry.getKey();

                    // An error is reported when the Activity is not active
                    // If the type of the class name does not match the Activity type
                    if(! typeUtils.isSubtype(typeElement.asType(), activityType.asType())) {throw new RuntimeException("The @parameter annotation is currently only available on the Activity class");
                    }

                    / / the Activity is
                    Personal_MainActivity == Personal_MainActivity
                    ClassName className = ClassName.get(typeElement);

                    // The method was successfully generated
                    ParameterFactory factory = new ParameterFactory.Builder(parameterSpec)
                            .setMessager(messager)
                            .setClassName(className)
                            .build();

                    // Personal_MainActivity t = (Personal_MainActivity) targetParameter;
                    factory.addFirstStatement();

                    // The number of lines
                    for (Element element : entry.getValue()) {
                        factory.buildStatement(element);
                    }

                    // The resulting class name ($$Parameter) for example, Personal_MainActivity$$Parameter
                    String finalClassName = typeElement.getSimpleName() + ProcessorConfig.PARAMETER_FILE_NAME;
                    messager.printMessage(Diagnostic.Kind.NOTE, "APT generate parameter class file:" +
                            className.packageName() + "." + finalClassName);

                    // Start generating a file, such as PersonalMainActivity$$Parameter
                    try {
                        JavaFile.builder(className.packageName(), / / package name
                                TypeSpec.classBuilder(finalClassName) / / the name of the class
                                        .addSuperinterface(ClassName.get(parameterType)) // implements ParameterGet to implement ParameterLoad
                                        .addModifiers(Modifier.PUBLIC) // public modifier
                                        .addMethod(factory.build()) // Method construction (method parameters + method body)
                                        .build()) // The class is built
                                .build() // JavaFile is built
                                .writeTo(filer); // The file generator starts generating class files
                    } catch(Exception e) { e.printStackTrace(); }}}}If (set.isempty ()) {set.isempty ()
        // 4 lessons false Execute once
        return false; // A few years ago, the research seems to remember to execute once}}Copy the code

4. Write arouter_API ParameterManager

import android.app.Activity;
import android.util.LruCache;

/** * Parameter load manager * TODO this is used to receive parameters ** Step 1: Find Personal_MainActivity$$Parameter Use Personal_MainActivity$$Parameter this to give it */
public class ParameterManager {

    private static ParameterManager instance;

    // private boolean isCallback;

    public static ParameterManager getInstance(a) {
        if (instance == null) {
            synchronized (ParameterManager.class) {
                if (instance == null) {
                    instance = newParameterManager(); }}}return instance;
    }

    // LRU cache key= class name value= parameter loading interface
    private LruCache<String, ParameterGet> cache;

    private ParameterManager(a) {
        cache = new LruCache<>(100);
    }

    // Why do we need to splice, this splice is to find him
    static final String FILE_SUFFIX_NAME = "$$Parameter"; // For this effect: Order_MainActivity + $$Parameter

    // The user only needs to call this method to receive arguments
    public void loadParameter(Activity activity) { // Get Personal_MainActivity
        String className = activity.getClass().getName(); // className == Personal_MainActivity

        ParameterGet parameterLoad = cache.get(className); If yes, if no
        if (null == parameterLoad) { // There is nothing in the cache to improve performance
            // splice such as Order_MainActivity + $$Parameter
            // Class load Order_MainActivity + $$Parameter
            try {
                // Class load Personal_MainActivity + $$ParameterClass<? > aClass = Class.forName(className + FILE_SUFFIX_NAME);ParameterLoad = Implements the interface class Personal_MainActivity
                parameterLoad = (ParameterGet) aClass.newInstance();
                cache.put(className, parameterLoad); // Save to cache
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        parameterLoad.getParameter(activity); // The final execution will execute our generated class}}Copy the code

5. Use the following RouterManager composition, such as pausing and accepting parameters

6. Componentized routing manager writing

1. Write arouter_API RouterManager

import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import android.util.LruCache;

import androidx.annotation.RequiresApi;

import com.xiangxue.arouter_annotation.bean.RouterBean;

ARouter$$Group$$personal --> ARouter$$Path$$personal * Use ARouter$$Group$$personal --> ARouter$$Path$$personal */
public class RouterManager {

    private String group; App, order, personal...
    private String path;  // Route path for example: /order/Order_MainActivity

    /** * select ARouter$$Group$$personal from ARouter$$Path$$personal * 2. Personal_mainactivity. class = personal_mainactivity. class

    // Singleton mode
    private static RouterManager instance;

    public static RouterManager getInstance(a) {
        if (instance == null) {
            synchronized (RouterManager.class) {
                if (instance == null) {
                    instance = newRouterManager(); }}}return instance;
    }

    // Provide performance LRU cache
    private LruCache<String, ARouterGroup> groupLruCache;
    private LruCache<String, ARouterPath> pathLruCache;

    // For splicing, for example, ARouter$$Group$$personal
    private final static String FILE_GROUP_NAME = "ARouter$$Group$$";

    private RouterManager(a) {
        groupLruCache = new LruCache<>(100);
        pathLruCache = new LruCache<>(100);
    }

    / * * * *@paramPath for example: /order/Order_MainActivity * *@return* /
    public BundleManager build(String path) {
        if(TextUtils.isEmpty(path) || ! path.startsWith("/")) {
            throw new IllegalArgumentException(/order/Order_MainActivity);
        }

        // Students can add their own
        // ...

        if (path.lastIndexOf("/") = =0) { // Just write one /
            throw new IllegalArgumentException(/order/Order_MainActivity);
        }

        // Intercept the group name /order/Order_MainActivity finalGroup=order
        String finalGroup = path.substring(1, path.indexOf("/".1)); // finalGroup = order

        if (TextUtils.isEmpty(finalGroup)) {
            throw new IllegalArgumentException(/order/Order_MainActivity);
        }

        // Prove that there is no problem, no exception is thrown
        this.path =  path;  // Final effect: /order/Order_MainActivity
        this.group = finalGroup; For example: order, personal

        // There is no problem with grooup and path app, order, personal /app/MainActivity

        return new BundleManager(); // Create a pattern Builder
    }

    // Real navigation
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    public Object navigation(Context context, BundleManager bundleManager) {
        ARouter$$Group$$personal address ARouter$$Group$$order ARouter$$Group$$app
        String groupClassName = context.getPackageName() + "." + FILE_GROUP_NAME + group;
        Log.e("derry >>>"."navigation: groupClassName=" + groupClassName);


        try {
            // TODO first reads the routing Group class file
            ARouterGroup loadGroup = groupLruCache.get(group);
            if (null == loadGroup) { // There is nothing in the cache
                // Load APT routing Group class file e.g. ARouter$$Group$$orderClass<? > aClass = Class.forName(groupClassName);// Initialize the class file
                loadGroup = (ARouterGroup) aClass.newInstance();

                // Save to cache
                groupLruCache.put(group, loadGroup);
            }

            if (loadGroup.getGroupMap().isEmpty()) {
                throw new RuntimeException("Routing table Group scrapped..."); // Group failed to load the class
            }

            // TODO reads the route Path class file
            ARouterPath loadPath = pathLruCache.get(path);
            if (null == loadPath) { // There is no Path in the cache
                // 1.invoke loadGroup
                // 2.Map<String, Class<? extends ARouterLoadPath>>
                Class<? extends ARouterPath> clazz = loadGroup.getGroupMap().get(group);

                // get ARouter$$Path$$personal
                loadPath = clazz.newInstance();

                // Save to cache
                pathLruCache.put(path, loadPath);
            }

            // TODO step 3 jump
            if(loadPath ! =null) { / / robust
                if (loadPath.getPathMap().isEmpty()) { // pathMap.get("key") == null
                    throw new RuntimeException("Routing table Path is dead...");
                }

                // Execute the operation last
                RouterBean routerBean = loadPath.getPathMap().get(path);

                if(routerBean ! =null) {
                    switch (routerBean.getTypeEnum()) {
                        case ACTIVITY:
                            Intent intent = new Intent(context, routerBean.getMyClass()); // For example, getClazz == order_mainactivity.class
                            intent.putExtras(bundleManager.getBundle()); // Carry parameters
                            context.startActivity(intent, bundleManager.getBundle());
                            break;
                        // Students can expand the types themselves}}}}catch (Exception e) {
            e.printStackTrace();
        }

        return null; }}Copy the code

2. Writing of Arouter_API BundleManager

import android.content.Context;
import android.os.Build;
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;

/** * when jumping, used for passing arguments */
public class BundleManager {

    // The Intent carries the value, which is saved here
    private Bundle bundle = new Bundle();

    public Bundle getBundle(a) {
        return this.bundle;
    }

    // Provide a method to the outside world that can carry parameters
    public BundleManager withString(@NonNull String key, @Nullable String value) {
        bundle.putString(key, value);
        return this; // The chain-call effect mimics the open source framework
    }

    public BundleManager withBoolean(@NonNull String key, @Nullable boolean value) {
        bundle.putBoolean(key, value);
        return this;
    }

    public BundleManager withInt(@NonNull String key, @Nullable int value) {
        bundle.putInt(key, value);
        return this;
    }

    public BundleManager withBundle(Bundle bundle) {
        this.bundle = bundle;
        return this;
    }

    // Derry only wrote here, students can add their own...

    // Complete the jump directly
    public Object navigation(Context context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            return RouterManager.getInstance().navigation(context, this);
        }
        return null; }}Copy the code

3. Demonstrate routing communication

 RouterManager.getInstance()
                .build("/personal/Personal_MainActivity")
                .withString("name"."Li Yuanba")
                .withString("sex"."Male")
                .withInt("age".99)
                .navigation(this);
Copy the code

7. Routing navigation