SetterStore.java revision fead9ca09b117136b35bc5bf137340a754f9eddd
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.tool.store; 17 18import org.apache.commons.lang3.StringUtils; 19 20import android.databinding.tool.reflection.ModelAnalyzer; 21import android.databinding.tool.reflection.ModelClass; 22import android.databinding.tool.reflection.ModelMethod; 23import android.databinding.tool.util.GenerationalClassUtil; 24import android.databinding.tool.util.L; 25 26import java.io.IOException; 27import java.io.Serializable; 28import java.util.ArrayList; 29import java.util.Arrays; 30import java.util.HashMap; 31import java.util.List; 32import java.util.Map; 33import java.util.Objects; 34import java.util.Set; 35 36import javax.annotation.processing.ProcessingEnvironment; 37import javax.lang.model.element.ExecutableElement; 38import javax.lang.model.element.TypeElement; 39import javax.lang.model.element.VariableElement; 40import javax.lang.model.type.ArrayType; 41import javax.lang.model.type.DeclaredType; 42import javax.lang.model.type.TypeMirror; 43 44public class SetterStore { 45 46 public static final String SETTER_STORE_FILE_EXT = "-setter_store.bin"; 47 48 private static SetterStore sStore; 49 50 private final IntermediateV1 mStore; 51 private final ModelAnalyzer mClassAnalyzer; 52 53 private SetterStore(ModelAnalyzer modelAnalyzer, IntermediateV1 store) { 54 mClassAnalyzer = modelAnalyzer; 55 mStore = store; 56 } 57 58 public static SetterStore get(ModelAnalyzer modelAnalyzer) { 59 if (sStore == null) { 60 sStore = load(modelAnalyzer, SetterStore.class.getClassLoader()); 61 } 62 return sStore; 63 } 64 65 private static SetterStore load(ModelAnalyzer modelAnalyzer, ClassLoader classLoader) { 66 IntermediateV1 store = new IntermediateV1(); 67 List<Intermediate> previousStores = GenerationalClassUtil 68 .loadObjects(classLoader, 69 new GenerationalClassUtil.ExtensionFilter(SETTER_STORE_FILE_EXT)); 70 for (Intermediate intermediate : previousStores) { 71 merge(store, intermediate); 72 } 73 return new SetterStore(modelAnalyzer, store); 74 } 75 76 public void addRenamedMethod(String attribute, String declaringClass, String method, 77 TypeElement declaredOn) { 78 HashMap<String, MethodDescription> renamed = mStore.renamedMethods.get(attribute); 79 if (renamed == null) { 80 renamed = new HashMap<String, MethodDescription>(); 81 mStore.renamedMethods.put(attribute, renamed); 82 } 83 MethodDescription methodDescription = 84 new MethodDescription(declaredOn.getQualifiedName().toString(), method); 85 L.d("STORE addmethod desc %s", methodDescription); 86 renamed.put(declaringClass, methodDescription); 87 } 88 89 public void addBindingAdapter(String attribute, ExecutableElement bindingMethod) { 90 L.d("STORE addBindingAdapter %s %s", attribute, bindingMethod); 91 HashMap<AccessorKey, MethodDescription> adapters = mStore.adapterMethods.get(attribute); 92 93 if (adapters == null) { 94 adapters = new HashMap<AccessorKey, MethodDescription>(); 95 mStore.adapterMethods.put(attribute, adapters); 96 } 97 List<? extends VariableElement> parameters = bindingMethod.getParameters(); 98 String view = getQualifiedName(parameters.get(0).asType()); 99 String value = getQualifiedName(parameters.get(1).asType()); 100 101 AccessorKey key = new AccessorKey(view, value); 102 if (adapters.containsKey(key)) { 103 throw new IllegalArgumentException("Already exists!"); 104 } 105 106 adapters.put(key, new MethodDescription(bindingMethod)); 107 } 108 109 public void addUntaggableTypes(String[] typeNames, TypeElement declaredOn) { 110 L.d("STORE addUntaggableTypes %s %s", Arrays.toString(typeNames), declaredOn); 111 String declaredType = declaredOn.getQualifiedName().toString(); 112 for (String type : typeNames) { 113 mStore.untaggableTypes.put(type, declaredType); 114 } 115 } 116 117 private static String getQualifiedName(TypeMirror type) { 118 switch (type.getKind()) { 119 case BOOLEAN: 120 case BYTE: 121 case SHORT: 122 case INT: 123 case LONG: 124 case CHAR: 125 case FLOAT: 126 case DOUBLE: 127 case VOID: 128 return type.toString(); 129 case ARRAY: 130 return getQualifiedName(((ArrayType) type).getComponentType()) + "[]"; 131 case DECLARED: 132 return ((TypeElement) ((DeclaredType) type).asElement()).getQualifiedName() 133 .toString(); 134 default: 135 return "-- no type --"; 136 } 137 } 138 139 public void addConversionMethod(ExecutableElement conversionMethod) { 140 L.d("STORE addConversionMethod %s", conversionMethod); 141 List<? extends VariableElement> parameters = conversionMethod.getParameters(); 142 String fromType = getQualifiedName(parameters.get(0).asType()); 143 String toType = getQualifiedName(conversionMethod.getReturnType()); 144 MethodDescription methodDescription = new MethodDescription(conversionMethod); 145 HashMap<String, MethodDescription> convertTo = mStore.conversionMethods.get(fromType); 146 if (convertTo == null) { 147 convertTo = new HashMap<String, MethodDescription>(); 148 mStore.conversionMethods.put(fromType, convertTo); 149 } 150 convertTo.put(toType, methodDescription); 151 } 152 153 public void clear(Set<String> classes) { 154 ArrayList<AccessorKey> removedAccessorKeys = new ArrayList<AccessorKey>(); 155 for (HashMap<AccessorKey, MethodDescription> adapters : mStore.adapterMethods.values()) { 156 for (AccessorKey key : adapters.keySet()) { 157 MethodDescription description = adapters.get(key); 158 if (classes.contains(description.type)) { 159 removedAccessorKeys.add(key); 160 } 161 } 162 removeFromMap(adapters, removedAccessorKeys); 163 } 164 165 ArrayList<String> removedRenamed = new ArrayList<String>(); 166 for (HashMap<String, MethodDescription> renamed : mStore.renamedMethods.values()) { 167 for (String key : renamed.keySet()) { 168 if (classes.contains(renamed.get(key).type)) { 169 removedRenamed.add(key); 170 } 171 } 172 removeFromMap(renamed, removedRenamed); 173 } 174 175 ArrayList<String> removedConversions = new ArrayList<String>(); 176 for (HashMap<String, MethodDescription> convertTos : mStore.conversionMethods.values()) { 177 for (String toType : convertTos.keySet()) { 178 MethodDescription methodDescription = convertTos.get(toType); 179 if (classes.contains(methodDescription.type)) { 180 removedConversions.add(toType); 181 } 182 } 183 removeFromMap(convertTos, removedConversions); 184 } 185 186 ArrayList<String> removedUntaggable = new ArrayList<String>(); 187 for (String typeName : mStore.untaggableTypes.keySet()) { 188 if (classes.contains(mStore.untaggableTypes.get(typeName))) { 189 removedUntaggable.add(typeName); 190 } 191 } 192 removeFromMap(mStore.untaggableTypes, removedUntaggable); 193 } 194 195 private static <K, V> void removeFromMap(Map<K, V> map, List<K> keys) { 196 for (K key : keys) { 197 map.remove(key); 198 } 199 keys.clear(); 200 } 201 202 public void write(String projectPackage, ProcessingEnvironment processingEnvironment) 203 throws IOException { 204 GenerationalClassUtil.writeIntermediateFile(processingEnvironment, 205 projectPackage, projectPackage + SETTER_STORE_FILE_EXT, mStore); 206 } 207 208 public SetterCall getSetterCall(String attribute, ModelClass viewType, 209 ModelClass valueType, Map<String, String> imports) { 210 if (!attribute.startsWith("android:")) { 211 int colon = attribute.indexOf(':'); 212 if (colon >= 0) { 213 attribute = attribute.substring(colon + 1); 214 } 215 } 216 SetterCall setterCall = null; 217 MethodDescription conversionMethod = null; 218 if (viewType != null) { 219 HashMap<AccessorKey, MethodDescription> adapters = mStore.adapterMethods.get(attribute); 220 ModelMethod bestSetterMethod = getBestSetter(viewType, valueType, attribute, imports); 221 ModelClass bestViewType = null; 222 ModelClass bestValueType = null; 223 if (bestSetterMethod != null) { 224 bestViewType = bestSetterMethod.getDeclaringClass(); 225 bestValueType = bestSetterMethod.getParameterTypes()[0]; 226 setterCall = new ModelMethodSetter(bestSetterMethod); 227 } 228 229 if (adapters != null) { 230 for (AccessorKey key : adapters.keySet()) { 231 try { 232 ModelClass adapterViewType = mClassAnalyzer 233 .findClass(key.viewType, imports); 234 if (adapterViewType.isAssignableFrom(viewType)) { 235 try { 236 ModelClass adapterValueType = mClassAnalyzer 237 .findClass(key.valueType, imports); 238 boolean isBetterView = bestViewType == null || 239 bestValueType.isAssignableFrom(adapterValueType); 240 if (isBetterParameter(valueType, adapterValueType, bestValueType, 241 isBetterView, imports)) { 242 bestViewType = adapterViewType; 243 bestValueType = adapterValueType; 244 MethodDescription adapter = adapters.get(key); 245 setterCall = new AdapterSetter(adapter); 246 } 247 248 } catch (Exception e) { 249 L.e(e, "Unknown class: %s", key.valueType); 250 } 251 } 252 } catch (Exception e) { 253 L.e(e, "Unknown class: %s", key.viewType); 254 } 255 } 256 } 257 258 conversionMethod = getConversionMethod(valueType, bestValueType, imports); 259 if (valueType.isObject() && setterCall != null && bestValueType.isNullable()) { 260 setterCall.setCast(bestValueType); 261 } 262 } 263 if (setterCall == null) { 264 setterCall = new DummySetter(getDefaultSetter(attribute)); 265 // might be an include tag etc. just note it and continue. 266 L.d("Cannot find the setter for attribute " + attribute + ". might be an include file," 267 + " moving on."); 268 } 269 setterCall.setConverter(conversionMethod); 270 return setterCall; 271 } 272 273 public boolean isUntaggable(String viewType) { 274 return mStore.untaggableTypes.containsKey(viewType); 275 } 276 277 private ModelMethod getBestSetter(ModelClass viewType, ModelClass argumentType, 278 String attribute, Map<String, String> imports) { 279 List<String> setterCandidates = new ArrayList<String>(); 280 HashMap<String, MethodDescription> renamed = mStore.renamedMethods.get(attribute); 281 if (renamed != null) { 282 for (String className : renamed.keySet()) { 283 try { 284 ModelClass renamedViewType = mClassAnalyzer.findClass(className, imports); 285 if (renamedViewType.isAssignableFrom(viewType)) { 286 setterCandidates.add(renamed.get(className).method); 287 break; 288 } 289 } catch (Exception e) { 290 //printMessage(Diagnostic.Kind.NOTE, "Unknown class: " + className); 291 } 292 } 293 } 294 setterCandidates.add(getDefaultSetter(attribute)); 295 setterCandidates.add(trimAttributeNamespace(attribute)); 296 297 ModelMethod bestMethod = null; 298 ModelClass bestParameterType = null; 299 List<ModelClass> args = new ArrayList<ModelClass>(); 300 args.add(argumentType); 301 for (String name : setterCandidates) { 302 ModelMethod[] methods = viewType.getMethods(name, 1); 303 304 for (ModelMethod method : methods) { 305 ModelClass[] parameterTypes = method.getParameterTypes(); 306 ModelClass param = parameterTypes[0]; 307 if (method.isVoid() && 308 isBetterParameter(argumentType, param, bestParameterType, true, imports)) { 309 bestParameterType = param; 310 bestMethod = method; 311 } 312 } 313 } 314 return bestMethod; 315 316 } 317 318 private static String trimAttributeNamespace(String attribute) { 319 final int colonIndex = attribute.indexOf(':'); 320 return colonIndex == -1 ? attribute : attribute.substring(colonIndex + 1); 321 } 322 323 private static String getDefaultSetter(String attribute) { 324 return "set" + StringUtils.capitalize(trimAttributeNamespace(attribute)); 325 } 326 327 private boolean isBetterParameter(ModelClass argument, ModelClass parameter, 328 ModelClass oldParameter, boolean isBetterViewTypeMatch, Map<String, String> imports) { 329 // Right view type. Check the value 330 if (!isBetterViewTypeMatch && oldParameter.equals(argument)) { 331 return false; 332 } else if (argument.equals(parameter)) { 333 // Exact match 334 return true; 335 } else if (!isBetterViewTypeMatch && 336 ModelMethod.isBoxingConversion(oldParameter, argument)) { 337 return false; 338 } else if (ModelMethod.isBoxingConversion(parameter, argument)) { 339 // Boxing/unboxing is second best 340 return true; 341 } else { 342 int oldConversionLevel = ModelMethod.getImplicitConversionLevel(oldParameter); 343 if (ModelMethod.isImplicitConversion(argument, parameter)) { 344 // Better implicit conversion 345 int conversionLevel = ModelMethod.getImplicitConversionLevel(parameter); 346 return oldConversionLevel < 0 || conversionLevel < oldConversionLevel; 347 } else if (oldConversionLevel >= 0) { 348 return false; 349 } else if (parameter.isAssignableFrom(argument)) { 350 // Right type, see if it is better than the current best match. 351 if (oldParameter == null) { 352 return true; 353 } else { 354 return oldParameter.isAssignableFrom(parameter); 355 } 356 } else { 357 MethodDescription conversionMethod = getConversionMethod(argument, parameter, 358 imports); 359 if (conversionMethod != null) { 360 return true; 361 } 362 if (getConversionMethod(argument, oldParameter, imports) != null) { 363 return false; 364 } 365 return argument.isObject() && !parameter.isPrimitive(); 366 } 367 } 368 } 369 370 private MethodDescription getConversionMethod(ModelClass from, ModelClass to, 371 Map<String, String> imports) { 372 if (from != null && to != null) { 373 for (String fromClassName : mStore.conversionMethods.keySet()) { 374 try { 375 ModelClass convertFrom = mClassAnalyzer.findClass(fromClassName, imports); 376 if (canUseForConversion(from, convertFrom)) { 377 HashMap<String, MethodDescription> conversion = 378 mStore.conversionMethods.get(fromClassName); 379 for (String toClassName : conversion.keySet()) { 380 try { 381 ModelClass convertTo = mClassAnalyzer.findClass(toClassName, 382 imports); 383 if (canUseForConversion(convertTo, to)) { 384 return conversion.get(toClassName); 385 } 386 } catch (Exception e) { 387 L.d(e, "Unknown class: %s", toClassName); 388 } 389 } 390 } 391 } catch (Exception e) { 392 L.d(e, "Unknown class: %s", fromClassName); 393 } 394 } 395 } 396 return null; 397 } 398 399 private boolean canUseForConversion(ModelClass from, ModelClass to) { 400 return from.equals(to) || ModelMethod.isBoxingConversion(from, to) || 401 to.isAssignableFrom(from); 402 } 403 404 private static void merge(IntermediateV1 store, Intermediate dumpStore) { 405 IntermediateV1 intermediateV1 = (IntermediateV1) dumpStore.upgrade(); 406 merge(store.adapterMethods, intermediateV1.adapterMethods); 407 merge(store.renamedMethods, intermediateV1.renamedMethods); 408 merge(store.conversionMethods, intermediateV1.conversionMethods); 409 store.untaggableTypes.putAll(intermediateV1.untaggableTypes); 410 } 411 412 private static <K, V> void merge(HashMap<K, HashMap<V, MethodDescription>> first, 413 HashMap<K, HashMap<V, MethodDescription>> second) { 414 for (K key : second.keySet()) { 415 HashMap<V, MethodDescription> firstVals = first.get(key); 416 HashMap<V, MethodDescription> secondVals = second.get(key); 417 if (firstVals == null) { 418 first.put(key, secondVals); 419 } else { 420 for (V key2 : secondVals.keySet()) { 421 if (!firstVals.containsKey(key2)) { 422 firstVals.put(key2, secondVals.get(key2)); 423 } 424 } 425 } 426 } 427 } 428 429 private static class MethodDescription implements Serializable { 430 431 private static final long serialVersionUID = 1; 432 433 public final String type; 434 435 public final String method; 436 437 public MethodDescription(String type, String method) { 438 this.type = type; 439 this.method = method; 440 L.d("BINARY created method desc 1 %s %s", type, method ); 441 } 442 443 public MethodDescription(ExecutableElement method) { 444 TypeElement enclosingClass = (TypeElement) method.getEnclosingElement(); 445 this.type = enclosingClass.getQualifiedName().toString(); 446 this.method = method.getSimpleName().toString(); 447 L.d("BINARY created method desc 2 %s %s, %s", type, this.method, method); 448 } 449 450 @Override 451 public boolean equals(Object obj) { 452 if (obj instanceof MethodDescription) { 453 MethodDescription that = (MethodDescription) obj; 454 return that.type.equals(this.type) && that.method.equals(this.method); 455 } else { 456 return false; 457 } 458 } 459 460 @Override 461 public int hashCode() { 462 return Objects.hash(type, method); 463 } 464 465 @Override 466 public String toString() { 467 return type + "." + method + "()"; 468 } 469 } 470 471 private static class AccessorKey implements Serializable { 472 473 private static final long serialVersionUID = 1; 474 475 public final String viewType; 476 477 public final String valueType; 478 479 public AccessorKey(String viewType, String valueType) { 480 this.viewType = viewType; 481 this.valueType = valueType; 482 } 483 484 @Override 485 public int hashCode() { 486 return Objects.hash(viewType, valueType); 487 } 488 489 @Override 490 public boolean equals(Object obj) { 491 if (obj instanceof AccessorKey) { 492 AccessorKey that = (AccessorKey) obj; 493 return viewType.equals(that.valueType) && valueType.equals(that.valueType); 494 } else { 495 return false; 496 } 497 } 498 499 @Override 500 public String toString() { 501 return "AK(" + viewType + ", " + valueType + ")"; 502 } 503 } 504 505 private interface Intermediate extends Serializable { 506 Intermediate upgrade(); 507 } 508 509 private static class IntermediateV1 implements Serializable, Intermediate { 510 private static final long serialVersionUID = 1; 511 public final HashMap<String, HashMap<AccessorKey, MethodDescription>> adapterMethods = 512 new HashMap<String, HashMap<AccessorKey, MethodDescription>>(); 513 public final HashMap<String, HashMap<String, MethodDescription>> renamedMethods = 514 new HashMap<String, HashMap<String, MethodDescription>>(); 515 public final HashMap<String, HashMap<String, MethodDescription>> conversionMethods = 516 new HashMap<String, HashMap<String, MethodDescription>>(); 517 public final HashMap<String, String> untaggableTypes = new HashMap<String, String>(); 518 519 public IntermediateV1() { 520 } 521 522 @Override 523 public Intermediate upgrade() { 524 return this; 525 } 526 } 527 528 public static class DummySetter extends SetterCall { 529 private String mMethodName; 530 531 public DummySetter(String methodName) { 532 mMethodName = methodName; 533 } 534 535 @Override 536 public String toJavaInternal(String viewExpression, String valueExpression) { 537 return viewExpression + "." + mMethodName + "(" + valueExpression + ")"; 538 } 539 540 @Override 541 public int getMinApi() { 542 return 1; 543 } 544 } 545 546 public static class AdapterSetter extends SetterCall { 547 final MethodDescription mAdapter; 548 549 public AdapterSetter(MethodDescription adapter) { 550 mAdapter = adapter; 551 } 552 553 @Override 554 public String toJavaInternal(String viewExpression, String valueExpression) { 555 return mAdapter.type + "." + mAdapter.method + "(" + viewExpression + ", " + 556 mCastString + valueExpression + ")"; 557 } 558 559 @Override 560 public int getMinApi() { 561 return 1; 562 } 563 } 564 565 public static class ModelMethodSetter extends SetterCall { 566 final ModelMethod mModelMethod; 567 568 public ModelMethodSetter(ModelMethod modelMethod) { 569 mModelMethod = modelMethod; 570 } 571 572 @Override 573 public String toJavaInternal(String viewExpression, String valueExpression) { 574 return viewExpression + "." + mModelMethod.getName() + "(" + mCastString + 575 valueExpression + ")"; 576 } 577 578 @Override 579 public int getMinApi() { 580 return mModelMethod.getMinApi(); 581 } 582 } 583 584 public static abstract class SetterCall { 585 private MethodDescription mConverter; 586 protected String mCastString = ""; 587 588 public SetterCall() { 589 } 590 591 public void setConverter(MethodDescription converter) { 592 mConverter = converter; 593 } 594 595 protected abstract String toJavaInternal(String viewExpression, String converted); 596 597 public final String toJava(String viewExpression, String valueExpression) { 598 return toJavaInternal(viewExpression, convertValue(valueExpression)); 599 } 600 601 protected String convertValue(String valueExpression) { 602 return mConverter == null ? valueExpression : 603 mConverter.type + "." + mConverter.method + "(" + valueExpression + ")"; 604 } 605 606 abstract public int getMinApi(); 607 608 public void setCast(ModelClass castTo) { 609 mCastString = "(" + castTo.toJavaCode() + ") "; 610 } 611 } 612} 613