SetterStore.java revision 1331801c598a377a2c16e1aed8f975b728adc06e
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 com.google.common.base.Preconditions; 19 20import org.apache.commons.lang3.StringUtils; 21 22import android.databinding.tool.reflection.ModelAnalyzer; 23import android.databinding.tool.reflection.ModelClass; 24import android.databinding.tool.reflection.ModelMethod; 25import android.databinding.tool.util.GenerationalClassUtil; 26import android.databinding.tool.util.L; 27 28import java.io.IOException; 29import java.io.Serializable; 30import java.util.ArrayList; 31import java.util.Arrays; 32import java.util.Collections; 33import java.util.Comparator; 34import java.util.HashMap; 35import java.util.Iterator; 36import java.util.List; 37import java.util.Map; 38import java.util.Objects; 39import java.util.Set; 40import java.util.TreeMap; 41 42import javax.annotation.processing.ProcessingEnvironment; 43import javax.lang.model.element.ExecutableElement; 44import javax.lang.model.element.TypeElement; 45import javax.lang.model.element.VariableElement; 46import javax.lang.model.type.ArrayType; 47import javax.lang.model.type.DeclaredType; 48import javax.lang.model.type.TypeMirror; 49 50public class SetterStore { 51 52 public static final String SETTER_STORE_FILE_EXT = "-setter_store.bin"; 53 54 private static SetterStore sStore; 55 56 private final IntermediateV1 mStore; 57 private final ModelAnalyzer mClassAnalyzer; 58 59 private Comparator<MultiAttributeSetter> COMPARE_MULTI_ATTRIBUTE_SETTERS = 60 new Comparator<MultiAttributeSetter>() { 61 @Override 62 public int compare(MultiAttributeSetter o1, MultiAttributeSetter o2) { 63 if (o1.attributes.length != o2.attributes.length) { 64 return o2.attributes.length - o1.attributes.length; 65 } 66 ModelClass view1 = mClassAnalyzer.findClass(o1.mKey.viewType, null); 67 ModelClass view2 = mClassAnalyzer.findClass(o2.mKey.viewType, null); 68 if (!view1.equals(view2)) { 69 if (view1.isAssignableFrom(view2)) { 70 return 1; 71 } else { 72 return -1; 73 } 74 } 75 if (!o1.mKey.attributeIndices.keySet() 76 .equals(o2.mKey.attributeIndices.keySet())) { 77 // order by attribute name 78 Iterator<String> o1Keys = o1.mKey.attributeIndices.keySet().iterator(); 79 Iterator<String> o2Keys = o2.mKey.attributeIndices.keySet().iterator(); 80 while (o1Keys.hasNext()) { 81 String key1 = o1Keys.next(); 82 String key2 = o2Keys.next(); 83 int compare = key1.compareTo(key2); 84 if (compare != 0) { 85 return compare; 86 } 87 } 88 Preconditions.checkState(false, 89 "The sets don't match! That means the keys shouldn't match also"); 90 } 91 // Same view type. Same attributes 92 for (String attribute : o1.mKey.attributeIndices.keySet()) { 93 final int index1 = o1.mKey.attributeIndices.get(attribute); 94 final int index2 = o2.mKey.attributeIndices.get(attribute); 95 ModelClass type1 = mClassAnalyzer 96 .findClass(o1.mKey.parameterTypes[index1], null); 97 ModelClass type2 = mClassAnalyzer 98 .findClass(o2.mKey.parameterTypes[index2], null); 99 if (type1.equals(type2)) { 100 continue; 101 } 102 if (o1.mCasts[index1] != null) { 103 if (o2.mCasts[index2] == null) { 104 return 1; // o2 is better 105 } else { 106 continue; // both are casts 107 } 108 } else if (o2.mCasts[index2] != null) { 109 return -1; // o1 is better 110 } 111 if (o1.mConverters[index1] != null) { 112 if (o2.mConverters[index2] == null) { 113 return 1; // o2 is better 114 } else { 115 continue; // both are conversions 116 } 117 } else if (o2.mConverters[index2] != null) { 118 return -1; // o1 is better 119 } 120 121 if (type1.isPrimitive()) { 122 if (type2.isPrimitive()) { 123 int type1ConversionLevel = ModelMethod 124 .getImplicitConversionLevel(type1); 125 int type2ConversionLevel = ModelMethod 126 .getImplicitConversionLevel(type2); 127 return type2ConversionLevel - type1ConversionLevel; 128 } else { 129 // type1 is primitive and has higher priority 130 return -1; 131 } 132 } else if (type2.isPrimitive()) { 133 return 1; 134 } 135 if (type1.isAssignableFrom(type2)) { 136 return 1; 137 } else if (type2.isAssignableFrom(type1)) { 138 return -1; 139 } 140 } 141 // hmmm... same view type, same attributes, same parameter types... ? 142 return 0; 143 } 144 }; 145 146 private SetterStore(ModelAnalyzer modelAnalyzer, IntermediateV1 store) { 147 mClassAnalyzer = modelAnalyzer; 148 mStore = store; 149 } 150 151 public static SetterStore get(ModelAnalyzer modelAnalyzer) { 152 if (sStore == null) { 153 sStore = load(modelAnalyzer, SetterStore.class.getClassLoader()); 154 } 155 return sStore; 156 } 157 158 private static SetterStore load(ModelAnalyzer modelAnalyzer, ClassLoader classLoader) { 159 IntermediateV1 store = new IntermediateV1(); 160 List<Intermediate> previousStores = GenerationalClassUtil 161 .loadObjects(classLoader, 162 new GenerationalClassUtil.ExtensionFilter(SETTER_STORE_FILE_EXT)); 163 for (Intermediate intermediate : previousStores) { 164 merge(store, intermediate); 165 } 166 return new SetterStore(modelAnalyzer, store); 167 } 168 169 public void addRenamedMethod(String attribute, String declaringClass, String method, 170 TypeElement declaredOn) { 171 attribute = stripNamespace(attribute); 172 HashMap<String, MethodDescription> renamed = mStore.renamedMethods.get(attribute); 173 if (renamed == null) { 174 renamed = new HashMap<String, MethodDescription>(); 175 mStore.renamedMethods.put(attribute, renamed); 176 } 177 MethodDescription methodDescription = 178 new MethodDescription(declaredOn.getQualifiedName().toString(), method); 179 L.d("STORE addmethod desc %s", methodDescription); 180 renamed.put(declaringClass, methodDescription); 181 } 182 183 public void addBindingAdapter(String attribute, ExecutableElement bindingMethod) { 184 attribute = stripNamespace(attribute); 185 L.d("STORE addBindingAdapter %s %s", attribute, bindingMethod); 186 HashMap<AccessorKey, MethodDescription> adapters = mStore.adapterMethods.get(attribute); 187 188 if (adapters == null) { 189 adapters = new HashMap<AccessorKey, MethodDescription>(); 190 mStore.adapterMethods.put(attribute, adapters); 191 } 192 List<? extends VariableElement> parameters = bindingMethod.getParameters(); 193 String view = getQualifiedName(parameters.get(0).asType()); 194 String value = getQualifiedName(parameters.get(1).asType()); 195 196 AccessorKey key = new AccessorKey(view, value); 197 if (adapters.containsKey(key)) { 198 throw new IllegalArgumentException("Already exists!"); 199 } 200 201 adapters.put(key, new MethodDescription(bindingMethod)); 202 } 203 204 public void addBindingAdapter(String[] attributes, ExecutableElement bindingMethod) { 205 L.d("STORE add multi-value BindingAdapter %d %s", attributes.length, bindingMethod); 206 MultiValueAdapterKey key = new MultiValueAdapterKey(bindingMethod, attributes); 207 MethodDescription methodDescription = new MethodDescription(bindingMethod); 208 mStore.multiValueAdapters.put(key, methodDescription); 209 } 210 211 private static String[] stripAttributes(String[] attributes) { 212 String[] strippedAttributes = new String[attributes.length]; 213 for (int i = 0; i < attributes.length; i++) { 214 strippedAttributes[i] = stripNamespace(attributes[i]); 215 } 216 return strippedAttributes; 217 } 218 219 public void addUntaggableTypes(String[] typeNames, TypeElement declaredOn) { 220 L.d("STORE addUntaggableTypes %s %s", Arrays.toString(typeNames), declaredOn); 221 String declaredType = declaredOn.getQualifiedName().toString(); 222 for (String type : typeNames) { 223 mStore.untaggableTypes.put(type, declaredType); 224 } 225 } 226 227 private static String getQualifiedName(TypeMirror type) { 228 switch (type.getKind()) { 229 case BOOLEAN: 230 case BYTE: 231 case SHORT: 232 case INT: 233 case LONG: 234 case CHAR: 235 case FLOAT: 236 case DOUBLE: 237 case VOID: 238 return type.toString(); 239 case ARRAY: 240 return getQualifiedName(((ArrayType) type).getComponentType()) + "[]"; 241 case DECLARED: 242 return ((TypeElement) ((DeclaredType) type).asElement()).getQualifiedName() 243 .toString(); 244 default: 245 return "-- no type --"; 246 } 247 } 248 249 public void addConversionMethod(ExecutableElement conversionMethod) { 250 L.d("STORE addConversionMethod %s", conversionMethod); 251 List<? extends VariableElement> parameters = conversionMethod.getParameters(); 252 String fromType = getQualifiedName(parameters.get(0).asType()); 253 String toType = getQualifiedName(conversionMethod.getReturnType()); 254 MethodDescription methodDescription = new MethodDescription(conversionMethod); 255 HashMap<String, MethodDescription> convertTo = mStore.conversionMethods.get(fromType); 256 if (convertTo == null) { 257 convertTo = new HashMap<String, MethodDescription>(); 258 mStore.conversionMethods.put(fromType, convertTo); 259 } 260 convertTo.put(toType, methodDescription); 261 } 262 263 public void clear(Set<String> classes) { 264 ArrayList<AccessorKey> removedAccessorKeys = new ArrayList<AccessorKey>(); 265 for (HashMap<AccessorKey, MethodDescription> adapters : mStore.adapterMethods.values()) { 266 for (AccessorKey key : adapters.keySet()) { 267 MethodDescription description = adapters.get(key); 268 if (classes.contains(description.type)) { 269 removedAccessorKeys.add(key); 270 } 271 } 272 removeFromMap(adapters, removedAccessorKeys); 273 } 274 275 ArrayList<String> removedRenamed = new ArrayList<String>(); 276 for (HashMap<String, MethodDescription> renamed : mStore.renamedMethods.values()) { 277 for (String key : renamed.keySet()) { 278 if (classes.contains(renamed.get(key).type)) { 279 removedRenamed.add(key); 280 } 281 } 282 removeFromMap(renamed, removedRenamed); 283 } 284 285 ArrayList<String> removedConversions = new ArrayList<String>(); 286 for (HashMap<String, MethodDescription> convertTos : mStore.conversionMethods.values()) { 287 for (String toType : convertTos.keySet()) { 288 MethodDescription methodDescription = convertTos.get(toType); 289 if (classes.contains(methodDescription.type)) { 290 removedConversions.add(toType); 291 } 292 } 293 removeFromMap(convertTos, removedConversions); 294 } 295 296 ArrayList<String> removedUntaggable = new ArrayList<String>(); 297 for (String typeName : mStore.untaggableTypes.keySet()) { 298 if (classes.contains(mStore.untaggableTypes.get(typeName))) { 299 removedUntaggable.add(typeName); 300 } 301 } 302 removeFromMap(mStore.untaggableTypes, removedUntaggable); 303 } 304 305 private static <K, V> void removeFromMap(Map<K, V> map, List<K> keys) { 306 for (K key : keys) { 307 map.remove(key); 308 } 309 keys.clear(); 310 } 311 312 public void write(String projectPackage, ProcessingEnvironment processingEnvironment) 313 throws IOException { 314 GenerationalClassUtil.writeIntermediateFile(processingEnvironment, 315 projectPackage, projectPackage + SETTER_STORE_FILE_EXT, mStore); 316 } 317 318 private static String stripNamespace(String attribute) { 319 if (!attribute.startsWith("android:")) { 320 int colon = attribute.indexOf(':'); 321 if (colon >= 0) { 322 attribute = attribute.substring(colon + 1); 323 } 324 } 325 return attribute; 326 } 327 328 public List<MultiAttributeSetter> getMultiAttributeSetterCalls(String[] attributes, 329 ModelClass viewType, ModelClass[] valueType) { 330 attributes = stripAttributes(attributes); 331 final ArrayList<MultiAttributeSetter> calls = new ArrayList<MultiAttributeSetter>(); 332 ArrayList<MultiAttributeSetter> matching = getMatchingMultiAttributeSetters(attributes, 333 viewType, valueType); 334 Collections.sort(matching, COMPARE_MULTI_ATTRIBUTE_SETTERS); 335 while (!matching.isEmpty()) { 336 MultiAttributeSetter bestMatch = matching.get(0); 337 calls.add(bestMatch); 338 removeConsumedAttributes(matching, bestMatch.attributes); 339 } 340 return calls; 341 } 342 343 // Removes all MultiAttributeSetters that require any of the values in attributes 344 private static void removeConsumedAttributes(ArrayList<MultiAttributeSetter> matching, 345 String[] attributes) { 346 for (int i = matching.size() - 1; i >= 0; i--) { 347 final MultiAttributeSetter setter = matching.get(i); 348 boolean found = false; 349 for (String attribute : attributes) { 350 if (isInArray(attribute, setter.attributes)) { 351 found = true; 352 break; 353 } 354 } 355 if (found) { 356 matching.remove(i); 357 } 358 } 359 } 360 361 // Linear search through the String array for a specific value. 362 private static boolean isInArray(String str, String[] array) { 363 for (String value : array) { 364 if (value.equals(str)) { 365 return true; 366 } 367 } 368 return false; 369 } 370 371 private ArrayList<MultiAttributeSetter> getMatchingMultiAttributeSetters(String[] attributes, 372 ModelClass viewType, ModelClass[] valueType) { 373 final ArrayList<MultiAttributeSetter> setters = new ArrayList<MultiAttributeSetter>(); 374 for (MultiValueAdapterKey adapter : mStore.multiValueAdapters.keySet()) { 375 if (adapter.attributes.length > attributes.length) { 376 continue; 377 } 378 final ModelClass viewClass = mClassAnalyzer.findClass(adapter.viewType, null); 379 if (!viewClass.isAssignableFrom(viewType)) { 380 continue; 381 } 382 final MethodDescription method = mStore.multiValueAdapters.get(adapter); 383 final MultiAttributeSetter setter = createMultiAttributeSetter(method, attributes, 384 valueType, adapter); 385 if (setter != null) { 386 setters.add(setter); 387 } 388 } 389 return setters; 390 } 391 392 private MultiAttributeSetter createMultiAttributeSetter(MethodDescription method, 393 String[] allAttributes, ModelClass[] attributeValues, MultiValueAdapterKey adapter) { 394 int matchingAttributes = 0; 395 String[] casts = new String[adapter.attributes.length]; 396 MethodDescription[] conversions = new MethodDescription[adapter.attributes.length]; 397 398 for (int i = 0; i < allAttributes.length; i++) { 399 Integer index = adapter.attributeIndices.get(allAttributes[i]); 400 if (index != null) { 401 matchingAttributes++; 402 final String parameterTypeStr = adapter.parameterTypes[index]; 403 final ModelClass parameterType = mClassAnalyzer.findClass(parameterTypeStr, null); 404 final ModelClass attributeType = attributeValues[i]; 405 if (!parameterType.isAssignableFrom(attributeType)) { 406 if (ModelMethod.isBoxingConversion(parameterType, attributeType)) { 407 // automatic boxing is ok 408 continue; 409 } else if (ModelMethod.isImplicitConversion(attributeType, parameterType)) { 410 // implicit conversion is ok 411 continue; 412 } 413 // Look for a converter 414 conversions[index] = getConversionMethod(attributeType, parameterType, null); 415 if (conversions[index] == null) { 416 if (attributeType.isObject()) { 417 // Cast is allowed also 418 casts[index] = parameterTypeStr; 419 } else { 420 // Parameter type mismatch 421 return null; 422 } 423 } 424 } 425 } 426 } 427 428 if (matchingAttributes != adapter.attributes.length) { 429 return null; 430 } else { 431 return new MultiAttributeSetter(adapter, adapter.attributes, method, conversions, 432 casts); 433 } 434 } 435 436 public SetterCall getSetterCall(String attribute, ModelClass viewType, 437 ModelClass valueType, Map<String, String> imports) { 438 attribute = stripNamespace(attribute); 439 SetterCall setterCall = null; 440 MethodDescription conversionMethod = null; 441 if (viewType != null) { 442 HashMap<AccessorKey, MethodDescription> adapters = mStore.adapterMethods.get(attribute); 443 ModelMethod bestSetterMethod = getBestSetter(viewType, valueType, attribute, imports); 444 ModelClass bestViewType = null; 445 ModelClass bestValueType = null; 446 if (bestSetterMethod != null) { 447 bestViewType = bestSetterMethod.getDeclaringClass(); 448 bestValueType = bestSetterMethod.getParameterTypes()[0]; 449 setterCall = new ModelMethodSetter(bestSetterMethod); 450 } 451 452 if (adapters != null) { 453 for (AccessorKey key : adapters.keySet()) { 454 try { 455 ModelClass adapterViewType = mClassAnalyzer 456 .findClass(key.viewType, imports); 457 if (adapterViewType != null && adapterViewType.isAssignableFrom(viewType)) { 458 try { 459 ModelClass adapterValueType = mClassAnalyzer 460 .findClass(key.valueType, imports); 461 boolean isBetterView = bestViewType == null || 462 bestValueType.isAssignableFrom(adapterValueType); 463 if (isBetterParameter(valueType, adapterValueType, bestValueType, 464 isBetterView, imports)) { 465 bestViewType = adapterViewType; 466 bestValueType = adapterValueType; 467 MethodDescription adapter = adapters.get(key); 468 setterCall = new AdapterSetter(adapter); 469 } 470 471 } catch (Exception e) { 472 L.e(e, "Unknown class: %s", key.valueType); 473 } 474 } 475 } catch (Exception e) { 476 L.e(e, "Unknown class: %s", key.viewType); 477 } 478 } 479 } 480 481 conversionMethod = getConversionMethod(valueType, bestValueType, imports); 482 if (valueType.isObject() && setterCall != null && bestValueType.isNullable()) { 483 setterCall.setCast(bestValueType); 484 } 485 } 486 if (setterCall == null) { 487 if (viewType != null && !viewType.isViewDataBinding()) { 488 L.e("Cannot find the setter for attribute '%s' on %s.", attribute, 489 viewType.getCanonicalName()); 490 } 491 setterCall = new DummySetter(getDefaultSetter(attribute)); 492 } 493 setterCall.setConverter(conversionMethod); 494 return setterCall; 495 } 496 497 public boolean isUntaggable(String viewType) { 498 return mStore.untaggableTypes.containsKey(viewType); 499 } 500 501 private ModelMethod getBestSetter(ModelClass viewType, ModelClass argumentType, 502 String attribute, Map<String, String> imports) { 503 List<String> setterCandidates = new ArrayList<String>(); 504 HashMap<String, MethodDescription> renamed = mStore.renamedMethods.get(attribute); 505 if (renamed != null) { 506 for (String className : renamed.keySet()) { 507 try { 508 ModelClass renamedViewType = mClassAnalyzer.findClass(className, imports); 509 if (renamedViewType.isAssignableFrom(viewType)) { 510 setterCandidates.add(renamed.get(className).method); 511 break; 512 } 513 } catch (Exception e) { 514 //printMessage(Diagnostic.Kind.NOTE, "Unknown class: " + className); 515 } 516 } 517 } 518 setterCandidates.add(getDefaultSetter(attribute)); 519 setterCandidates.add(trimAttributeNamespace(attribute)); 520 521 ModelMethod bestMethod = null; 522 ModelClass bestParameterType = null; 523 List<ModelClass> args = new ArrayList<ModelClass>(); 524 args.add(argumentType); 525 for (String name : setterCandidates) { 526 ModelMethod[] methods = viewType.getMethods(name, 1); 527 528 for (ModelMethod method : methods) { 529 ModelClass[] parameterTypes = method.getParameterTypes(); 530 ModelClass param = parameterTypes[0]; 531 if (method.isVoid() && 532 isBetterParameter(argumentType, param, bestParameterType, true, imports)) { 533 bestParameterType = param; 534 bestMethod = method; 535 } 536 } 537 } 538 return bestMethod; 539 540 } 541 542 private static String trimAttributeNamespace(String attribute) { 543 final int colonIndex = attribute.indexOf(':'); 544 return colonIndex == -1 ? attribute : attribute.substring(colonIndex + 1); 545 } 546 547 private static String getDefaultSetter(String attribute) { 548 return "set" + StringUtils.capitalize(trimAttributeNamespace(attribute)); 549 } 550 551 private boolean isBetterParameter(ModelClass argument, ModelClass parameter, 552 ModelClass oldParameter, boolean isBetterViewTypeMatch, Map<String, String> imports) { 553 // Right view type. Check the value 554 if (!isBetterViewTypeMatch && oldParameter.equals(argument)) { 555 return false; 556 } else if (argument.equals(parameter)) { 557 // Exact match 558 return true; 559 } else if (!isBetterViewTypeMatch && 560 ModelMethod.isBoxingConversion(oldParameter, argument)) { 561 return false; 562 } else if (ModelMethod.isBoxingConversion(parameter, argument)) { 563 // Boxing/unboxing is second best 564 return true; 565 } else { 566 int oldConversionLevel = ModelMethod.getImplicitConversionLevel(oldParameter); 567 if (ModelMethod.isImplicitConversion(argument, parameter)) { 568 // Better implicit conversion 569 int conversionLevel = ModelMethod.getImplicitConversionLevel(parameter); 570 return oldConversionLevel < 0 || conversionLevel < oldConversionLevel; 571 } else if (oldConversionLevel >= 0) { 572 return false; 573 } else if (parameter.isAssignableFrom(argument)) { 574 // Right type, see if it is better than the current best match. 575 if (oldParameter == null) { 576 return true; 577 } else { 578 return oldParameter.isAssignableFrom(parameter); 579 } 580 } else { 581 MethodDescription conversionMethod = getConversionMethod(argument, parameter, 582 imports); 583 if (conversionMethod != null) { 584 return true; 585 } 586 if (getConversionMethod(argument, oldParameter, imports) != null) { 587 return false; 588 } 589 return argument.isObject() && !parameter.isPrimitive(); 590 } 591 } 592 } 593 594 private MethodDescription getConversionMethod(ModelClass from, ModelClass to, 595 Map<String, String> imports) { 596 if (from != null && to != null) { 597 for (String fromClassName : mStore.conversionMethods.keySet()) { 598 try { 599 ModelClass convertFrom = mClassAnalyzer.findClass(fromClassName, imports); 600 if (canUseForConversion(from, convertFrom)) { 601 HashMap<String, MethodDescription> conversion = 602 mStore.conversionMethods.get(fromClassName); 603 for (String toClassName : conversion.keySet()) { 604 try { 605 ModelClass convertTo = mClassAnalyzer.findClass(toClassName, 606 imports); 607 if (canUseForConversion(convertTo, to)) { 608 return conversion.get(toClassName); 609 } 610 } catch (Exception e) { 611 L.d(e, "Unknown class: %s", toClassName); 612 } 613 } 614 } 615 } catch (Exception e) { 616 L.d(e, "Unknown class: %s", fromClassName); 617 } 618 } 619 } 620 return null; 621 } 622 623 private boolean canUseForConversion(ModelClass from, ModelClass to) { 624 return from.equals(to) || ModelMethod.isBoxingConversion(from, to) || 625 to.isAssignableFrom(from); 626 } 627 628 private static void merge(IntermediateV1 store, Intermediate dumpStore) { 629 IntermediateV1 intermediateV1 = (IntermediateV1) dumpStore.upgrade(); 630 merge(store.adapterMethods, intermediateV1.adapterMethods); 631 merge(store.renamedMethods, intermediateV1.renamedMethods); 632 merge(store.conversionMethods, intermediateV1.conversionMethods); 633 store.multiValueAdapters.putAll(intermediateV1.multiValueAdapters); 634 store.untaggableTypes.putAll(intermediateV1.untaggableTypes); 635 } 636 637 private static <K, V> void merge(HashMap<K, HashMap<V, MethodDescription>> first, 638 HashMap<K, HashMap<V, MethodDescription>> second) { 639 for (K key : second.keySet()) { 640 HashMap<V, MethodDescription> firstVals = first.get(key); 641 HashMap<V, MethodDescription> secondVals = second.get(key); 642 if (firstVals == null) { 643 first.put(key, secondVals); 644 } else { 645 for (V key2 : secondVals.keySet()) { 646 if (!firstVals.containsKey(key2)) { 647 firstVals.put(key2, secondVals.get(key2)); 648 } 649 } 650 } 651 } 652 } 653 654 private static class MultiValueAdapterKey implements Serializable { 655 private static final long serialVersionUID = 1; 656 657 public final String viewType; 658 659 public final String[] attributes; 660 661 public final String[] parameterTypes; 662 663 public final TreeMap<String, Integer> attributeIndices = new TreeMap<String, Integer>(); 664 665 public MultiValueAdapterKey(ExecutableElement method, String[] attributes) { 666 this.attributes = stripAttributes(attributes); 667 List<? extends VariableElement> parameters = method.getParameters(); 668 this.viewType = getQualifiedName(parameters.get(0).asType()); 669 this.parameterTypes = new String[parameters.size() - 1]; 670 for (int i = 0; i < parameterTypes.length; i++) { 671 this.parameterTypes[i] = getQualifiedName(parameters.get(i + 1).asType()); 672 attributeIndices.put(this.attributes[i], i); 673 } 674 } 675 676 @Override 677 public boolean equals(Object obj) { 678 if (!(obj instanceof MultiValueAdapterKey)) { 679 return false; 680 } 681 final MultiValueAdapterKey that = (MultiValueAdapterKey) obj; 682 if (!this.viewType.equals(that.viewType) || 683 this.attributes.length != that.attributes.length || 684 !this.attributeIndices.keySet().equals(that.attributeIndices.keySet())) { 685 return false; 686 } 687 688 for (int i = 0; i < this.attributes.length; i++) { 689 final int thatIndex = that.attributeIndices.get(this.attributes[i]); 690 final String thisParameter = parameterTypes[i]; 691 final String thatParameter = that.parameterTypes[thatIndex]; 692 if (!thisParameter.equals(thatParameter)) { 693 return false; 694 } 695 } 696 return true; 697 } 698 699 @Override 700 public int hashCode() { 701 return Objects.hash(viewType, attributeIndices.keySet()); 702 } 703 } 704 705 private static class MethodDescription implements Serializable { 706 707 private static final long serialVersionUID = 1; 708 709 public final String type; 710 711 public final String method; 712 713 public MethodDescription(String type, String method) { 714 this.type = type; 715 this.method = method; 716 L.d("BINARY created method desc 1 %s %s", type, method ); 717 } 718 719 public MethodDescription(ExecutableElement method) { 720 TypeElement enclosingClass = (TypeElement) method.getEnclosingElement(); 721 this.type = enclosingClass.getQualifiedName().toString(); 722 this.method = method.getSimpleName().toString(); 723 L.d("BINARY created method desc 2 %s %s, %s", type, this.method, method); 724 } 725 726 @Override 727 public boolean equals(Object obj) { 728 if (obj instanceof MethodDescription) { 729 MethodDescription that = (MethodDescription) obj; 730 return that.type.equals(this.type) && that.method.equals(this.method); 731 } else { 732 return false; 733 } 734 } 735 736 @Override 737 public int hashCode() { 738 return Objects.hash(type, method); 739 } 740 741 @Override 742 public String toString() { 743 return type + "." + method + "()"; 744 } 745 } 746 747 private static class AccessorKey implements Serializable { 748 749 private static final long serialVersionUID = 1; 750 751 public final String viewType; 752 753 public final String valueType; 754 755 public AccessorKey(String viewType, String valueType) { 756 this.viewType = viewType; 757 this.valueType = valueType; 758 } 759 760 @Override 761 public int hashCode() { 762 return Objects.hash(viewType, valueType); 763 } 764 765 @Override 766 public boolean equals(Object obj) { 767 if (obj instanceof AccessorKey) { 768 AccessorKey that = (AccessorKey) obj; 769 return viewType.equals(that.valueType) && valueType.equals(that.valueType); 770 } else { 771 return false; 772 } 773 } 774 775 @Override 776 public String toString() { 777 return "AK(" + viewType + ", " + valueType + ")"; 778 } 779 } 780 781 private interface Intermediate extends Serializable { 782 Intermediate upgrade(); 783 } 784 785 private static class IntermediateV1 implements Serializable, Intermediate { 786 private static final long serialVersionUID = 1; 787 public final HashMap<String, HashMap<AccessorKey, MethodDescription>> adapterMethods = 788 new HashMap<String, HashMap<AccessorKey, MethodDescription>>(); 789 public final HashMap<String, HashMap<String, MethodDescription>> renamedMethods = 790 new HashMap<String, HashMap<String, MethodDescription>>(); 791 public final HashMap<String, HashMap<String, MethodDescription>> conversionMethods = 792 new HashMap<String, HashMap<String, MethodDescription>>(); 793 public final HashMap<String, String> untaggableTypes = new HashMap<String, String>(); 794 public final HashMap<MultiValueAdapterKey, MethodDescription> multiValueAdapters = 795 new HashMap<MultiValueAdapterKey, MethodDescription>(); 796 797 public IntermediateV1() { 798 } 799 800 @Override 801 public Intermediate upgrade() { 802 return this; 803 } 804 } 805 806 public static class DummySetter extends SetterCall { 807 private String mMethodName; 808 809 public DummySetter(String methodName) { 810 mMethodName = methodName; 811 } 812 813 @Override 814 public String toJavaInternal(String viewExpression, String valueExpression) { 815 return viewExpression + "." + mMethodName + "(" + valueExpression + ")"; 816 } 817 818 @Override 819 public int getMinApi() { 820 return 1; 821 } 822 } 823 824 public static class AdapterSetter extends SetterCall { 825 final MethodDescription mAdapter; 826 827 public AdapterSetter(MethodDescription adapter) { 828 mAdapter = adapter; 829 } 830 831 @Override 832 public String toJavaInternal(String viewExpression, String valueExpression) { 833 return mAdapter.type + "." + mAdapter.method + "(" + viewExpression + ", " + 834 mCastString + valueExpression + ")"; 835 } 836 837 @Override 838 public int getMinApi() { 839 return 1; 840 } 841 } 842 843 public static class ModelMethodSetter extends SetterCall { 844 final ModelMethod mModelMethod; 845 846 public ModelMethodSetter(ModelMethod modelMethod) { 847 mModelMethod = modelMethod; 848 } 849 850 @Override 851 public String toJavaInternal(String viewExpression, String valueExpression) { 852 return viewExpression + "." + mModelMethod.getName() + "(" + mCastString + 853 valueExpression + ")"; 854 } 855 856 @Override 857 public int getMinApi() { 858 return mModelMethod.getMinApi(); 859 } 860 } 861 862 public static interface BindingSetterCall { 863 String toJava(String viewExpression, String... valueExpressions); 864 865 int getMinApi(); 866 } 867 868 public static abstract class SetterCall implements BindingSetterCall { 869 private MethodDescription mConverter; 870 protected String mCastString = ""; 871 872 public SetterCall() { 873 } 874 875 public void setConverter(MethodDescription converter) { 876 mConverter = converter; 877 } 878 879 protected abstract String toJavaInternal(String viewExpression, String converted); 880 881 @Override 882 public final String toJava(String viewExpression, String... valueExpression) { 883 Preconditions.checkArgument(valueExpression.length == 1); 884 return toJavaInternal(viewExpression, convertValue(valueExpression[0])); 885 } 886 887 protected String convertValue(String valueExpression) { 888 return mConverter == null ? valueExpression : 889 mConverter.type + "." + mConverter.method + "(" + valueExpression + ")"; 890 } 891 892 abstract public int getMinApi(); 893 894 public void setCast(ModelClass castTo) { 895 mCastString = "(" + castTo.toJavaCode() + ") "; 896 } 897 } 898 899 public static class MultiAttributeSetter implements BindingSetterCall { 900 public final String[] attributes; 901 private final MethodDescription mAdapter; 902 private final MethodDescription[] mConverters; 903 private final String[] mCasts; 904 private final MultiValueAdapterKey mKey; 905 906 public MultiAttributeSetter(MultiValueAdapterKey key, String[] attributes, 907 MethodDescription adapter, MethodDescription[] converters, String[] casts) { 908 Preconditions.checkArgument(converters != null && 909 converters.length == attributes.length && 910 casts != null && casts.length == attributes.length); 911 this.attributes = attributes; 912 this.mAdapter = adapter; 913 this.mConverters = converters; 914 this.mCasts = casts; 915 this.mKey = key; 916 } 917 918 @Override 919 public final String toJava(String viewExpression, String[] valueExpressions) { 920 Preconditions.checkArgument(valueExpressions.length == attributes.length, 921 "MultiAttributeSetter needs %s items, received %s", 922 Arrays.toString(attributes), Arrays.toString(valueExpressions)); 923 StringBuilder sb = new StringBuilder(); 924 sb.append(mAdapter.type) 925 .append('.') 926 .append(mAdapter.method) 927 .append('(') 928 .append(viewExpression); 929 for (int i = 0; i < valueExpressions.length; i++) { 930 sb.append(','); 931 if (mConverters[i] != null) { 932 final MethodDescription converter = mConverters[i]; 933 sb.append(converter.type) 934 .append('.') 935 .append(converter.method) 936 .append('(') 937 .append(valueExpressions[i]) 938 .append(')'); 939 } else { 940 if (mCasts[i] != null) { 941 sb.append('(') 942 .append(mCasts[i]) 943 .append(')'); 944 } 945 sb.append(valueExpressions[i]); 946 } 947 } 948 sb.append(')'); 949 return sb.toString(); 950 } 951 952 @Override 953 public int getMinApi() { 954 return 1; 955 } 956 957 @Override 958 public String toString() { 959 return "MultiAttributeSetter{" + 960 "attributes=" + Arrays.toString(attributes) + 961 ", mAdapter=" + mAdapter + 962 ", mConverters=" + Arrays.toString(mConverters) + 963 ", mCasts=" + Arrays.toString(mCasts) + 964 ", mKey=" + mKey + 965 '}'; 966 } 967 } 968} 969