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