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