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