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