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