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