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