• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
ProcessMethodAdapters()49     public ProcessMethodAdapters() {
50     }
51 
52     @Override
onHandleStep(RoundEnvironment roundEnv, ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo)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
onProcessingOver(RoundEnvironment roundEnvironment, ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo)76     public void onProcessingOver(RoundEnvironment roundEnvironment,
77             ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) {
78 
79     }
80 
addBindingAdapters(RoundEnvironment roundEnv, ProcessingEnvironment processingEnv, SetterStore store)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 
takesComponent(ExecutableElement executableElement, ProcessingEnvironment processingEnvironment)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 
warnAttributeNamespace(String attribute)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 
warnAttributeNamespaces(String[] attributes)183     private static void warnAttributeNamespaces(String[] attributes) {
184         for (String attribute : attributes) {
185             warnAttributeNamespace(attribute);
186         }
187     }
188 
addRenamed(RoundEnvironment roundEnv, ProcessingEnvironment processingEnv, SetterStore store)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 
addConversions(RoundEnvironment roundEnv, ProcessingEnvironment processingEnv, SetterStore store)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 
addUntaggable(RoundEnvironment roundEnv, ProcessingEnvironment processingEnv, SetterStore store)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 
clearIncrementalClasses(RoundEnvironment roundEnv, SetterStore store)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