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

android.databinding.annotationprocessor.ProcessBindable Maven / Gradle / Ivy

Go to download

The annotation processor for Data Binding. Generates binding classes for runtime.

The newest version!
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.databinding.annotationprocessor;

import android.databinding.Bindable;
import android.databinding.BindingBuildInfo;
import android.databinding.tool.CompilerChef.BindableHolder;
import android.databinding.tool.DataBindingCompilerArgs;
import android.databinding.tool.util.GenerationalClassUtil;
import android.databinding.tool.util.L;
import android.databinding.tool.util.Preconditions;
import android.databinding.tool.writer.BRWriter;
import android.databinding.tool.writer.JavaFileWriter;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.util.Types;

// binding app info and library info are necessary to trigger this.
public class ProcessBindable extends ProcessDataBinding.ProcessingStep implements BindableHolder {
    Intermediate mProperties;
    HashMap> mLayoutVariables = new HashMap>();

    @Override
    public boolean onHandleStep(RoundEnvironment roundEnv, ProcessingEnvironment processingEnv,
            DataBindingCompilerArgs args) {
        if (mProperties == null) {
            mProperties = new IntermediateV1(args.getModulePackage());
            mergeLayoutVariables();
            mLayoutVariables.clear();
            TypeElement observableType = processingEnv.getElementUtils().
                    getTypeElement("android.databinding.Observable");
            Types typeUtils = processingEnv.getTypeUtils();
            for (Element element : AnnotationUtil
                    .getElementsAnnotatedWith(roundEnv, Bindable.class)) {
                Element enclosingElement = element.getEnclosingElement();
                ElementKind kind = enclosingElement.getKind();
                if (kind != ElementKind.CLASS && kind != ElementKind.INTERFACE) {
                    L.e("Bindable must be on a member field or method. The enclosing type is %s",
                            enclosingElement.getKind());
                }
                TypeElement enclosing = (TypeElement) enclosingElement;
                if (!typeUtils.isAssignable(enclosing.asType(), observableType.asType())) {
                    L.e("Bindable must be on a member in an Observable class. %s is not Observable",
                            enclosingElement.getSimpleName());
                }
                String name = getPropertyName(element);
                if (name != null) {
                    Preconditions
                            .checkNotNull(mProperties, "Must receive app / library info before "
                                    + "Bindable fields.");
                    mProperties.addProperty(enclosing.getQualifiedName().toString(), name);
                }
            }
            GenerationalClassUtil.writeIntermediateFile(mProperties.getPackage(),
                    createIntermediateFileName(mProperties.getPackage()), mProperties);
            generateBRClasses(args, mProperties.getPackage());
        }
        return false;
    }

    @Override
    public void addVariable(String variableName, String containingClassName) {
        HashSet variableNames = mLayoutVariables.get(containingClassName);
        if (variableNames == null) {
            variableNames = new HashSet();
            mLayoutVariables.put(containingClassName, variableNames);
        }
        variableNames.add(variableName);
    }

    @Override
    public void onProcessingOver(RoundEnvironment roundEnvironment,
            ProcessingEnvironment processingEnvironment, DataBindingCompilerArgs args) {
    }

    private String createIntermediateFileName(String appPkg) {
        return appPkg + GenerationalClassUtil.ExtensionFilter.BR.getExtension();
    }

    private void generateBRClasses(DataBindingCompilerArgs compilerArgs, String pkg) {
        DataBindingCompilerArgs.Type artifactType = compilerArgs.artifactType();
        L.d("************* Generating BR file %s. use final: %s", pkg, artifactType.name());
        HashSet properties = new HashSet();
        mProperties.captureProperties(properties);
        Collection previousIntermediates = loadPreviousBRFiles(pkg + ".BR");
        for (Intermediate intermediate : previousIntermediates) {
            intermediate.captureProperties(properties);
        }
        final JavaFileWriter writer = getWriter();
        boolean useFinal = artifactType == DataBindingCompilerArgs.Type.APPLICATION
                || compilerArgs.isTestVariant();
        BRWriter brWriter = new BRWriter(properties, useFinal);
        writer.writeToFile(pkg + ".BR", brWriter.write(pkg));
        // generate BR files for others only if we are compiling an app
        // if we are compiling a test, we'll respect the application
        if (compilerArgs.isApp() != compilerArgs.isTestVariant() ||
                compilerArgs.isEnabledForTests()) {
            // generate BR for all previous packages
            for (Intermediate intermediate : previousIntermediates) {
                writer.writeToFile(intermediate.getPackage() + ".BR",
                        brWriter.write(intermediate.getPackage()));
            }
        }
        mCallback.onBrWriterReady(brWriter);
    }

    private String getPropertyName(Element element) {
        switch (element.getKind()) {
            case FIELD:
                return stripPrefixFromField((VariableElement) element);
            case METHOD:
                return stripPrefixFromMethod((ExecutableElement) element);
            default:
                L.e("@Bindable is not allowed on %s", element.getKind());
                return null;
        }
    }

    private static String stripPrefixFromField(VariableElement element) {
        Name name = element.getSimpleName();
        if (name.length() >= 2) {
            char firstChar = name.charAt(0);
            char secondChar = name.charAt(1);
            if (name.length() > 2 && firstChar == 'm' && secondChar == '_') {
                char thirdChar = name.charAt(2);
                if (Character.isJavaIdentifierStart(thirdChar)) {
                    return "" + Character.toLowerCase(thirdChar) +
                            name.subSequence(3, name.length());
                }
            } else if ((firstChar == 'm' && Character.isUpperCase(secondChar)) ||
                    (firstChar == '_' && Character.isJavaIdentifierStart(secondChar))) {
                return "" + Character.toLowerCase(secondChar) + name.subSequence(2, name.length());
            }
        }
        return name.toString();
    }

    private String stripPrefixFromMethod(ExecutableElement element) {
        Name name = element.getSimpleName();
        CharSequence propertyName;
        if (isGetter(element) || isSetter(element)) {
            propertyName = name.subSequence(3, name.length());
        } else if (isBooleanGetter(element)) {
            propertyName = name.subSequence(2, name.length());
        } else {
            L.e("@Bindable associated with method must follow JavaBeans convention %s", element);
            return null;
        }
        char firstChar = propertyName.charAt(0);
        return "" + Character.toLowerCase(firstChar) +
                propertyName.subSequence(1, propertyName.length());
    }

    private void mergeLayoutVariables() {
        for (String containingClass : mLayoutVariables.keySet()) {
            for (String variable : mLayoutVariables.get(containingClass)) {
                mProperties.addProperty(containingClass, variable);
            }
        }
    }

    private static boolean prefixes(CharSequence sequence, String prefix) {
        boolean prefixes = false;
        if (sequence.length() > prefix.length()) {
            int count = prefix.length();
            prefixes = true;
            for (int i = 0; i < count; i++) {
                if (sequence.charAt(i) != prefix.charAt(i)) {
                    prefixes = false;
                    break;
                }
            }
        }
        return prefixes;
    }

    private static boolean isGetter(ExecutableElement element) {
        Name name = element.getSimpleName();
        return prefixes(name, "get") &&
                Character.isJavaIdentifierStart(name.charAt(3)) &&
                element.getParameters().isEmpty() &&
                element.getReturnType().getKind() != TypeKind.VOID;
    }

    private static boolean isSetter(ExecutableElement element) {
        Name name = element.getSimpleName();
        return prefixes(name, "set") &&
                Character.isJavaIdentifierStart(name.charAt(3)) &&
                element.getParameters().size() == 1 &&
                element.getReturnType().getKind() == TypeKind.VOID;
    }

    private static boolean isBooleanGetter(ExecutableElement element) {
        Name name = element.getSimpleName();
        return prefixes(name, "is") &&
                Character.isJavaIdentifierStart(name.charAt(2)) &&
                element.getParameters().isEmpty() &&
                element.getReturnType().getKind() == TypeKind.BOOLEAN;
    }

    @SuppressWarnings("CodeBlock2Expr")
    private Collection loadPreviousBRFiles(String... excludePackages) {
        List brFiles = GenerationalClassUtil
                .loadObjects(GenerationalClassUtil.ExtensionFilter.BR);
        Set excludeMap = Arrays.stream(excludePackages).collect(Collectors.toSet());
        // dedupe
        Map items = new HashMap<>();
        brFiles.stream()
                .filter(intermediate -> !excludeMap.contains(intermediate.getPackage()))
                .forEach(intermediate -> {
                    items.put(intermediate.getPackage(), intermediate);
                });
        return items.values();
    }

    private interface Intermediate extends Serializable {

        void captureProperties(Set properties);

        void addProperty(String className, String propertyName);

        boolean hasValues();

        String getPackage();
    }

    private static class IntermediateV1 implements Serializable, Intermediate {

        private static final long serialVersionUID = 2L;

        private String mPackage;
        private final HashMap> mProperties = new HashMap>();

        public IntermediateV1(String aPackage) {
            mPackage = aPackage;
        }

        @Override
        public void captureProperties(Set properties) {
            for (HashSet propertySet : mProperties.values()) {
                properties.addAll(propertySet);
            }
        }

        @Override
        public void addProperty(String className, String propertyName) {
            HashSet properties = mProperties.get(className);
            if (properties == null) {
                properties = new HashSet();
                mProperties.put(className, properties);
            }
            properties.add(propertyName);
        }

        @Override
        public boolean hasValues() {
            return !mProperties.isEmpty();
        }

        @Override
        public String getPackage() {
            return mPackage;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy