All Downloads are FREE. Search and download functionalities are using the official Maven repository.

android.databinding.tool.store.SetterStore Maven / Gradle / Ivy

Go to download

The annotation processor for Data Binding. Generates binding classes for runtime.

The newest version!
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package android.databinding.tool.store;

import android.databinding.InverseBindingListener;
import android.databinding.tool.processing.ErrorMessages;
import android.databinding.tool.reflection.ModelAnalyzer;
import android.databinding.tool.reflection.ModelClass;
import android.databinding.tool.reflection.ModelMethod;
import android.databinding.tool.reflection.annotation.AnnotationTypeUtil;
import android.databinding.tool.util.GenerationalClassUtil;
import android.databinding.tool.util.L;
import android.databinding.tool.util.Preconditions;
import android.databinding.tool.util.StringUtils;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;

import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;

public class SetterStore {
    private static SetterStore sStore;

    private final IntermediateV3 mStore;
    private final ModelAnalyzer mClassAnalyzer;
    private HashMap> mInstanceAdapters;
    private final HashSet mInverseEventAttributes = new HashSet();

    private Comparator COMPARE_MULTI_ATTRIBUTE_SETTERS =
            new Comparator() {
                @Override
                public int compare(MultiAttributeSetter o1, MultiAttributeSetter o2) {
                    if (o1.attributes.length != o2.attributes.length) {
                        return o2.attributes.length - o1.attributes.length;
                    }
                    if (o1.mKey.attributeIndices.size() != o2.mKey.attributeIndices.size()) {
                        return o2.mKey.attributeIndices.size() - o1.mKey.attributeIndices.size();
                    }
                    ModelClass view1 = mClassAnalyzer.findClass(o1.mKey.viewType, null).erasure();
                    ModelClass view2 = mClassAnalyzer.findClass(o2.mKey.viewType, null).erasure();
                    if (!view1.equals(view2)) {
                        if (view1.isAssignableFrom(view2)) {
                            return 1;
                        } else {
                            return -1;
                        }
                    }
                    if (!o1.mKey.attributeIndices.keySet()
                            .equals(o2.mKey.attributeIndices.keySet())) {
                        // order by attribute name
                        Iterator o1Keys = o1.mKey.attributeIndices.keySet().iterator();
                        Iterator o2Keys = o2.mKey.attributeIndices.keySet().iterator();
                        while (o1Keys.hasNext() && o2Keys.hasNext()) {
                            String key1 = o1Keys.next();
                            String key2 = o2Keys.next();
                            int compare = key1.compareTo(key2);
                            if (compare != 0) {
                                return compare;
                            }
                        }
                        Preconditions.check(false,
                                "The sets don't match! That means the keys shouldn't match also");
                    }
                    // Same view type. Same attributes
                    for (String attribute : o1.mKey.attributeIndices.keySet()) {
                        final int index1 = o1.mKey.attributeIndices.get(attribute);
                        final int index2 = o2.mKey.attributeIndices.get(attribute);
                        ModelClass type1 = mClassAnalyzer
                                .findClass(o1.mKey.parameterTypes[index1], null);
                        ModelClass type2 = mClassAnalyzer
                                .findClass(o2.mKey.parameterTypes[index2], null);
                        if (type1.equals(type2)) {
                            continue;
                        }
                        if (o1.mCasts[index1] != null) {
                            if (o2.mCasts[index2] == null) {
                                return 1; // o2 is better
                            } else {
                                continue; // both are casts
                            }
                        } else if (o2.mCasts[index2] != null) {
                            return -1; // o1 is better
                        }
                        if (o1.mConverters[index1] != null) {
                            if (o2.mConverters[index2] == null) {
                                return 1; // o2 is better
                            } else {
                                continue; // both are conversions
                            }
                        } else if (o2.mConverters[index2] != null) {
                            return -1; // o1 is better
                        }

                        if (type1.isPrimitive()) {
                            if (type2.isPrimitive()) {
                                int type1ConversionLevel = ModelMethod
                                        .getImplicitConversionLevel(type1);
                                int type2ConversionLevel = ModelMethod
                                        .getImplicitConversionLevel(type2);
                                return type2ConversionLevel - type1ConversionLevel;
                            } else {
                                // type1 is primitive and has higher priority
                                return -1;
                            }
                        } else if (type2.isPrimitive()) {
                            return 1;
                        }
                        if (type1.isAssignableFrom(type2)) {
                            return 1;
                        } else if (type2.isAssignableFrom(type1)) {
                            return -1;
                        }
                    }
                    // hmmm... same view type, same attributes, same parameter types... ?
                    return 0;
                }
            };

    private SetterStore(ModelAnalyzer modelAnalyzer, IntermediateV3 store) {
        mClassAnalyzer = modelAnalyzer;
        mStore = store;
        for (HashMap adapter : mStore.inverseAdapters.values()) {
            for (InverseDescription inverseDescription : adapter.values()) {
                mInverseEventAttributes.add(inverseDescription.event);
            }
        }
        for (HashMap method : mStore.inverseMethods.values()) {
            for (InverseDescription inverseDescription : method.values()) {
                mInverseEventAttributes.add(inverseDescription.event);
            }
        }
    }

    public static SetterStore get(ModelAnalyzer modelAnalyzer) {
        if (sStore == null) {
            sStore = load(modelAnalyzer);
        }
        return sStore;
    }

    private static SetterStore load(ModelAnalyzer modelAnalyzer) {
        IntermediateV3 store = new IntermediateV3();
        List previousStores = GenerationalClassUtil
                .loadObjects(GenerationalClassUtil.ExtensionFilter.SETTER_STORE);
        for (Intermediate intermediate : previousStores) {
            merge(store, intermediate);
        }
        return new SetterStore(modelAnalyzer, store);
    }

    public void addRenamedMethod(String attribute, String declaringClass, String method,
            TypeElement declaredOn) {
        attribute = stripNamespace(attribute);
        HashMap renamed = mStore.renamedMethods.get(attribute);
        if (renamed == null) {
            renamed = new HashMap();
            mStore.renamedMethods.put(attribute, renamed);
        }
        MethodDescription methodDescription = new MethodDescription(
                declaredOn.getQualifiedName().toString(), method);
        L.d("STORE addmethod desc %s", methodDescription);
        renamed.put(declaringClass, methodDescription);
    }

    public void addInverseBindingMethod(String attribute, String event, String declaringClass,
            String method, TypeElement declaredOn) {
        attribute = stripNamespace(attribute);
        event = stripNamespace(event);
        HashMap inverseMethods = mStore.inverseMethods.get(attribute);
        if (inverseMethods == null) {
            inverseMethods = new HashMap();
            mStore.inverseMethods.put(attribute, inverseMethods);
        }
        InverseDescription methodDescription = new InverseDescription(
                declaredOn.getQualifiedName().toString(), method, event);
        L.d("STORE addInverseMethod desc %s", methodDescription);
        inverseMethods.put(declaringClass, methodDescription);
    }

    public void addInverseMethod(ProcessingEnvironment processingEnvironment,
            ExecutableElement method, ExecutableElement inverse) {
        InverseMethodDescription from = new InverseMethodDescription(processingEnvironment, method);
        InverseMethodDescription to = new InverseMethodDescription(processingEnvironment, inverse);

        String storedToName = mStore.twoWayMethods.get(from);
        if (storedToName != null && !to.method.equals(storedToName)) {
            throw new IllegalArgumentException(String.format(
                    "InverseMethod from %s to %s does not match expected method '%s'",
                    method, inverse, storedToName));
        }
        String storedFromName = mStore.twoWayMethods.get(to);
        if (storedFromName != null && !from.method.equals(storedFromName)) {
            throw new IllegalArgumentException(String.format(
                    "InverseMethod from %s to %s does not match expected method '%s'",
                    inverse, method, storedFromName));
        }
        mStore.twoWayMethods.put(from, to.method);
        mStore.twoWayMethods.put(to, from.method);
    }

    public void addBindingAdapter(ProcessingEnvironment processingEnv, String attribute,
            ExecutableElement bindingMethod, boolean takesComponent) {
        attribute = stripNamespace(attribute);
        L.d("STORE addBindingAdapter %s %s", attribute, bindingMethod);
        HashMap adapters = mStore.adapterMethods.get(attribute);

        if (adapters == null) {
            adapters = new HashMap();
            mStore.adapterMethods.put(attribute, adapters);
        }
        List parameters = bindingMethod.getParameters();
        final int viewIndex = takesComponent ? 1 : 0;
        TypeMirror viewType = eraseType(processingEnv, parameters.get(viewIndex).asType());
        String view = getQualifiedName(viewType);
        TypeMirror parameterType = eraseType(processingEnv, parameters.get(viewIndex + 1).asType());
        String value = getQualifiedName(parameterType);

        AccessorKey key = new AccessorKey(view, value);
        if (adapters.containsKey(key)) {
            throw new IllegalArgumentException("Already exists!");
        }

        adapters.put(key, new MethodDescription(bindingMethod, 1, takesComponent));

        if (mClassAnalyzer.findClass(value, null).isObservableField()) {
            ExecutableType executableType = (ExecutableType) bindingMethod.asType();
            L.w(bindingMethod, ErrorMessages.OBSERVABLE_FIELD_USE,
                    AnnotationTypeUtil.getInstance().toJava(bindingMethod, executableType));
        }
    }

    public void addInverseAdapter(ProcessingEnvironment processingEnv, String attribute,
            String event, ExecutableElement bindingMethod, boolean takesComponent) {
        attribute = stripNamespace(attribute);
        event = stripNamespace(event);
        L.d("STORE addInverseAdapter %s %s", attribute, bindingMethod);
        HashMap adapters = mStore.inverseAdapters.get(attribute);

        if (adapters == null) {
            adapters = new HashMap();
            mStore.inverseAdapters.put(attribute, adapters);
        }
        List parameters = bindingMethod.getParameters();
        final int viewIndex = takesComponent ? 1 : 0;
        TypeMirror viewType = eraseType(processingEnv, parameters.get(viewIndex).asType());
        String view = getQualifiedName(viewType);
        TypeMirror returnType = eraseType(processingEnv, bindingMethod.getReturnType());
        String value = getQualifiedName(returnType);

        AccessorKey key = new AccessorKey(view, value);
        if (adapters.containsKey(key)) {
            throw new IllegalArgumentException("Already exists!");
        }

        adapters.put(key, new InverseDescription(bindingMethod, event, takesComponent));
    }

    private static TypeMirror eraseType(ProcessingEnvironment processingEnv,
            TypeMirror typeMirror) {
        if (hasTypeVar(typeMirror)) {
            return processingEnv.getTypeUtils().erasure(typeMirror);
        } else {
            return typeMirror;
        }
    }

    private static ModelClass eraseType(ModelClass modelClass) {
        if (hasTypeVar(modelClass)) {
            return modelClass.erasure();
        } else {
            return modelClass;
        }
    }

    private static boolean hasTypeVar(TypeMirror typeMirror) {
        TypeKind kind = typeMirror.getKind();
        if (kind == TypeKind.TYPEVAR) {
            return true;
        } else if (kind == TypeKind.ARRAY) {
            return hasTypeVar(((ArrayType) typeMirror).getComponentType());
        } else if (kind == TypeKind.DECLARED) {
            DeclaredType declaredType = (DeclaredType) typeMirror;
            List typeArguments = declaredType.getTypeArguments();
            if (typeArguments == null || typeArguments.isEmpty()) {
                return false;
            }
            for (TypeMirror arg : typeArguments) {
                if (hasTypeVar(arg)) {
                    return true;
                }
            }
            return false;
        } else {
            return false;
        }
    }

    private static boolean hasTypeVar(ModelClass type) {
        if (type.isTypeVar()) {
            return true;
        } else if (type.isArray()) {
            return hasTypeVar(type.getComponentType());
        } else {
            List typeArguments = type.getTypeArguments();
            if (typeArguments == null) {
                return false;
            }
            for (ModelClass arg : typeArguments) {
                if (hasTypeVar(arg)) {
                    return true;
                }
            }
            return false;
        }
    }

    public void addBindingAdapter(ProcessingEnvironment processingEnv, String[] attributes,
            ExecutableElement bindingMethod, boolean takesComponent, boolean requireAll) {
        L.d("STORE add multi-value BindingAdapter %d %s", attributes.length, bindingMethod);
        MultiValueAdapterKey key = new MultiValueAdapterKey(processingEnv, bindingMethod,
                attributes, takesComponent, requireAll);
        testRepeatedAttributes(key, bindingMethod);
        MethodDescription methodDescription = new MethodDescription(bindingMethod,
                attributes.length, takesComponent);
        mStore.multiValueAdapters.put(key, methodDescription);

        final long numObservableFields = Arrays.stream(key.parameterTypes)
                .map(type -> mClassAnalyzer.findClass(type, null))
                .filter(type -> type.isObservableField())
                .count();
        if (numObservableFields > 0) {
            ExecutableType executableType = (ExecutableType) bindingMethod.asType();
            L.w(bindingMethod, ErrorMessages.OBSERVABLE_FIELD_USE,
                    AnnotationTypeUtil.getInstance().toJava(bindingMethod, executableType));
        }
    }

    private static void testRepeatedAttributes(MultiValueAdapterKey key, ExecutableElement method) {
        if (key.attributes.length != key.attributeIndices.size()) {
            HashSet names = new HashSet<>();
            for (String attr : key.attributes) {
                if (names.contains(attr)) {
                    L.e(method, "Attribute \"" + attr + "\" is supplied multiple times in " +
                            "BindingAdapter " + method.toString());
                }
                names.add(attr);
            }
        }
    }

    private static String[] stripAttributes(String[] attributes) {
        String[] strippedAttributes = new String[attributes.length];
        for (int i = 0; i < attributes.length; i++) {
            if (attributes[i] != null) {
                strippedAttributes[i] = stripNamespace(attributes[i]);
            }
        }
        return strippedAttributes;
    }

    public void addUntaggableTypes(String[] typeNames, TypeElement declaredOn) {
        L.d("STORE addUntaggableTypes %s %s", Arrays.toString(typeNames), declaredOn);
        String declaredType = declaredOn.getQualifiedName().toString();
        for (String type : typeNames) {
            mStore.untaggableTypes.put(type, declaredType);
        }
    }

    private static String getQualifiedName(TypeMirror type) {
        final TypeKind kind = type.getKind();
        if (kind == TypeKind.ARRAY) {
            return getQualifiedName(((ArrayType) type).getComponentType()) + "[]";
        } else if (kind == TypeKind.DECLARED && isIncompleteType(type)) {
            DeclaredType declaredType = (DeclaredType) type;
            return declaredType.asElement().toString();
        } else {
            return ((AnnotationTypeUtil)AnnotationTypeUtil.getInstance()).toJava(type);
        }
    }

    private static boolean isIncompleteType(TypeMirror type) {
        final TypeKind kind = type.getKind();
        if (kind == TypeKind.TYPEVAR || kind == TypeKind.WILDCARD) {
            return true;
        } else if (kind == TypeKind.DECLARED) {
            DeclaredType declaredType = (DeclaredType) type;
            List typeArgs = declaredType.getTypeArguments();
            if (typeArgs == null) {
                return false;
            }
            for (TypeMirror arg : typeArgs) {
                if (isIncompleteType(arg)) {
                    return true;
                }
            }
        }
        return false;
    }

    public void addConversionMethod(ExecutableElement conversionMethod) {
        L.d("STORE addConversionMethod %s", conversionMethod);
        List parameters = conversionMethod.getParameters();
        String fromType = getQualifiedName(parameters.get(0).asType());
        String toType = getQualifiedName(conversionMethod.getReturnType());
        MethodDescription methodDescription = new MethodDescription(conversionMethod, 1, false);
        HashMap convertTo = mStore.conversionMethods.get(fromType);
        if (convertTo == null) {
            convertTo = new HashMap();
            mStore.conversionMethods.put(fromType, convertTo);
        }
        convertTo.put(toType, methodDescription);
    }

    public void clear(Set classes) {
        ArrayList removedAccessorKeys = new ArrayList();
        for (HashMap adapters : mStore.adapterMethods.values()) {
            for (AccessorKey key : adapters.keySet()) {
                MethodDescription description = adapters.get(key);
                if (classes.contains(description.type)) {
                    removedAccessorKeys.add(key);
                }
            }
            removeFromMap(adapters, removedAccessorKeys);
        }

        ArrayList removedRenamed = new ArrayList();
        for (HashMap renamed : mStore.renamedMethods.values()) {
            for (String key : renamed.keySet()) {
                if (classes.contains(renamed.get(key).type)) {
                    removedRenamed.add(key);
                }
            }
            removeFromMap(renamed, removedRenamed);
        }

        ArrayList removedConversions = new ArrayList();
        for (HashMap convertTos : mStore.conversionMethods.values()) {
            for (String toType : convertTos.keySet()) {
                MethodDescription methodDescription = convertTos.get(toType);
                if (classes.contains(methodDescription.type)) {
                    removedConversions.add(toType);
                }
            }
            removeFromMap(convertTos, removedConversions);
        }

        ArrayList removedUntaggable = new ArrayList();
        for (String typeName : mStore.untaggableTypes.keySet()) {
            if (classes.contains(mStore.untaggableTypes.get(typeName))) {
                removedUntaggable.add(typeName);
            }
        }
        removeFromMap(mStore.untaggableTypes, removedUntaggable);
    }

    private static  void removeFromMap(Map map, List keys) {
        for (K key : keys) {
            map.remove(key);
        }
        keys.clear();
    }

    public void write(String projectPackage, ProcessingEnvironment processingEnvironment)
            throws IOException {
        GenerationalClassUtil.writeIntermediateFile(projectPackage, projectPackage +
                        GenerationalClassUtil.ExtensionFilter.SETTER_STORE.getExtension(), mStore);
    }

    private static String stripNamespace(String attribute) {
        if (!attribute.startsWith("android:")) {
            int colon = attribute.indexOf(':');
            if (colon >= 0) {
                attribute = attribute.substring(colon + 1);
            }
        }
        return attribute;
    }

    public boolean isTwoWayEventAttribute(String attribute) {
        attribute = stripNamespace(attribute);
        return mInverseEventAttributes.contains(attribute);
    }
    public List getMultiAttributeSetterCalls(String[] attributes,
            ModelClass viewType, ModelClass[] valueType) {
        attributes = stripAttributes(attributes);
        final ArrayList calls = new ArrayList();
        if (viewType != null && viewType.isGeneric()) {
            List viewGenerics = viewType.getTypeArguments();
            for (int i = 0; i < valueType.length; i++) {
                valueType[i] = eraseType(valueType[i], viewGenerics);
            }
            viewType = viewType.erasure();
        }
        ArrayList matching = getMatchingMultiAttributeSetters(attributes,
                viewType, valueType);
        Collections.sort(matching, COMPARE_MULTI_ATTRIBUTE_SETTERS);
        while (!matching.isEmpty()) {
            MultiAttributeSetter bestMatch = matching.get(0);
            calls.add(bestMatch);
            removeConsumedAttributes(matching, bestMatch.attributes);
        }
        return calls;
    }

    private static String simpleName(String className) {
        int dotIndex = className.lastIndexOf('.');
        if (dotIndex < 0) {
            return className;
        } else {
            return className.substring(dotIndex + 1);
        }
    }

    public Map> getComponentBindingAdapters() {
        ensureInstanceAdapters();
        return mInstanceAdapters;
    }

    private String getBindingAdapterCall(String className) {
        ensureInstanceAdapters();
        final String simpleName = simpleName(className);
        List adapters = mInstanceAdapters.get(simpleName);
        if (adapters.size() == 1) {
            return "get" + simpleName + "()";
        } else {
            int index = adapters.indexOf(className) + 1;
            return "get" + simpleName + index + "()";
        }
    }

    private void ensureInstanceAdapters() {
        if (mInstanceAdapters == null) {
            HashSet adapters = new HashSet();
            for (HashMap methods : mStore.adapterMethods.values()) {
                for (MethodDescription method : methods.values()) {
                    if (!method.isStatic) {
                        adapters.add(method.type);
                    }
                }
            }
            for (MethodDescription method : mStore.multiValueAdapters.values()) {
                if (!method.isStatic) {
                    adapters.add(method.type);
                }
            }
            for (Map methods : mStore.inverseAdapters.values()) {
                for (InverseDescription method : methods.values()) {
                    if (!method.isStatic) {
                        adapters.add(method.type);
                    }
                }
            }
            mInstanceAdapters = new HashMap>();
            for (String adapter : adapters) {
                final String simpleName = simpleName(adapter);
                List list = mInstanceAdapters.get(simpleName);
                if (list == null) {
                    list = new ArrayList();
                    mInstanceAdapters.put(simpleName, list);
                }
                list.add(adapter);
            }
            for (List list : mInstanceAdapters.values()) {
                if (list.size() > 1) {
                    Collections.sort(list);
                }
            }
        }
    }

    // Removes all MultiAttributeSetters that require any of the values in attributes
    private static void removeConsumedAttributes(ArrayList matching,
            String[] attributes) {
        for (int i = matching.size() - 1; i >= 0; i--) {
            final MultiAttributeSetter setter = matching.get(i);
            boolean found = false;
            for (String attribute : attributes) {
                if (isInArray(attribute, setter.attributes)) {
                    found = true;
                    break;
                }
            }
            if (found) {
                matching.remove(i);
            }
        }
    }

    // Linear search through the String array for a specific value.
    private static boolean isInArray(String str, String[] array) {
        for (String value : array) {
            if (value.equals(str)) {
                return true;
            }
        }
        return false;
    }

    private ArrayList getMatchingMultiAttributeSetters(String[] attributes,
            ModelClass viewType, ModelClass[] valueType) {
        final ArrayList setters = new ArrayList();
        for (MultiValueAdapterKey adapter : mStore.multiValueAdapters.keySet()) {
            if (adapter.requireAll && adapter.attributes.length > attributes.length) {
                continue;
            }
            ModelClass viewClass = mClassAnalyzer.findClass(adapter.viewType, null);
            if (viewClass.isGeneric()) {
                viewClass = viewClass.erasure();
            }
            if (!viewClass.isAssignableFrom(viewType)) {
                continue;
            }
            final MethodDescription method = mStore.multiValueAdapters.get(adapter);
            final MultiAttributeSetter setter = createMultiAttributeSetter(method, attributes,
                    valueType, adapter);
            if (setter != null) {
                setters.add(setter);
            }
        }
        return setters;
    }

    private MultiAttributeSetter createMultiAttributeSetter(MethodDescription method,
            String[] allAttributes, ModelClass[] attributeValues, MultiValueAdapterKey adapter) {
        int matchingAttributes = 0;
        String[] casts = new String[adapter.attributes.length];
        MethodDescription[] conversions = new MethodDescription[adapter.attributes.length];
        boolean[] supplied = new boolean[adapter.attributes.length];

        for (int i = 0; i < allAttributes.length; i++) {
            Integer index = adapter.attributeIndices.get(allAttributes[i]);
            if (index != null) {
                supplied[index] = true;
                matchingAttributes++;
                final String parameterTypeStr = adapter.parameterTypes[index];
                final ModelClass parameterType = eraseType(
                        mClassAnalyzer.findClass(parameterTypeStr, null));
                final ModelClass attributeType = attributeValues[i];
                if (!parameterType.isAssignableFrom(attributeType)) {
                    if (ModelMethod.isBoxingConversion(parameterType, attributeType)) {
                        // automatic boxing is ok
                        continue;
                    } else if (ModelMethod.isImplicitConversion(attributeType, parameterType)) {
                        // implicit conversion is ok
                        continue;
                    }
                    // Look for a converter
                    conversions[index] = getConversionMethod(attributeType, parameterType, null);
                    if (conversions[index] == null) {
                        if (attributeType.isObject()) {
                            // Cast is allowed also
                            casts[index] = parameterTypeStr;
                        } else {
                            // Parameter type mismatch
                            return null;
                        }
                    }
                }
            }
        }

        if ((adapter.requireAll && matchingAttributes != adapter.attributes.length) ||
                matchingAttributes == 0) {
            return null;
        } else {
            return new MultiAttributeSetter(adapter, supplied, method, conversions, casts);
        }
    }

    public SetterCall getSetterCall(String attribute, ModelClass viewType,
            ModelClass valueType, Map imports) {
        attribute = stripNamespace(attribute);
        SetterCall setterCall = null;
        MethodDescription conversionMethod = null;
        if (viewType != null) {
            viewType = viewType.erasure();
            HashMap adapters = mStore.adapterMethods.get(attribute);
            ModelMethod bestSetterMethod = getBestSetter(viewType, valueType, attribute, imports);
            ModelClass bestViewType = null;
            ModelClass bestValueType = null;
            if (bestSetterMethod != null) {
                bestViewType = bestSetterMethod.getDeclaringClass();
                bestValueType = bestSetterMethod.getParameterTypes()[0];
                setterCall = new ModelMethodSetter(bestSetterMethod);
            }

            if (adapters != null) {
                for (AccessorKey key : adapters.keySet()) {
                    try {
                        ModelClass adapterViewType = mClassAnalyzer
                                .findClass(key.viewType, imports).erasure();
                        if (adapterViewType != null && adapterViewType.isAssignableFrom(viewType)) {
                            try {
                                L.d("setter parameter type is %s", key.valueType);
                                final ModelClass adapterValueType = eraseType(mClassAnalyzer
                                        .findClass(key.valueType, imports));
                                L.d("setter %s takes type %s, compared to %s",
                                        adapters.get(key).method, adapterValueType.toJavaCode(),
                                        valueType.toJavaCode());
                                boolean isBetterView = bestViewType == null ||
                                        bestViewType.isAssignableFrom(adapterViewType);
                                if (isBetterParameter(valueType, adapterValueType, bestValueType,
                                        isBetterView, imports)) {
                                    bestViewType = adapterViewType;
                                    bestValueType = adapterValueType;
                                    MethodDescription adapter = adapters.get(key);
                                    setterCall = new AdapterSetter(adapter, adapterValueType);
                                }

                            } catch (Exception e) {
                                L.e(e, "Unknown class: %s", key.valueType);
                            }
                        }
                    } catch (Exception e) {
                        L.e(e, "Unknown class: %s", key.viewType);
                    }
                }
            }

            conversionMethod = getConversionMethod(valueType, bestValueType, imports);
            if (valueType.isObject() && setterCall != null && bestValueType.isNullable()) {
                setterCall.setCast(bestValueType);
            }
        }
        if (setterCall == null) {
            if (viewType != null && !viewType.isViewDataBinding()) {
                return null; // no setter found!!
            }
            setterCall = new DummySetter(getDefaultSetter(attribute));
        }
        setterCall.setConverter(conversionMethod);
        return setterCall;
    }

    public BindingGetterCall getGetterCall(String attribute, ModelClass viewType,
            ModelClass valueType, Map imports) {
        if (viewType == null) {
            return null;
        } else if (viewType.isViewDataBinding()) {
            return new ViewDataBindingGetterCall(viewType, attribute);
        }

        attribute = stripNamespace(attribute);
        viewType = viewType.erasure();

        InverseMethod bestMethod = getBestGetter(viewType, valueType, attribute, imports);
        HashMap adapters = mStore.inverseAdapters.get(attribute);
        if (adapters != null) {
            for (AccessorKey key : adapters.keySet()) {
                try {
                    ModelClass adapterViewType = mClassAnalyzer
                            .findClass(key.viewType, imports).erasure();
                    if (adapterViewType != null && adapterViewType.isAssignableFrom(viewType)) {
                        try {
                            L.d("getter return type is %s", key.valueType);
                            final ModelClass adapterValueType = eraseType(mClassAnalyzer
                                    .findClass(key.valueType, imports));
                            L.d("getter %s returns type %s, compared to %s",
                                    adapters.get(key).method, adapterValueType.toJavaCode(),
                                    valueType);
                            boolean isBetterView = bestMethod.viewType == null ||
                                    bestMethod.viewType.isAssignableFrom(adapterViewType);
                            if (valueType == null ||
                                    isBetterParameter(adapterValueType, valueType,
                                            bestMethod.returnType, isBetterView, imports)) {
                                bestMethod.viewType = adapterViewType;
                                bestMethod.returnType = adapterValueType;
                                InverseDescription inverseDescription = adapters.get(key);
                                ModelClass listenerType = ModelAnalyzer.getInstance().findClass(
                                        InverseBindingListener.class);
                                BindingSetterCall eventCall = getSetterCall(
                                        inverseDescription.event, viewType, listenerType, imports);
                                if (eventCall == null) {
                                    List setters =
                                            getMultiAttributeSetterCalls(
                                                    new String[]{inverseDescription.event},
                                                    viewType, new ModelClass[] {listenerType});
                                    if (setters.size() != 1) {
                                        L.e("Could not find event '%s' on View type '%s'",
                                                inverseDescription.event,
                                                viewType.getCanonicalName());
                                    } else {
                                        bestMethod.call = new AdapterGetter(inverseDescription,
                                                setters.get(0), key.valueType);
                                    }
                                } else {
                                    bestMethod.call = new AdapterGetter(inverseDescription,
                                            eventCall, key.valueType);
                                }
                            }

                        } catch (Exception e) {
                            L.e(e, "Unknown class: %s", key.valueType);
                        }
                    }
                } catch (Exception e) {
                    L.e(e, "Unknown class: %s", key.viewType);
                }
            }
        }

        return bestMethod.call;
    }

    public String getInverseMethod(ModelMethod method) {
        InverseMethodDescription description = new InverseMethodDescription(method);
        return mStore.twoWayMethods.get(description);
    }

    public boolean isUntaggable(String viewType) {
        return mStore.untaggableTypes.containsKey(viewType);
    }

    private ModelMethod getBestSetter(ModelClass viewType, ModelClass argumentType,
            String attribute, Map imports) {
        if (viewType.isGeneric()) {
            argumentType = eraseType(argumentType, viewType.getTypeArguments());
            viewType = viewType.erasure();
        }
        List setterCandidates = new ArrayList();
        HashMap renamed = mStore.renamedMethods.get(attribute);
        if (renamed != null) {
            for (String className : renamed.keySet()) {
                try {
                    ModelClass renamedViewType = mClassAnalyzer.findClass(className, imports);
                    if (renamedViewType.erasure().isAssignableFrom(viewType)) {
                        setterCandidates.add(renamed.get(className).method);
                        break;
                    }
                } catch (Exception e) {
                    //printMessage(Diagnostic.Kind.NOTE, "Unknown class: " + className);
                }
            }
        }
        setterCandidates.add(getDefaultSetter(attribute));
        setterCandidates.add(trimAttributeNamespace(attribute));

        ModelMethod bestMethod = null;
        ModelClass bestParameterType = null;
        List args = new ArrayList();
        args.add(argumentType);
        for (String name : setterCandidates) {
            ModelMethod[] methods = viewType.getMethods(name, 1);

            for (ModelMethod method : methods) {
                ModelClass[] parameterTypes = method.getParameterTypes();
                ModelClass param = parameterTypes[0];
                if (method.isVoid() &&
                        isBetterParameter(argumentType, param, bestParameterType, true, imports)) {
                    bestParameterType = param;
                    bestMethod = method;
                }
            }
        }
        return bestMethod;
    }

    private InverseMethod getBestGetter(ModelClass viewType, ModelClass valueType,
            String attribute, Map imports) {
        if (viewType.isGeneric()) {
            if (valueType != null) {
                valueType = eraseType(valueType, viewType.getTypeArguments());
            }
            viewType = viewType.erasure();
        }
        ModelClass bestReturnType = null;
        InverseDescription bestDescription = null;
        ModelClass bestViewType = null;
        ModelMethod bestMethod = null;

        HashMap inverseMethods = mStore.inverseMethods.get(attribute);
        if (inverseMethods != null) {
            for (String className : inverseMethods.keySet()) {
                try {
                    ModelClass methodViewType = mClassAnalyzer.findClass(className, imports);
                    if (methodViewType.erasure().isAssignableFrom(viewType)) {
                        boolean isBetterViewType = bestViewType == null ||
                                bestViewType.isAssignableFrom(methodViewType);
                        final InverseDescription inverseDescription = inverseMethods.get(className);
                        final String name =  inverseDescription.method.isEmpty() ?
                                trimAttributeNamespace(attribute) : inverseDescription.method;
                        ModelMethod method = methodViewType.findInstanceGetter(name);
                        ModelClass returnType = method.getReturnType(null); // no parameters
                        if (valueType == null || bestReturnType == null ||
                                isBetterParameter(returnType, valueType, bestReturnType,
                                        isBetterViewType, imports)) {
                            bestDescription = inverseDescription;
                            bestReturnType = returnType;
                            bestViewType = methodViewType;
                            bestMethod = method;
                        }
                    }
                } catch (Exception e) {
                    //printMessage(Diagnostic.Kind.NOTE, "Unknown class: " + className);
                }
            }
        }

        BindingGetterCall call = null;
        if (bestDescription != null) {
            final ModelClass listenerType = ModelAnalyzer.getInstance().findClass(
                    InverseBindingListener.class);
            SetterCall eventSetter = getSetterCall(bestDescription.event, viewType,
                    listenerType, imports);
            if (eventSetter == null) {
                List setters = getMultiAttributeSetterCalls(
                        new String[] {bestDescription.event}, viewType,
                        new ModelClass[] {listenerType});
                if (setters.size() != 1) {
                    L.e("Could not find event '%s' on View type '%s'", bestDescription.event,
                            viewType.getCanonicalName());
                    bestViewType = null;
                    bestReturnType = null;
                } else {
                    call = new ViewGetterCall(bestDescription, bestMethod, setters.get(0));
                }
            } else {
                call = new ViewGetterCall(bestDescription, bestMethod, eventSetter);
            }
        }
        return new InverseMethod(call, bestReturnType, bestViewType);
    }

    private static ModelClass eraseType(ModelClass type, List typeParameters) {
        List typeArguments = type.getTypeArguments();
        if (typeArguments == null || typeParameters == null) {
            return type;
        }
        for (ModelClass arg : typeArguments) {
            if (typeParameters.contains(arg)) {
                return type.erasure();
            }
        }
        return type;
    }

    private static String trimAttributeNamespace(String attribute) {
        final int colonIndex = attribute.indexOf(':');
        return colonIndex == -1 ? attribute : attribute.substring(colonIndex + 1);
    }

    private static String getDefaultSetter(String attribute) {
        return "set" + StringUtils.capitalize(trimAttributeNamespace(attribute));
    }

    private boolean isBetterParameter(ModelClass argument, ModelClass parameter,
            ModelClass oldParameter, boolean isBetterViewTypeMatch, Map imports) {
        // Right view type. Check the value
        if (!isBetterViewTypeMatch && oldParameter.equals(argument)) {
            return false;
        } else if (argument.equals(parameter)) {
            // Exact match
            return true;
        } else if (!isBetterViewTypeMatch &&
                ModelMethod.isBoxingConversion(oldParameter, argument)) {
            return false;
        } else if (ModelMethod.isBoxingConversion(parameter, argument)) {
            // Boxing/unboxing is second best
            return true;
        } else {
            int oldConversionLevel = ModelMethod.getImplicitConversionLevel(oldParameter);
            if (ModelMethod.isImplicitConversion(argument, parameter)) {
                // Better implicit conversion
                int conversionLevel = ModelMethod.getImplicitConversionLevel(parameter);
                return oldConversionLevel < 0 || conversionLevel < oldConversionLevel;
            } else if (oldConversionLevel >= 0) {
                return false;
            } else if (parameter.isAssignableFrom(argument)) {
                // Right type, see if it is better than the current best match.
                if (oldParameter == null) {
                    return true;
                } else {
                    return oldParameter.isAssignableFrom(parameter);
                }
            } else {
                MethodDescription conversionMethod = getConversionMethod(argument, parameter,
                        imports);
                if (conversionMethod != null) {
                    return true;
                }
                if (getConversionMethod(argument, oldParameter, imports) != null) {
                    return false;
                }
                return argument.isObject() && !parameter.isPrimitive();
            }
        }
    }

    private MethodDescription getConversionMethod(ModelClass from, ModelClass to,
            Map imports) {
        if (from != null && to != null) {
            if (to.isObject()) {
                return null;
            }
            for (String fromClassName : mStore.conversionMethods.keySet()) {
                try {
                    ModelClass convertFrom = mClassAnalyzer.findClass(fromClassName, imports);
                    if (canUseForConversion(from, convertFrom)) {
                        HashMap conversion =
                                mStore.conversionMethods.get(fromClassName);
                        for (String toClassName : conversion.keySet()) {
                            try {
                                ModelClass convertTo = mClassAnalyzer.findClass(toClassName,
                                        imports);
                                if (canUseForConversion(convertTo, to)) {
                                    return conversion.get(toClassName);
                                }
                            } catch (Exception e) {
                                L.d(e, "Unknown class: %s", toClassName);
                            }
                        }
                    }
                } catch (Exception e) {
                    L.d(e, "Unknown class: %s", fromClassName);
                }
            }
        }
        return null;
    }

    private boolean canUseForConversion(ModelClass from, ModelClass to) {
        if (from.isIncomplete() || to.isIncomplete()) {
            from = from.erasure();
            to = to.erasure();
        }
        return from.equals(to) || ModelMethod.isBoxingConversion(from, to) ||
                to.isAssignableFrom(from);
    }

    private static void merge(IntermediateV3 store, Intermediate dumpStore) {
        IntermediateV3 intermediateV3 = (IntermediateV3) dumpStore.upgrade();
        merge(store.adapterMethods, intermediateV3.adapterMethods);
        merge(store.renamedMethods, intermediateV3.renamedMethods);
        merge(store.conversionMethods, intermediateV3.conversionMethods);
        store.multiValueAdapters.putAll(intermediateV3.multiValueAdapters);
        store.untaggableTypes.putAll(intermediateV3.untaggableTypes);
        merge(store.inverseAdapters, intermediateV3.inverseAdapters);
        merge(store.inverseMethods, intermediateV3.inverseMethods);
        store.twoWayMethods.putAll(intermediateV3.twoWayMethods);
    }

    private static  void merge(HashMap> first,
            HashMap> second) {
        for (K key : second.keySet()) {
            HashMap firstVals = first.get(key);
            HashMap secondVals = second.get(key);
            if (firstVals == null) {
                first.put(key, secondVals);
            } else {
                for (V key2 : secondVals.keySet()) {
                    if (!firstVals.containsKey(key2)) {
                        firstVals.put(key2, secondVals.get(key2));
                    }
                }
            }
        }
    }

    private static String createAdapterCall(MethodDescription adapter,
            String componentExpression, String viewExpression, String... args) {
        StringBuilder sb = new StringBuilder();

        if (adapter.isStatic) {
            sb.append(adapter.type);
        } else {
            final SetterStore setterStore = SetterStore.get(ModelAnalyzer.getInstance());
            final String binderCall =  setterStore.getBindingAdapterCall(adapter.type);
            sb.append(componentExpression).append('.').append(binderCall);
        }
        sb.append('.').append(adapter.method).append('(');
        if (adapter.componentClass != null) {
            if (!"DataBindingComponent".equals(adapter.componentClass)) {
                sb.append('(').append(adapter.componentClass).append(") ");
            }
            sb.append(componentExpression).append(", ");
        }
        sb.append(viewExpression);
        for (String arg: args) {
            sb.append(", ").append(arg);
        }
        sb.append(')');
        return sb.toString();
    }

    private static class MultiValueAdapterKey implements Serializable {
        private static final long serialVersionUID = 1;

        public final String viewType;

        public final String[] attributes;

        public final String[] parameterTypes;

        public final boolean requireAll;

        public final TreeMap attributeIndices = new TreeMap();

        public MultiValueAdapterKey(ProcessingEnvironment processingEnv,
                ExecutableElement method, String[] attributes, boolean takesComponent,
                boolean requireAll) {
            this.attributes = stripAttributes(attributes);
            this.requireAll = requireAll;
            List parameters = method.getParameters();
            final int argStart = 1 + (takesComponent ? 1 : 0);
            this.viewType = getQualifiedName(eraseType(processingEnv,
                    parameters.get(argStart - 1).asType()));
            this.parameterTypes = new String[attributes.length];
            for (int i = 0; i < attributes.length; i++) {
                TypeMirror typeMirror = eraseType(processingEnv,
                        parameters.get(i + argStart).asType());
                this.parameterTypes[i] = getQualifiedName(typeMirror);
                attributeIndices.put(this.attributes[i], i);
            }
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof MultiValueAdapterKey)) {
                return false;
            }
            final MultiValueAdapterKey that = (MultiValueAdapterKey) obj;
            if (!this.viewType.equals(that.viewType) ||
                    this.attributes.length != that.attributes.length ||
                    !this.attributeIndices.keySet().equals(that.attributeIndices.keySet())) {
                return false;
            }

            for (int i = 0; i < this.attributes.length; i++) {
                final int thatIndex = that.attributeIndices.get(this.attributes[i]);
                final String thisParameter = parameterTypes[i];
                final String thatParameter = that.parameterTypes[thatIndex];
                if (!thisParameter.equals(thatParameter)) {
                    return false;
                }
            }
            return true;
        }

        @Override
        public int hashCode() {
            return mergedHashCode(viewType, attributeIndices.keySet());
        }
    }

    private static int mergedHashCode(Object... objects) {
        return Arrays.hashCode(objects);
    }

    private static class MethodDescription implements Serializable {

        private static final long serialVersionUID = 1;

        public final String type;

        public final String method;

        public final boolean requiresOldValue;

        public final boolean isStatic;

        public final String componentClass;

        public MethodDescription(String type, String method) {
            this.type = type;
            this.method = method;
            this.requiresOldValue = false;
            this.isStatic = true;
            this.componentClass = null;
            L.d("BINARY created method desc 1 %s %s", type, method );
        }

        public MethodDescription(ExecutableElement method, int numAttributes,
                boolean takesComponent) {
            TypeElement enclosingClass = (TypeElement) method.getEnclosingElement();
            this.type = enclosingClass.getQualifiedName().toString();
            this.method = method.getSimpleName().toString();
            final int argStart = 1 + (takesComponent ? 1 : 0);
            this.requiresOldValue = method.getParameters().size() - argStart == numAttributes * 2;
            this.isStatic = method.getModifiers().contains(Modifier.STATIC);
            this.componentClass = takesComponent
                    ? getQualifiedName(method.getParameters().get(0).asType())
                    : null;

            L.d("BINARY created method desc 2 %s %s, %s", type, this.method, method);
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof MethodDescription) {
                MethodDescription that = (MethodDescription) obj;
                return that.type.equals(this.type) && that.method.equals(this.method);
            } else {
                return false;
            }
        }

        @Override
        public int hashCode() {
            return mergedHashCode(type, method);
        }

        @Override
        public String toString() {
            return type + "." + method + "()";
        }
    }

    private static class InverseDescription extends MethodDescription {
        private static final long serialVersionUID = 1;

        public final String event;

        public InverseDescription(String type, String method, String event) {
            super(type, method);
            this.event = event;
        }

        public InverseDescription(ExecutableElement method, String event, boolean takesComponent) {
            super(method, 1, takesComponent);
            this.event = event;
        }

        @Override
        public boolean equals(Object obj) {
            if (!super.equals(obj) || !(obj instanceof InverseDescription)) {
                return false;
            }
            return event.equals(((InverseDescription) obj).event);
        }

        @Override
        public int hashCode() {
            return mergedHashCode(type, method, event);
        }
    }

    private static class AccessorKey implements Serializable {

        private static final long serialVersionUID = 1;

        public final String viewType;

        public final String valueType;

        public AccessorKey(String viewType, String valueType) {
            this.viewType = viewType;
            this.valueType = valueType;
        }

        @Override
        public int hashCode() {
            return mergedHashCode(viewType, valueType);
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof AccessorKey) {
                AccessorKey that = (AccessorKey) obj;
                return viewType.equals(that.valueType) && valueType.equals(that.valueType);
            } else {
                return false;
            }
        }

        @Override
        public String toString() {
            return "AK(" + viewType + ", " + valueType + ")";
        }
    }

    private static class InverseMethodDescription implements Serializable {
        private static final long serialVersionUID = 0xC00L;

        public final boolean isStatic;
        public final String returnType;
        public final String method;
        public final String[] parameterTypes;
        public final String type;

        public InverseMethodDescription(ProcessingEnvironment env, ExecutableElement method) {
            this.isStatic = method.getModifiers().contains(Modifier.STATIC);
            Types typeUtils = env.getTypeUtils();
            this.returnType = getQualifiedName(typeUtils.erasure(method.getReturnType()));
            this.method = method.getSimpleName().toString();
            TypeElement enclosingClass = (TypeElement) method.getEnclosingElement();
            this.type = enclosingClass.getQualifiedName().toString();

            List parameters = method.getParameters();
            this.parameterTypes = new String[parameters.size()];

            for (int i = 0; i < parameters.size(); i++) {
                VariableElement param  = parameters.get(i);
                TypeMirror type = typeUtils.erasure(param.asType());
                this.parameterTypes[i] = getQualifiedName(type);
            }
        }

        public InverseMethodDescription(ModelMethod method) {
            this.isStatic = method.isStatic();
            this.returnType = method.getReturnType().erasure().getCanonicalName();
            this.method = method.getName();
            this.type = method.getDeclaringClass().getCanonicalName();

            ModelClass[] parameters = method.getParameterTypes();
            this.parameterTypes = new String[parameters.length];

            for (int i = 0; i < parameters.length; i++) {
                this.parameterTypes[i] = parameters[i].erasure().getCanonicalName();
            }
        }

        @Override
        public int hashCode() {
            return mergedHashCode(type, isStatic, returnType, method,
                    Arrays.hashCode(parameterTypes));
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof InverseMethodDescription) {
                InverseMethodDescription that = (InverseMethodDescription) obj;
                return this.isStatic == that.isStatic &&
                        this.type.equals(that.type) &&
                        this.returnType.equals(that.returnType) &&
                        this.method.equals(that.method) &&
                        Arrays.equals(this.parameterTypes, that.parameterTypes);
            } else {
                return false;
            }
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            if (isStatic) {
                sb.append("static ");
            }
            sb.append(returnType)
                    .append(' ')
                    .append(type)
                    .append('.')
                    .append(method)
                    .append('(');
            for (int i = 0; i < parameterTypes.length; i++) {
                if (i != 0) {
                    sb.append(", ");
                }
                sb.append(parameterTypes[i]);
            }
            sb.append(')');
            return sb.toString();
        }
    }

    private interface Intermediate extends Serializable {
        Intermediate upgrade();
    }

    private static class IntermediateV1 implements Serializable, Intermediate {
        private static final long serialVersionUID = 1;
        public final HashMap> adapterMethods =
                new HashMap>();
        public final HashMap> renamedMethods =
                new HashMap>();
        public final HashMap> conversionMethods =
                new HashMap>();
        public final HashMap untaggableTypes = new HashMap();
        public final HashMap multiValueAdapters =
                new HashMap();

        public IntermediateV1() {
        }

        @Override
        public Intermediate upgrade() {
            IntermediateV2 v2 = new IntermediateV2();
            v2.adapterMethods.putAll(adapterMethods);
            v2.renamedMethods.putAll(renamedMethods);
            v2.conversionMethods.putAll(conversionMethods);
            v2.untaggableTypes.putAll(untaggableTypes);
            v2.multiValueAdapters.putAll(multiValueAdapters);
            return v2.upgrade();
        }
    }

    private static class IntermediateV2 extends IntermediateV1 {
        private static final long serialVersionUID = 0xA45C2EB637E35C07L;
        public final HashMap> inverseAdapters =
                new HashMap>();
        public final HashMap> inverseMethods =
                new HashMap>();

        @Override
        public Intermediate upgrade() {
            IntermediateV3 v3 = new IntermediateV3();
            v3.adapterMethods.putAll(adapterMethods);
            v3.renamedMethods.putAll(renamedMethods);
            v3.conversionMethods.putAll(conversionMethods);
            v3.untaggableTypes.putAll(untaggableTypes);
            v3.multiValueAdapters.putAll(multiValueAdapters);
            v3.inverseAdapters.putAll(inverseAdapters);
            v3.inverseMethods.putAll(inverseMethods);
            return v3.upgrade();
        }
    }

    private static class IntermediateV3 extends IntermediateV2 {
        private static final long serialVersionUID = 0xC00L;
        public final HashMap twoWayMethods = new HashMap<>();

        @Override
        public Intermediate upgrade() {
            return this;
        }
    }

    public static class DummySetter extends SetterCall {
        private String mMethodName;

        public DummySetter(String methodName) {
            mMethodName = methodName;
        }

        @Override
        public String toJavaInternal(String componentExpression, String viewExpression,
                String valueExpression) {
            return viewExpression + "." + mMethodName + "(" + valueExpression + ")";
        }

        @Override
        public String toJavaInternal(String componentExpression, String viewExpression,
                String oldValue, String valueExpression) {
            return viewExpression + "." + mMethodName + "(" + valueExpression + ")";
        }

        @Override
        public int getMinApi() {
            return 1;
        }

        @Override
        public boolean requiresOldValue() {
            return false;
        }

        @Override
        public ModelClass[] getParameterTypes() {
            return new ModelClass[] {
                    ModelAnalyzer.getInstance().findClass(Object.class)
            };
        }

        @Override
        public String getBindingAdapterInstanceClass() {
            return null;
        }

        @Override
        public String getDescription() {
            return "view." + mMethodName;
        }
    }

    public static class AdapterSetter extends SetterCall {
        final MethodDescription mAdapter;
        final ModelClass mParameterType;

        public AdapterSetter(MethodDescription adapter, ModelClass parameterType) {
            mAdapter = adapter;
            mParameterType = parameterType;
        }

        @Override
        public String toJavaInternal(String componentExpression, String viewExpression,
                String valueExpression) {
            return createAdapterCall(mAdapter, componentExpression,
                    viewExpression, mCastString + valueExpression);
        }

        @Override
        protected String toJavaInternal(String componentExpression, String viewExpression,
                String oldValue, String valueExpression) {
            return createAdapterCall(mAdapter, componentExpression,
                    viewExpression, mCastString + oldValue, mCastString + valueExpression);
        }

        @Override
        public int getMinApi() {
            return 1;
        }

        @Override
        public boolean requiresOldValue() {
            return mAdapter.requiresOldValue;
        }

        @Override
        public ModelClass[] getParameterTypes() {
            return new ModelClass[] { mParameterType };
        }

        @Override
        public String getBindingAdapterInstanceClass() {
            return mAdapter.isStatic ? null : mAdapter.type;
        }

        @Override
        public String getDescription() {
            return mAdapter.type + "." + mAdapter.method;
        }
    }

    public static class ModelMethodSetter extends SetterCall {
        final ModelMethod mModelMethod;

        public ModelMethodSetter(ModelMethod modelMethod) {
            mModelMethod = modelMethod;
        }

        @Override
        public String toJavaInternal(String componentExpression, String viewExpression,
                String valueExpression) {
            return viewExpression + "." + mModelMethod.getName() + "(" + mCastString +
                    valueExpression + ")";
        }

        @Override
        protected String toJavaInternal(String componentExpression, String viewExpression,
                String oldValue, String valueExpression) {
            return viewExpression + "." + mModelMethod.getName() + "(" +
                    mCastString + oldValue + ", " + mCastString + valueExpression + ")";
        }

        @Override
        public int getMinApi() {
            return mModelMethod.getMinApi();
        }

        @Override
        public boolean requiresOldValue() {
            return mModelMethod.getParameterTypes().length == 3;
        }

        @Override
        public ModelClass[] getParameterTypes() {
            return new ModelClass[] { mModelMethod.getParameterTypes()[0] };
        }

        @Override
        public String getBindingAdapterInstanceClass() {
            return null;
        }

        @Override
        public String getDescription() {
            String args = Arrays.stream(mModelMethod.getParameterTypes())
                    .map(param -> param.toJavaCode())
                    .collect(Collectors.joining(", "));
            return mModelMethod.getDeclaringClass().toJavaCode() + '.' +
                    mModelMethod.getName() + '(' + args + ')';

        }
    }

    public interface BindingSetterCall {
        String toJava(String componentExpression, String viewExpression,
                String... valueExpressions);

        int getMinApi();

        boolean requiresOldValue();

        ModelClass[] getParameterTypes();

        String getBindingAdapterInstanceClass();

        // A description of the setter method to be used in an error message
        String getDescription();
    }

    public static abstract class SetterCall implements BindingSetterCall {
        private MethodDescription mConverter;
        protected String mCastString = "";

        public SetterCall() {
        }

        public void setConverter(MethodDescription converter) {
            mConverter = converter;
        }

        protected abstract String toJavaInternal(String componentExpression, String viewExpression,
                String converted);

        protected abstract String toJavaInternal(String componentExpression, String viewExpression,
                String oldValue, String converted);

        @Override
        public final String toJava(String componentExpression, String viewExpression,
                String... valueExpression) {
            Preconditions.check(valueExpression.length == 2, "value expressions size must be 2");
            if (requiresOldValue()) {
                return toJavaInternal(componentExpression, viewExpression,
                        convertValue(valueExpression[0]), convertValue(valueExpression[1]));
            } else {
                return toJavaInternal(componentExpression, viewExpression,
                        convertValue(valueExpression[1]));
            }
        }

        protected String convertValue(String valueExpression) {
            return mConverter == null ? valueExpression :
                    mConverter.type + "." + mConverter.method + "(" + valueExpression + ")";
        }

        abstract public int getMinApi();

        public void setCast(ModelClass castTo) {
            mCastString = "(" + castTo.toJavaCode() + ") ";
        }
    }

    public static class MultiAttributeSetter implements BindingSetterCall {
        public final String[] attributes;
        private final MethodDescription mAdapter;
        private final MethodDescription[] mConverters;
        private final String[] mCasts;
        private final MultiValueAdapterKey mKey;
        private final boolean[] mSupplied;

        public MultiAttributeSetter(MultiValueAdapterKey key, boolean[] supplied,
                MethodDescription adapter, MethodDescription[] converters, String[] casts) {
            Preconditions.check(converters != null &&
                    converters.length == key.attributes.length &&
                    casts != null && casts.length == key.attributes.length &&
                    supplied.length == key.attributes.length,
                    "invalid arguments to create multi attr setter");
            this.mAdapter = adapter;
            this.mConverters = converters;
            this.mCasts = casts;
            this.mKey = key;
            this.mSupplied = supplied;
            if (key.requireAll) {
                this.attributes = key.attributes;
            } else {
                int numSupplied = 0;
                for (int i = 0; i < mKey.attributes.length; i++) {
                    if (supplied[i]) {
                        numSupplied++;
                    }
                }
                if (numSupplied == key.attributes.length) {
                    this.attributes = key.attributes;
                } else {
                    this.attributes = new String[numSupplied];
                    int attrIndex = 0;
                    for (int i = 0; i < key.attributes.length; i++) {
                        if (supplied[i]) {
                            attributes[attrIndex++] = key.attributes[i];
                        }
                    }
                }
            }
        }

        @Override
        public final String toJava(String componentExpression, String viewExpression,
                String... valueExpressions) {
            Preconditions.check(valueExpressions.length == attributes.length * 2,
                    "MultiAttributeSetter needs %s items, received %s",
                    Arrays.toString(attributes), Arrays.toString(valueExpressions));
            final int numAttrs = mKey.attributes.length;
            String[] args = new String[numAttrs + (requiresOldValue() ? numAttrs : 0)];

            final int startIndex = mAdapter.requiresOldValue ? 0 : numAttrs;
            int attrIndex = mAdapter.requiresOldValue ? 0 : attributes.length;
            final ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
            StringBuilder argBuilder = new StringBuilder();
            final int endIndex = numAttrs * 2;
            for (int i = startIndex; i < endIndex; i++) {
                argBuilder.setLength(0);
                if (!mSupplied[i % numAttrs]) {
                    final String paramType = mKey.parameterTypes[i % numAttrs];
                    final String defaultValue = modelAnalyzer.getDefaultValue(paramType);
                    argBuilder.append('(')
                            .append(paramType)
                            .append(')')
                            .append(defaultValue);
                } else {
                    if (mConverters[i % numAttrs] != null) {
                        final MethodDescription converter = mConverters[i % numAttrs];
                        argBuilder.append(converter.type)
                                .append('.')
                                .append(converter.method)
                                .append('(')
                                .append(valueExpressions[attrIndex])
                                .append(')');
                    } else {
                        if (mCasts[i % numAttrs] != null) {
                            argBuilder.append('(')
                                    .append(mCasts[i % numAttrs])
                                    .append(')');
                        }
                        argBuilder.append(valueExpressions[attrIndex]);
                    }
                    attrIndex++;
                }
                args[i - startIndex] = argBuilder.toString();
            }
            return createAdapterCall(mAdapter, componentExpression, viewExpression, args);
        }

        @Override
        public int getMinApi() {
            return 1;
        }

        @Override
        public boolean requiresOldValue() {
            return mAdapter.requiresOldValue;
        }

        @Override
        public ModelClass[] getParameterTypes() {
            ModelClass[] parameters = new ModelClass[attributes.length];
            String[] paramTypeStrings = mKey.parameterTypes;
            ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
            int attrIndex = 0;
            for (int i = 0; i < mKey.attributes.length; i++) {
                if (mSupplied[i]) {
                    parameters[attrIndex++] = modelAnalyzer.findClass(paramTypeStrings[i], null);
                }
            }
            return parameters;
        }

        @Override
        public String getBindingAdapterInstanceClass() {
            return mAdapter.isStatic ? null : mAdapter.type;
        }

        @Override
        public String toString() {
            return "MultiAttributeSetter{" +
                    "attributes=" + Arrays.toString(attributes) +
                    ", mAdapter=" + mAdapter +
                    ", mConverters=" + Arrays.toString(mConverters) +
                    ", mCasts=" + Arrays.toString(mCasts) +
                    ", mKey=" + mKey +
                    '}';
        }

        @Override
        public String getDescription() {
            return mAdapter.type + "." + mAdapter.method;
        }
    }

    public static class ViewDataBindingEventSetter implements BindingSetterCall {

        public ViewDataBindingEventSetter() {
        }

        @Override
        public String toJava(String componentExpression, String viewExpression,
                String... valueExpressions) {
            return "setBindingInverseListener(" + viewExpression + ", " +
                    valueExpressions[0] + ", " + valueExpressions[1] + ")";
        }

        @Override
        public int getMinApi() {
            return 0;
        }

        @Override
        public boolean requiresOldValue() {
            return true;
        }

        @Override
        public ModelClass[] getParameterTypes() {
            ModelClass[] parameterTypes = new ModelClass[1];
            parameterTypes[0] = ModelAnalyzer.getInstance().findClass(
                    "android.databinding.ViewDataBinder.PropertyChangedInverseListener", null);
            return parameterTypes;
        }

        @Override
        public String getBindingAdapterInstanceClass() {
            return null;
        }

        @Override
        public String getDescription() {
            return "ViewDataBinding.setBindingInverseListener";
        }
    }

    public interface BindingGetterCall {
        String toJava(String componentExpression, String viewExpression);

        String getGetterType();

        int getMinApi();

        String getBindingAdapterInstanceClass();

        void setBindingAdapterCall(String method);

        BindingSetterCall getEvent();

        String getEventAttribute();
    }

    public static class ViewDataBindingGetterCall implements BindingGetterCall {
        private final String mGetter;
        private final BindingSetterCall mEventSetter;
        private final String mAttribute;
        private final ModelClass mBindingClass;

        public ViewDataBindingGetterCall(ModelClass bindingClass, String attribute) {
            final int colonIndex = attribute.indexOf(':');
            mAttribute = attribute.substring(colonIndex + 1);
            mGetter = "get" + StringUtils.capitalize(mAttribute);
            mEventSetter = new ViewDataBindingEventSetter();
            mBindingClass = bindingClass;
        }

        @Override
        public String toJava(String componentExpression, String viewExpression) {
            return viewExpression + "." + mGetter + "()";
        }

        @Override
        public String getGetterType() {
            return mBindingClass.findInstanceGetter(mGetter).getReturnType().toJavaCode();
        }

        @Override
        public int getMinApi() {
            return 0;
        }

        @Override
        public String getBindingAdapterInstanceClass() {
            return null;
        }

        @Override
        public void setBindingAdapterCall(String method) {
        }

        @Override
        public BindingSetterCall getEvent() {
            return mEventSetter;
        }

        @Override
        public String getEventAttribute() {
            return mAttribute;
        }
    }

    public static class ViewGetterCall implements BindingGetterCall {
        private final InverseDescription mInverseDescription;
        private final BindingSetterCall mEventCall;
        private final ModelMethod mMethod;

        public ViewGetterCall(InverseDescription inverseDescription, ModelMethod method,
                BindingSetterCall eventCall) {
            mInverseDescription = inverseDescription;
            mEventCall = eventCall;
            mMethod = method;
        }

        @Override
        public BindingSetterCall getEvent() {
            return mEventCall;
        }

        @Override
        public String getEventAttribute() {
            return mInverseDescription.event;
        }

        @Override
        public String toJava(String componentExpression, String viewExpression) {
            return viewExpression + "." + mMethod.getName() + "()";
        }

        @Override
        public String getGetterType() {
            return mMethod.getReturnType().toJavaCode();
        }

        @Override
        public int getMinApi() {
            return mMethod.getMinApi();
        }

        @Override
        public String getBindingAdapterInstanceClass() {
            return null;
        }

        @Override
        public void setBindingAdapterCall(String method) {
        }
    }

    public static class AdapterGetter implements BindingGetterCall {
        private final InverseDescription mInverseDescription;
        private String mBindingAdapterCall;
        private final BindingSetterCall mEventCall;
        private final String mGetterType;

        public AdapterGetter(InverseDescription description, BindingSetterCall eventCall,
                String getterType) {
            mInverseDescription = description;
            mEventCall = eventCall;
            mGetterType = getterType;
        }

        @Override
        public String getGetterType() {
            return mGetterType;
        }

        @Override
        public String toJava(String componentExpression, String viewExpression) {
            StringBuilder sb = new StringBuilder();

            if (mInverseDescription.isStatic) {
                sb.append(mInverseDescription.type);
            } else {
                sb.append(componentExpression).append('.').append(mBindingAdapterCall);
            }
            sb.append('.').append(mInverseDescription.method).append('(');
            if (mInverseDescription.componentClass != null) {
                if (!"DataBindingComponent".equals(mInverseDescription.componentClass)) {
                    sb.append('(').append(mInverseDescription.componentClass).append(") ");
                }
                sb.append(componentExpression).append(", ");
            }
            sb.append(viewExpression).append(')');
            return sb.toString();
        }

        @Override
        public int getMinApi() {
            return 1;
        }

        @Override
        public String getBindingAdapterInstanceClass() {
            return mInverseDescription.isStatic ? null : mInverseDescription.type;
        }

        @Override
        public void setBindingAdapterCall(String method) {
            mBindingAdapterCall = method;
        }

        @Override
        public BindingSetterCall getEvent() {
            return mEventCall;
        }

        @Override
        public String getEventAttribute() {
            return mInverseDescription.event;
        }
    }

    private static class InverseMethod {
        public BindingGetterCall call;
        public ModelClass returnType;
        public ModelClass viewType;

        public InverseMethod(BindingGetterCall call, ModelClass returnType, ModelClass viewType) {
            this.call = call;
            this.returnType = returnType;
            this.viewType = viewType;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy