background

What if the dot fields are repeated when you do data typing?

Such as:

Declare statistics dot field.

    /**
     * 页面1打开次数
     */
    public static final int COUNT_KEY_1 = 10007;
    /**
     * 页面2打开次数
     */
    public static final int COUNT_KEY_2 = 10008;
Copy the code

Students in other teams doing advertising modules do not know that 10007 and 10008 fields are occupied. Went on to say

    /** ** AD open times */
    public static final int COUNT_KEY_AD = 10008;
Copy the code

At this time, field 10008 is contaminated, and the number of times of opening advertisement and page 2 cannot be accurately counted.

In practice, two scenarios are prone to appeal accidents.

  1. Multiple programmers develop the dotting requirements in parallel, and the dotting files are automatically merged when the code is merged.
  2. Different modules do not know what fields have been used by each other.

Expect a tool that can detect fields that are defined twice

solution

Convert to a syntax error

IOS students can use enum syntax feature + protocol to resolve. Example code is as follows:

enum Model: Int: ModelProtocol {
    case home = 11
    case sounds = 12
    
    public func toCode(a) -> Int64 {
        self.rawValue
    }
}


enum SleepPlan: Int64.EventCodeProtocol {
    var model: Model {
        return .home
    }
    case click = 0001
    case show = 0002
    
    public func toCode(a) -> Int64 {
        self.rawValue
    }
    
    public func type(a) -> EventTypeCode {
        .count
    }
    public func immediately(a) -> Bool {
        true}}protocol ModelProtocol {
    func toCode(a) -> Int64
}

extension EventCodeProtocol {
    var model: ModelProtocol
}

func log(code: EventCodeProtocol) {
    code.model.toCode() * 1000 + code.toCode()
}
Copy the code

Exchange under the implementation principle, is to let the definition of repeated dot trigger a compiler syntax error, so that the compiler failed to compile, so that developers know that there is a field repeated definition.

So how to develop on Android,Java can use switch… case… Syntax cannot have the property of repeating field declarations. Turn the appeal question into the following code

So the question is:How do I implement this check function?Writing by hand is time-consuming, labor-intensive and prone to making mistakes.

APT to realize

Instead of maintaining this function manually,APT has the advantage of being generated automatically and thus less prone to error.

After referring to the implementation of ButterKnife and EventBus, the realization steps of APT are summarized as follows:

1. Make notes

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface Statistics {
    Type type(a);

    public enum Type {
        String, Int
    }
}
Copy the code

Then reference it in the original project

@Statistics(type = Statistics.Type.Int)
public class StaticDemo2 {
Copy the code

The purpose of this step is to let APT know which classes to handle.

2. Construct a handler

@AutoService(Processor.class)
public class StatisticsProcessor extends AbstractProcessor {
    private Filer mFilerUtils;       // File management tool class
    private Types mTypesUtils;    // Type handling utility classes
    private Elements mElementsUtils;  // Element handles the utility class
    static Set<String> typeSet = new HashSet<>();
    private Map<Statistics.Type, List<String>> dataMap = new HashMap<>();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);

        mFilerUtils = processingEnv.getFiler();
        mTypesUtils = processingEnv.getTypeUtils();
        mElementsUtils = processingEnv.getElementUtils();

    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
        System.out.println("start process");
        if (set! =null && set.size() ! =0) {
            Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Statistics.class);
            categories(elements);
            if (dataMap.size() > 0) {
                Element simpleElement = elements.iterator().next();
                String code = generateCode(simpleElement, dataMap.get(Statistics.Type.String), dataMap
                        .get(Statistics.Type.Int));
                String helperClassName = "StatisticsChecker"; // Build the name of the helper class to be generated
                try {
                    JavaFileObject jfo = mFilerUtils.createSourceFile(helperClassName);
                    Writer writer = jfo.openWriter();
                    writer.write(code);
                    writer.flush();
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return true; }}return false;
    }


    private void categories(Set<? extends Element> elements) {
        if (typeSet.size() == 0) {
            Statistics.Type[] types = Statistics.Type.values();
            for (int i = 0; i < types.length; i++) { typeSet.add(types[i].name().toLowerCase()); }}for (Element element : elements) {
            Statistics statistics = element.getAnnotation(Statistics.class);
            Symbol.ClassSymbol cE = (Symbol.ClassSymbol) element;
            String preName = cE.className();
            List eleSubList = element.getEnclosedElements();

            Statistics.Type type = statistics.type();
            List list = dataMap.get(type);
            if (list == null) {
                list = new ArrayList<String>();
                dataMap.put(type, list);
            }

            for (Object e : eleSubList) {
                if (e instanceof Symbol.VarSymbol) {
                    Symbol.VarSymbol el = (Symbol.VarSymbol) e;
                    if (typeSet.contains(el.type.tsym.name.toString().toLowerCase())) {
                        // We can see that there are no duplicate fields in this code, but to make the results more intuitive, we should generate this code
                        list.add(preName + "." + (el).getSimpleName().toString());
                    }
                }
            }
        }
    }

    private String generateCode(Element typeElement, List<String> listStr, List<String> listInt) {
        String packageName = ((PackageElement) mElementsUtils.getPackageOf(typeElement))
                .getQualifiedName().toString(); // Get the package name to bind to
        String helperClassName = "StatisticsChecker";   // The name of the help class to be generated

        StringBuilder builder = new StringBuilder();
        builder.append("package ").append(packageName).append("; \n");
        builder.append("\n");
        builder.append("public class ").append(helperClassName);
        builder.append(" {\n");
        builder.append("\tvoid check() {\n");

        if(listStr ! =null && listStr.size() > 0) {
            builder.append("\t\tString countStr = null; \n");
            builder.append("\t\tswitch (countStr) {\n");

            for (String caseValue : listStr) {
                builder.append("\t\t\t");
                builder.append(String.format("case %s:\n", caseValue));
            }
            builder.append("\t\t}\n");
        }

        if(listInt ! =null && listInt.size() > 0) {
            builder.append("\t\tint countInt = 0; \n");
            builder.append("\t\tswitch (countInt) {\n");

            for (String caseValue : listInt) {
                builder.append("\t\t\t");
                builder.append(String.format("case %s:\n", caseValue));
            }
            builder.append("\t\t}\n");
        }


        builder.append("\t}\n");
        builder.append("}\n");

        returnbuilder.toString(); }}Copy the code

This is a template that inherits the AbstractProcessor class and implements its Process method. The goal is to generate code automatically at compile time.

3. The injection

ButterKnife also has an injection procedure, which calls automatically generated code in the original project, but we use it for static inspection only.

conclusion

To this APT automatic detection of repeated fields is realized, but there are still several problems not solved:

  1. AbstractProcessor process is a process function. My way is to debug and write at the same time, so how to debug APT code?
  2. Once I’ve written this code, how can I reuse it for other projects? APT modular development + generate JCenter dependencies
  3. This is just an implementation of Java, what if koltin wrote the integration project or Java & Kotlin mixed development? APT supports Koltin and project dependencies.