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 package android.databinding.annotationprocessor;
     17 
     18 import android.databinding.BindingAdapter;
     19 import android.databinding.BindingBuildInfo;
     20 import android.databinding.BindingConversion;
     21 import android.databinding.BindingMethod;
     22 import android.databinding.BindingMethods;
     23 import android.databinding.Untaggable;
     24 import android.databinding.tool.reflection.ModelAnalyzer;
     25 import android.databinding.tool.store.SetterStore;
     26 import android.databinding.tool.util.L;
     27 import android.databinding.tool.util.Preconditions;
     28 
     29 import java.io.IOException;
     30 import java.util.HashSet;
     31 import java.util.List;
     32 
     33 import javax.annotation.processing.ProcessingEnvironment;
     34 import javax.annotation.processing.RoundEnvironment;
     35 import javax.lang.model.element.Element;
     36 import javax.lang.model.element.ElementKind;
     37 import javax.lang.model.element.ExecutableElement;
     38 import javax.lang.model.element.Modifier;
     39 import javax.lang.model.element.TypeElement;
     40 import javax.lang.model.element.VariableElement;
     41 import javax.lang.model.type.MirroredTypeException;
     42 import javax.lang.model.type.TypeKind;
     43 import javax.lang.model.type.TypeMirror;
     44 import javax.lang.model.util.Elements;
     45 import javax.lang.model.util.Types;
     46 import javax.tools.Diagnostic;
     47 
     48 public class ProcessMethodAdapters extends ProcessDataBinding.ProcessingStep {
     49     public ProcessMethodAdapters() {
     50     }
     51 
     52     @Override
     53     public boolean onHandleStep(RoundEnvironment roundEnv,
     54             ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) {
     55         L.d("processing adapters");
     56         final ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
     57         Preconditions.checkNotNull(modelAnalyzer, "Model analyzer should be"
     58                 + " initialized first");
     59         SetterStore store = SetterStore.get(modelAnalyzer);
     60         clearIncrementalClasses(roundEnv, store);
     61 
     62         addBindingAdapters(roundEnv, processingEnvironment, store);
     63         addRenamed(roundEnv, processingEnvironment, store);
     64         addConversions(roundEnv, processingEnvironment, store);
     65         addUntaggable(roundEnv, processingEnvironment, store);
     66 
     67         try {
     68             store.write(buildInfo.modulePackage(), processingEnvironment);
     69         } catch (IOException e) {
     70             L.e(e, "Could not write BindingAdapter intermediate file.");
     71         }
     72         return true;
     73     }
     74 
     75     @Override
     76     public void onProcessingOver(RoundEnvironment roundEnvironment,
     77             ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) {
     78 
     79     }
     80 
     81     private void addBindingAdapters(RoundEnvironment roundEnv, ProcessingEnvironment
     82             processingEnv, SetterStore store) {
     83         for (Element element : AnnotationUtil
     84                 .getElementsAnnotatedWith(roundEnv, BindingAdapter.class)) {
     85             if (element.getKind() != ElementKind.METHOD ||
     86                     !element.getModifiers().contains(Modifier.PUBLIC)) {
     87                 L.e("@BindingAdapter on invalid element: %s", element);
     88                 continue;
     89             }
     90             BindingAdapter bindingAdapter = element.getAnnotation(BindingAdapter.class);
     91 
     92             ExecutableElement executableElement = (ExecutableElement) element;
     93             List<? extends VariableElement> parameters = executableElement.getParameters();
     94             if (bindingAdapter.value().length == 0) {
     95                 L.e("@BindingAdapter requires at least one attribute. %s", element);
     96                 continue;
     97             }
     98 
     99             final boolean takesComponent = takesComponent(executableElement, processingEnv);
    100             final int startIndex = 1 + (takesComponent ? 1 : 0);
    101             final int numAttributes = bindingAdapter.value().length;
    102             final int numAdditionalArgs = parameters.size() - startIndex;
    103             if (numAdditionalArgs == (2 * numAttributes)) {
    104                 // This BindingAdapter takes old and new values. Make sure they are properly ordered
    105                 Types typeUtils = processingEnv.getTypeUtils();
    106                 boolean hasParameterError = false;
    107                 for (int i = startIndex; i < numAttributes + startIndex; i++) {
    108                     if (!typeUtils.isSameType(parameters.get(i).asType(),
    109                             parameters.get(i + numAttributes).asType())) {
    110                         L.e("BindingAdapter %s: old values should be followed by new values. " +
    111                                 "Parameter %d must be the same type as parameter %d.",
    112                                 executableElement, i + 1, i + numAttributes + 1);
    113                         hasParameterError = true;
    114                         break;
    115                     }
    116                 }
    117                 if (hasParameterError) {
    118                     continue;
    119                 }
    120             } else if (numAdditionalArgs != numAttributes) {
    121                 L.e("@BindingAdapter %s has %d attributes and %d value parameters. There should " +
    122                         "be %d or %d value parameters.", executableElement, numAttributes,
    123                         numAdditionalArgs, numAttributes, numAttributes * 2);
    124                 continue;
    125             }
    126             warnAttributeNamespaces(bindingAdapter.value());
    127             try {
    128                 if (numAttributes == 1) {
    129                     final String attribute = bindingAdapter.value()[0];
    130                     L.d("------------------ @BindingAdapter for %s", element);
    131                     store.addBindingAdapter(processingEnv, attribute, executableElement,
    132                             takesComponent);
    133                 } else {
    134                     store.addBindingAdapter(processingEnv, bindingAdapter.value(),
    135                             executableElement, takesComponent);
    136                 }
    137             } catch (IllegalArgumentException e) {
    138                 L.e(e, "@BindingAdapter for duplicate View and parameter type: %s", element);
    139             }
    140         }
    141     }
    142 
    143     private static boolean takesComponent(ExecutableElement executableElement,
    144             ProcessingEnvironment processingEnvironment) {
    145         List<? extends VariableElement> parameters = executableElement.getParameters();
    146         Elements elementUtils = processingEnvironment.getElementUtils();
    147         TypeMirror viewElement = elementUtils.getTypeElement("android.view.View").asType();
    148         if (parameters.size() < 2) {
    149             return false; // Validation will fail in the caller
    150         }
    151         TypeMirror parameter1 = parameters.get(0).asType();
    152         Types typeUtils = processingEnvironment.getTypeUtils();
    153         if (parameter1.getKind() == TypeKind.DECLARED &&
    154                 typeUtils.isAssignable(parameter1, viewElement)) {
    155             return false; // first parameter is a View
    156         }
    157         if (parameters.size() < 3) {
    158             TypeMirror viewStubProxy = elementUtils.
    159                     getTypeElement("android.databinding.ViewStubProxy").asType();
    160             if (!typeUtils.isAssignable(parameter1, viewStubProxy)) {
    161                 L.e("@BindingAdapter %s is applied to a method that has two parameters, the " +
    162                         "first must be a View type", executableElement);
    163             }
    164             return false;
    165         }
    166         TypeMirror parameter2 = parameters.get(1).asType();
    167         if (typeUtils.isAssignable(parameter2, viewElement)) {
    168             return true; // second parameter is a View
    169         }
    170         L.e("@BindingAdapter %s is applied to a method that doesn't take a View subclass as the " +
    171                 "first or second parameter. When a BindingAdapter uses a DataBindingComponent, " +
    172                 "the component parameter is first and the View parameter is second, otherwise " +
    173                 "the View parameter is first.", executableElement);
    174         return false;
    175     }
    176 
    177     private static void warnAttributeNamespace(String attribute) {
    178         if (attribute.contains(":") && !attribute.startsWith("android:")) {
    179             L.w("Application namespace for attribute %s will be ignored.", attribute);
    180         }
    181     }
    182 
    183     private static void warnAttributeNamespaces(String[] attributes) {
    184         for (String attribute : attributes) {
    185             warnAttributeNamespace(attribute);
    186         }
    187     }
    188 
    189     private void addRenamed(RoundEnvironment roundEnv, ProcessingEnvironment processingEnv,
    190             SetterStore store) {
    191         for (Element element : AnnotationUtil
    192                 .getElementsAnnotatedWith(roundEnv, BindingMethods.class)) {
    193             BindingMethods bindingMethods = element.getAnnotation(BindingMethods.class);
    194 
    195             for (BindingMethod bindingMethod : bindingMethods.value()) {
    196                 final String attribute = bindingMethod.attribute();
    197                 final String method = bindingMethod.method();
    198                 warnAttributeNamespace(attribute);
    199                 String type;
    200                 try {
    201                     type = bindingMethod.type().getCanonicalName();
    202                 } catch (MirroredTypeException e) {
    203                     type = e.getTypeMirror().toString();
    204                 }
    205                 store.addRenamedMethod(attribute, type, method, (TypeElement) element);
    206             }
    207         }
    208     }
    209 
    210     private void addConversions(RoundEnvironment roundEnv,
    211             ProcessingEnvironment processingEnv, SetterStore store) {
    212         for (Element element : AnnotationUtil
    213                 .getElementsAnnotatedWith(roundEnv, BindingConversion.class)) {
    214             if (element.getKind() != ElementKind.METHOD ||
    215                     !element.getModifiers().contains(Modifier.STATIC) ||
    216                     !element.getModifiers().contains(Modifier.PUBLIC)) {
    217                 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
    218                         "@BindingConversion is only allowed on public static methods: " + element);
    219                 continue;
    220             }
    221 
    222             ExecutableElement executableElement = (ExecutableElement) element;
    223             if (executableElement.getParameters().size() != 1) {
    224                 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
    225                         "@BindingConversion method should have one parameter: " + element);
    226                 continue;
    227             }
    228             if (executableElement.getReturnType().getKind() == TypeKind.VOID) {
    229                 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
    230                         "@BindingConversion method must return a value: " + element);
    231                 continue;
    232             }
    233             processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
    234                     "added conversion: " + element);
    235             store.addConversionMethod(executableElement);
    236         }
    237     }
    238 
    239     private void addUntaggable(RoundEnvironment roundEnv,
    240             ProcessingEnvironment processingEnv, SetterStore store) {
    241         for (Element element : AnnotationUtil.getElementsAnnotatedWith(roundEnv, Untaggable.class)) {
    242             Untaggable untaggable = element.getAnnotation(Untaggable.class);
    243             store.addUntaggableTypes(untaggable.value(), (TypeElement) element);
    244         }
    245     }
    246 
    247     private void clearIncrementalClasses(RoundEnvironment roundEnv, SetterStore store) {
    248         HashSet<String> classes = new HashSet<String>();
    249 
    250         for (Element element : AnnotationUtil
    251                 .getElementsAnnotatedWith(roundEnv, BindingAdapter.class)) {
    252             TypeElement containingClass = (TypeElement) element.getEnclosingElement();
    253             classes.add(containingClass.getQualifiedName().toString());
    254         }
    255         for (Element element : AnnotationUtil
    256                 .getElementsAnnotatedWith(roundEnv, BindingMethods.class)) {
    257             classes.add(((TypeElement) element).getQualifiedName().toString());
    258         }
    259         for (Element element : AnnotationUtil
    260                 .getElementsAnnotatedWith(roundEnv, BindingConversion.class)) {
    261             classes.add(((TypeElement) element.getEnclosingElement()).getQualifiedName().
    262                     toString());
    263         }
    264         for (Element element : AnnotationUtil.getElementsAnnotatedWith(roundEnv, Untaggable.class)) {
    265             classes.add(((TypeElement) element).getQualifiedName().toString());
    266         }
    267         store.clear(classes);
    268     }
    269 
    270 }
    271