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.InverseBindingAdapter; 24import android.databinding.InverseBindingMethod; 25import android.databinding.InverseBindingMethods; 26import android.databinding.Untaggable; 27import android.databinding.tool.reflection.ModelAnalyzer; 28import android.databinding.tool.store.SetterStore; 29import android.databinding.tool.util.L; 30import android.databinding.tool.util.Preconditions; 31 32import java.io.IOException; 33import java.util.HashSet; 34import java.util.List; 35 36import javax.annotation.processing.ProcessingEnvironment; 37import javax.annotation.processing.RoundEnvironment; 38import javax.lang.model.element.Element; 39import javax.lang.model.element.ElementKind; 40import javax.lang.model.element.ExecutableElement; 41import javax.lang.model.element.Modifier; 42import javax.lang.model.element.TypeElement; 43import javax.lang.model.element.VariableElement; 44import javax.lang.model.type.MirroredTypeException; 45import javax.lang.model.type.TypeKind; 46import javax.lang.model.type.TypeMirror; 47import javax.lang.model.util.Elements; 48import javax.lang.model.util.Types; 49 50public class ProcessMethodAdapters extends ProcessDataBinding.ProcessingStep { 51 private final static String INVERSE_BINDING_EVENT_ATTR_SUFFIX = "AttrChanged"; 52 53 public ProcessMethodAdapters() { 54 } 55 56 @Override 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 82 public void onProcessingOver(RoundEnvironment roundEnvironment, 83 ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) { 84 85 } 86 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 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 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 191 private static void warnAttributeNamespaces(Element element, String[] attributes) { 192 for (String attribute : attributes) { 193 warnAttributeNamespace(element, attribute); 194 } 195 } 196 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 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 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 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 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 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