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