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