• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.databinding.tool.store;
17 
18 import org.apache.commons.lang3.StringUtils;
19 
20 import android.databinding.tool.reflection.ModelAnalyzer;
21 import android.databinding.tool.reflection.ModelClass;
22 import android.databinding.tool.reflection.ModelMethod;
23 import android.databinding.tool.util.GenerationalClassUtil;
24 import android.databinding.tool.util.L;
25 import android.databinding.tool.util.Preconditions;
26 
27 import java.io.IOException;
28 import java.io.Serializable;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.Collections;
32 import java.util.Comparator;
33 import java.util.HashMap;
34 import java.util.Iterator;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Set;
38 import java.util.TreeMap;
39 
40 import javax.annotation.processing.ProcessingEnvironment;
41 import javax.lang.model.element.ExecutableElement;
42 import javax.lang.model.element.Modifier;
43 import javax.lang.model.element.TypeElement;
44 import javax.lang.model.element.VariableElement;
45 import javax.lang.model.type.ArrayType;
46 import javax.lang.model.type.DeclaredType;
47 import javax.lang.model.type.TypeKind;
48 import javax.lang.model.type.TypeMirror;
49 
50 public class SetterStore {
51     private static SetterStore sStore;
52 
53     private final IntermediateV1 mStore;
54     private final ModelAnalyzer mClassAnalyzer;
55 
56     private Comparator<MultiAttributeSetter> COMPARE_MULTI_ATTRIBUTE_SETTERS =
57             new Comparator<MultiAttributeSetter>() {
58                 @Override
59                 public int compare(MultiAttributeSetter o1, MultiAttributeSetter o2) {
60                     if (o1.attributes.length != o2.attributes.length) {
61                         return o2.attributes.length - o1.attributes.length;
62                     }
63                     ModelClass view1 = mClassAnalyzer.findClass(o1.mKey.viewType, null).erasure();
64                     ModelClass view2 = mClassAnalyzer.findClass(o2.mKey.viewType, null).erasure();
65                     if (!view1.equals(view2)) {
66                         if (view1.isAssignableFrom(view2)) {
67                             return 1;
68                         } else {
69                             return -1;
70                         }
71                     }
72                     if (!o1.mKey.attributeIndices.keySet()
73                             .equals(o2.mKey.attributeIndices.keySet())) {
74                         // order by attribute name
75                         Iterator<String> o1Keys = o1.mKey.attributeIndices.keySet().iterator();
76                         Iterator<String> o2Keys = o2.mKey.attributeIndices.keySet().iterator();
77                         while (o1Keys.hasNext()) {
78                             String key1 = o1Keys.next();
79                             String key2 = o2Keys.next();
80                             int compare = key1.compareTo(key2);
81                             if (compare != 0) {
82                                 return compare;
83                             }
84                         }
85                         Preconditions.check(false,
86                                 "The sets don't match! That means the keys shouldn't match also");
87                     }
88                     // Same view type. Same attributes
89                     for (String attribute : o1.mKey.attributeIndices.keySet()) {
90                         final int index1 = o1.mKey.attributeIndices.get(attribute);
91                         final int index2 = o2.mKey.attributeIndices.get(attribute);
92                         ModelClass type1 = mClassAnalyzer
93                                 .findClass(o1.mKey.parameterTypes[index1], null);
94                         ModelClass type2 = mClassAnalyzer
95                                 .findClass(o2.mKey.parameterTypes[index2], null);
96                         if (type1.equals(type2)) {
97                             continue;
98                         }
99                         if (o1.mCasts[index1] != null) {
100                             if (o2.mCasts[index2] == null) {
101                                 return 1; // o2 is better
102                             } else {
103                                 continue; // both are casts
104                             }
105                         } else if (o2.mCasts[index2] != null) {
106                             return -1; // o1 is better
107                         }
108                         if (o1.mConverters[index1] != null) {
109                             if (o2.mConverters[index2] == null) {
110                                 return 1; // o2 is better
111                             } else {
112                                 continue; // both are conversions
113                             }
114                         } else if (o2.mConverters[index2] != null) {
115                             return -1; // o1 is better
116                         }
117 
118                         if (type1.isPrimitive()) {
119                             if (type2.isPrimitive()) {
120                                 int type1ConversionLevel = ModelMethod
121                                         .getImplicitConversionLevel(type1);
122                                 int type2ConversionLevel = ModelMethod
123                                         .getImplicitConversionLevel(type2);
124                                 return type2ConversionLevel - type1ConversionLevel;
125                             } else {
126                                 // type1 is primitive and has higher priority
127                                 return -1;
128                             }
129                         } else if (type2.isPrimitive()) {
130                             return 1;
131                         }
132                         if (type1.isAssignableFrom(type2)) {
133                             return 1;
134                         } else if (type2.isAssignableFrom(type1)) {
135                             return -1;
136                         }
137                     }
138                     // hmmm... same view type, same attributes, same parameter types... ?
139                     return 0;
140                 }
141             };
142 
SetterStore(ModelAnalyzer modelAnalyzer, IntermediateV1 store)143     private SetterStore(ModelAnalyzer modelAnalyzer, IntermediateV1 store) {
144         mClassAnalyzer = modelAnalyzer;
145         mStore = store;
146     }
147 
get(ModelAnalyzer modelAnalyzer)148     public static SetterStore get(ModelAnalyzer modelAnalyzer) {
149         if (sStore == null) {
150             sStore = load(modelAnalyzer);
151         }
152         return sStore;
153     }
154 
load(ModelAnalyzer modelAnalyzer)155     private static SetterStore load(ModelAnalyzer modelAnalyzer) {
156         IntermediateV1 store = new IntermediateV1();
157         List<Intermediate> previousStores = GenerationalClassUtil
158                 .loadObjects(GenerationalClassUtil.ExtensionFilter.SETTER_STORE);
159         for (Intermediate intermediate : previousStores) {
160             merge(store, intermediate);
161         }
162         return new SetterStore(modelAnalyzer, store);
163     }
164 
addRenamedMethod(String attribute, String declaringClass, String method, TypeElement declaredOn)165     public void addRenamedMethod(String attribute, String declaringClass, String method,
166             TypeElement declaredOn) {
167         attribute = stripNamespace(attribute);
168         HashMap<String, MethodDescription> renamed = mStore.renamedMethods.get(attribute);
169         if (renamed == null) {
170             renamed = new HashMap<String, MethodDescription>();
171             mStore.renamedMethods.put(attribute, renamed);
172         }
173         MethodDescription methodDescription = new MethodDescription(
174                 declaredOn.getQualifiedName().toString(), method);
175         L.d("STORE addmethod desc %s", methodDescription);
176         renamed.put(declaringClass, methodDescription);
177     }
178 
addBindingAdapter(ProcessingEnvironment processingEnv, String attribute, ExecutableElement bindingMethod, boolean takesComponent)179     public void addBindingAdapter(ProcessingEnvironment processingEnv, String attribute,
180             ExecutableElement bindingMethod, boolean takesComponent) {
181         attribute = stripNamespace(attribute);
182         L.d("STORE addBindingAdapter %s %s", attribute, bindingMethod);
183         HashMap<AccessorKey, MethodDescription> adapters = mStore.adapterMethods.get(attribute);
184 
185         if (adapters == null) {
186             adapters = new HashMap<AccessorKey, MethodDescription>();
187             mStore.adapterMethods.put(attribute, adapters);
188         }
189         List<? extends VariableElement> parameters = bindingMethod.getParameters();
190         final int viewIndex = takesComponent ? 1 : 0;
191         TypeMirror viewType = eraseType(processingEnv, parameters.get(viewIndex).asType());
192         String view = getQualifiedName(viewType);
193         TypeMirror parameterType = eraseType(processingEnv, parameters.get(viewIndex + 1).asType());
194         String value = getQualifiedName(parameterType);
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, 1, takesComponent));
202     }
203 
eraseType(ProcessingEnvironment processingEnv, TypeMirror typeMirror)204     private static TypeMirror eraseType(ProcessingEnvironment processingEnv,
205             TypeMirror typeMirror) {
206         if (hasTypeVar(typeMirror)) {
207             return processingEnv.getTypeUtils().erasure(typeMirror);
208         } else {
209             return typeMirror;
210         }
211     }
212 
eraseType(ModelClass modelClass)213     private static ModelClass eraseType(ModelClass modelClass) {
214         if (hasTypeVar(modelClass)) {
215             return modelClass.erasure();
216         } else {
217             return modelClass;
218         }
219     }
220 
hasTypeVar(TypeMirror typeMirror)221     private static boolean hasTypeVar(TypeMirror typeMirror) {
222         TypeKind kind = typeMirror.getKind();
223         if (kind == TypeKind.TYPEVAR) {
224             return true;
225         } else if (kind == TypeKind.ARRAY) {
226             return hasTypeVar(((ArrayType) typeMirror).getComponentType());
227         } else if (kind == TypeKind.DECLARED) {
228             DeclaredType declaredType = (DeclaredType) typeMirror;
229             List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
230             if (typeArguments == null || typeArguments.isEmpty()) {
231                 return false;
232             }
233             for (TypeMirror arg : typeArguments) {
234                 if (hasTypeVar(arg)) {
235                     return true;
236                 }
237             }
238             return false;
239         } else {
240             return false;
241         }
242     }
243 
hasTypeVar(ModelClass type)244     private static boolean hasTypeVar(ModelClass type) {
245         if (type.isTypeVar()) {
246             return true;
247         } else if (type.isArray()) {
248             return hasTypeVar(type.getComponentType());
249         } else {
250             List<ModelClass> typeArguments = type.getTypeArguments();
251             if (typeArguments == null) {
252                 return false;
253             }
254             for (ModelClass arg : typeArguments) {
255                 if (hasTypeVar(arg)) {
256                     return true;
257                 }
258             }
259             return false;
260         }
261     }
262 
addBindingAdapter(ProcessingEnvironment processingEnv, String[] attributes, ExecutableElement bindingMethod, boolean takesComponent)263     public void addBindingAdapter(ProcessingEnvironment processingEnv, String[] attributes,
264             ExecutableElement bindingMethod, boolean takesComponent) {
265         L.d("STORE add multi-value BindingAdapter %d %s", attributes.length, bindingMethod);
266         MultiValueAdapterKey key = new MultiValueAdapterKey(processingEnv, bindingMethod,
267                 attributes, takesComponent);
268         MethodDescription methodDescription = new MethodDescription(bindingMethod,
269                 attributes.length, takesComponent);
270         mStore.multiValueAdapters.put(key, methodDescription);
271     }
272 
stripAttributes(String[] attributes)273     private static String[] stripAttributes(String[] attributes) {
274         String[] strippedAttributes = new String[attributes.length];
275         for (int i = 0; i < attributes.length; i++) {
276             strippedAttributes[i] = stripNamespace(attributes[i]);
277         }
278         return strippedAttributes;
279     }
280 
addUntaggableTypes(String[] typeNames, TypeElement declaredOn)281     public void addUntaggableTypes(String[] typeNames, TypeElement declaredOn) {
282         L.d("STORE addUntaggableTypes %s %s", Arrays.toString(typeNames), declaredOn);
283         String declaredType = declaredOn.getQualifiedName().toString();
284         for (String type : typeNames) {
285             mStore.untaggableTypes.put(type, declaredType);
286         }
287     }
288 
getQualifiedName(TypeMirror type)289     private static String getQualifiedName(TypeMirror type) {
290         if (type.getKind() == TypeKind.ARRAY) {
291             return getQualifiedName(((ArrayType) type).getComponentType()) + "[]";
292         } else {
293             return type.toString();
294         }
295     }
296 
addConversionMethod(ExecutableElement conversionMethod)297     public void addConversionMethod(ExecutableElement conversionMethod) {
298         L.d("STORE addConversionMethod %s", conversionMethod);
299         List<? extends VariableElement> parameters = conversionMethod.getParameters();
300         String fromType = getQualifiedName(parameters.get(0).asType());
301         String toType = getQualifiedName(conversionMethod.getReturnType());
302         MethodDescription methodDescription = new MethodDescription(conversionMethod, 1, false);
303         HashMap<String, MethodDescription> convertTo = mStore.conversionMethods.get(fromType);
304         if (convertTo == null) {
305             convertTo = new HashMap<String, MethodDescription>();
306             mStore.conversionMethods.put(fromType, convertTo);
307         }
308         convertTo.put(toType, methodDescription);
309     }
310 
clear(Set<String> classes)311     public void clear(Set<String> classes) {
312         ArrayList<AccessorKey> removedAccessorKeys = new ArrayList<AccessorKey>();
313         for (HashMap<AccessorKey, MethodDescription> adapters : mStore.adapterMethods.values()) {
314             for (AccessorKey key : adapters.keySet()) {
315                 MethodDescription description = adapters.get(key);
316                 if (classes.contains(description.type)) {
317                     removedAccessorKeys.add(key);
318                 }
319             }
320             removeFromMap(adapters, removedAccessorKeys);
321         }
322 
323         ArrayList<String> removedRenamed = new ArrayList<String>();
324         for (HashMap<String, MethodDescription> renamed : mStore.renamedMethods.values()) {
325             for (String key : renamed.keySet()) {
326                 if (classes.contains(renamed.get(key).type)) {
327                     removedRenamed.add(key);
328                 }
329             }
330             removeFromMap(renamed, removedRenamed);
331         }
332 
333         ArrayList<String> removedConversions = new ArrayList<String>();
334         for (HashMap<String, MethodDescription> convertTos : mStore.conversionMethods.values()) {
335             for (String toType : convertTos.keySet()) {
336                 MethodDescription methodDescription = convertTos.get(toType);
337                 if (classes.contains(methodDescription.type)) {
338                     removedConversions.add(toType);
339                 }
340             }
341             removeFromMap(convertTos, removedConversions);
342         }
343 
344         ArrayList<String> removedUntaggable = new ArrayList<String>();
345         for (String typeName : mStore.untaggableTypes.keySet()) {
346             if (classes.contains(mStore.untaggableTypes.get(typeName))) {
347                 removedUntaggable.add(typeName);
348             }
349         }
350         removeFromMap(mStore.untaggableTypes, removedUntaggable);
351     }
352 
removeFromMap(Map<K, V> map, List<K> keys)353     private static <K, V> void removeFromMap(Map<K, V> map, List<K> keys) {
354         for (K key : keys) {
355             map.remove(key);
356         }
357         keys.clear();
358     }
359 
write(String projectPackage, ProcessingEnvironment processingEnvironment)360     public void write(String projectPackage, ProcessingEnvironment processingEnvironment)
361             throws IOException {
362         GenerationalClassUtil.writeIntermediateFile(processingEnvironment,
363                 projectPackage, projectPackage +
364                         GenerationalClassUtil.ExtensionFilter.SETTER_STORE.getExtension(), mStore);
365     }
366 
stripNamespace(String attribute)367     private static String stripNamespace(String attribute) {
368         if (!attribute.startsWith("android:")) {
369             int colon = attribute.indexOf(':');
370             if (colon >= 0) {
371                 attribute = attribute.substring(colon + 1);
372             }
373         }
374         return attribute;
375     }
376 
getMultiAttributeSetterCalls(String[] attributes, ModelClass viewType, ModelClass[] valueType)377     public List<MultiAttributeSetter> getMultiAttributeSetterCalls(String[] attributes,
378             ModelClass viewType, ModelClass[] valueType) {
379         attributes = stripAttributes(attributes);
380         final ArrayList<MultiAttributeSetter> calls = new ArrayList<MultiAttributeSetter>();
381         if (viewType != null && viewType.isGeneric()) {
382             List<ModelClass> viewGenerics = viewType.getTypeArguments();
383             for (int i = 0; i < valueType.length; i++) {
384                 valueType[i] = eraseType(valueType[i], viewGenerics);
385             }
386             viewType = viewType.erasure();
387         }
388         ArrayList<MultiAttributeSetter> matching = getMatchingMultiAttributeSetters(attributes,
389                 viewType, valueType);
390         Collections.sort(matching, COMPARE_MULTI_ATTRIBUTE_SETTERS);
391         while (!matching.isEmpty()) {
392             MultiAttributeSetter bestMatch = matching.get(0);
393             calls.add(bestMatch);
394             removeConsumedAttributes(matching, bestMatch.attributes);
395         }
396         return calls;
397     }
398 
399     // Removes all MultiAttributeSetters that require any of the values in attributes
removeConsumedAttributes(ArrayList<MultiAttributeSetter> matching, String[] attributes)400     private static void removeConsumedAttributes(ArrayList<MultiAttributeSetter> matching,
401             String[] attributes) {
402         for (int i = matching.size() - 1; i >= 0; i--) {
403             final MultiAttributeSetter setter = matching.get(i);
404             boolean found = false;
405             for (String attribute : attributes) {
406                 if (isInArray(attribute, setter.attributes)) {
407                     found = true;
408                     break;
409                 }
410             }
411             if (found) {
412                 matching.remove(i);
413             }
414         }
415     }
416 
417     // Linear search through the String array for a specific value.
isInArray(String str, String[] array)418     private static boolean isInArray(String str, String[] array) {
419         for (String value : array) {
420             if (value.equals(str)) {
421                 return true;
422             }
423         }
424         return false;
425     }
426 
getMatchingMultiAttributeSetters(String[] attributes, ModelClass viewType, ModelClass[] valueType)427     private ArrayList<MultiAttributeSetter> getMatchingMultiAttributeSetters(String[] attributes,
428             ModelClass viewType, ModelClass[] valueType) {
429         final ArrayList<MultiAttributeSetter> setters = new ArrayList<MultiAttributeSetter>();
430         for (MultiValueAdapterKey adapter : mStore.multiValueAdapters.keySet()) {
431             if (adapter.attributes.length > attributes.length) {
432                 continue;
433             }
434             ModelClass viewClass = mClassAnalyzer.findClass(adapter.viewType, null);
435             if (viewClass.isGeneric()) {
436                 viewClass = viewClass.erasure();
437             }
438             if (!viewClass.isAssignableFrom(viewType)) {
439                 continue;
440             }
441             final MethodDescription method = mStore.multiValueAdapters.get(adapter);
442             final MultiAttributeSetter setter = createMultiAttributeSetter(method, attributes,
443                     valueType, adapter);
444             if (setter != null) {
445                 setters.add(setter);
446             }
447         }
448         return setters;
449     }
450 
createMultiAttributeSetter(MethodDescription method, String[] allAttributes, ModelClass[] attributeValues, MultiValueAdapterKey adapter)451     private MultiAttributeSetter createMultiAttributeSetter(MethodDescription method,
452             String[] allAttributes, ModelClass[] attributeValues, MultiValueAdapterKey adapter) {
453         int matchingAttributes = 0;
454         String[] casts = new String[adapter.attributes.length];
455         MethodDescription[] conversions = new MethodDescription[adapter.attributes.length];
456 
457         for (int i = 0; i < allAttributes.length; i++) {
458             Integer index = adapter.attributeIndices.get(allAttributes[i]);
459             if (index != null) {
460                 matchingAttributes++;
461                 final String parameterTypeStr = adapter.parameterTypes[index];
462                 final ModelClass parameterType = eraseType(
463                         mClassAnalyzer.findClass(parameterTypeStr, null));
464                 final ModelClass attributeType = attributeValues[i];
465                 if (!parameterType.isAssignableFrom(attributeType)) {
466                     if (ModelMethod.isBoxingConversion(parameterType, attributeType)) {
467                         // automatic boxing is ok
468                         continue;
469                     } else if (ModelMethod.isImplicitConversion(attributeType, parameterType)) {
470                         // implicit conversion is ok
471                         continue;
472                     }
473                     // Look for a converter
474                     conversions[index] = getConversionMethod(attributeType, parameterType, null);
475                     if (conversions[index] == null) {
476                         if (attributeType.isObject()) {
477                             // Cast is allowed also
478                             casts[index] = parameterTypeStr;
479                         } else {
480                             // Parameter type mismatch
481                             return null;
482                         }
483                     }
484                 }
485             }
486         }
487 
488         if (matchingAttributes != adapter.attributes.length) {
489             return null;
490         } else {
491             return new MultiAttributeSetter(adapter, adapter.attributes, method, conversions,
492                     casts);
493         }
494     }
495 
getSetterCall(String attribute, ModelClass viewType, ModelClass valueType, Map<String, String> imports)496     public SetterCall getSetterCall(String attribute, ModelClass viewType,
497             ModelClass valueType, Map<String, String> imports) {
498         attribute = stripNamespace(attribute);
499         SetterCall setterCall = null;
500         MethodDescription conversionMethod = null;
501         if (viewType != null) {
502             viewType = viewType.erasure();
503             HashMap<AccessorKey, MethodDescription> adapters = mStore.adapterMethods.get(attribute);
504             ModelMethod bestSetterMethod = getBestSetter(viewType, valueType, attribute, imports);
505             ModelClass bestViewType = null;
506             ModelClass bestValueType = null;
507             if (bestSetterMethod != null) {
508                 bestViewType = bestSetterMethod.getDeclaringClass();
509                 bestValueType = bestSetterMethod.getParameterTypes()[0];
510                 setterCall = new ModelMethodSetter(bestSetterMethod);
511             }
512 
513             if (adapters != null) {
514                 for (AccessorKey key : adapters.keySet()) {
515                     try {
516                         ModelClass adapterViewType = mClassAnalyzer
517                                 .findClass(key.viewType, imports).erasure();
518                         if (adapterViewType != null && adapterViewType.isAssignableFrom(viewType)) {
519                             try {
520                                 L.d("setter parameter type is %s", key.valueType);
521                                 final ModelClass adapterValueType = eraseType(mClassAnalyzer
522                                         .findClass(key.valueType, imports));
523                                 L.d("setter %s takes type %s, compared to %s",
524                                         adapters.get(key).method, adapterValueType.toJavaCode(),
525                                         valueType.toJavaCode());
526                                 boolean isBetterView = bestViewType == null ||
527                                         bestValueType.isAssignableFrom(adapterValueType);
528                                 if (isBetterParameter(valueType, adapterValueType, bestValueType,
529                                         isBetterView, imports)) {
530                                     bestViewType = adapterViewType;
531                                     bestValueType = adapterValueType;
532                                     MethodDescription adapter = adapters.get(key);
533                                     setterCall = new AdapterSetter(adapter, adapterValueType);
534                                 }
535 
536                             } catch (Exception e) {
537                                 L.e(e, "Unknown class: %s", key.valueType);
538                             }
539                         }
540                     } catch (Exception e) {
541                         L.e(e, "Unknown class: %s", key.viewType);
542                     }
543                 }
544             }
545 
546             conversionMethod = getConversionMethod(valueType, bestValueType, imports);
547             if (valueType.isObject() && setterCall != null && bestValueType.isNullable()) {
548                 setterCall.setCast(bestValueType);
549             }
550         }
551         if (setterCall == null) {
552             if (viewType != null && !viewType.isViewDataBinding()) {
553                 return null; // no setter found!!
554             }
555             setterCall = new DummySetter(getDefaultSetter(attribute));
556         }
557         setterCall.setConverter(conversionMethod);
558         return setterCall;
559     }
560 
isUntaggable(String viewType)561     public boolean isUntaggable(String viewType) {
562         return mStore.untaggableTypes.containsKey(viewType);
563     }
564 
getBestSetter(ModelClass viewType, ModelClass argumentType, String attribute, Map<String, String> imports)565     private ModelMethod getBestSetter(ModelClass viewType, ModelClass argumentType,
566             String attribute, Map<String, String> imports) {
567         if (viewType.isGeneric()) {
568             argumentType = eraseType(argumentType, viewType.getTypeArguments());
569             viewType = viewType.erasure();
570         }
571         List<String> setterCandidates = new ArrayList<String>();
572         HashMap<String, MethodDescription> renamed = mStore.renamedMethods.get(attribute);
573         if (renamed != null) {
574             for (String className : renamed.keySet()) {
575                 try {
576                     ModelClass renamedViewType = mClassAnalyzer.findClass(className, imports);
577                     if (renamedViewType.erasure().isAssignableFrom(viewType)) {
578                         setterCandidates.add(renamed.get(className).method);
579                         break;
580                     }
581                 } catch (Exception e) {
582                     //printMessage(Diagnostic.Kind.NOTE, "Unknown class: " + className);
583                 }
584             }
585         }
586         setterCandidates.add(getDefaultSetter(attribute));
587         setterCandidates.add(trimAttributeNamespace(attribute));
588 
589         ModelMethod bestMethod = null;
590         ModelClass bestParameterType = null;
591         List<ModelClass> args = new ArrayList<ModelClass>();
592         args.add(argumentType);
593         for (String name : setterCandidates) {
594             ModelMethod[] methods = viewType.getMethods(name, 1);
595 
596             for (ModelMethod method : methods) {
597                 ModelClass[] parameterTypes = method.getParameterTypes();
598                 ModelClass param = parameterTypes[0];
599                 if (method.isVoid() &&
600                         isBetterParameter(argumentType, param, bestParameterType, true, imports)) {
601                     bestParameterType = param;
602                     bestMethod = method;
603                 }
604             }
605         }
606         return bestMethod;
607     }
608 
eraseType(ModelClass type, List<ModelClass> typeParameters)609     private static ModelClass eraseType(ModelClass type, List<ModelClass> typeParameters) {
610         List<ModelClass> typeArguments = type.getTypeArguments();
611         if (typeArguments == null || typeParameters == null) {
612             return type;
613         }
614         for (ModelClass arg : typeArguments) {
615             if (typeParameters.contains(arg)) {
616                 return type.erasure();
617             }
618         }
619         return type;
620     }
621 
trimAttributeNamespace(String attribute)622     private static String trimAttributeNamespace(String attribute) {
623         final int colonIndex = attribute.indexOf(':');
624         return colonIndex == -1 ? attribute : attribute.substring(colonIndex + 1);
625     }
626 
getDefaultSetter(String attribute)627     private static String getDefaultSetter(String attribute) {
628         return "set" + StringUtils.capitalize(trimAttributeNamespace(attribute));
629     }
630 
isBetterParameter(ModelClass argument, ModelClass parameter, ModelClass oldParameter, boolean isBetterViewTypeMatch, Map<String, String> imports)631     private boolean isBetterParameter(ModelClass argument, ModelClass parameter,
632             ModelClass oldParameter, boolean isBetterViewTypeMatch, Map<String, String> imports) {
633         // Right view type. Check the value
634         if (!isBetterViewTypeMatch && oldParameter.equals(argument)) {
635             return false;
636         } else if (argument.equals(parameter)) {
637             // Exact match
638             return true;
639         } else if (!isBetterViewTypeMatch &&
640                 ModelMethod.isBoxingConversion(oldParameter, argument)) {
641             return false;
642         } else if (ModelMethod.isBoxingConversion(parameter, argument)) {
643             // Boxing/unboxing is second best
644             return true;
645         } else {
646             int oldConversionLevel = ModelMethod.getImplicitConversionLevel(oldParameter);
647             if (ModelMethod.isImplicitConversion(argument, parameter)) {
648                 // Better implicit conversion
649                 int conversionLevel = ModelMethod.getImplicitConversionLevel(parameter);
650                 return oldConversionLevel < 0 || conversionLevel < oldConversionLevel;
651             } else if (oldConversionLevel >= 0) {
652                 return false;
653             } else if (parameter.isAssignableFrom(argument)) {
654                 // Right type, see if it is better than the current best match.
655                 if (oldParameter == null) {
656                     return true;
657                 } else {
658                     return oldParameter.isAssignableFrom(parameter);
659                 }
660             } else {
661                 MethodDescription conversionMethod = getConversionMethod(argument, parameter,
662                         imports);
663                 if (conversionMethod != null) {
664                     return true;
665                 }
666                 if (getConversionMethod(argument, oldParameter, imports) != null) {
667                     return false;
668                 }
669                 return argument.isObject() && !parameter.isPrimitive();
670             }
671         }
672     }
673 
getConversionMethod(ModelClass from, ModelClass to, Map<String, String> imports)674     private MethodDescription getConversionMethod(ModelClass from, ModelClass to,
675             Map<String, String> imports) {
676         if (from != null && to != null) {
677             for (String fromClassName : mStore.conversionMethods.keySet()) {
678                 try {
679                     ModelClass convertFrom = mClassAnalyzer.findClass(fromClassName, imports);
680                     if (canUseForConversion(from, convertFrom)) {
681                         HashMap<String, MethodDescription> conversion =
682                                 mStore.conversionMethods.get(fromClassName);
683                         for (String toClassName : conversion.keySet()) {
684                             try {
685                                 ModelClass convertTo = mClassAnalyzer.findClass(toClassName,
686                                         imports);
687                                 if (canUseForConversion(convertTo, to)) {
688                                     return conversion.get(toClassName);
689                                 }
690                             } catch (Exception e) {
691                                 L.d(e, "Unknown class: %s", toClassName);
692                             }
693                         }
694                     }
695                 } catch (Exception e) {
696                     L.d(e, "Unknown class: %s", fromClassName);
697                 }
698             }
699         }
700         return null;
701     }
702 
canUseForConversion(ModelClass from, ModelClass to)703     private boolean canUseForConversion(ModelClass from, ModelClass to) {
704         return from.equals(to) || ModelMethod.isBoxingConversion(from, to) ||
705                 to.isAssignableFrom(from);
706     }
707 
merge(IntermediateV1 store, Intermediate dumpStore)708     private static void merge(IntermediateV1 store, Intermediate dumpStore) {
709         IntermediateV1 intermediateV1 = (IntermediateV1) dumpStore.upgrade();
710         merge(store.adapterMethods, intermediateV1.adapterMethods);
711         merge(store.renamedMethods, intermediateV1.renamedMethods);
712         merge(store.conversionMethods, intermediateV1.conversionMethods);
713         store.multiValueAdapters.putAll(intermediateV1.multiValueAdapters);
714         store.untaggableTypes.putAll(intermediateV1.untaggableTypes);
715     }
716 
merge(HashMap<K, HashMap<V, MethodDescription>> first, HashMap<K, HashMap<V, MethodDescription>> second)717     private static <K, V> void merge(HashMap<K, HashMap<V, MethodDescription>> first,
718             HashMap<K, HashMap<V, MethodDescription>> second) {
719         for (K key : second.keySet()) {
720             HashMap<V, MethodDescription> firstVals = first.get(key);
721             HashMap<V, MethodDescription> secondVals = second.get(key);
722             if (firstVals == null) {
723                 first.put(key, secondVals);
724             } else {
725                 for (V key2 : secondVals.keySet()) {
726                     if (!firstVals.containsKey(key2)) {
727                         firstVals.put(key2, secondVals.get(key2));
728                     }
729                 }
730             }
731         }
732     }
733 
createAdapterCall(MethodDescription adapter, String bindingAdapterCall, String componentExpression, String viewExpression, String... args)734     private static String createAdapterCall(MethodDescription adapter, String bindingAdapterCall,
735             String componentExpression, String viewExpression, String... args) {
736         StringBuilder sb = new StringBuilder();
737 
738         if (adapter.isStatic) {
739             sb.append(adapter.type);
740         } else {
741             sb.append(componentExpression).append('.').append(bindingAdapterCall);
742         }
743         sb.append('.').append(adapter.method).append('(');
744         if (adapter.componentClass != null) {
745             if (!"DataBindingComponent".equals(adapter.componentClass)) {
746                 sb.append('(').append(adapter.componentClass).append(") ");
747             }
748             sb.append(componentExpression).append(", ");
749         }
750         sb.append(viewExpression);
751         for (String arg: args) {
752             sb.append(", ").append(arg);
753         }
754         sb.append(')');
755         return sb.toString();
756     }
757 
758     private static class MultiValueAdapterKey implements Serializable {
759         private static final long serialVersionUID = 1;
760 
761         public final String viewType;
762 
763         public final String[] attributes;
764 
765         public final String[] parameterTypes;
766 
767         public final TreeMap<String, Integer> attributeIndices = new TreeMap<String, Integer>();
768 
MultiValueAdapterKey(ProcessingEnvironment processingEnv, ExecutableElement method, String[] attributes, boolean takesComponent)769         public MultiValueAdapterKey(ProcessingEnvironment processingEnv,
770                 ExecutableElement method, String[] attributes, boolean takesComponent) {
771             this.attributes = stripAttributes(attributes);
772             List<? extends VariableElement> parameters = method.getParameters();
773             final int argStart = 1 + (takesComponent ? 1 : 0);
774             this.viewType = getQualifiedName(eraseType(processingEnv,
775                     parameters.get(argStart - 1).asType()));
776             this.parameterTypes = new String[parameters.size() - argStart];
777             for (int i = 0; i < attributes.length; i++) {
778                 TypeMirror typeMirror = eraseType(processingEnv,
779                         parameters.get(i + argStart).asType());
780                 this.parameterTypes[i] = getQualifiedName(typeMirror);
781                 attributeIndices.put(this.attributes[i], i);
782             }
783         }
784 
785         @Override
equals(Object obj)786         public boolean equals(Object obj) {
787             if (!(obj instanceof MultiValueAdapterKey)) {
788                 return false;
789             }
790             final MultiValueAdapterKey that = (MultiValueAdapterKey) obj;
791             if (!this.viewType.equals(that.viewType) ||
792                     this.attributes.length != that.attributes.length ||
793                     !this.attributeIndices.keySet().equals(that.attributeIndices.keySet())) {
794                 return false;
795             }
796 
797             for (int i = 0; i < this.attributes.length; i++) {
798                 final int thatIndex = that.attributeIndices.get(this.attributes[i]);
799                 final String thisParameter = parameterTypes[i];
800                 final String thatParameter = that.parameterTypes[thatIndex];
801                 if (!thisParameter.equals(thatParameter)) {
802                     return false;
803                 }
804             }
805             return true;
806         }
807 
808         @Override
hashCode()809         public int hashCode() {
810             return mergedHashCode(viewType, attributeIndices.keySet());
811         }
812     }
813 
mergedHashCode(Object... objects)814     private static int mergedHashCode(Object... objects) {
815         return Arrays.hashCode(objects);
816     }
817 
818     private static class MethodDescription implements Serializable {
819 
820         private static final long serialVersionUID = 1;
821 
822         public final String type;
823 
824         public final String method;
825 
826         public final boolean requiresOldValue;
827 
828         public final boolean isStatic;
829 
830         public final String componentClass;
831 
MethodDescription(String type, String method)832         public MethodDescription(String type, String method) {
833             this.type = type;
834             this.method = method;
835             this.requiresOldValue = false;
836             this.isStatic = true;
837             this.componentClass = null;
838             L.d("BINARY created method desc 1 %s %s", type, method );
839         }
840 
MethodDescription(ExecutableElement method, int numAttributes, boolean takesComponent)841         public MethodDescription(ExecutableElement method, int numAttributes,
842                 boolean takesComponent) {
843             TypeElement enclosingClass = (TypeElement) method.getEnclosingElement();
844             this.type = enclosingClass.getQualifiedName().toString();
845             this.method = method.getSimpleName().toString();
846             final int argStart = 1 + (takesComponent ? 1 : 0);
847             this.requiresOldValue = method.getParameters().size() - argStart == numAttributes * 2;
848             this.isStatic = method.getModifiers().contains(Modifier.STATIC);
849             this.componentClass = takesComponent
850                     ? getQualifiedName(method.getParameters().get(0).asType())
851                     : null;
852 
853             L.d("BINARY created method desc 2 %s %s, %s", type, this.method, method);
854         }
855 
856         @Override
equals(Object obj)857         public boolean equals(Object obj) {
858             if (obj instanceof MethodDescription) {
859                 MethodDescription that = (MethodDescription) obj;
860                 return that.type.equals(this.type) && that.method.equals(this.method);
861             } else {
862                 return false;
863             }
864         }
865 
866         @Override
hashCode()867         public int hashCode() {
868             return mergedHashCode(type, method);
869         }
870 
871         @Override
toString()872         public String toString() {
873             return type + "." + method + "()";
874         }
875     }
876 
877     private static class AccessorKey implements Serializable {
878 
879         private static final long serialVersionUID = 1;
880 
881         public final String viewType;
882 
883         public final String valueType;
884 
AccessorKey(String viewType, String valueType)885         public AccessorKey(String viewType, String valueType) {
886             this.viewType = viewType;
887             this.valueType = valueType;
888         }
889 
890         @Override
hashCode()891         public int hashCode() {
892             return mergedHashCode(viewType, valueType);
893         }
894 
895         @Override
equals(Object obj)896         public boolean equals(Object obj) {
897             if (obj instanceof AccessorKey) {
898                 AccessorKey that = (AccessorKey) obj;
899                 return viewType.equals(that.valueType) && valueType.equals(that.valueType);
900             } else {
901                 return false;
902             }
903         }
904 
905         @Override
toString()906         public String toString() {
907             return "AK(" + viewType + ", " + valueType + ")";
908         }
909     }
910 
911     private interface Intermediate extends Serializable {
upgrade()912         Intermediate upgrade();
913     }
914 
915     private static class IntermediateV1 implements Serializable, Intermediate {
916         private static final long serialVersionUID = 1;
917         public final HashMap<String, HashMap<AccessorKey, MethodDescription>> adapterMethods =
918                 new HashMap<String, HashMap<AccessorKey, MethodDescription>>();
919         public final HashMap<String, HashMap<String, MethodDescription>> renamedMethods =
920                 new HashMap<String, HashMap<String, MethodDescription>>();
921         public final HashMap<String, HashMap<String, MethodDescription>> conversionMethods =
922                 new HashMap<String, HashMap<String, MethodDescription>>();
923         public final HashMap<String, String> untaggableTypes = new HashMap<String, String>();
924         public final HashMap<MultiValueAdapterKey, MethodDescription> multiValueAdapters =
925                 new HashMap<MultiValueAdapterKey, MethodDescription>();
926 
IntermediateV1()927         public IntermediateV1() {
928         }
929 
930         @Override
upgrade()931         public Intermediate upgrade() {
932             return this;
933         }
934     }
935 
936     public static class DummySetter extends SetterCall {
937         private String mMethodName;
938 
DummySetter(String methodName)939         public DummySetter(String methodName) {
940             mMethodName = methodName;
941         }
942 
943         @Override
toJavaInternal(String componentExpression, String viewExpression, String valueExpression)944         public String toJavaInternal(String componentExpression, String viewExpression,
945                 String valueExpression) {
946             return viewExpression + "." + mMethodName + "(" + valueExpression + ")";
947         }
948 
949         @Override
toJavaInternal(String componentExpression, String viewExpression, String oldValue, String valueExpression)950         public String toJavaInternal(String componentExpression, String viewExpression,
951                 String oldValue, String valueExpression) {
952             return viewExpression + "." + mMethodName + "(" + valueExpression + ")";
953         }
954 
955         @Override
getMinApi()956         public int getMinApi() {
957             return 1;
958         }
959 
960         @Override
requiresOldValue()961         public boolean requiresOldValue() {
962             return false;
963         }
964 
965         @Override
getParameterTypes()966         public ModelClass[] getParameterTypes() {
967             return new ModelClass[] {
968                     ModelAnalyzer.getInstance().findClass(Object.class)
969             };
970         }
971 
972         @Override
getBindingAdapterInstanceClass()973         public String getBindingAdapterInstanceClass() {
974             return null;
975         }
976 
977         @Override
setBindingAdapterCall(String method)978         public void setBindingAdapterCall(String method) {
979         }
980     }
981 
982     public static class AdapterSetter extends SetterCall {
983         final MethodDescription mAdapter;
984         final ModelClass mParameterType;
985         String mBindingAdapterCall;
986 
AdapterSetter(MethodDescription adapter, ModelClass parameterType)987         public AdapterSetter(MethodDescription adapter, ModelClass parameterType) {
988             mAdapter = adapter;
989             mParameterType = parameterType;
990         }
991 
992         @Override
toJavaInternal(String componentExpression, String viewExpression, String valueExpression)993         public String toJavaInternal(String componentExpression, String viewExpression,
994                 String valueExpression) {
995             return createAdapterCall(mAdapter, mBindingAdapterCall, componentExpression,
996                     viewExpression, mCastString + valueExpression);
997         }
998 
999         @Override
toJavaInternal(String componentExpression, String viewExpression, String oldValue, String valueExpression)1000         protected String toJavaInternal(String componentExpression, String viewExpression,
1001                 String oldValue, String valueExpression) {
1002             return createAdapterCall(mAdapter, mBindingAdapterCall, componentExpression,
1003                     viewExpression, mCastString + oldValue, mCastString + valueExpression);
1004         }
1005 
1006         @Override
getMinApi()1007         public int getMinApi() {
1008             return 1;
1009         }
1010 
1011         @Override
requiresOldValue()1012         public boolean requiresOldValue() {
1013             return mAdapter.requiresOldValue;
1014         }
1015 
1016         @Override
getParameterTypes()1017         public ModelClass[] getParameterTypes() {
1018             return new ModelClass[] { mParameterType };
1019         }
1020 
1021         @Override
getBindingAdapterInstanceClass()1022         public String getBindingAdapterInstanceClass() {
1023             return mAdapter.isStatic ? null : mAdapter.type;
1024         }
1025 
1026         @Override
setBindingAdapterCall(String method)1027         public void setBindingAdapterCall(String method) {
1028             mBindingAdapterCall = method;
1029         }
1030     }
1031 
1032     public static class ModelMethodSetter extends SetterCall {
1033         final ModelMethod mModelMethod;
1034 
ModelMethodSetter(ModelMethod modelMethod)1035         public ModelMethodSetter(ModelMethod modelMethod) {
1036             mModelMethod = modelMethod;
1037         }
1038 
1039         @Override
toJavaInternal(String componentExpression, String viewExpression, String valueExpression)1040         public String toJavaInternal(String componentExpression, String viewExpression,
1041                 String valueExpression) {
1042             return viewExpression + "." + mModelMethod.getName() + "(" + mCastString +
1043                     valueExpression + ")";
1044         }
1045 
1046         @Override
toJavaInternal(String componentExpression, String viewExpression, String oldValue, String valueExpression)1047         protected String toJavaInternal(String componentExpression, String viewExpression,
1048                 String oldValue, String valueExpression) {
1049             return viewExpression + "." + mModelMethod.getName() + "(" +
1050                     mCastString + oldValue + ", " + mCastString + valueExpression + ")";
1051         }
1052 
1053         @Override
getMinApi()1054         public int getMinApi() {
1055             return mModelMethod.getMinApi();
1056         }
1057 
1058         @Override
requiresOldValue()1059         public boolean requiresOldValue() {
1060             return mModelMethod.getParameterTypes().length == 3;
1061         }
1062 
1063         @Override
getParameterTypes()1064         public ModelClass[] getParameterTypes() {
1065             return new ModelClass[] { mModelMethod.getParameterTypes()[0] };
1066         }
1067 
1068         @Override
getBindingAdapterInstanceClass()1069         public String getBindingAdapterInstanceClass() {
1070             return null;
1071         }
1072 
1073         @Override
setBindingAdapterCall(String method)1074         public void setBindingAdapterCall(String method) {
1075         }
1076     }
1077 
1078     public interface BindingSetterCall {
toJava(String componentExpression, String viewExpression, String... valueExpressions)1079         String toJava(String componentExpression, String viewExpression,
1080                 String... valueExpressions);
1081 
getMinApi()1082         int getMinApi();
1083 
requiresOldValue()1084         boolean requiresOldValue();
1085 
getParameterTypes()1086         ModelClass[] getParameterTypes();
1087 
getBindingAdapterInstanceClass()1088         String getBindingAdapterInstanceClass();
1089 
setBindingAdapterCall(String method)1090         void setBindingAdapterCall(String method);
1091     }
1092 
1093     public static abstract class SetterCall implements BindingSetterCall {
1094         private MethodDescription mConverter;
1095         protected String mCastString = "";
1096 
SetterCall()1097         public SetterCall() {
1098         }
1099 
setConverter(MethodDescription converter)1100         public void setConverter(MethodDescription converter) {
1101             mConverter = converter;
1102         }
1103 
toJavaInternal(String componentExpression, String viewExpression, String converted)1104         protected abstract String toJavaInternal(String componentExpression, String viewExpression,
1105                 String converted);
1106 
toJavaInternal(String componentExpression, String viewExpression, String oldValue, String converted)1107         protected abstract String toJavaInternal(String componentExpression, String viewExpression,
1108                 String oldValue, String converted);
1109 
1110         @Override
toJava(String componentExpression, String viewExpression, String... valueExpression)1111         public final String toJava(String componentExpression, String viewExpression,
1112                 String... valueExpression) {
1113             Preconditions.check(valueExpression.length == 2, "value expressions size must be 2");
1114             if (requiresOldValue()) {
1115                 return toJavaInternal(componentExpression, viewExpression,
1116                         convertValue(valueExpression[0]), convertValue(valueExpression[1]));
1117             } else {
1118                 return toJavaInternal(componentExpression, viewExpression,
1119                         convertValue(valueExpression[1]));
1120             }
1121         }
1122 
convertValue(String valueExpression)1123         protected String convertValue(String valueExpression) {
1124             return mConverter == null ? valueExpression :
1125                     mConverter.type + "." + mConverter.method + "(" + valueExpression + ")";
1126         }
1127 
getMinApi()1128         abstract public int getMinApi();
1129 
setCast(ModelClass castTo)1130         public void setCast(ModelClass castTo) {
1131             mCastString = "(" + castTo.toJavaCode() + ") ";
1132         }
1133     }
1134 
1135     public static class MultiAttributeSetter implements BindingSetterCall {
1136         public final String[] attributes;
1137         private final MethodDescription mAdapter;
1138         private final MethodDescription[] mConverters;
1139         private final String[] mCasts;
1140         private final MultiValueAdapterKey mKey;
1141         String mBindingAdapterCall;
1142 
MultiAttributeSetter(MultiValueAdapterKey key, String[] attributes, MethodDescription adapter, MethodDescription[] converters, String[] casts)1143         public MultiAttributeSetter(MultiValueAdapterKey key, String[] attributes,
1144                 MethodDescription adapter, MethodDescription[] converters, String[] casts) {
1145             Preconditions.check(converters != null &&
1146                     converters.length == attributes.length &&
1147                     casts != null && casts.length == attributes.length,
1148                     "invalid arguments to create multi attr setter");
1149             this.attributes = attributes;
1150             this.mAdapter = adapter;
1151             this.mConverters = converters;
1152             this.mCasts = casts;
1153             this.mKey = key;
1154         }
1155 
1156         @Override
toJava(String componentExpression, String viewExpression, String[] valueExpressions)1157         public final String toJava(String componentExpression, String viewExpression,
1158                 String[] valueExpressions) {
1159             Preconditions.check(valueExpressions.length == attributes.length * 2,
1160                     "MultiAttributeSetter needs %s items, received %s",
1161                     Arrays.toString(attributes), Arrays.toString(valueExpressions));
1162             final int numAttrs = attributes.length;
1163             String[] args = new String[numAttrs + (requiresOldValue() ? numAttrs : 0)];
1164 
1165             final int startIndex = mAdapter.requiresOldValue ? 0 : attributes.length;
1166             StringBuilder argBuilder = new StringBuilder();
1167             for (int i = startIndex; i < valueExpressions.length; i++) {
1168                 argBuilder.setLength(0);
1169                 if (mConverters[i % attributes.length] != null) {
1170                     final MethodDescription converter = mConverters[i % attributes.length];
1171                     argBuilder.append(converter.type)
1172                             .append('.')
1173                             .append(converter.method)
1174                             .append('(')
1175                             .append(valueExpressions[i])
1176                             .append(')');
1177                 } else {
1178                     if (mCasts[i % attributes.length] != null) {
1179                         argBuilder.append('(')
1180                                 .append(mCasts[i % attributes.length])
1181                                 .append(')');
1182                     }
1183                     argBuilder.append(valueExpressions[i]);
1184                 }
1185                 args[i - startIndex] = argBuilder.toString();
1186             }
1187             return createAdapterCall(mAdapter, mBindingAdapterCall, componentExpression,
1188                     viewExpression, args);
1189         }
1190 
1191         @Override
getMinApi()1192         public int getMinApi() {
1193             return 1;
1194         }
1195 
1196         @Override
requiresOldValue()1197         public boolean requiresOldValue() {
1198             return mAdapter.requiresOldValue;
1199         }
1200 
1201         @Override
getParameterTypes()1202         public ModelClass[] getParameterTypes() {
1203             ModelClass[] parameters = new ModelClass[attributes.length];
1204             String[] paramTypeStrings = mKey.parameterTypes;
1205             ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
1206             for (int i = 0; i < attributes.length; i++) {
1207                 parameters[i] = modelAnalyzer.findClass(paramTypeStrings[i], null);
1208             }
1209             return parameters;
1210         }
1211 
1212         @Override
getBindingAdapterInstanceClass()1213         public String getBindingAdapterInstanceClass() {
1214             return mAdapter.isStatic ? null : mAdapter.type;
1215         }
1216 
1217         @Override
setBindingAdapterCall(String method)1218         public void setBindingAdapterCall(String method) {
1219             mBindingAdapterCall = method;
1220         }
1221 
1222         @Override
toString()1223         public String toString() {
1224             return "MultiAttributeSetter{" +
1225                     "attributes=" + Arrays.toString(attributes) +
1226                     ", mAdapter=" + mAdapter +
1227                     ", mConverters=" + Arrays.toString(mConverters) +
1228                     ", mCasts=" + Arrays.toString(mCasts) +
1229                     ", mKey=" + mKey +
1230                     '}';
1231         }
1232     }
1233 }
1234