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