Support generics in conversion parameters.

Bug 23490384

Change-Id: I691da60a671d15c73cf2753ff830f9effb360e96
diff --git a/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java b/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java
index e95ba39..aa6634b 100644
--- a/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java
+++ b/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java
@@ -143,6 +143,11 @@
     public abstract boolean isTypeVar();
 
     /**
+     * @return whether this is a wildcard type argument or not.
+     */
+    public abstract boolean isWildcard();
+
+    /**
      * @return whether or not this ModelClass is java.lang.Object and not a primitive or subclass.
      */
     public boolean isObject() {
@@ -477,6 +482,21 @@
         return matching;
     }
 
+    public boolean isIncomplete() {
+        if (isTypeVar() || isWildcard()) {
+            return true;
+        }
+        List<ModelClass> typeArgs = getTypeArguments();
+        if (typeArgs != null) {
+            for (ModelClass typeArg : typeArgs) {
+                if (typeArg.isIncomplete()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     protected abstract ModelField[] getDeclaredFields();
 
     protected abstract ModelMethod[] getDeclaredMethods();
diff --git a/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationClass.java b/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationClass.java
index dcc708a..02e767e 100644
--- a/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationClass.java
+++ b/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationClass.java
@@ -55,27 +55,12 @@
 
     @Override
     public String toJavaCode() {
-        if (isIncomplete(mTypeMirror)) {
+        if (isIncomplete()) {
             return getCanonicalName();
         }
         return mTypeMirror.toString();
     }
 
-    private static boolean isIncomplete(TypeMirror typeMirror) {
-        final TypeKind typeKind = typeMirror.getKind();
-        if (typeKind == TypeKind.DECLARED) {
-            DeclaredType declaredType = (DeclaredType) typeMirror;
-            for (TypeMirror typeArg : declaredType.getTypeArguments()) {
-                if (isIncomplete(typeArg)) {
-                    return true;
-                }
-            }
-        } else if (typeKind == TypeKind.TYPEVAR) {
-            return true;
-        }
-        return false;
-    }
-
     @Override
     public boolean isArray() {
         return mTypeMirror.getKind() == TypeKind.ARRAY;
@@ -263,6 +248,11 @@
     }
 
     @Override
+    public boolean isWildcard() {
+        return mTypeMirror.getKind() == TypeKind.WILDCARD;
+    }
+
+    @Override
     public boolean isInterface() {
         return mTypeMirror.getKind() == TypeKind.DECLARED &&
                 ((DeclaredType)mTypeMirror).asElement().getKind() == ElementKind.INTERFACE;
diff --git a/compiler/src/main/java/android/databinding/tool/store/SetterStore.java b/compiler/src/main/java/android/databinding/tool/store/SetterStore.java
index b5b579a..a31337a 100644
--- a/compiler/src/main/java/android/databinding/tool/store/SetterStore.java
+++ b/compiler/src/main/java/android/databinding/tool/store/SetterStore.java
@@ -287,13 +287,36 @@
     }
 
     private static String getQualifiedName(TypeMirror type) {
-        if (type.getKind() == TypeKind.ARRAY) {
+        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 type.toString();
         }
     }
 
+    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<? extends TypeMirror> 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<? extends VariableElement> parameters = conversionMethod.getParameters();
@@ -706,6 +729,10 @@
     }
 
     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);
     }
diff --git a/compiler/src/test/java/android/databinding/tool/reflection/java/JavaClass.java b/compiler/src/test/java/android/databinding/tool/reflection/java/JavaClass.java
index cc6891e..3136e7f 100644
--- a/compiler/src/test/java/android/databinding/tool/reflection/java/JavaClass.java
+++ b/compiler/src/test/java/android/databinding/tool/reflection/java/JavaClass.java
@@ -125,6 +125,11 @@
     }
 
     @Override
+    public boolean isWildcard() {
+        return false;
+    }
+
+    @Override
     public boolean isInterface() {
         return mClass.isInterface();
     }
diff --git a/integration-tests/TestApp/app/src/androidTestApi7/java/android/databinding/testapp/ConverterTest.java b/integration-tests/TestApp/app/src/androidTestApi7/java/android/databinding/testapp/ConverterTest.java
new file mode 100644
index 0000000..35a98fc
--- /dev/null
+++ b/integration-tests/TestApp/app/src/androidTestApi7/java/android/databinding/testapp/ConverterTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.testapp;
+
+import android.databinding.testapp.databinding.ConvertersBinding;
+
+import android.test.UiThreadTest;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+
+public class ConverterTest extends BaseDataBinderTest<ConvertersBinding> {
+    public ConverterTest() {
+        super(ConvertersBinding.class);
+    }
+
+    @UiThreadTest
+    public void testGenericConverter() {
+        initBinder();
+        ArrayList<String> values = new ArrayList<String>();
+        LinkedList<String> linkedValues = new LinkedList<String>();
+        values.add("Hello");
+        values.add("World");
+        linkedValues.add("Holy");
+        linkedValues.add("Cow!");
+        mBinder.setList(values);
+        mBinder.setLinked(linkedValues);
+        mBinder.executePendingBindings();
+        assertEquals("Hello World", mBinder.textView1.getText().toString());
+        assertEquals("Holy Cow!", mBinder.textView2.getText().toString());
+    }
+}
diff --git a/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/adapter/GenericConverter.java b/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/adapter/GenericConverter.java
new file mode 100644
index 0000000..a8aae55
--- /dev/null
+++ b/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/adapter/GenericConverter.java
@@ -0,0 +1,47 @@
+/*
+ * 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.testapp.adapter;
+
+import android.databinding.BindingConversion;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+public class GenericConverter {
+    @BindingConversion
+    public static <T> String convertArrayList(ArrayList<T> values) {
+        return convert(values);
+    }
+
+    @BindingConversion
+    public static String convertLinkedList(LinkedList<?> values) {
+        return convert(values);
+    }
+
+    private static <T> String convert(List<T> values) {
+        if (values == null) {
+            return "";
+        }
+        StringBuilder vals = new StringBuilder();
+        for (T val : values) {
+            if (vals.length() != 0) {
+                vals.append(' ');
+            }
+            vals.append(val);
+        }
+        return vals.toString();
+    }
+}
diff --git a/integration-tests/TestApp/app/src/main/res/layout/converters.xml b/integration-tests/TestApp/app/src/main/res/layout/converters.xml
new file mode 100644
index 0000000..f133819
--- /dev/null
+++ b/integration-tests/TestApp/app/src/main/res/layout/converters.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+    <data>
+        <variable name="list" type="java.util.ArrayList&lt;String>"/>
+        <variable name="linked" type="java.util.LinkedList&lt;String>"/>
+    </data>
+    <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+        <TextView
+                android:id="@+id/textView1"
+                android:layout_width="wrap_content" android:layout_height="wrap_content"
+                android:text="@{list}"/>
+        <TextView
+                android:id="@+id/textView2"
+                android:layout_width="wrap_content" android:layout_height="wrap_content"
+                android:text="@{linked}"/>
+    </LinearLayout>
+</layout>
\ No newline at end of file