SetterStore.java revision 97758524d3953793b50e3e0121ef3cbdc047b35b
15a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt/*
25a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt * Copyright (C) 2015 The Android Open Source Project
35a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt *
45a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt * Licensed under the Apache License, Version 2.0 (the "License");
55a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt * you may not use this file except in compliance with the License.
65a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt * You may obtain a copy of the License at
75a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt *
85a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt *      http://www.apache.org/licenses/LICENSE-2.0
95a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt *
105a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt * Unless required by applicable law or agreed to in writing, software
115a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt * distributed under the License is distributed on an "AS IS" BASIS,
125a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
135a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt * See the License for the specific language governing permissions and
145a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt * limitations under the License.
155a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt */
165a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtpackage android.databinding.tool.store;
175a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt
185a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport org.apache.commons.lang3.StringUtils;
195a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt
205a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport android.databinding.tool.reflection.ModelAnalyzer;
215a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport android.databinding.tool.reflection.ModelClass;
225a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport android.databinding.tool.reflection.ModelMethod;
235a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport android.databinding.tool.util.GenerationalClassUtil;
245a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport android.databinding.tool.util.L;
255a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport android.databinding.tool.util.Preconditions;
265a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt
275a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport java.io.IOException;
285a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport java.io.Serializable;
295a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport java.util.ArrayList;
305a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport java.util.Arrays;
315a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport java.util.Collections;
325a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport java.util.Comparator;
335a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport java.util.HashMap;
345a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport java.util.Iterator;
355a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport java.util.List;
365a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport java.util.Map;
375a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport java.util.Set;
385a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport java.util.TreeMap;
395a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt
405a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport javax.annotation.processing.ProcessingEnvironment;
414ae50e65ef0eefe6d5c356acbc1839f8eac68af5Dmitry Shmidtimport javax.lang.model.element.ExecutableElement;
425a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport javax.lang.model.element.Modifier;
435a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport javax.lang.model.element.TypeElement;
445a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport javax.lang.model.element.VariableElement;
455a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport javax.lang.model.type.ArrayType;
465a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport javax.lang.model.type.DeclaredType;
475a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport javax.lang.model.type.TypeKind;
485a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtimport javax.lang.model.type.TypeMirror;
495a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt
505a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidtpublic class SetterStore {
515a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt    private static SetterStore sStore;
527d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt
535a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt    private final IntermediateV1 mStore;
545a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt    private final ModelAnalyzer mClassAnalyzer;
555a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt
565a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt    private Comparator<MultiAttributeSetter> COMPARE_MULTI_ATTRIBUTE_SETTERS =
575a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            new Comparator<MultiAttributeSetter>() {
587d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt                @Override
595a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                public int compare(MultiAttributeSetter o1, MultiAttributeSetter o2) {
605a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                    if (o1.attributes.length != o2.attributes.length) {
615a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        return o2.attributes.length - o1.attributes.length;
625a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                    }
635a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                    ModelClass view1 = mClassAnalyzer.findClass(o1.mKey.viewType, null).erasure();
645a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                    ModelClass view2 = mClassAnalyzer.findClass(o2.mKey.viewType, null).erasure();
655a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                    if (!view1.equals(view2)) {
665a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        if (view1.isAssignableFrom(view2)) {
675a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                            return 1;
685a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        } else {
695a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                            return -1;
705a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        }
715a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                    }
725a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                    if (!o1.mKey.attributeIndices.keySet()
735a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                            .equals(o2.mKey.attributeIndices.keySet())) {
745a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        // order by attribute name
755a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        Iterator<String> o1Keys = o1.mKey.attributeIndices.keySet().iterator();
765a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        Iterator<String> o2Keys = o2.mKey.attributeIndices.keySet().iterator();
775a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        while (o1Keys.hasNext()) {
785a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                            String key1 = o1Keys.next();
795a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                            String key2 = o2Keys.next();
805a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                            int compare = key1.compareTo(key2);
815a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                            if (compare != 0) {
825a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                                return compare;
835a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                            }
845a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        }
855a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        Preconditions.check(false,
865a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                                "The sets don't match! That means the keys shouldn't match also");
875a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                    }
885a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                    // Same view type. Same attributes
895a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                    for (String attribute : o1.mKey.attributeIndices.keySet()) {
905a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        final int index1 = o1.mKey.attributeIndices.get(attribute);
915a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        final int index2 = o2.mKey.attributeIndices.get(attribute);
925a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        ModelClass type1 = mClassAnalyzer
935a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                                .findClass(o1.mKey.parameterTypes[index1], null);
945a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        ModelClass type2 = mClassAnalyzer
955a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                                .findClass(o2.mKey.parameterTypes[index2], null);
96ebd93af924f6e54fb4982b3312ff875a4896b62bDmitry Shmidt                        if (type1.equals(type2)) {
975a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                            continue;
985a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        }
995a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        if (o1.mCasts[index1] != null) {
1005a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                            if (o2.mCasts[index2] == null) {
1015a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                                return 1; // o2 is better
1025a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                            } else {
1035a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                                continue; // both are casts
1045a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                            }
1055a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        } else if (o2.mCasts[index2] != null) {
1065a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                            return -1; // o1 is better
1075a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        }
1085a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        if (o1.mConverters[index1] != null) {
1095a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                            if (o2.mConverters[index2] == null) {
1105a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                                return 1; // o2 is better
1115a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                            } else {
1125a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                                continue; // both are conversions
1135a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                            }
1145a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        } else if (o2.mConverters[index2] != null) {
1155a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                            return -1; // o1 is better
1165a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        }
1175a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt
1185a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        if (type1.isPrimitive()) {
1195a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                            if (type2.isPrimitive()) {
1205a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                                int type1ConversionLevel = ModelMethod
1215a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                                        .getImplicitConversionLevel(type1);
1225a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                                int type2ConversionLevel = ModelMethod
1235a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                                        .getImplicitConversionLevel(type2);
1245a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                                return type2ConversionLevel - type1ConversionLevel;
1255a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                            } else {
1265a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                                // type1 is primitive and has higher priority
1275a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                                return -1;
1285a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                            }
1295a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        } else if (type2.isPrimitive()) {
1305a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                            return 1;
1315a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        }
1325a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        if (type1.isAssignableFrom(type2)) {
1335a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                            return 1;
1345a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        } else if (type2.isAssignableFrom(type1)) {
1355a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                            return -1;
1365a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        }
1375a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                    }
1385a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                    // hmmm... same view type, same attributes, same parameter types... ?
1395a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                    return 0;
1405a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                }
1415a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            };
1425a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt
1435a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt    private SetterStore(ModelAnalyzer modelAnalyzer, IntermediateV1 store) {
1445a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        mClassAnalyzer = modelAnalyzer;
1455a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        mStore = store;
1465a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt    }
1475a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt
1485a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt    public static SetterStore get(ModelAnalyzer modelAnalyzer) {
1495a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        if (sStore == null) {
1505a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            sStore = load(modelAnalyzer);
1515a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        }
1525a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        return sStore;
1537d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt    }
1545a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt
1557d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt    private static SetterStore load(ModelAnalyzer modelAnalyzer) {
1565a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        IntermediateV1 store = new IntermediateV1();
1575a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        List<Intermediate> previousStores = GenerationalClassUtil
1587d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt                .loadObjects(GenerationalClassUtil.ExtensionFilter.SETTER_STORE);
1597d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        for (Intermediate intermediate : previousStores) {
1605a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            merge(store, intermediate);
1617d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        }
1627d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        return new SetterStore(modelAnalyzer, store);
1635a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt    }
1645a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt
1655a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt    public void addRenamedMethod(String attribute, String declaringClass, String method,
1665a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            TypeElement declaredOn) {
1677d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        attribute = stripNamespace(attribute);
1685a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        HashMap<String, MethodDescription> renamed = mStore.renamedMethods.get(attribute);
1695a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        if (renamed == null) {
1705a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            renamed = new HashMap<String, MethodDescription>();
1715a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            mStore.renamedMethods.put(attribute, renamed);
1729839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        }
1739839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        MethodDescription methodDescription = new MethodDescription(
1749839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt                declaredOn.getQualifiedName().toString(), method);
1759839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        L.d("STORE addmethod desc %s", methodDescription);
1769839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        renamed.put(declaringClass, methodDescription);
1779839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt    }
1789839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt
1799839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt    public void addBindingAdapter(ProcessingEnvironment processingEnv, String attribute,
1809839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            ExecutableElement bindingMethod, boolean takesComponent) {
1819839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        attribute = stripNamespace(attribute);
1829839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        L.d("STORE addBindingAdapter %s %s", attribute, bindingMethod);
1839839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        HashMap<AccessorKey, MethodDescription> adapters = mStore.adapterMethods.get(attribute);
1849839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt
1859839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        if (adapters == null) {
1869839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            adapters = new HashMap<AccessorKey, MethodDescription>();
1879839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            mStore.adapterMethods.put(attribute, adapters);
1885a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        }
1895a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        List<? extends VariableElement> parameters = bindingMethod.getParameters();
1907d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        final int viewIndex = takesComponent ? 1 : 0;
1915a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        TypeMirror viewType = eraseType(processingEnv, parameters.get(viewIndex).asType());
1927d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        String view = getQualifiedName(viewType);
1935a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        TypeMirror parameterType = eraseType(processingEnv, parameters.get(viewIndex + 1).asType());
1945a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        String value = getQualifiedName(parameterType);
1957d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt
1967d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        AccessorKey key = new AccessorKey(view, value);
1977d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        if (adapters.containsKey(key)) {
1987d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt            throw new IllegalArgumentException("Already exists!");
1995a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        }
2007d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt
2017d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        adapters.put(key, new MethodDescription(bindingMethod, 1, takesComponent));
2027d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt    }
2037d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt
2045a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt    private static TypeMirror eraseType(ProcessingEnvironment processingEnv,
2055a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            TypeMirror typeMirror) {
2067d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        if (hasTypeVar(typeMirror)) {
2075a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            return processingEnv.getTypeUtils().erasure(typeMirror);
2085a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        } else {
2095a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            return typeMirror;
2104ae50e65ef0eefe6d5c356acbc1839f8eac68af5Dmitry Shmidt        }
2115a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt    }
2125a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt
2135a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt    private static ModelClass eraseType(ModelClass modelClass) {
2145a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        if (hasTypeVar(modelClass)) {
2155a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            return modelClass.erasure();
2165a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        } else {
2179839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            return modelClass;
2189839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        }
2199839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt    }
2209839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt
2219839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt    private static boolean hasTypeVar(TypeMirror typeMirror) {
2229839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        TypeKind kind = typeMirror.getKind();
2239839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        if (kind == TypeKind.TYPEVAR) {
2249839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            return true;
2255a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        } else if (kind == TypeKind.ARRAY) {
2265a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            return hasTypeVar(((ArrayType) typeMirror).getComponentType());
2277d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        } else if (kind == TypeKind.DECLARED) {
2285a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            DeclaredType declaredType = (DeclaredType) typeMirror;
2297d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt            List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
2305a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            if (typeArguments == null || typeArguments.isEmpty()) {
2315a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                return false;
2327d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt            }
2337d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt            for (TypeMirror arg : typeArguments) {
2345a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                if (hasTypeVar(arg)) {
2357d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt                    return true;
2367d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt                }
2375a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            }
2385a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            return false;
2397d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        } else {
2405a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            return false;
2415a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        }
2425a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt    }
2435a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt
2445a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt    private static boolean hasTypeVar(ModelClass type) {
2459839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        if (type.isTypeVar()) {
2469839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            return true;
2479839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        } else if (type.isArray()) {
2489839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            return hasTypeVar(type.getComponentType());
2499839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        } else {
2509839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            List<ModelClass> typeArguments = type.getTypeArguments();
2519839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            if (typeArguments == null) {
2529839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt                return false;
2539839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            }
2549839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            for (ModelClass arg : typeArguments) {
2559839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt                if (hasTypeVar(arg)) {
2569839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt                    return true;
2579839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt                }
2589839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            }
2599839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            return false;
2609839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        }
2619839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt    }
2629839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt
2639839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt    public void addBindingAdapter(ProcessingEnvironment processingEnv, String[] attributes,
2649839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            ExecutableElement bindingMethod, boolean takesComponent, boolean requireAll) {
2659839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        L.d("STORE add multi-value BindingAdapter %d %s", attributes.length, bindingMethod);
2669839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        MultiValueAdapterKey key = new MultiValueAdapterKey(processingEnv, bindingMethod,
2675a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                attributes, takesComponent, requireAll);
2685a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        MethodDescription methodDescription = new MethodDescription(bindingMethod,
2697d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt                attributes.length, takesComponent);
2705a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        mStore.multiValueAdapters.put(key, methodDescription);
2715a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt    }
2727d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt
2737d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt    private static String[] stripAttributes(String[] attributes) {
2747d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        String[] strippedAttributes = new String[attributes.length];
2757d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        for (int i = 0; i < attributes.length; i++) {
2767d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt            strippedAttributes[i] = stripNamespace(attributes[i]);
2777d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        }
2785a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        return strippedAttributes;
2797d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt    }
2807d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt
2817d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt    public void addUntaggableTypes(String[] typeNames, TypeElement declaredOn) {
2827d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        L.d("STORE addUntaggableTypes %s %s", Arrays.toString(typeNames), declaredOn);
2837d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        String declaredType = declaredOn.getQualifiedName().toString();
2847d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        for (String type : typeNames) {
2855a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            mStore.untaggableTypes.put(type, declaredType);
2865a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        }
2875a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt    }
2885a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt
2897d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt    private static String getQualifiedName(TypeMirror type) {
2907d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        if (type.getKind() == TypeKind.ARRAY) {
2917d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt            return getQualifiedName(((ArrayType) type).getComponentType()) + "[]";
2927d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        } else {
2937d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt            return type.toString();
2945a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        }
2957d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt    }
2967d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt
2977d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt    public void addConversionMethod(ExecutableElement conversionMethod) {
2987d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        L.d("STORE addConversionMethod %s", conversionMethod);
2997d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        List<? extends VariableElement> parameters = conversionMethod.getParameters();
3005a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        String fromType = getQualifiedName(parameters.get(0).asType());
3015a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        String toType = getQualifiedName(conversionMethod.getReturnType());
3025a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        MethodDescription methodDescription = new MethodDescription(conversionMethod, 1, false);
3037d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        HashMap<String, MethodDescription> convertTo = mStore.conversionMethods.get(fromType);
3045a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        if (convertTo == null) {
3055a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            convertTo = new HashMap<String, MethodDescription>();
3065a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            mStore.conversionMethods.put(fromType, convertTo);
3075a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        }
3084ae50e65ef0eefe6d5c356acbc1839f8eac68af5Dmitry Shmidt        convertTo.put(toType, methodDescription);
3095a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt    }
3104ae50e65ef0eefe6d5c356acbc1839f8eac68af5Dmitry Shmidt
3115a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt    public void clear(Set<String> classes) {
3125a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        ArrayList<AccessorKey> removedAccessorKeys = new ArrayList<AccessorKey>();
3135a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        for (HashMap<AccessorKey, MethodDescription> adapters : mStore.adapterMethods.values()) {
3145a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            for (AccessorKey key : adapters.keySet()) {
3154ae50e65ef0eefe6d5c356acbc1839f8eac68af5Dmitry Shmidt                MethodDescription description = adapters.get(key);
3165a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                if (classes.contains(description.type)) {
3174ae50e65ef0eefe6d5c356acbc1839f8eac68af5Dmitry Shmidt                    removedAccessorKeys.add(key);
3185a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                }
3195a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            }
3209839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            removeFromMap(adapters, removedAccessorKeys);
3219839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        }
3229839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt
3239839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        ArrayList<String> removedRenamed = new ArrayList<String>();
3249839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        for (HashMap<String, MethodDescription> renamed : mStore.renamedMethods.values()) {
3259839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            for (String key : renamed.keySet()) {
3269839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt                if (classes.contains(renamed.get(key).type)) {
3279839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt                    removedRenamed.add(key);
3289839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt                }
3299839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            }
3309839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            removeFromMap(renamed, removedRenamed);
3319839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        }
3329839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt
3339839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        ArrayList<String> removedConversions = new ArrayList<String>();
3349839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        for (HashMap<String, MethodDescription> convertTos : mStore.conversionMethods.values()) {
3355a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            for (String toType : convertTos.keySet()) {
3365a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                MethodDescription methodDescription = convertTos.get(toType);
3377d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt                if (classes.contains(methodDescription.type)) {
3385a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                    removedConversions.add(toType);
3395a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                }
3407d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt            }
3417d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt            removeFromMap(convertTos, removedConversions);
3427d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        }
3435a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt
3447d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        ArrayList<String> removedUntaggable = new ArrayList<String>();
3457d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        for (String typeName : mStore.untaggableTypes.keySet()) {
3467d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt            if (classes.contains(mStore.untaggableTypes.get(typeName))) {
3475a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                removedUntaggable.add(typeName);
3485a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            }
3495a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        }
3507d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        removeFromMap(mStore.untaggableTypes, removedUntaggable);
3517d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt    }
3525a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt
3537d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt    private static <K, V> void removeFromMap(Map<K, V> map, List<K> keys) {
3547d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        for (K key : keys) {
3555a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            map.remove(key);
3565a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        }
3577d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        keys.clear();
3585a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt    }
3594ae50e65ef0eefe6d5c356acbc1839f8eac68af5Dmitry Shmidt
3605a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt    public void write(String projectPackage, ProcessingEnvironment processingEnvironment)
3615a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            throws IOException {
3625a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        GenerationalClassUtil.writeIntermediateFile(processingEnvironment,
3635a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                projectPackage, projectPackage +
3645a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                        GenerationalClassUtil.ExtensionFilter.SETTER_STORE.getExtension(), mStore);
3655a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt    }
3665a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt
3679839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt    private static String stripNamespace(String attribute) {
3689839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        if (!attribute.startsWith("android:")) {
3699839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            int colon = attribute.indexOf(':');
3709839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            if (colon >= 0) {
3719839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt                attribute = attribute.substring(colon + 1);
3729839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            }
3739839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        }
3749839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        return attribute;
3759839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt    }
3769839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt
3779839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt    public List<MultiAttributeSetter> getMultiAttributeSetterCalls(String[] attributes,
3789839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            ModelClass viewType, ModelClass[] valueType) {
3799839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        attributes = stripAttributes(attributes);
3809839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        final ArrayList<MultiAttributeSetter> calls = new ArrayList<MultiAttributeSetter>();
3819839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        if (viewType != null && viewType.isGeneric()) {
3829839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            List<ModelClass> viewGenerics = viewType.getTypeArguments();
3839839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            for (int i = 0; i < valueType.length; i++) {
3849839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt                valueType[i] = eraseType(valueType[i], viewGenerics);
3859839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            }
3869839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            viewType = viewType.erasure();
3879839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        }
3889839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        ArrayList<MultiAttributeSetter> matching = getMatchingMultiAttributeSetters(attributes,
3899839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt                viewType, valueType);
3909839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        Collections.sort(matching, COMPARE_MULTI_ATTRIBUTE_SETTERS);
3919839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        while (!matching.isEmpty()) {
3929839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            MultiAttributeSetter bestMatch = matching.get(0);
3939839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            calls.add(bestMatch);
3949839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            removeConsumedAttributes(matching, bestMatch.attributes);
3959839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        }
3969839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt        return calls;
3979839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt    }
3989839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt
3999839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt    // Removes all MultiAttributeSetters that require any of the values in attributes
4009839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt    private static void removeConsumedAttributes(ArrayList<MultiAttributeSetter> matching,
4019839ecd75c832023d4d13fd2917a8c28261ff668Dmitry Shmidt            String[] attributes) {
4025a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        for (int i = matching.size() - 1; i >= 0; i--) {
4035a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            final MultiAttributeSetter setter = matching.get(i);
4045a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            boolean found = false;
4057d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt            for (String attribute : attributes) {
4065a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                if (isInArray(attribute, setter.attributes)) {
4077d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt                    found = true;
4085a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                    break;
4095a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                }
4107d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt            }
4117d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt            if (found) {
4125a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                matching.remove(i);
4137d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt            }
4147d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt        }
4155a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt    }
4165a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt
4177d17530e229db79208e99741071df97ea4faeec6Dmitry Shmidt    // Linear search through the String array for a specific value.
4185a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt    private static boolean isInArray(String str, String[] array) {
4195a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        for (String value : array) {
4205a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            if (value.equals(str)) {
4215a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt                return true;
4225a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt            }
4235a1480c7c46c4236d93bfd303dde32062bee04acDmitry Shmidt        }
424        return false;
425    }
426
427    private ArrayList<MultiAttributeSetter> getMatchingMultiAttributeSetters(String[] attributes,
428            ModelClass viewType, ModelClass[] valueType) {
429        final ArrayList<MultiAttributeSetter> setters = new ArrayList<MultiAttributeSetter>();
430        for (MultiValueAdapterKey adapter : mStore.multiValueAdapters.keySet()) {
431            if (adapter.requireAll && adapter.attributes.length > attributes.length) {
432                continue;
433            }
434            ModelClass viewClass = mClassAnalyzer.findClass(adapter.viewType, null);
435            if (viewClass.isGeneric()) {
436                viewClass = viewClass.erasure();
437            }
438            if (!viewClass.isAssignableFrom(viewType)) {
439                continue;
440            }
441            final MethodDescription method = mStore.multiValueAdapters.get(adapter);
442            final MultiAttributeSetter setter = createMultiAttributeSetter(method, attributes,
443                    valueType, adapter);
444            if (setter != null) {
445                setters.add(setter);
446            }
447        }
448        return setters;
449    }
450
451    private MultiAttributeSetter createMultiAttributeSetter(MethodDescription method,
452            String[] allAttributes, ModelClass[] attributeValues, MultiValueAdapterKey adapter) {
453        int matchingAttributes = 0;
454        String[] casts = new String[adapter.attributes.length];
455        MethodDescription[] conversions = new MethodDescription[adapter.attributes.length];
456        boolean[] supplied = new boolean[adapter.attributes.length];
457
458        for (int i = 0; i < allAttributes.length; i++) {
459            Integer index = adapter.attributeIndices.get(allAttributes[i]);
460            if (index != null) {
461                supplied[index] = true;
462                matchingAttributes++;
463                final String parameterTypeStr = adapter.parameterTypes[index];
464                final ModelClass parameterType = eraseType(
465                        mClassAnalyzer.findClass(parameterTypeStr, null));
466                final ModelClass attributeType = attributeValues[i];
467                if (!parameterType.isAssignableFrom(attributeType)) {
468                    if (ModelMethod.isBoxingConversion(parameterType, attributeType)) {
469                        // automatic boxing is ok
470                        continue;
471                    } else if (ModelMethod.isImplicitConversion(attributeType, parameterType)) {
472                        // implicit conversion is ok
473                        continue;
474                    }
475                    // Look for a converter
476                    conversions[index] = getConversionMethod(attributeType, parameterType, null);
477                    if (conversions[index] == null) {
478                        if (attributeType.isObject()) {
479                            // Cast is allowed also
480                            casts[index] = parameterTypeStr;
481                        } else {
482                            // Parameter type mismatch
483                            return null;
484                        }
485                    }
486                }
487            }
488        }
489
490        if ((adapter.requireAll && matchingAttributes != adapter.attributes.length) ||
491                matchingAttributes == 0) {
492            return null;
493        } else {
494            return new MultiAttributeSetter(adapter, supplied, method, conversions, casts);
495        }
496    }
497
498    public SetterCall getSetterCall(String attribute, ModelClass viewType,
499            ModelClass valueType, Map<String, String> imports) {
500        attribute = stripNamespace(attribute);
501        SetterCall setterCall = null;
502        MethodDescription conversionMethod = null;
503        if (viewType != null) {
504            viewType = viewType.erasure();
505            HashMap<AccessorKey, MethodDescription> adapters = mStore.adapterMethods.get(attribute);
506            ModelMethod bestSetterMethod = getBestSetter(viewType, valueType, attribute, imports);
507            ModelClass bestViewType = null;
508            ModelClass bestValueType = null;
509            if (bestSetterMethod != null) {
510                bestViewType = bestSetterMethod.getDeclaringClass();
511                bestValueType = bestSetterMethod.getParameterTypes()[0];
512                setterCall = new ModelMethodSetter(bestSetterMethod);
513            }
514
515            if (adapters != null) {
516                for (AccessorKey key : adapters.keySet()) {
517                    try {
518                        ModelClass adapterViewType = mClassAnalyzer
519                                .findClass(key.viewType, imports).erasure();
520                        if (adapterViewType != null && adapterViewType.isAssignableFrom(viewType)) {
521                            try {
522                                L.d("setter parameter type is %s", key.valueType);
523                                final ModelClass adapterValueType = eraseType(mClassAnalyzer
524                                        .findClass(key.valueType, imports));
525                                L.d("setter %s takes type %s, compared to %s",
526                                        adapters.get(key).method, adapterValueType.toJavaCode(),
527                                        valueType.toJavaCode());
528                                boolean isBetterView = bestViewType == null ||
529                                        bestValueType.isAssignableFrom(adapterValueType);
530                                if (isBetterParameter(valueType, adapterValueType, bestValueType,
531                                        isBetterView, imports)) {
532                                    bestViewType = adapterViewType;
533                                    bestValueType = adapterValueType;
534                                    MethodDescription adapter = adapters.get(key);
535                                    setterCall = new AdapterSetter(adapter, adapterValueType);
536                                }
537
538                            } catch (Exception e) {
539                                L.e(e, "Unknown class: %s", key.valueType);
540                            }
541                        }
542                    } catch (Exception e) {
543                        L.e(e, "Unknown class: %s", key.viewType);
544                    }
545                }
546            }
547
548            conversionMethod = getConversionMethod(valueType, bestValueType, imports);
549            if (valueType.isObject() && setterCall != null && bestValueType.isNullable()) {
550                setterCall.setCast(bestValueType);
551            }
552        }
553        if (setterCall == null) {
554            if (viewType != null && !viewType.isViewDataBinding()) {
555                return null; // no setter found!!
556            }
557            setterCall = new DummySetter(getDefaultSetter(attribute));
558        }
559        setterCall.setConverter(conversionMethod);
560        return setterCall;
561    }
562
563    public boolean isUntaggable(String viewType) {
564        return mStore.untaggableTypes.containsKey(viewType);
565    }
566
567    private ModelMethod getBestSetter(ModelClass viewType, ModelClass argumentType,
568            String attribute, Map<String, String> imports) {
569        if (viewType.isGeneric()) {
570            argumentType = eraseType(argumentType, viewType.getTypeArguments());
571            viewType = viewType.erasure();
572        }
573        List<String> setterCandidates = new ArrayList<String>();
574        HashMap<String, MethodDescription> renamed = mStore.renamedMethods.get(attribute);
575        if (renamed != null) {
576            for (String className : renamed.keySet()) {
577                try {
578                    ModelClass renamedViewType = mClassAnalyzer.findClass(className, imports);
579                    if (renamedViewType.erasure().isAssignableFrom(viewType)) {
580                        setterCandidates.add(renamed.get(className).method);
581                        break;
582                    }
583                } catch (Exception e) {
584                    //printMessage(Diagnostic.Kind.NOTE, "Unknown class: " + className);
585                }
586            }
587        }
588        setterCandidates.add(getDefaultSetter(attribute));
589        setterCandidates.add(trimAttributeNamespace(attribute));
590
591        ModelMethod bestMethod = null;
592        ModelClass bestParameterType = null;
593        List<ModelClass> args = new ArrayList<ModelClass>();
594        args.add(argumentType);
595        for (String name : setterCandidates) {
596            ModelMethod[] methods = viewType.getMethods(name, 1);
597
598            for (ModelMethod method : methods) {
599                ModelClass[] parameterTypes = method.getParameterTypes();
600                ModelClass param = parameterTypes[0];
601                if (method.isVoid() &&
602                        isBetterParameter(argumentType, param, bestParameterType, true, imports)) {
603                    bestParameterType = param;
604                    bestMethod = method;
605                }
606            }
607        }
608        return bestMethod;
609    }
610
611    private static ModelClass eraseType(ModelClass type, List<ModelClass> typeParameters) {
612        List<ModelClass> typeArguments = type.getTypeArguments();
613        if (typeArguments == null || typeParameters == null) {
614            return type;
615        }
616        for (ModelClass arg : typeArguments) {
617            if (typeParameters.contains(arg)) {
618                return type.erasure();
619            }
620        }
621        return type;
622    }
623
624    private static String trimAttributeNamespace(String attribute) {
625        final int colonIndex = attribute.indexOf(':');
626        return colonIndex == -1 ? attribute : attribute.substring(colonIndex + 1);
627    }
628
629    private static String getDefaultSetter(String attribute) {
630        return "set" + StringUtils.capitalize(trimAttributeNamespace(attribute));
631    }
632
633    private boolean isBetterParameter(ModelClass argument, ModelClass parameter,
634            ModelClass oldParameter, boolean isBetterViewTypeMatch, Map<String, String> imports) {
635        // Right view type. Check the value
636        if (!isBetterViewTypeMatch && oldParameter.equals(argument)) {
637            return false;
638        } else if (argument.equals(parameter)) {
639            // Exact match
640            return true;
641        } else if (!isBetterViewTypeMatch &&
642                ModelMethod.isBoxingConversion(oldParameter, argument)) {
643            return false;
644        } else if (ModelMethod.isBoxingConversion(parameter, argument)) {
645            // Boxing/unboxing is second best
646            return true;
647        } else {
648            int oldConversionLevel = ModelMethod.getImplicitConversionLevel(oldParameter);
649            if (ModelMethod.isImplicitConversion(argument, parameter)) {
650                // Better implicit conversion
651                int conversionLevel = ModelMethod.getImplicitConversionLevel(parameter);
652                return oldConversionLevel < 0 || conversionLevel < oldConversionLevel;
653            } else if (oldConversionLevel >= 0) {
654                return false;
655            } else if (parameter.isAssignableFrom(argument)) {
656                // Right type, see if it is better than the current best match.
657                if (oldParameter == null) {
658                    return true;
659                } else {
660                    return oldParameter.isAssignableFrom(parameter);
661                }
662            } else {
663                MethodDescription conversionMethod = getConversionMethod(argument, parameter,
664                        imports);
665                if (conversionMethod != null) {
666                    return true;
667                }
668                if (getConversionMethod(argument, oldParameter, imports) != null) {
669                    return false;
670                }
671                return argument.isObject() && !parameter.isPrimitive();
672            }
673        }
674    }
675
676    private MethodDescription getConversionMethod(ModelClass from, ModelClass to,
677            Map<String, String> imports) {
678        if (from != null && to != null) {
679            if (to.isObject()) {
680                return null;
681            }
682            for (String fromClassName : mStore.conversionMethods.keySet()) {
683                try {
684                    ModelClass convertFrom = mClassAnalyzer.findClass(fromClassName, imports);
685                    if (canUseForConversion(from, convertFrom)) {
686                        HashMap<String, MethodDescription> conversion =
687                                mStore.conversionMethods.get(fromClassName);
688                        for (String toClassName : conversion.keySet()) {
689                            try {
690                                ModelClass convertTo = mClassAnalyzer.findClass(toClassName,
691                                        imports);
692                                if (canUseForConversion(convertTo, to)) {
693                                    return conversion.get(toClassName);
694                                }
695                            } catch (Exception e) {
696                                L.d(e, "Unknown class: %s", toClassName);
697                            }
698                        }
699                    }
700                } catch (Exception e) {
701                    L.d(e, "Unknown class: %s", fromClassName);
702                }
703            }
704        }
705        return null;
706    }
707
708    private boolean canUseForConversion(ModelClass from, ModelClass to) {
709        return from.equals(to) || ModelMethod.isBoxingConversion(from, to) ||
710                to.isAssignableFrom(from);
711    }
712
713    private static void merge(IntermediateV1 store, Intermediate dumpStore) {
714        IntermediateV1 intermediateV1 = (IntermediateV1) dumpStore.upgrade();
715        merge(store.adapterMethods, intermediateV1.adapterMethods);
716        merge(store.renamedMethods, intermediateV1.renamedMethods);
717        merge(store.conversionMethods, intermediateV1.conversionMethods);
718        store.multiValueAdapters.putAll(intermediateV1.multiValueAdapters);
719        store.untaggableTypes.putAll(intermediateV1.untaggableTypes);
720    }
721
722    private static <K, V> void merge(HashMap<K, HashMap<V, MethodDescription>> first,
723            HashMap<K, HashMap<V, MethodDescription>> second) {
724        for (K key : second.keySet()) {
725            HashMap<V, MethodDescription> firstVals = first.get(key);
726            HashMap<V, MethodDescription> secondVals = second.get(key);
727            if (firstVals == null) {
728                first.put(key, secondVals);
729            } else {
730                for (V key2 : secondVals.keySet()) {
731                    if (!firstVals.containsKey(key2)) {
732                        firstVals.put(key2, secondVals.get(key2));
733                    }
734                }
735            }
736        }
737    }
738
739    private static String createAdapterCall(MethodDescription adapter, String bindingAdapterCall,
740            String componentExpression, String viewExpression, String... args) {
741        StringBuilder sb = new StringBuilder();
742
743        if (adapter.isStatic) {
744            sb.append(adapter.type);
745        } else {
746            sb.append(componentExpression).append('.').append(bindingAdapterCall);
747        }
748        sb.append('.').append(adapter.method).append('(');
749        if (adapter.componentClass != null) {
750            if (!"DataBindingComponent".equals(adapter.componentClass)) {
751                sb.append('(').append(adapter.componentClass).append(") ");
752            }
753            sb.append(componentExpression).append(", ");
754        }
755        sb.append(viewExpression);
756        for (String arg: args) {
757            sb.append(", ").append(arg);
758        }
759        sb.append(')');
760        return sb.toString();
761    }
762
763    private static class MultiValueAdapterKey implements Serializable {
764        private static final long serialVersionUID = 1;
765
766        public final String viewType;
767
768        public final String[] attributes;
769
770        public final String[] parameterTypes;
771
772        public final boolean requireAll;
773
774        public final TreeMap<String, Integer> attributeIndices = new TreeMap<String, Integer>();
775
776        public MultiValueAdapterKey(ProcessingEnvironment processingEnv,
777                ExecutableElement method, String[] attributes, boolean takesComponent,
778                boolean requireAll) {
779            this.attributes = stripAttributes(attributes);
780            this.requireAll = requireAll;
781            List<? extends VariableElement> parameters = method.getParameters();
782            final int argStart = 1 + (takesComponent ? 1 : 0);
783            this.viewType = getQualifiedName(eraseType(processingEnv,
784                    parameters.get(argStart - 1).asType()));
785            this.parameterTypes = new String[parameters.size() - argStart];
786            for (int i = 0; i < attributes.length; i++) {
787                TypeMirror typeMirror = eraseType(processingEnv,
788                        parameters.get(i + argStart).asType());
789                this.parameterTypes[i] = getQualifiedName(typeMirror);
790                attributeIndices.put(this.attributes[i], i);
791            }
792        }
793
794        @Override
795        public boolean equals(Object obj) {
796            if (!(obj instanceof MultiValueAdapterKey)) {
797                return false;
798            }
799            final MultiValueAdapterKey that = (MultiValueAdapterKey) obj;
800            if (!this.viewType.equals(that.viewType) ||
801                    this.attributes.length != that.attributes.length ||
802                    !this.attributeIndices.keySet().equals(that.attributeIndices.keySet())) {
803                return false;
804            }
805
806            for (int i = 0; i < this.attributes.length; i++) {
807                final int thatIndex = that.attributeIndices.get(this.attributes[i]);
808                final String thisParameter = parameterTypes[i];
809                final String thatParameter = that.parameterTypes[thatIndex];
810                if (!thisParameter.equals(thatParameter)) {
811                    return false;
812                }
813            }
814            return true;
815        }
816
817        @Override
818        public int hashCode() {
819            return mergedHashCode(viewType, attributeIndices.keySet());
820        }
821    }
822
823    private static int mergedHashCode(Object... objects) {
824        return Arrays.hashCode(objects);
825    }
826
827    private static class MethodDescription implements Serializable {
828
829        private static final long serialVersionUID = 1;
830
831        public final String type;
832
833        public final String method;
834
835        public final boolean requiresOldValue;
836
837        public final boolean isStatic;
838
839        public final String componentClass;
840
841        public MethodDescription(String type, String method) {
842            this.type = type;
843            this.method = method;
844            this.requiresOldValue = false;
845            this.isStatic = true;
846            this.componentClass = null;
847            L.d("BINARY created method desc 1 %s %s", type, method );
848        }
849
850        public MethodDescription(ExecutableElement method, int numAttributes,
851                boolean takesComponent) {
852            TypeElement enclosingClass = (TypeElement) method.getEnclosingElement();
853            this.type = enclosingClass.getQualifiedName().toString();
854            this.method = method.getSimpleName().toString();
855            final int argStart = 1 + (takesComponent ? 1 : 0);
856            this.requiresOldValue = method.getParameters().size() - argStart == numAttributes * 2;
857            this.isStatic = method.getModifiers().contains(Modifier.STATIC);
858            this.componentClass = takesComponent
859                    ? getQualifiedName(method.getParameters().get(0).asType())
860                    : null;
861
862            L.d("BINARY created method desc 2 %s %s, %s", type, this.method, method);
863        }
864
865        @Override
866        public boolean equals(Object obj) {
867            if (obj instanceof MethodDescription) {
868                MethodDescription that = (MethodDescription) obj;
869                return that.type.equals(this.type) && that.method.equals(this.method);
870            } else {
871                return false;
872            }
873        }
874
875        @Override
876        public int hashCode() {
877            return mergedHashCode(type, method);
878        }
879
880        @Override
881        public String toString() {
882            return type + "." + method + "()";
883        }
884    }
885
886    private static class AccessorKey implements Serializable {
887
888        private static final long serialVersionUID = 1;
889
890        public final String viewType;
891
892        public final String valueType;
893
894        public AccessorKey(String viewType, String valueType) {
895            this.viewType = viewType;
896            this.valueType = valueType;
897        }
898
899        @Override
900        public int hashCode() {
901            return mergedHashCode(viewType, valueType);
902        }
903
904        @Override
905        public boolean equals(Object obj) {
906            if (obj instanceof AccessorKey) {
907                AccessorKey that = (AccessorKey) obj;
908                return viewType.equals(that.valueType) && valueType.equals(that.valueType);
909            } else {
910                return false;
911            }
912        }
913
914        @Override
915        public String toString() {
916            return "AK(" + viewType + ", " + valueType + ")";
917        }
918    }
919
920    private interface Intermediate extends Serializable {
921        Intermediate upgrade();
922    }
923
924    private static class IntermediateV1 implements Serializable, Intermediate {
925        private static final long serialVersionUID = 1;
926        public final HashMap<String, HashMap<AccessorKey, MethodDescription>> adapterMethods =
927                new HashMap<String, HashMap<AccessorKey, MethodDescription>>();
928        public final HashMap<String, HashMap<String, MethodDescription>> renamedMethods =
929                new HashMap<String, HashMap<String, MethodDescription>>();
930        public final HashMap<String, HashMap<String, MethodDescription>> conversionMethods =
931                new HashMap<String, HashMap<String, MethodDescription>>();
932        public final HashMap<String, String> untaggableTypes = new HashMap<String, String>();
933        public final HashMap<MultiValueAdapterKey, MethodDescription> multiValueAdapters =
934                new HashMap<MultiValueAdapterKey, MethodDescription>();
935
936        public IntermediateV1() {
937        }
938
939        @Override
940        public Intermediate upgrade() {
941            return this;
942        }
943    }
944
945    public static class DummySetter extends SetterCall {
946        private String mMethodName;
947
948        public DummySetter(String methodName) {
949            mMethodName = methodName;
950        }
951
952        @Override
953        public String toJavaInternal(String componentExpression, String viewExpression,
954                String valueExpression) {
955            return viewExpression + "." + mMethodName + "(" + valueExpression + ")";
956        }
957
958        @Override
959        public String toJavaInternal(String componentExpression, String viewExpression,
960                String oldValue, String valueExpression) {
961            return viewExpression + "." + mMethodName + "(" + valueExpression + ")";
962        }
963
964        @Override
965        public int getMinApi() {
966            return 1;
967        }
968
969        @Override
970        public boolean requiresOldValue() {
971            return false;
972        }
973
974        @Override
975        public ModelClass[] getParameterTypes() {
976            return new ModelClass[] {
977                    ModelAnalyzer.getInstance().findClass(Object.class)
978            };
979        }
980
981        @Override
982        public String getBindingAdapterInstanceClass() {
983            return null;
984        }
985
986        @Override
987        public void setBindingAdapterCall(String method) {
988        }
989    }
990
991    public static class AdapterSetter extends SetterCall {
992        final MethodDescription mAdapter;
993        final ModelClass mParameterType;
994        String mBindingAdapterCall;
995
996        public AdapterSetter(MethodDescription adapter, ModelClass parameterType) {
997            mAdapter = adapter;
998            mParameterType = parameterType;
999        }
1000
1001        @Override
1002        public String toJavaInternal(String componentExpression, String viewExpression,
1003                String valueExpression) {
1004            return createAdapterCall(mAdapter, mBindingAdapterCall, componentExpression,
1005                    viewExpression, mCastString + valueExpression);
1006        }
1007
1008        @Override
1009        protected String toJavaInternal(String componentExpression, String viewExpression,
1010                String oldValue, String valueExpression) {
1011            return createAdapterCall(mAdapter, mBindingAdapterCall, componentExpression,
1012                    viewExpression, mCastString + oldValue, mCastString + valueExpression);
1013        }
1014
1015        @Override
1016        public int getMinApi() {
1017            return 1;
1018        }
1019
1020        @Override
1021        public boolean requiresOldValue() {
1022            return mAdapter.requiresOldValue;
1023        }
1024
1025        @Override
1026        public ModelClass[] getParameterTypes() {
1027            return new ModelClass[] { mParameterType };
1028        }
1029
1030        @Override
1031        public String getBindingAdapterInstanceClass() {
1032            return mAdapter.isStatic ? null : mAdapter.type;
1033        }
1034
1035        @Override
1036        public void setBindingAdapterCall(String method) {
1037            mBindingAdapterCall = method;
1038        }
1039    }
1040
1041    public static class ModelMethodSetter extends SetterCall {
1042        final ModelMethod mModelMethod;
1043
1044        public ModelMethodSetter(ModelMethod modelMethod) {
1045            mModelMethod = modelMethod;
1046        }
1047
1048        @Override
1049        public String toJavaInternal(String componentExpression, String viewExpression,
1050                String valueExpression) {
1051            return viewExpression + "." + mModelMethod.getName() + "(" + mCastString +
1052                    valueExpression + ")";
1053        }
1054
1055        @Override
1056        protected String toJavaInternal(String componentExpression, String viewExpression,
1057                String oldValue, String valueExpression) {
1058            return viewExpression + "." + mModelMethod.getName() + "(" +
1059                    mCastString + oldValue + ", " + mCastString + valueExpression + ")";
1060        }
1061
1062        @Override
1063        public int getMinApi() {
1064            return mModelMethod.getMinApi();
1065        }
1066
1067        @Override
1068        public boolean requiresOldValue() {
1069            return mModelMethod.getParameterTypes().length == 3;
1070        }
1071
1072        @Override
1073        public ModelClass[] getParameterTypes() {
1074            return new ModelClass[] { mModelMethod.getParameterTypes()[0] };
1075        }
1076
1077        @Override
1078        public String getBindingAdapterInstanceClass() {
1079            return null;
1080        }
1081
1082        @Override
1083        public void setBindingAdapterCall(String method) {
1084        }
1085    }
1086
1087    public interface BindingSetterCall {
1088        String toJava(String componentExpression, String viewExpression,
1089                String... valueExpressions);
1090
1091        int getMinApi();
1092
1093        boolean requiresOldValue();
1094
1095        ModelClass[] getParameterTypes();
1096
1097        String getBindingAdapterInstanceClass();
1098
1099        void setBindingAdapterCall(String method);
1100    }
1101
1102    public static abstract class SetterCall implements BindingSetterCall {
1103        private MethodDescription mConverter;
1104        protected String mCastString = "";
1105
1106        public SetterCall() {
1107        }
1108
1109        public void setConverter(MethodDescription converter) {
1110            mConverter = converter;
1111        }
1112
1113        protected abstract String toJavaInternal(String componentExpression, String viewExpression,
1114                String converted);
1115
1116        protected abstract String toJavaInternal(String componentExpression, String viewExpression,
1117                String oldValue, String converted);
1118
1119        @Override
1120        public final String toJava(String componentExpression, String viewExpression,
1121                String... valueExpression) {
1122            Preconditions.check(valueExpression.length == 2, "value expressions size must be 2");
1123            if (requiresOldValue()) {
1124                return toJavaInternal(componentExpression, viewExpression,
1125                        convertValue(valueExpression[0]), convertValue(valueExpression[1]));
1126            } else {
1127                return toJavaInternal(componentExpression, viewExpression,
1128                        convertValue(valueExpression[1]));
1129            }
1130        }
1131
1132        protected String convertValue(String valueExpression) {
1133            return mConverter == null ? valueExpression :
1134                    mConverter.type + "." + mConverter.method + "(" + valueExpression + ")";
1135        }
1136
1137        abstract public int getMinApi();
1138
1139        public void setCast(ModelClass castTo) {
1140            mCastString = "(" + castTo.toJavaCode() + ") ";
1141        }
1142    }
1143
1144    public static class MultiAttributeSetter implements BindingSetterCall {
1145        public final String[] attributes;
1146        private final MethodDescription mAdapter;
1147        private final MethodDescription[] mConverters;
1148        private final String[] mCasts;
1149        private final MultiValueAdapterKey mKey;
1150        private final boolean[] mSupplied;
1151        String mBindingAdapterCall;
1152
1153        public MultiAttributeSetter(MultiValueAdapterKey key, boolean[] supplied,
1154                MethodDescription adapter, MethodDescription[] converters, String[] casts) {
1155            Preconditions.check(converters != null &&
1156                    converters.length == key.attributes.length &&
1157                    casts != null && casts.length == key.attributes.length &&
1158                    supplied.length == key.attributes.length,
1159                    "invalid arguments to create multi attr setter");
1160            this.mAdapter = adapter;
1161            this.mConverters = converters;
1162            this.mCasts = casts;
1163            this.mKey = key;
1164            this.mSupplied = supplied;
1165            if (key.requireAll) {
1166                this.attributes = key.attributes;
1167            } else {
1168                int numSupplied = 0;
1169                for (int i = 0; i < mKey.attributes.length; i++) {
1170                    if (supplied[i]) {
1171                        numSupplied++;
1172                    }
1173                }
1174                if (numSupplied == key.attributes.length) {
1175                    this.attributes = key.attributes;
1176                } else {
1177                    this.attributes = new String[numSupplied];
1178                    int attrIndex = 0;
1179                    for (int i = 0; i < key.attributes.length; i++) {
1180                        if (supplied[i]) {
1181                            attributes[attrIndex++] = key.attributes[i];
1182                        }
1183                    }
1184                }
1185            }
1186        }
1187
1188        @Override
1189        public final String toJava(String componentExpression, String viewExpression,
1190                String[] valueExpressions) {
1191            Preconditions.check(valueExpressions.length == attributes.length * 2,
1192                    "MultiAttributeSetter needs %s items, received %s",
1193                    Arrays.toString(attributes), Arrays.toString(valueExpressions));
1194            final int numAttrs = mKey.attributes.length;
1195            String[] args = new String[numAttrs + (requiresOldValue() ? numAttrs : 0)];
1196
1197            final int startIndex = mAdapter.requiresOldValue ? 0 : numAttrs;
1198            int attrIndex = mAdapter.requiresOldValue ? 0 : attributes.length;
1199            final ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
1200            StringBuilder argBuilder = new StringBuilder();
1201            final int endIndex = numAttrs * 2;
1202            for (int i = startIndex; i < endIndex; i++) {
1203                argBuilder.setLength(0);
1204                if (!mSupplied[i % numAttrs]) {
1205                    final String paramType = mKey.parameterTypes[i % numAttrs];
1206                    final String defaultValue = modelAnalyzer.getDefaultValue(paramType);
1207                    argBuilder.append('(')
1208                            .append(paramType)
1209                            .append(')')
1210                            .append(defaultValue);
1211                } else {
1212                    if (mConverters[i % numAttrs] != null) {
1213                        final MethodDescription converter = mConverters[i % numAttrs];
1214                        argBuilder.append(converter.type)
1215                                .append('.')
1216                                .append(converter.method)
1217                                .append('(')
1218                                .append(valueExpressions[attrIndex])
1219                                .append(')');
1220                    } else {
1221                        if (mCasts[i % numAttrs] != null) {
1222                            argBuilder.append('(')
1223                                    .append(mCasts[i % numAttrs])
1224                                    .append(')');
1225                        }
1226                        argBuilder.append(valueExpressions[attrIndex]);
1227                    }
1228                    attrIndex++;
1229                }
1230                args[i - startIndex] = argBuilder.toString();
1231            }
1232            return createAdapterCall(mAdapter, mBindingAdapterCall, componentExpression,
1233                    viewExpression, args);
1234        }
1235
1236        @Override
1237        public int getMinApi() {
1238            return 1;
1239        }
1240
1241        @Override
1242        public boolean requiresOldValue() {
1243            return mAdapter.requiresOldValue;
1244        }
1245
1246        @Override
1247        public ModelClass[] getParameterTypes() {
1248            ModelClass[] parameters = new ModelClass[attributes.length];
1249            String[] paramTypeStrings = mKey.parameterTypes;
1250            ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
1251            int attrIndex = 0;
1252            for (int i = 0; i < mKey.attributes.length; i++) {
1253                if (mSupplied[i]) {
1254                    parameters[attrIndex++] = modelAnalyzer.findClass(paramTypeStrings[i], null);
1255                }
1256            }
1257            return parameters;
1258        }
1259
1260        @Override
1261        public String getBindingAdapterInstanceClass() {
1262            return mAdapter.isStatic ? null : mAdapter.type;
1263        }
1264
1265        @Override
1266        public void setBindingAdapterCall(String method) {
1267            mBindingAdapterCall = method;
1268        }
1269
1270        @Override
1271        public String toString() {
1272            return "MultiAttributeSetter{" +
1273                    "attributes=" + Arrays.toString(attributes) +
1274                    ", mAdapter=" + mAdapter +
1275                    ", mConverters=" + Arrays.toString(mConverters) +
1276                    ", mCasts=" + Arrays.toString(mCasts) +
1277                    ", mKey=" + mKey +
1278                    '}';
1279        }
1280    }
1281}
1282