Home | History | Annotate | Download | only in annotationprocessor
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.databinding.annotationprocessor;
     18 
     19 import android.databinding.Bindable;
     20 import android.databinding.BindingBuildInfo;
     21 import android.databinding.tool.CompilerChef.BindableHolder;
     22 import android.databinding.tool.util.GenerationalClassUtil;
     23 import android.databinding.tool.util.L;
     24 import android.databinding.tool.util.Preconditions;
     25 import android.databinding.tool.writer.BRWriter;
     26 import android.databinding.tool.writer.JavaFileWriter;
     27 
     28 import java.io.Serializable;
     29 import java.util.HashMap;
     30 import java.util.HashSet;
     31 import java.util.List;
     32 import java.util.Set;
     33 
     34 import javax.annotation.processing.ProcessingEnvironment;
     35 import javax.annotation.processing.RoundEnvironment;
     36 import javax.lang.model.element.Element;
     37 import javax.lang.model.element.ElementKind;
     38 import javax.lang.model.element.ExecutableElement;
     39 import javax.lang.model.element.Name;
     40 import javax.lang.model.element.TypeElement;
     41 import javax.lang.model.element.VariableElement;
     42 import javax.lang.model.type.TypeKind;
     43 import javax.lang.model.util.Types;
     44 
     45 // binding app info and library info are necessary to trigger this.
     46 public class ProcessBindable extends ProcessDataBinding.ProcessingStep implements BindableHolder {
     47     Intermediate mProperties;
     48     HashMap<String, HashSet<String>> mLayoutVariables = new HashMap<String, HashSet<String>>();
     49 
     50     @Override
     51     public boolean onHandleStep(RoundEnvironment roundEnv, ProcessingEnvironment processingEnv,
     52             BindingBuildInfo buildInfo) {
     53         if (mProperties == null) {
     54             mProperties = new IntermediateV1(buildInfo.modulePackage());
     55             mergeLayoutVariables();
     56             mLayoutVariables.clear();
     57             TypeElement observableType = processingEnv.getElementUtils().
     58                     getTypeElement("android.databinding.Observable");
     59             Types typeUtils = processingEnv.getTypeUtils();
     60             for (Element element : AnnotationUtil
     61                     .getElementsAnnotatedWith(roundEnv, Bindable.class)) {
     62                 Element enclosingElement = element.getEnclosingElement();
     63                 ElementKind kind = enclosingElement.getKind();
     64                 if (kind != ElementKind.CLASS && kind != ElementKind.INTERFACE) {
     65                     L.e("Bindable must be on a member field or method. The enclosing type is %s",
     66                             enclosingElement.getKind());
     67                 }
     68                 TypeElement enclosing = (TypeElement) enclosingElement;
     69                 if (!typeUtils.isAssignable(enclosing.asType(), observableType.asType())) {
     70                     L.e("Bindable must be on a member in an Observable class. %s is not Observable",
     71                             enclosingElement.getSimpleName());
     72                 }
     73                 String name = getPropertyName(element);
     74                 if (name != null) {
     75                     Preconditions
     76                             .checkNotNull(mProperties, "Must receive app / library info before "
     77                                     + "Bindable fields.");
     78                     mProperties.addProperty(enclosing.getQualifiedName().toString(), name);
     79                 }
     80             }
     81             GenerationalClassUtil.writeIntermediateFile(processingEnv,
     82                     mProperties.getPackage(),
     83                     createIntermediateFileName(mProperties.getPackage()), mProperties);
     84             generateBRClasses(!buildInfo.isLibrary(), mProperties.getPackage());
     85         }
     86         return false;
     87     }
     88 
     89     @Override
     90     public void addVariable(String variableName, String containingClassName) {
     91         HashSet<String> variableNames = mLayoutVariables.get(containingClassName);
     92         if (variableNames == null) {
     93             variableNames = new HashSet<String>();
     94             mLayoutVariables.put(containingClassName, variableNames);
     95         }
     96         variableNames.add(variableName);
     97     }
     98 
     99     @Override
    100     public void onProcessingOver(RoundEnvironment roundEnvironment,
    101             ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) {
    102     }
    103 
    104     private String createIntermediateFileName(String appPkg) {
    105         return appPkg + GenerationalClassUtil.ExtensionFilter.BR.getExtension();
    106     }
    107 
    108     private void generateBRClasses(boolean useFinalFields, String pkg) {
    109         L.d("************* Generating BR file %s. use final: %s", pkg, useFinalFields);
    110         HashSet<String> properties = new HashSet<String>();
    111         mProperties.captureProperties(properties);
    112         List<Intermediate> previousIntermediates = loadPreviousBRFiles();
    113         for (Intermediate intermediate : previousIntermediates) {
    114             intermediate.captureProperties(properties);
    115         }
    116         final JavaFileWriter writer = getWriter();
    117         BRWriter brWriter = new BRWriter(properties, useFinalFields);
    118         writer.writeToFile(pkg + ".BR", brWriter.write(pkg));
    119         //writeBRClass(useFinalFields, pkg, properties);
    120         if (useFinalFields) {
    121             // generate BR for all previous packages
    122             for (Intermediate intermediate : previousIntermediates) {
    123                 writer.writeToFile(intermediate.getPackage() + ".BR",
    124                         brWriter.write(intermediate.getPackage()));
    125             }
    126         }
    127         mCallback.onBrWriterReady(brWriter);
    128     }
    129 
    130     private String getPropertyName(Element element) {
    131         switch (element.getKind()) {
    132             case FIELD:
    133                 return stripPrefixFromField((VariableElement) element);
    134             case METHOD:
    135                 return stripPrefixFromMethod((ExecutableElement) element);
    136             default:
    137                 L.e("@Bindable is not allowed on %s", element.getKind());
    138                 return null;
    139         }
    140     }
    141 
    142     private static String stripPrefixFromField(VariableElement element) {
    143         Name name = element.getSimpleName();
    144         if (name.length() >= 2) {
    145             char firstChar = name.charAt(0);
    146             char secondChar = name.charAt(1);
    147             if (name.length() > 2 && firstChar == 'm' && secondChar == '_') {
    148                 char thirdChar = name.charAt(2);
    149                 if (Character.isJavaIdentifierStart(thirdChar)) {
    150                     return "" + Character.toLowerCase(thirdChar) +
    151                             name.subSequence(3, name.length());
    152                 }
    153             } else if ((firstChar == 'm' && Character.isUpperCase(secondChar)) ||
    154                     (firstChar == '_' && Character.isJavaIdentifierStart(secondChar))) {
    155                 return "" + Character.toLowerCase(secondChar) + name.subSequence(2, name.length());
    156             }
    157         }
    158         return name.toString();
    159     }
    160 
    161     private String stripPrefixFromMethod(ExecutableElement element) {
    162         Name name = element.getSimpleName();
    163         CharSequence propertyName;
    164         if (isGetter(element) || isSetter(element)) {
    165             propertyName = name.subSequence(3, name.length());
    166         } else if (isBooleanGetter(element)) {
    167             propertyName = name.subSequence(2, name.length());
    168         } else {
    169             L.e("@Bindable associated with method must follow JavaBeans convention %s", element);
    170             return null;
    171         }
    172         char firstChar = propertyName.charAt(0);
    173         return "" + Character.toLowerCase(firstChar) +
    174                 propertyName.subSequence(1, propertyName.length());
    175     }
    176 
    177     private void mergeLayoutVariables() {
    178         for (String containingClass : mLayoutVariables.keySet()) {
    179             for (String variable : mLayoutVariables.get(containingClass)) {
    180                 mProperties.addProperty(containingClass, variable);
    181             }
    182         }
    183     }
    184 
    185     private static boolean prefixes(CharSequence sequence, String prefix) {
    186         boolean prefixes = false;
    187         if (sequence.length() > prefix.length()) {
    188             int count = prefix.length();
    189             prefixes = true;
    190             for (int i = 0; i < count; i++) {
    191                 if (sequence.charAt(i) != prefix.charAt(i)) {
    192                     prefixes = false;
    193                     break;
    194                 }
    195             }
    196         }
    197         return prefixes;
    198     }
    199 
    200     private static boolean isGetter(ExecutableElement element) {
    201         Name name = element.getSimpleName();
    202         return prefixes(name, "get") &&
    203                 Character.isJavaIdentifierStart(name.charAt(3)) &&
    204                 element.getParameters().isEmpty() &&
    205                 element.getReturnType().getKind() != TypeKind.VOID;
    206     }
    207 
    208     private static boolean isSetter(ExecutableElement element) {
    209         Name name = element.getSimpleName();
    210         return prefixes(name, "set") &&
    211                 Character.isJavaIdentifierStart(name.charAt(3)) &&
    212                 element.getParameters().size() == 1 &&
    213                 element.getReturnType().getKind() == TypeKind.VOID;
    214     }
    215 
    216     private static boolean isBooleanGetter(ExecutableElement element) {
    217         Name name = element.getSimpleName();
    218         return prefixes(name, "is") &&
    219                 Character.isJavaIdentifierStart(name.charAt(2)) &&
    220                 element.getParameters().isEmpty() &&
    221                 element.getReturnType().getKind() == TypeKind.BOOLEAN;
    222     }
    223 
    224     private List<Intermediate> loadPreviousBRFiles() {
    225         return GenerationalClassUtil
    226                 .loadObjects(GenerationalClassUtil.ExtensionFilter.BR);
    227     }
    228 
    229     private interface Intermediate extends Serializable {
    230 
    231         void captureProperties(Set<String> properties);
    232 
    233         void addProperty(String className, String propertyName);
    234 
    235         boolean hasValues();
    236 
    237         String getPackage();
    238     }
    239 
    240     private static class IntermediateV1 implements Serializable, Intermediate {
    241 
    242         private static final long serialVersionUID = 2L;
    243 
    244         private String mPackage;
    245         private final HashMap<String, HashSet<String>> mProperties = new HashMap<String, HashSet<String>>();
    246 
    247         public IntermediateV1(String aPackage) {
    248             mPackage = aPackage;
    249         }
    250 
    251         @Override
    252         public void captureProperties(Set<String> properties) {
    253             for (HashSet<String> propertySet : mProperties.values()) {
    254                 properties.addAll(propertySet);
    255             }
    256         }
    257 
    258         @Override
    259         public void addProperty(String className, String propertyName) {
    260             HashSet<String> properties = mProperties.get(className);
    261             if (properties == null) {
    262                 properties = new HashSet<String>();
    263                 mProperties.put(className, properties);
    264             }
    265             properties.add(propertyName);
    266         }
    267 
    268         @Override
    269         public boolean hasValues() {
    270             return !mProperties.isEmpty();
    271         }
    272 
    273         @Override
    274         public String getPackage() {
    275             return mPackage;
    276         }
    277     }
    278 }
    279