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 android.databinding.InverseBindingListener;
19import android.databinding.tool.reflection.ModelAnalyzer;
20import android.databinding.tool.reflection.ModelClass;
21import android.databinding.tool.reflection.ModelMethod;
22import android.databinding.tool.util.GenerationalClassUtil;
23import android.databinding.tool.util.L;
24import android.databinding.tool.util.Preconditions;
25import android.databinding.tool.util.StringUtils;
26
27import java.io.IOException;
28import java.io.Serializable;
29import java.util.ArrayList;
30import java.util.Arrays;
31import java.util.Collections;
32import java.util.Comparator;
33import java.util.HashMap;
34import java.util.HashSet;
35import java.util.Iterator;
36import java.util.List;
37import java.util.Map;
38import java.util.Set;
39import java.util.TreeMap;
40
41import javax.annotation.processing.ProcessingEnvironment;
42import javax.lang.model.element.ExecutableElement;
43import javax.lang.model.element.Modifier;
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.TypeKind;
49import javax.lang.model.type.TypeMirror;
50
51public class SetterStore {
52    private static SetterStore sStore;
53
54    private final IntermediateV2 mStore;
55    private final ModelAnalyzer mClassAnalyzer;
56    private HashMap<String, List<String>> mInstanceAdapters;
57    private final HashSet<String> mInverseEventAttributes = new HashSet<String>();
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).erasure();
67                    ModelClass view2 = mClassAnalyzer.findClass(o2.mKey.viewType, null).erasure();
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.check(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, IntermediateV2 store) {
147        mClassAnalyzer = modelAnalyzer;
148        mStore = store;
149        for (HashMap<AccessorKey, InverseDescription> adapter : mStore.inverseAdapters.values()) {
150            for (InverseDescription inverseDescription : adapter.values()) {
151                mInverseEventAttributes.add(inverseDescription.event);
152            }
153        }
154        for (HashMap<String, InverseDescription> method : mStore.inverseMethods.values()) {
155            for (InverseDescription inverseDescription : method.values()) {
156                mInverseEventAttributes.add(inverseDescription.event);
157            }
158        }
159    }
160
161    public static SetterStore get(ModelAnalyzer modelAnalyzer) {
162        if (sStore == null) {
163            sStore = load(modelAnalyzer);
164        }
165        return sStore;
166    }
167
168    private static SetterStore load(ModelAnalyzer modelAnalyzer) {
169        IntermediateV2 store = new IntermediateV2();
170        List<Intermediate> previousStores = GenerationalClassUtil
171                .loadObjects(GenerationalClassUtil.ExtensionFilter.SETTER_STORE);
172        for (Intermediate intermediate : previousStores) {
173            merge(store, intermediate);
174        }
175        return new SetterStore(modelAnalyzer, store);
176    }
177
178    public void addRenamedMethod(String attribute, String declaringClass, String method,
179            TypeElement declaredOn) {
180        attribute = stripNamespace(attribute);
181        HashMap<String, MethodDescription> renamed = mStore.renamedMethods.get(attribute);
182        if (renamed == null) {
183            renamed = new HashMap<String, MethodDescription>();
184            mStore.renamedMethods.put(attribute, renamed);
185        }
186        MethodDescription methodDescription = new MethodDescription(
187                declaredOn.getQualifiedName().toString(), method);
188        L.d("STORE addmethod desc %s", methodDescription);
189        renamed.put(declaringClass, methodDescription);
190    }
191
192    public void addInverseMethod(String attribute, String event, String declaringClass,
193            String method, TypeElement declaredOn) {
194        attribute = stripNamespace(attribute);
195        event = stripNamespace(event);
196        HashMap<String, InverseDescription> inverseMethods = mStore.inverseMethods.get(attribute);
197        if (inverseMethods == null) {
198            inverseMethods = new HashMap<String, InverseDescription>();
199            mStore.inverseMethods.put(attribute, inverseMethods);
200        }
201        InverseDescription methodDescription = new InverseDescription(
202                declaredOn.getQualifiedName().toString(), method, event);
203        L.d("STORE addInverseMethod desc %s", methodDescription);
204        inverseMethods.put(declaringClass, methodDescription);
205    }
206
207    public void addBindingAdapter(ProcessingEnvironment processingEnv, String attribute,
208            ExecutableElement bindingMethod, boolean takesComponent) {
209        attribute = stripNamespace(attribute);
210        L.d("STORE addBindingAdapter %s %s", attribute, bindingMethod);
211        HashMap<AccessorKey, MethodDescription> adapters = mStore.adapterMethods.get(attribute);
212
213        if (adapters == null) {
214            adapters = new HashMap<AccessorKey, MethodDescription>();
215            mStore.adapterMethods.put(attribute, adapters);
216        }
217        List<? extends VariableElement> parameters = bindingMethod.getParameters();
218        final int viewIndex = takesComponent ? 1 : 0;
219        TypeMirror viewType = eraseType(processingEnv, parameters.get(viewIndex).asType());
220        String view = getQualifiedName(viewType);
221        TypeMirror parameterType = eraseType(processingEnv, parameters.get(viewIndex + 1).asType());
222        String value = getQualifiedName(parameterType);
223
224        AccessorKey key = new AccessorKey(view, value);
225        if (adapters.containsKey(key)) {
226            throw new IllegalArgumentException("Already exists!");
227        }
228
229        adapters.put(key, new MethodDescription(bindingMethod, 1, takesComponent));
230    }
231
232    public void addInverseAdapter(ProcessingEnvironment processingEnv, String attribute,
233            String event, ExecutableElement bindingMethod, boolean takesComponent) {
234        attribute = stripNamespace(attribute);
235        event = stripNamespace(event);
236        L.d("STORE addInverseAdapter %s %s", attribute, bindingMethod);
237        HashMap<AccessorKey, InverseDescription> adapters = mStore.inverseAdapters.get(attribute);
238
239        if (adapters == null) {
240            adapters = new HashMap<AccessorKey, InverseDescription>();
241            mStore.inverseAdapters.put(attribute, adapters);
242        }
243        List<? extends VariableElement> parameters = bindingMethod.getParameters();
244        final int viewIndex = takesComponent ? 1 : 0;
245        TypeMirror viewType = eraseType(processingEnv, parameters.get(viewIndex).asType());
246        String view = getQualifiedName(viewType);
247        TypeMirror returnType = eraseType(processingEnv, bindingMethod.getReturnType());
248        String value = getQualifiedName(returnType);
249
250        AccessorKey key = new AccessorKey(view, value);
251        if (adapters.containsKey(key)) {
252            throw new IllegalArgumentException("Already exists!");
253        }
254
255        adapters.put(key, new InverseDescription(bindingMethod, event, takesComponent));
256    }
257
258    private static TypeMirror eraseType(ProcessingEnvironment processingEnv,
259            TypeMirror typeMirror) {
260        if (hasTypeVar(typeMirror)) {
261            return processingEnv.getTypeUtils().erasure(typeMirror);
262        } else {
263            return typeMirror;
264        }
265    }
266
267    private static ModelClass eraseType(ModelClass modelClass) {
268        if (hasTypeVar(modelClass)) {
269            return modelClass.erasure();
270        } else {
271            return modelClass;
272        }
273    }
274
275    private static boolean hasTypeVar(TypeMirror typeMirror) {
276        TypeKind kind = typeMirror.getKind();
277        if (kind == TypeKind.TYPEVAR) {
278            return true;
279        } else if (kind == TypeKind.ARRAY) {
280            return hasTypeVar(((ArrayType) typeMirror).getComponentType());
281        } else if (kind == TypeKind.DECLARED) {
282            DeclaredType declaredType = (DeclaredType) typeMirror;
283            List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
284            if (typeArguments == null || typeArguments.isEmpty()) {
285                return false;
286            }
287            for (TypeMirror arg : typeArguments) {
288                if (hasTypeVar(arg)) {
289                    return true;
290                }
291            }
292            return false;
293        } else {
294            return false;
295        }
296    }
297
298    private static boolean hasTypeVar(ModelClass type) {
299        if (type.isTypeVar()) {
300            return true;
301        } else if (type.isArray()) {
302            return hasTypeVar(type.getComponentType());
303        } else {
304            List<ModelClass> typeArguments = type.getTypeArguments();
305            if (typeArguments == null) {
306                return false;
307            }
308            for (ModelClass arg : typeArguments) {
309                if (hasTypeVar(arg)) {
310                    return true;
311                }
312            }
313            return false;
314        }
315    }
316
317    public void addBindingAdapter(ProcessingEnvironment processingEnv, String[] attributes,
318            ExecutableElement bindingMethod, boolean takesComponent, boolean requireAll) {
319        L.d("STORE add multi-value BindingAdapter %d %s", attributes.length, bindingMethod);
320        MultiValueAdapterKey key = new MultiValueAdapterKey(processingEnv, bindingMethod,
321                attributes, takesComponent, requireAll);
322        MethodDescription methodDescription = new MethodDescription(bindingMethod,
323                attributes.length, takesComponent);
324        mStore.multiValueAdapters.put(key, methodDescription);
325    }
326
327    private static String[] stripAttributes(String[] attributes) {
328        String[] strippedAttributes = new String[attributes.length];
329        for (int i = 0; i < attributes.length; i++) {
330            if (attributes[i] != null) {
331                strippedAttributes[i] = stripNamespace(attributes[i]);
332            }
333        }
334        return strippedAttributes;
335    }
336
337    public void addUntaggableTypes(String[] typeNames, TypeElement declaredOn) {
338        L.d("STORE addUntaggableTypes %s %s", Arrays.toString(typeNames), declaredOn);
339        String declaredType = declaredOn.getQualifiedName().toString();
340        for (String type : typeNames) {
341            mStore.untaggableTypes.put(type, declaredType);
342        }
343    }
344
345    private static String getQualifiedName(TypeMirror type) {
346        final TypeKind kind = type.getKind();
347        if (kind == TypeKind.ARRAY) {
348            return getQualifiedName(((ArrayType) type).getComponentType()) + "[]";
349        } else if (kind == TypeKind.DECLARED && isIncompleteType(type)) {
350            DeclaredType declaredType = (DeclaredType) type;
351            return declaredType.asElement().toString();
352        } else {
353            return type.toString();
354        }
355    }
356
357    private static boolean isIncompleteType(TypeMirror type) {
358        final TypeKind kind = type.getKind();
359        if (kind == TypeKind.TYPEVAR || kind == TypeKind.WILDCARD) {
360            return true;
361        } else if (kind == TypeKind.DECLARED) {
362            DeclaredType declaredType = (DeclaredType) type;
363            List<? extends TypeMirror> typeArgs = declaredType.getTypeArguments();
364            if (typeArgs == null) {
365                return false;
366            }
367            for (TypeMirror arg : typeArgs) {
368                if (isIncompleteType(arg)) {
369                    return true;
370                }
371            }
372        }
373        return false;
374    }
375
376    public void addConversionMethod(ExecutableElement conversionMethod) {
377        L.d("STORE addConversionMethod %s", conversionMethod);
378        List<? extends VariableElement> parameters = conversionMethod.getParameters();
379        String fromType = getQualifiedName(parameters.get(0).asType());
380        String toType = getQualifiedName(conversionMethod.getReturnType());
381        MethodDescription methodDescription = new MethodDescription(conversionMethod, 1, false);
382        HashMap<String, MethodDescription> convertTo = mStore.conversionMethods.get(fromType);
383        if (convertTo == null) {
384            convertTo = new HashMap<String, MethodDescription>();
385            mStore.conversionMethods.put(fromType, convertTo);
386        }
387        convertTo.put(toType, methodDescription);
388    }
389
390    public void clear(Set<String> classes) {
391        ArrayList<AccessorKey> removedAccessorKeys = new ArrayList<AccessorKey>();
392        for (HashMap<AccessorKey, MethodDescription> adapters : mStore.adapterMethods.values()) {
393            for (AccessorKey key : adapters.keySet()) {
394                MethodDescription description = adapters.get(key);
395                if (classes.contains(description.type)) {
396                    removedAccessorKeys.add(key);
397                }
398            }
399            removeFromMap(adapters, removedAccessorKeys);
400        }
401
402        ArrayList<String> removedRenamed = new ArrayList<String>();
403        for (HashMap<String, MethodDescription> renamed : mStore.renamedMethods.values()) {
404            for (String key : renamed.keySet()) {
405                if (classes.contains(renamed.get(key).type)) {
406                    removedRenamed.add(key);
407                }
408            }
409            removeFromMap(renamed, removedRenamed);
410        }
411
412        ArrayList<String> removedConversions = new ArrayList<String>();
413        for (HashMap<String, MethodDescription> convertTos : mStore.conversionMethods.values()) {
414            for (String toType : convertTos.keySet()) {
415                MethodDescription methodDescription = convertTos.get(toType);
416                if (classes.contains(methodDescription.type)) {
417                    removedConversions.add(toType);
418                }
419            }
420            removeFromMap(convertTos, removedConversions);
421        }
422
423        ArrayList<String> removedUntaggable = new ArrayList<String>();
424        for (String typeName : mStore.untaggableTypes.keySet()) {
425            if (classes.contains(mStore.untaggableTypes.get(typeName))) {
426                removedUntaggable.add(typeName);
427            }
428        }
429        removeFromMap(mStore.untaggableTypes, removedUntaggable);
430    }
431
432    private static <K, V> void removeFromMap(Map<K, V> map, List<K> keys) {
433        for (K key : keys) {
434            map.remove(key);
435        }
436        keys.clear();
437    }
438
439    public void write(String projectPackage, ProcessingEnvironment processingEnvironment)
440            throws IOException {
441        GenerationalClassUtil.writeIntermediateFile(processingEnvironment,
442                projectPackage, projectPackage +
443                        GenerationalClassUtil.ExtensionFilter.SETTER_STORE.getExtension(), mStore);
444    }
445
446    private static String stripNamespace(String attribute) {
447        if (!attribute.startsWith("android:")) {
448            int colon = attribute.indexOf(':');
449            if (colon >= 0) {
450                attribute = attribute.substring(colon + 1);
451            }
452        }
453        return attribute;
454    }
455
456    public boolean isTwoWayEventAttribute(String attribute) {
457        attribute = stripNamespace(attribute);
458        return mInverseEventAttributes.contains(attribute);
459    }
460    public List<MultiAttributeSetter> getMultiAttributeSetterCalls(String[] attributes,
461            ModelClass viewType, ModelClass[] valueType) {
462        attributes = stripAttributes(attributes);
463        final ArrayList<MultiAttributeSetter> calls = new ArrayList<MultiAttributeSetter>();
464        if (viewType != null && viewType.isGeneric()) {
465            List<ModelClass> viewGenerics = viewType.getTypeArguments();
466            for (int i = 0; i < valueType.length; i++) {
467                valueType[i] = eraseType(valueType[i], viewGenerics);
468            }
469            viewType = viewType.erasure();
470        }
471        ArrayList<MultiAttributeSetter> matching = getMatchingMultiAttributeSetters(attributes,
472                viewType, valueType);
473        Collections.sort(matching, COMPARE_MULTI_ATTRIBUTE_SETTERS);
474        while (!matching.isEmpty()) {
475            MultiAttributeSetter bestMatch = matching.get(0);
476            calls.add(bestMatch);
477            removeConsumedAttributes(matching, bestMatch.attributes);
478        }
479        return calls;
480    }
481
482    private static String simpleName(String className) {
483        int dotIndex = className.lastIndexOf('.');
484        if (dotIndex < 0) {
485            return className;
486        } else {
487            return className.substring(dotIndex + 1);
488        }
489    }
490
491    public Map<String, List<String>> getComponentBindingAdapters() {
492        ensureInstanceAdapters();
493        return mInstanceAdapters;
494    }
495
496    private String getBindingAdapterCall(String className) {
497        ensureInstanceAdapters();
498        final String simpleName = simpleName(className);
499        List<String> adapters = mInstanceAdapters.get(simpleName);
500        if (adapters.size() == 1) {
501            return "get" + simpleName + "()";
502        } else {
503            int index = adapters.indexOf(className) + 1;
504            return "get" + simpleName + index + "()";
505        }
506    }
507
508    private void ensureInstanceAdapters() {
509        if (mInstanceAdapters == null) {
510            HashSet<String> adapters = new HashSet<String>();
511            for (HashMap<AccessorKey, MethodDescription> methods : mStore.adapterMethods.values()) {
512                for (MethodDescription method : methods.values()) {
513                    if (!method.isStatic) {
514                        adapters.add(method.type);
515                    }
516                }
517            }
518            for (MethodDescription method : mStore.multiValueAdapters.values()) {
519                if (!method.isStatic) {
520                    adapters.add(method.type);
521                }
522            }
523            for (Map<AccessorKey, InverseDescription> methods : mStore.inverseAdapters.values()) {
524                for (InverseDescription method : methods.values()) {
525                    if (!method.isStatic) {
526                        adapters.add(method.type);
527                    }
528                }
529            }
530            mInstanceAdapters = new HashMap<String, List<String>>();
531            for (String adapter : adapters) {
532                final String simpleName = simpleName(adapter);
533                List<String> list = mInstanceAdapters.get(simpleName);
534                if (list == null) {
535                    list = new ArrayList<String>();
536                    mInstanceAdapters.put(simpleName, list);
537                }
538                list.add(adapter);
539            }
540            for (List<String> list : mInstanceAdapters.values()) {
541                if (list.size() > 1) {
542                    Collections.sort(list);
543                }
544            }
545        }
546    }
547
548    // Removes all MultiAttributeSetters that require any of the values in attributes
549    private static void removeConsumedAttributes(ArrayList<MultiAttributeSetter> matching,
550            String[] attributes) {
551        for (int i = matching.size() - 1; i >= 0; i--) {
552            final MultiAttributeSetter setter = matching.get(i);
553            boolean found = false;
554            for (String attribute : attributes) {
555                if (isInArray(attribute, setter.attributes)) {
556                    found = true;
557                    break;
558                }
559            }
560            if (found) {
561                matching.remove(i);
562            }
563        }
564    }
565
566    // Linear search through the String array for a specific value.
567    private static boolean isInArray(String str, String[] array) {
568        for (String value : array) {
569            if (value.equals(str)) {
570                return true;
571            }
572        }
573        return false;
574    }
575
576    private ArrayList<MultiAttributeSetter> getMatchingMultiAttributeSetters(String[] attributes,
577            ModelClass viewType, ModelClass[] valueType) {
578        final ArrayList<MultiAttributeSetter> setters = new ArrayList<MultiAttributeSetter>();
579        for (MultiValueAdapterKey adapter : mStore.multiValueAdapters.keySet()) {
580            if (adapter.requireAll && adapter.attributes.length > attributes.length) {
581                continue;
582            }
583            ModelClass viewClass = mClassAnalyzer.findClass(adapter.viewType, null);
584            if (viewClass.isGeneric()) {
585                viewClass = viewClass.erasure();
586            }
587            if (!viewClass.isAssignableFrom(viewType)) {
588                continue;
589            }
590            final MethodDescription method = mStore.multiValueAdapters.get(adapter);
591            final MultiAttributeSetter setter = createMultiAttributeSetter(method, attributes,
592                    valueType, adapter);
593            if (setter != null) {
594                setters.add(setter);
595            }
596        }
597        return setters;
598    }
599
600    private MultiAttributeSetter createMultiAttributeSetter(MethodDescription method,
601            String[] allAttributes, ModelClass[] attributeValues, MultiValueAdapterKey adapter) {
602        int matchingAttributes = 0;
603        String[] casts = new String[adapter.attributes.length];
604        MethodDescription[] conversions = new MethodDescription[adapter.attributes.length];
605        boolean[] supplied = new boolean[adapter.attributes.length];
606
607        for (int i = 0; i < allAttributes.length; i++) {
608            Integer index = adapter.attributeIndices.get(allAttributes[i]);
609            if (index != null) {
610                supplied[index] = true;
611                matchingAttributes++;
612                final String parameterTypeStr = adapter.parameterTypes[index];
613                final ModelClass parameterType = eraseType(
614                        mClassAnalyzer.findClass(parameterTypeStr, null));
615                final ModelClass attributeType = attributeValues[i];
616                if (!parameterType.isAssignableFrom(attributeType)) {
617                    if (ModelMethod.isBoxingConversion(parameterType, attributeType)) {
618                        // automatic boxing is ok
619                        continue;
620                    } else if (ModelMethod.isImplicitConversion(attributeType, parameterType)) {
621                        // implicit conversion is ok
622                        continue;
623                    }
624                    // Look for a converter
625                    conversions[index] = getConversionMethod(attributeType, parameterType, null);
626                    if (conversions[index] == null) {
627                        if (attributeType.isObject()) {
628                            // Cast is allowed also
629                            casts[index] = parameterTypeStr;
630                        } else {
631                            // Parameter type mismatch
632                            return null;
633                        }
634                    }
635                }
636            }
637        }
638
639        if ((adapter.requireAll && matchingAttributes != adapter.attributes.length) ||
640                matchingAttributes == 0) {
641            return null;
642        } else {
643            return new MultiAttributeSetter(adapter, supplied, method, conversions, casts);
644        }
645    }
646
647    public SetterCall getSetterCall(String attribute, ModelClass viewType,
648            ModelClass valueType, Map<String, String> imports) {
649        attribute = stripNamespace(attribute);
650        SetterCall setterCall = null;
651        MethodDescription conversionMethod = null;
652        if (viewType != null) {
653            viewType = viewType.erasure();
654            HashMap<AccessorKey, MethodDescription> adapters = mStore.adapterMethods.get(attribute);
655            ModelMethod bestSetterMethod = getBestSetter(viewType, valueType, attribute, imports);
656            ModelClass bestViewType = null;
657            ModelClass bestValueType = null;
658            if (bestSetterMethod != null) {
659                bestViewType = bestSetterMethod.getDeclaringClass();
660                bestValueType = bestSetterMethod.getParameterTypes()[0];
661                setterCall = new ModelMethodSetter(bestSetterMethod);
662            }
663
664            if (adapters != null) {
665                for (AccessorKey key : adapters.keySet()) {
666                    try {
667                        ModelClass adapterViewType = mClassAnalyzer
668                                .findClass(key.viewType, imports).erasure();
669                        if (adapterViewType != null && adapterViewType.isAssignableFrom(viewType)) {
670                            try {
671                                L.d("setter parameter type is %s", key.valueType);
672                                final ModelClass adapterValueType = eraseType(mClassAnalyzer
673                                        .findClass(key.valueType, imports));
674                                L.d("setter %s takes type %s, compared to %s",
675                                        adapters.get(key).method, adapterValueType.toJavaCode(),
676                                        valueType.toJavaCode());
677                                boolean isBetterView = bestViewType == null ||
678                                        bestViewType.isAssignableFrom(adapterViewType);
679                                if (isBetterParameter(valueType, adapterValueType, bestValueType,
680                                        isBetterView, imports)) {
681                                    bestViewType = adapterViewType;
682                                    bestValueType = adapterValueType;
683                                    MethodDescription adapter = adapters.get(key);
684                                    setterCall = new AdapterSetter(adapter, adapterValueType);
685                                }
686
687                            } catch (Exception e) {
688                                L.e(e, "Unknown class: %s", key.valueType);
689                            }
690                        }
691                    } catch (Exception e) {
692                        L.e(e, "Unknown class: %s", key.viewType);
693                    }
694                }
695            }
696
697            conversionMethod = getConversionMethod(valueType, bestValueType, imports);
698            if (valueType.isObject() && setterCall != null && bestValueType.isNullable()) {
699                setterCall.setCast(bestValueType);
700            }
701        }
702        if (setterCall == null) {
703            if (viewType != null && !viewType.isViewDataBinding()) {
704                return null; // no setter found!!
705            }
706            setterCall = new DummySetter(getDefaultSetter(attribute));
707        }
708        setterCall.setConverter(conversionMethod);
709        return setterCall;
710    }
711
712    public BindingGetterCall getGetterCall(String attribute, ModelClass viewType,
713            ModelClass valueType, Map<String, String> imports) {
714        if (viewType == null) {
715            return null;
716        } else if (viewType.isViewDataBinding()) {
717            return new ViewDataBindingGetterCall(attribute);
718        }
719
720        attribute = stripNamespace(attribute);
721        viewType = viewType.erasure();
722
723        InverseMethod bestMethod = getBestGetter(viewType, valueType, attribute, imports);
724        HashMap<AccessorKey, InverseDescription> adapters = mStore.inverseAdapters.get(attribute);
725        if (adapters != null) {
726            for (AccessorKey key : adapters.keySet()) {
727                try {
728                    ModelClass adapterViewType = mClassAnalyzer
729                            .findClass(key.viewType, imports).erasure();
730                    if (adapterViewType != null && adapterViewType.isAssignableFrom(viewType)) {
731                        try {
732                            L.d("getter return type is %s", key.valueType);
733                            final ModelClass adapterValueType = eraseType(mClassAnalyzer
734                                    .findClass(key.valueType, imports));
735                            L.d("getter %s returns type %s, compared to %s",
736                                    adapters.get(key).method, adapterValueType.toJavaCode(),
737                                    valueType);
738                            boolean isBetterView = bestMethod.viewType == null ||
739                                    bestMethod.viewType.isAssignableFrom(adapterViewType);
740                            if (valueType == null ||
741                                    isBetterParameter(adapterValueType, valueType,
742                                            bestMethod.returnType, isBetterView, imports)) {
743                                bestMethod.viewType = adapterViewType;
744                                bestMethod.returnType = adapterValueType;
745                                InverseDescription inverseDescription = adapters.get(key);
746                                ModelClass listenerType = ModelAnalyzer.getInstance().findClass(
747                                        InverseBindingListener.class);
748                                BindingSetterCall eventCall = getSetterCall(
749                                        inverseDescription.event, viewType, listenerType, imports);
750                                if (eventCall == null) {
751                                    List<MultiAttributeSetter> setters =
752                                            getMultiAttributeSetterCalls(
753                                                    new String[]{inverseDescription.event},
754                                                    viewType, new ModelClass[] {listenerType});
755                                    if (setters.size() != 1) {
756                                        L.e("Could not find event '%s' on View type '%s'",
757                                                inverseDescription.event,
758                                                viewType.getCanonicalName());
759                                    } else {
760                                        bestMethod.call = new AdapterGetter(inverseDescription,
761                                                setters.get(0));
762                                    }
763                                } else {
764                                    bestMethod.call = new AdapterGetter(inverseDescription,
765                                            eventCall);
766                                }
767                            }
768
769                        } catch (Exception e) {
770                            L.e(e, "Unknown class: %s", key.valueType);
771                        }
772                    }
773                } catch (Exception e) {
774                    L.e(e, "Unknown class: %s", key.viewType);
775                }
776            }
777        }
778
779        return bestMethod.call;
780    }
781
782    public boolean isUntaggable(String viewType) {
783        return mStore.untaggableTypes.containsKey(viewType);
784    }
785
786    private ModelMethod getBestSetter(ModelClass viewType, ModelClass argumentType,
787            String attribute, Map<String, String> imports) {
788        if (viewType.isGeneric()) {
789            argumentType = eraseType(argumentType, viewType.getTypeArguments());
790            viewType = viewType.erasure();
791        }
792        List<String> setterCandidates = new ArrayList<String>();
793        HashMap<String, MethodDescription> renamed = mStore.renamedMethods.get(attribute);
794        if (renamed != null) {
795            for (String className : renamed.keySet()) {
796                try {
797                    ModelClass renamedViewType = mClassAnalyzer.findClass(className, imports);
798                    if (renamedViewType.erasure().isAssignableFrom(viewType)) {
799                        setterCandidates.add(renamed.get(className).method);
800                        break;
801                    }
802                } catch (Exception e) {
803                    //printMessage(Diagnostic.Kind.NOTE, "Unknown class: " + className);
804                }
805            }
806        }
807        setterCandidates.add(getDefaultSetter(attribute));
808        setterCandidates.add(trimAttributeNamespace(attribute));
809
810        ModelMethod bestMethod = null;
811        ModelClass bestParameterType = null;
812        List<ModelClass> args = new ArrayList<ModelClass>();
813        args.add(argumentType);
814        for (String name : setterCandidates) {
815            ModelMethod[] methods = viewType.getMethods(name, 1);
816
817            for (ModelMethod method : methods) {
818                ModelClass[] parameterTypes = method.getParameterTypes();
819                ModelClass param = parameterTypes[0];
820                if (method.isVoid() &&
821                        isBetterParameter(argumentType, param, bestParameterType, true, imports)) {
822                    bestParameterType = param;
823                    bestMethod = method;
824                }
825            }
826        }
827        return bestMethod;
828    }
829
830    private InverseMethod getBestGetter(ModelClass viewType, ModelClass valueType,
831            String attribute, Map<String, String> imports) {
832        if (viewType.isGeneric()) {
833            if (valueType != null) {
834                valueType = eraseType(valueType, viewType.getTypeArguments());
835            }
836            viewType = viewType.erasure();
837        }
838        ModelClass bestReturnType = null;
839        InverseDescription bestDescription = null;
840        ModelClass bestViewType = null;
841        ModelMethod bestMethod = null;
842
843        HashMap<String, InverseDescription> inverseMethods = mStore.inverseMethods.get(attribute);
844        if (inverseMethods != null) {
845            for (String className : inverseMethods.keySet()) {
846                try {
847                    ModelClass methodViewType = mClassAnalyzer.findClass(className, imports);
848                    if (methodViewType.erasure().isAssignableFrom(viewType)) {
849                        boolean isBetterViewType = bestViewType == null ||
850                                bestViewType.isAssignableFrom(methodViewType);
851                        final InverseDescription inverseDescription = inverseMethods.get(className);
852                        final String name =  inverseDescription.method.isEmpty() ?
853                                trimAttributeNamespace(attribute) : inverseDescription.method;
854                        ModelMethod method = methodViewType.findInstanceGetter(name);
855                        ModelClass returnType = method.getReturnType(null); // no parameters
856                        if (valueType == null || bestReturnType == null ||
857                                isBetterParameter(returnType, valueType, bestReturnType,
858                                        isBetterViewType, imports)) {
859                            bestDescription = inverseDescription;
860                            bestReturnType = returnType;
861                            bestViewType = methodViewType;
862                            bestMethod = method;
863                        }
864                    }
865                } catch (Exception e) {
866                    //printMessage(Diagnostic.Kind.NOTE, "Unknown class: " + className);
867                }
868            }
869        }
870
871        BindingGetterCall call = null;
872        if (bestDescription != null) {
873            final ModelClass listenerType = ModelAnalyzer.getInstance().findClass(
874                    InverseBindingListener.class);
875            SetterCall eventSetter = getSetterCall(bestDescription.event, viewType,
876                    listenerType, imports);
877            if (eventSetter == null) {
878                List<MultiAttributeSetter> setters = getMultiAttributeSetterCalls(
879                        new String[] {bestDescription.event}, viewType,
880                        new ModelClass[] {listenerType});
881                if (setters.size() != 1) {
882                    L.e("Could not find event '%s' on View type '%s'", bestDescription.event,
883                            viewType.getCanonicalName());
884                    bestViewType = null;
885                    bestReturnType = null;
886                } else {
887                    call = new ViewGetterCall(bestDescription, bestMethod, setters.get(0));
888                }
889            } else {
890                call = new ViewGetterCall(bestDescription, bestMethod, eventSetter);
891            }
892        }
893        return new InverseMethod(call, bestReturnType, bestViewType);
894    }
895
896    private static ModelClass eraseType(ModelClass type, List<ModelClass> typeParameters) {
897        List<ModelClass> typeArguments = type.getTypeArguments();
898        if (typeArguments == null || typeParameters == null) {
899            return type;
900        }
901        for (ModelClass arg : typeArguments) {
902            if (typeParameters.contains(arg)) {
903                return type.erasure();
904            }
905        }
906        return type;
907    }
908
909    private static String trimAttributeNamespace(String attribute) {
910        final int colonIndex = attribute.indexOf(':');
911        return colonIndex == -1 ? attribute : attribute.substring(colonIndex + 1);
912    }
913
914    private static String getDefaultSetter(String attribute) {
915        return "set" + StringUtils.capitalize(trimAttributeNamespace(attribute));
916    }
917
918    private boolean isBetterParameter(ModelClass argument, ModelClass parameter,
919            ModelClass oldParameter, boolean isBetterViewTypeMatch, Map<String, String> imports) {
920        // Right view type. Check the value
921        if (!isBetterViewTypeMatch && oldParameter.equals(argument)) {
922            return false;
923        } else if (argument.equals(parameter)) {
924            // Exact match
925            return true;
926        } else if (!isBetterViewTypeMatch &&
927                ModelMethod.isBoxingConversion(oldParameter, argument)) {
928            return false;
929        } else if (ModelMethod.isBoxingConversion(parameter, argument)) {
930            // Boxing/unboxing is second best
931            return true;
932        } else {
933            int oldConversionLevel = ModelMethod.getImplicitConversionLevel(oldParameter);
934            if (ModelMethod.isImplicitConversion(argument, parameter)) {
935                // Better implicit conversion
936                int conversionLevel = ModelMethod.getImplicitConversionLevel(parameter);
937                return oldConversionLevel < 0 || conversionLevel < oldConversionLevel;
938            } else if (oldConversionLevel >= 0) {
939                return false;
940            } else if (parameter.isAssignableFrom(argument)) {
941                // Right type, see if it is better than the current best match.
942                if (oldParameter == null) {
943                    return true;
944                } else {
945                    return oldParameter.isAssignableFrom(parameter);
946                }
947            } else {
948                MethodDescription conversionMethod = getConversionMethod(argument, parameter,
949                        imports);
950                if (conversionMethod != null) {
951                    return true;
952                }
953                if (getConversionMethod(argument, oldParameter, imports) != null) {
954                    return false;
955                }
956                return argument.isObject() && !parameter.isPrimitive();
957            }
958        }
959    }
960
961    private MethodDescription getConversionMethod(ModelClass from, ModelClass to,
962            Map<String, String> imports) {
963        if (from != null && to != null) {
964            if (to.isObject()) {
965                return null;
966            }
967            for (String fromClassName : mStore.conversionMethods.keySet()) {
968                try {
969                    ModelClass convertFrom = mClassAnalyzer.findClass(fromClassName, imports);
970                    if (canUseForConversion(from, convertFrom)) {
971                        HashMap<String, MethodDescription> conversion =
972                                mStore.conversionMethods.get(fromClassName);
973                        for (String toClassName : conversion.keySet()) {
974                            try {
975                                ModelClass convertTo = mClassAnalyzer.findClass(toClassName,
976                                        imports);
977                                if (canUseForConversion(convertTo, to)) {
978                                    return conversion.get(toClassName);
979                                }
980                            } catch (Exception e) {
981                                L.d(e, "Unknown class: %s", toClassName);
982                            }
983                        }
984                    }
985                } catch (Exception e) {
986                    L.d(e, "Unknown class: %s", fromClassName);
987                }
988            }
989        }
990        return null;
991    }
992
993    private boolean canUseForConversion(ModelClass from, ModelClass to) {
994        if (from.isIncomplete() || to.isIncomplete()) {
995            from = from.erasure();
996            to = to.erasure();
997        }
998        return from.equals(to) || ModelMethod.isBoxingConversion(from, to) ||
999                to.isAssignableFrom(from);
1000    }
1001
1002    private static void merge(IntermediateV2 store, Intermediate dumpStore) {
1003        IntermediateV2 intermediateV2 = (IntermediateV2) dumpStore.upgrade();
1004        merge(store.adapterMethods, intermediateV2.adapterMethods);
1005        merge(store.renamedMethods, intermediateV2.renamedMethods);
1006        merge(store.conversionMethods, intermediateV2.conversionMethods);
1007        store.multiValueAdapters.putAll(intermediateV2.multiValueAdapters);
1008        store.untaggableTypes.putAll(intermediateV2.untaggableTypes);
1009        merge(store.inverseAdapters, intermediateV2.inverseAdapters);
1010        merge(store.inverseMethods, intermediateV2.inverseMethods);
1011    }
1012
1013    private static <K, V, D> void merge(HashMap<K, HashMap<V, D>> first,
1014            HashMap<K, HashMap<V, D>> second) {
1015        for (K key : second.keySet()) {
1016            HashMap<V, D> firstVals = first.get(key);
1017            HashMap<V, D> secondVals = second.get(key);
1018            if (firstVals == null) {
1019                first.put(key, secondVals);
1020            } else {
1021                for (V key2 : secondVals.keySet()) {
1022                    if (!firstVals.containsKey(key2)) {
1023                        firstVals.put(key2, secondVals.get(key2));
1024                    }
1025                }
1026            }
1027        }
1028    }
1029
1030    private static String createAdapterCall(MethodDescription adapter,
1031            String componentExpression, String viewExpression, String... args) {
1032        StringBuilder sb = new StringBuilder();
1033
1034        if (adapter.isStatic) {
1035            sb.append(adapter.type);
1036        } else {
1037            final SetterStore setterStore = SetterStore.get(ModelAnalyzer.getInstance());
1038            final String binderCall =  setterStore.getBindingAdapterCall(adapter.type);
1039            sb.append(componentExpression).append('.').append(binderCall);
1040        }
1041        sb.append('.').append(adapter.method).append('(');
1042        if (adapter.componentClass != null) {
1043            if (!"DataBindingComponent".equals(adapter.componentClass)) {
1044                sb.append('(').append(adapter.componentClass).append(") ");
1045            }
1046            sb.append(componentExpression).append(", ");
1047        }
1048        sb.append(viewExpression);
1049        for (String arg: args) {
1050            sb.append(", ").append(arg);
1051        }
1052        sb.append(')');
1053        return sb.toString();
1054    }
1055
1056    private static class MultiValueAdapterKey implements Serializable {
1057        private static final long serialVersionUID = 1;
1058
1059        public final String viewType;
1060
1061        public final String[] attributes;
1062
1063        public final String[] parameterTypes;
1064
1065        public final boolean requireAll;
1066
1067        public final TreeMap<String, Integer> attributeIndices = new TreeMap<String, Integer>();
1068
1069        public MultiValueAdapterKey(ProcessingEnvironment processingEnv,
1070                ExecutableElement method, String[] attributes, boolean takesComponent,
1071                boolean requireAll) {
1072            this.attributes = stripAttributes(attributes);
1073            this.requireAll = requireAll;
1074            List<? extends VariableElement> parameters = method.getParameters();
1075            final int argStart = 1 + (takesComponent ? 1 : 0);
1076            this.viewType = getQualifiedName(eraseType(processingEnv,
1077                    parameters.get(argStart - 1).asType()));
1078            this.parameterTypes = new String[parameters.size() - argStart];
1079            for (int i = 0; i < attributes.length; i++) {
1080                TypeMirror typeMirror = eraseType(processingEnv,
1081                        parameters.get(i + argStart).asType());
1082                this.parameterTypes[i] = getQualifiedName(typeMirror);
1083                attributeIndices.put(this.attributes[i], i);
1084            }
1085        }
1086
1087        @Override
1088        public boolean equals(Object obj) {
1089            if (!(obj instanceof MultiValueAdapterKey)) {
1090                return false;
1091            }
1092            final MultiValueAdapterKey that = (MultiValueAdapterKey) obj;
1093            if (!this.viewType.equals(that.viewType) ||
1094                    this.attributes.length != that.attributes.length ||
1095                    !this.attributeIndices.keySet().equals(that.attributeIndices.keySet())) {
1096                return false;
1097            }
1098
1099            for (int i = 0; i < this.attributes.length; i++) {
1100                final int thatIndex = that.attributeIndices.get(this.attributes[i]);
1101                final String thisParameter = parameterTypes[i];
1102                final String thatParameter = that.parameterTypes[thatIndex];
1103                if (!thisParameter.equals(thatParameter)) {
1104                    return false;
1105                }
1106            }
1107            return true;
1108        }
1109
1110        @Override
1111        public int hashCode() {
1112            return mergedHashCode(viewType, attributeIndices.keySet());
1113        }
1114    }
1115
1116    private static int mergedHashCode(Object... objects) {
1117        return Arrays.hashCode(objects);
1118    }
1119
1120    private static class MethodDescription implements Serializable {
1121
1122        private static final long serialVersionUID = 1;
1123
1124        public final String type;
1125
1126        public final String method;
1127
1128        public final boolean requiresOldValue;
1129
1130        public final boolean isStatic;
1131
1132        public final String componentClass;
1133
1134        public MethodDescription(String type, String method) {
1135            this.type = type;
1136            this.method = method;
1137            this.requiresOldValue = false;
1138            this.isStatic = true;
1139            this.componentClass = null;
1140            L.d("BINARY created method desc 1 %s %s", type, method );
1141        }
1142
1143        public MethodDescription(ExecutableElement method, int numAttributes,
1144                boolean takesComponent) {
1145            TypeElement enclosingClass = (TypeElement) method.getEnclosingElement();
1146            this.type = enclosingClass.getQualifiedName().toString();
1147            this.method = method.getSimpleName().toString();
1148            final int argStart = 1 + (takesComponent ? 1 : 0);
1149            this.requiresOldValue = method.getParameters().size() - argStart == numAttributes * 2;
1150            this.isStatic = method.getModifiers().contains(Modifier.STATIC);
1151            this.componentClass = takesComponent
1152                    ? getQualifiedName(method.getParameters().get(0).asType())
1153                    : null;
1154
1155            L.d("BINARY created method desc 2 %s %s, %s", type, this.method, method);
1156        }
1157
1158        @Override
1159        public boolean equals(Object obj) {
1160            if (obj instanceof MethodDescription) {
1161                MethodDescription that = (MethodDescription) obj;
1162                return that.type.equals(this.type) && that.method.equals(this.method);
1163            } else {
1164                return false;
1165            }
1166        }
1167
1168        @Override
1169        public int hashCode() {
1170            return mergedHashCode(type, method);
1171        }
1172
1173        @Override
1174        public String toString() {
1175            return type + "." + method + "()";
1176        }
1177    }
1178
1179    private static class InverseDescription extends MethodDescription {
1180        private static final long serialVersionUID = 1;
1181
1182        public final String event;
1183
1184        public InverseDescription(String type, String method, String event) {
1185            super(type, method);
1186            this.event = event;
1187        }
1188
1189        public InverseDescription(ExecutableElement method, String event, boolean takesComponent) {
1190            super(method, 1, takesComponent);
1191            this.event = event;
1192        }
1193
1194        @Override
1195        public boolean equals(Object obj) {
1196            if (!super.equals(obj) || !(obj instanceof InverseDescription)) {
1197                return false;
1198            }
1199            return event.equals(((InverseDescription) obj).event);
1200        }
1201
1202        @Override
1203        public int hashCode() {
1204            return mergedHashCode(type, method, event);
1205        }
1206    }
1207
1208    private static class AccessorKey implements Serializable {
1209
1210        private static final long serialVersionUID = 1;
1211
1212        public final String viewType;
1213
1214        public final String valueType;
1215
1216        public AccessorKey(String viewType, String valueType) {
1217            this.viewType = viewType;
1218            this.valueType = valueType;
1219        }
1220
1221        @Override
1222        public int hashCode() {
1223            return mergedHashCode(viewType, valueType);
1224        }
1225
1226        @Override
1227        public boolean equals(Object obj) {
1228            if (obj instanceof AccessorKey) {
1229                AccessorKey that = (AccessorKey) obj;
1230                return viewType.equals(that.valueType) && valueType.equals(that.valueType);
1231            } else {
1232                return false;
1233            }
1234        }
1235
1236        @Override
1237        public String toString() {
1238            return "AK(" + viewType + ", " + valueType + ")";
1239        }
1240    }
1241
1242    private interface Intermediate extends Serializable {
1243        Intermediate upgrade();
1244    }
1245
1246    private static class IntermediateV1 implements Serializable, Intermediate {
1247        private static final long serialVersionUID = 1;
1248        public final HashMap<String, HashMap<AccessorKey, MethodDescription>> adapterMethods =
1249                new HashMap<String, HashMap<AccessorKey, MethodDescription>>();
1250        public final HashMap<String, HashMap<String, MethodDescription>> renamedMethods =
1251                new HashMap<String, HashMap<String, MethodDescription>>();
1252        public final HashMap<String, HashMap<String, MethodDescription>> conversionMethods =
1253                new HashMap<String, HashMap<String, MethodDescription>>();
1254        public final HashMap<String, String> untaggableTypes = new HashMap<String, String>();
1255        public final HashMap<MultiValueAdapterKey, MethodDescription> multiValueAdapters =
1256                new HashMap<MultiValueAdapterKey, MethodDescription>();
1257
1258        public IntermediateV1() {
1259        }
1260
1261        @Override
1262        public Intermediate upgrade() {
1263            IntermediateV2 v2 = new IntermediateV2();
1264            v2.adapterMethods.putAll(adapterMethods);
1265            v2.renamedMethods.putAll(renamedMethods);
1266            v2.conversionMethods.putAll(conversionMethods);
1267            v2.untaggableTypes.putAll(untaggableTypes);
1268            v2.multiValueAdapters.putAll(multiValueAdapters);
1269            return v2;
1270        }
1271    }
1272
1273    private static class IntermediateV2 extends IntermediateV1 {
1274        public final HashMap<String, HashMap<AccessorKey, InverseDescription>> inverseAdapters =
1275                new HashMap<String, HashMap<AccessorKey, InverseDescription>>();
1276        public final HashMap<String, HashMap<String, InverseDescription>> inverseMethods =
1277                new HashMap<String, HashMap<String, InverseDescription>>();
1278
1279        @Override
1280        public Intermediate upgrade() {
1281            return this;
1282        }
1283    }
1284
1285    public static class DummySetter extends SetterCall {
1286        private String mMethodName;
1287
1288        public DummySetter(String methodName) {
1289            mMethodName = methodName;
1290        }
1291
1292        @Override
1293        public String toJavaInternal(String componentExpression, String viewExpression,
1294                String valueExpression) {
1295            return viewExpression + "." + mMethodName + "(" + valueExpression + ")";
1296        }
1297
1298        @Override
1299        public String toJavaInternal(String componentExpression, String viewExpression,
1300                String oldValue, String valueExpression) {
1301            return viewExpression + "." + mMethodName + "(" + valueExpression + ")";
1302        }
1303
1304        @Override
1305        public int getMinApi() {
1306            return 1;
1307        }
1308
1309        @Override
1310        public boolean requiresOldValue() {
1311            return false;
1312        }
1313
1314        @Override
1315        public ModelClass[] getParameterTypes() {
1316            return new ModelClass[] {
1317                    ModelAnalyzer.getInstance().findClass(Object.class)
1318            };
1319        }
1320
1321        @Override
1322        public String getBindingAdapterInstanceClass() {
1323            return null;
1324        }
1325    }
1326
1327    public static class AdapterSetter extends SetterCall {
1328        final MethodDescription mAdapter;
1329        final ModelClass mParameterType;
1330
1331        public AdapterSetter(MethodDescription adapter, ModelClass parameterType) {
1332            mAdapter = adapter;
1333            mParameterType = parameterType;
1334        }
1335
1336        @Override
1337        public String toJavaInternal(String componentExpression, String viewExpression,
1338                String valueExpression) {
1339            return createAdapterCall(mAdapter, componentExpression,
1340                    viewExpression, mCastString + valueExpression);
1341        }
1342
1343        @Override
1344        protected String toJavaInternal(String componentExpression, String viewExpression,
1345                String oldValue, String valueExpression) {
1346            return createAdapterCall(mAdapter, componentExpression,
1347                    viewExpression, mCastString + oldValue, mCastString + valueExpression);
1348        }
1349
1350        @Override
1351        public int getMinApi() {
1352            return 1;
1353        }
1354
1355        @Override
1356        public boolean requiresOldValue() {
1357            return mAdapter.requiresOldValue;
1358        }
1359
1360        @Override
1361        public ModelClass[] getParameterTypes() {
1362            return new ModelClass[] { mParameterType };
1363        }
1364
1365        @Override
1366        public String getBindingAdapterInstanceClass() {
1367            return mAdapter.isStatic ? null : mAdapter.type;
1368        }
1369    }
1370
1371    public static class ModelMethodSetter extends SetterCall {
1372        final ModelMethod mModelMethod;
1373
1374        public ModelMethodSetter(ModelMethod modelMethod) {
1375            mModelMethod = modelMethod;
1376        }
1377
1378        @Override
1379        public String toJavaInternal(String componentExpression, String viewExpression,
1380                String valueExpression) {
1381            return viewExpression + "." + mModelMethod.getName() + "(" + mCastString +
1382                    valueExpression + ")";
1383        }
1384
1385        @Override
1386        protected String toJavaInternal(String componentExpression, String viewExpression,
1387                String oldValue, String valueExpression) {
1388            return viewExpression + "." + mModelMethod.getName() + "(" +
1389                    mCastString + oldValue + ", " + mCastString + valueExpression + ")";
1390        }
1391
1392        @Override
1393        public int getMinApi() {
1394            return mModelMethod.getMinApi();
1395        }
1396
1397        @Override
1398        public boolean requiresOldValue() {
1399            return mModelMethod.getParameterTypes().length == 3;
1400        }
1401
1402        @Override
1403        public ModelClass[] getParameterTypes() {
1404            return new ModelClass[] { mModelMethod.getParameterTypes()[0] };
1405        }
1406
1407        @Override
1408        public String getBindingAdapterInstanceClass() {
1409            return null;
1410        }
1411    }
1412
1413    public interface BindingSetterCall {
1414        String toJava(String componentExpression, String viewExpression,
1415                String... valueExpressions);
1416
1417        int getMinApi();
1418
1419        boolean requiresOldValue();
1420
1421        ModelClass[] getParameterTypes();
1422
1423        String getBindingAdapterInstanceClass();
1424    }
1425
1426    public static abstract class SetterCall implements BindingSetterCall {
1427        private MethodDescription mConverter;
1428        protected String mCastString = "";
1429
1430        public SetterCall() {
1431        }
1432
1433        public void setConverter(MethodDescription converter) {
1434            mConverter = converter;
1435        }
1436
1437        protected abstract String toJavaInternal(String componentExpression, String viewExpression,
1438                String converted);
1439
1440        protected abstract String toJavaInternal(String componentExpression, String viewExpression,
1441                String oldValue, String converted);
1442
1443        @Override
1444        public final String toJava(String componentExpression, String viewExpression,
1445                String... valueExpression) {
1446            Preconditions.check(valueExpression.length == 2, "value expressions size must be 2");
1447            if (requiresOldValue()) {
1448                return toJavaInternal(componentExpression, viewExpression,
1449                        convertValue(valueExpression[0]), convertValue(valueExpression[1]));
1450            } else {
1451                return toJavaInternal(componentExpression, viewExpression,
1452                        convertValue(valueExpression[1]));
1453            }
1454        }
1455
1456        protected String convertValue(String valueExpression) {
1457            return mConverter == null ? valueExpression :
1458                    mConverter.type + "." + mConverter.method + "(" + valueExpression + ")";
1459        }
1460
1461        abstract public int getMinApi();
1462
1463        public void setCast(ModelClass castTo) {
1464            mCastString = "(" + castTo.toJavaCode() + ") ";
1465        }
1466    }
1467
1468    public static class MultiAttributeSetter implements BindingSetterCall {
1469        public final String[] attributes;
1470        private final MethodDescription mAdapter;
1471        private final MethodDescription[] mConverters;
1472        private final String[] mCasts;
1473        private final MultiValueAdapterKey mKey;
1474        private final boolean[] mSupplied;
1475
1476        public MultiAttributeSetter(MultiValueAdapterKey key, boolean[] supplied,
1477                MethodDescription adapter, MethodDescription[] converters, String[] casts) {
1478            Preconditions.check(converters != null &&
1479                    converters.length == key.attributes.length &&
1480                    casts != null && casts.length == key.attributes.length &&
1481                    supplied.length == key.attributes.length,
1482                    "invalid arguments to create multi attr setter");
1483            this.mAdapter = adapter;
1484            this.mConverters = converters;
1485            this.mCasts = casts;
1486            this.mKey = key;
1487            this.mSupplied = supplied;
1488            if (key.requireAll) {
1489                this.attributes = key.attributes;
1490            } else {
1491                int numSupplied = 0;
1492                for (int i = 0; i < mKey.attributes.length; i++) {
1493                    if (supplied[i]) {
1494                        numSupplied++;
1495                    }
1496                }
1497                if (numSupplied == key.attributes.length) {
1498                    this.attributes = key.attributes;
1499                } else {
1500                    this.attributes = new String[numSupplied];
1501                    int attrIndex = 0;
1502                    for (int i = 0; i < key.attributes.length; i++) {
1503                        if (supplied[i]) {
1504                            attributes[attrIndex++] = key.attributes[i];
1505                        }
1506                    }
1507                }
1508            }
1509        }
1510
1511        @Override
1512        public final String toJava(String componentExpression, String viewExpression,
1513                String[] valueExpressions) {
1514            Preconditions.check(valueExpressions.length == attributes.length * 2,
1515                    "MultiAttributeSetter needs %s items, received %s",
1516                    Arrays.toString(attributes), Arrays.toString(valueExpressions));
1517            final int numAttrs = mKey.attributes.length;
1518            String[] args = new String[numAttrs + (requiresOldValue() ? numAttrs : 0)];
1519
1520            final int startIndex = mAdapter.requiresOldValue ? 0 : numAttrs;
1521            int attrIndex = mAdapter.requiresOldValue ? 0 : attributes.length;
1522            final ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
1523            StringBuilder argBuilder = new StringBuilder();
1524            final int endIndex = numAttrs * 2;
1525            for (int i = startIndex; i < endIndex; i++) {
1526                argBuilder.setLength(0);
1527                if (!mSupplied[i % numAttrs]) {
1528                    final String paramType = mKey.parameterTypes[i % numAttrs];
1529                    final String defaultValue = modelAnalyzer.getDefaultValue(paramType);
1530                    argBuilder.append('(')
1531                            .append(paramType)
1532                            .append(')')
1533                            .append(defaultValue);
1534                } else {
1535                    if (mConverters[i % numAttrs] != null) {
1536                        final MethodDescription converter = mConverters[i % numAttrs];
1537                        argBuilder.append(converter.type)
1538                                .append('.')
1539                                .append(converter.method)
1540                                .append('(')
1541                                .append(valueExpressions[attrIndex])
1542                                .append(')');
1543                    } else {
1544                        if (mCasts[i % numAttrs] != null) {
1545                            argBuilder.append('(')
1546                                    .append(mCasts[i % numAttrs])
1547                                    .append(')');
1548                        }
1549                        argBuilder.append(valueExpressions[attrIndex]);
1550                    }
1551                    attrIndex++;
1552                }
1553                args[i - startIndex] = argBuilder.toString();
1554            }
1555            return createAdapterCall(mAdapter, componentExpression, viewExpression, args);
1556        }
1557
1558        @Override
1559        public int getMinApi() {
1560            return 1;
1561        }
1562
1563        @Override
1564        public boolean requiresOldValue() {
1565            return mAdapter.requiresOldValue;
1566        }
1567
1568        @Override
1569        public ModelClass[] getParameterTypes() {
1570            ModelClass[] parameters = new ModelClass[attributes.length];
1571            String[] paramTypeStrings = mKey.parameterTypes;
1572            ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
1573            int attrIndex = 0;
1574            for (int i = 0; i < mKey.attributes.length; i++) {
1575                if (mSupplied[i]) {
1576                    parameters[attrIndex++] = modelAnalyzer.findClass(paramTypeStrings[i], null);
1577                }
1578            }
1579            return parameters;
1580        }
1581
1582        @Override
1583        public String getBindingAdapterInstanceClass() {
1584            return mAdapter.isStatic ? null : mAdapter.type;
1585        }
1586
1587        @Override
1588        public String toString() {
1589            return "MultiAttributeSetter{" +
1590                    "attributes=" + Arrays.toString(attributes) +
1591                    ", mAdapter=" + mAdapter +
1592                    ", mConverters=" + Arrays.toString(mConverters) +
1593                    ", mCasts=" + Arrays.toString(mCasts) +
1594                    ", mKey=" + mKey +
1595                    '}';
1596        }
1597    }
1598
1599    public static class ViewDataBindingEventSetter implements BindingSetterCall {
1600
1601        public ViewDataBindingEventSetter() {
1602        }
1603
1604        @Override
1605        public String toJava(String componentExpression, String viewExpression,
1606                String... valueExpressions) {
1607            return "setBindingInverseListener(" + viewExpression + ", " +
1608                    valueExpressions[0] + ", " + valueExpressions[1] + ")";
1609        }
1610
1611        @Override
1612        public int getMinApi() {
1613            return 0;
1614        }
1615
1616        @Override
1617        public boolean requiresOldValue() {
1618            return true;
1619        }
1620
1621        @Override
1622        public ModelClass[] getParameterTypes() {
1623            ModelClass[] parameterTypes = new ModelClass[1];
1624            parameterTypes[0] = ModelAnalyzer.getInstance().findClass(
1625                    "android.databinding.ViewDataBinder.PropertyChangedInverseListener", null);
1626            return parameterTypes;
1627        }
1628
1629        @Override
1630        public String getBindingAdapterInstanceClass() {
1631            return null;
1632        }
1633    }
1634
1635    public interface BindingGetterCall {
1636        String toJava(String componentExpression, String viewExpression);
1637
1638        int getMinApi();
1639
1640        String getBindingAdapterInstanceClass();
1641
1642        void setBindingAdapterCall(String method);
1643
1644        BindingSetterCall getEvent();
1645
1646        String getEventAttribute();
1647    }
1648
1649    public static class ViewDataBindingGetterCall implements BindingGetterCall {
1650        private final String mGetter;
1651        private final BindingSetterCall mEventSetter;
1652        private final String mAttribute;
1653
1654        public ViewDataBindingGetterCall(String attribute) {
1655            final int colonIndex = attribute.indexOf(':');
1656            mAttribute = attribute.substring(colonIndex + 1);
1657            mGetter = "get" + StringUtils.capitalize(mAttribute);
1658            mEventSetter = new ViewDataBindingEventSetter();
1659        }
1660
1661        @Override
1662        public String toJava(String componentExpression, String viewExpression) {
1663            return viewExpression + "." + mGetter + "()";
1664        }
1665
1666        @Override
1667        public int getMinApi() {
1668            return 0;
1669        }
1670
1671        @Override
1672        public String getBindingAdapterInstanceClass() {
1673            return null;
1674        }
1675
1676        @Override
1677        public void setBindingAdapterCall(String method) {
1678        }
1679
1680        @Override
1681        public BindingSetterCall getEvent() {
1682            return mEventSetter;
1683        }
1684
1685        @Override
1686        public String getEventAttribute() {
1687            return mAttribute;
1688        }
1689    }
1690
1691    public static class ViewGetterCall implements BindingGetterCall {
1692        private final InverseDescription mInverseDescription;
1693        private final BindingSetterCall mEventCall;
1694        private final ModelMethod mMethod;
1695
1696        public ViewGetterCall(InverseDescription inverseDescription, ModelMethod method,
1697                BindingSetterCall eventCall) {
1698            mInverseDescription = inverseDescription;
1699            mEventCall = eventCall;
1700            mMethod = method;
1701        }
1702
1703        @Override
1704        public BindingSetterCall getEvent() {
1705            return mEventCall;
1706        }
1707
1708        @Override
1709        public String getEventAttribute() {
1710            return mInverseDescription.event;
1711        }
1712
1713        @Override
1714        public String toJava(String componentExpression, String viewExpression) {
1715            return viewExpression + "." + mMethod.getName() + "()";
1716        }
1717
1718        @Override
1719        public int getMinApi() {
1720            return mMethod.getMinApi();
1721        }
1722
1723        @Override
1724        public String getBindingAdapterInstanceClass() {
1725            return null;
1726        }
1727
1728        @Override
1729        public void setBindingAdapterCall(String method) {
1730        }
1731    }
1732
1733    public static class AdapterGetter implements BindingGetterCall {
1734        private final InverseDescription mInverseDescription;
1735        private String mBindingAdapterCall;
1736        private final BindingSetterCall mEventCall;
1737
1738        public AdapterGetter(InverseDescription description, BindingSetterCall eventCall) {
1739            mInverseDescription = description;
1740            mEventCall = eventCall;
1741        }
1742
1743        @Override
1744        public String toJava(String componentExpression, String viewExpression) {
1745            StringBuilder sb = new StringBuilder();
1746
1747            if (mInverseDescription.isStatic) {
1748                sb.append(mInverseDescription.type);
1749            } else {
1750                sb.append(componentExpression).append('.').append(mBindingAdapterCall);
1751            }
1752            sb.append('.').append(mInverseDescription.method).append('(');
1753            if (mInverseDescription.componentClass != null) {
1754                if (!"DataBindingComponent".equals(mInverseDescription.componentClass)) {
1755                    sb.append('(').append(mInverseDescription.componentClass).append(") ");
1756                }
1757                sb.append(componentExpression).append(", ");
1758            }
1759            sb.append(viewExpression).append(')');
1760            return sb.toString();
1761        }
1762
1763        @Override
1764        public int getMinApi() {
1765            return 1;
1766        }
1767
1768        @Override
1769        public String getBindingAdapterInstanceClass() {
1770            return mInverseDescription.isStatic ? null : mInverseDescription.type;
1771        }
1772
1773        @Override
1774        public void setBindingAdapterCall(String method) {
1775            mBindingAdapterCall = method;
1776        }
1777
1778        @Override
1779        public BindingSetterCall getEvent() {
1780            return mEventCall;
1781        }
1782
1783        @Override
1784        public String getEventAttribute() {
1785            return mInverseDescription.event;
1786        }
1787    }
1788
1789    private static class InverseMethod {
1790        public BindingGetterCall call;
1791        public ModelClass returnType;
1792        public ModelClass viewType;
1793
1794        public InverseMethod(BindingGetterCall call, ModelClass returnType, ModelClass viewType) {
1795            this.call = call;
1796            this.returnType = returnType;
1797            this.viewType = viewType;
1798        }
1799    }
1800}
1801