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