Check API version for methods called via binding

In data binding, setting an attribute actually means calling a method, which might be
an issue if the method is added after a certain API.

This CL introduces a change which will check called methods per api and add necessary
API check code to avoid calling those methods in older platforms.

This CL also resurrects the Java Model Analyzer (in testing) and also fixes compiler tests.

Bug: 19593398
Change-Id: I0da4194625231cf43125e1b43338069e7d191eb9
diff --git a/tools/data-binding/compiler/src/test/java/com/android/databinding/MockObservable.java b/tools/data-binding/TestApp/src/androidTest/java/com/android/databinding/library/DataBinderTrojan.java
similarity index 68%
rename from tools/data-binding/compiler/src/test/java/com/android/databinding/MockObservable.java
rename to tools/data-binding/TestApp/src/androidTest/java/com/android/databinding/library/DataBinderTrojan.java
index f7e3650..eeba4f2 100644
--- a/tools/data-binding/compiler/src/test/java/com/android/databinding/MockObservable.java
+++ b/tools/data-binding/TestApp/src/androidTest/java/com/android/databinding/library/DataBinderTrojan.java
@@ -1,9 +1,12 @@
 /*
  * 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.
@@ -11,11 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.databinding;
+package com.android.databinding.library;
 
 /**
- * Mock class for Observable interface, used for testing.
+ * This helper is used to toggle DataBinder's package private values to change behavior for testing
  */
-public class MockObservable {
-
+public class DataBinderTrojan {
+    public static void setBuildSdkInt(int level) {
+        DataBinder.SDK_INT = level;
+    }
 }
diff --git a/tools/data-binding/TestApp/src/androidTest/java/com/android/databinding/testapp/NewApiTest.java b/tools/data-binding/TestApp/src/androidTest/java/com/android/databinding/testapp/NewApiTest.java
new file mode 100644
index 0000000..9b28237
--- /dev/null
+++ b/tools/data-binding/TestApp/src/androidTest/java/com/android/databinding/testapp/NewApiTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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 com.android.databinding.testapp;
+
+import com.android.databinding.library.DataBinderTrojan;
+import com.android.databinding.testapp.generated.NewApiLayoutBinder;
+
+import android.content.Context;
+import android.os.Build;
+import android.test.UiThreadTest;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+public class NewApiTest extends BaseDataBinderTest<NewApiLayoutBinder> {
+    public NewApiTest() {
+        super(NewApiLayoutBinder.class, R.layout.new_api_layout);
+    }
+
+    @UiThreadTest
+    public void testSetElevation() {
+        mBinder.setElevation(3);
+        mBinder.setName("foo");
+        mBinder.setChildren(new ArrayList<View>());
+        mBinder.rebindDirty();
+        assertEquals("foo", mBinder.getTextView().getText().toString());
+        assertEquals(3f, mBinder.getTextView().getElevation());
+    }
+
+    @UiThreadTest
+    public void testSetElevationOlderAPI() {
+        DataBinderTrojan.setBuildSdkInt(1);
+        try {
+            TextView textView = mBinder.getTextView();
+            float originalElevation = textView.getElevation();
+            mBinder.setElevation(3);
+            mBinder.setName("foo2");
+            mBinder.rebindDirty();
+            assertEquals("foo2", textView.getText().toString());
+            assertEquals(originalElevation, textView.getElevation());
+        } finally {
+            DataBinderTrojan.setBuildSdkInt(Build.VERSION.SDK_INT);
+        }
+    }
+
+    @UiThreadTest
+    public void testGeneric() {
+        ArrayList<View> views = new ArrayList<>();
+        mBinder.setChildren(views);
+        mBinder.rebindDirty();
+        assertEquals(1, views.size());
+        assertSame(mBinder.getTextView(), views.get(0));
+    }
+
+    @UiThreadTest
+    public void testGenericOlderApi() {
+        DataBinderTrojan.setBuildSdkInt(1);
+        try {
+            ArrayList<View> views = new ArrayList<>();
+            mBinder.setChildren(views);
+            mBinder.rebindDirty();
+            // we should not call the api on older platforms.
+            assertEquals(0, views.size());
+        } finally {
+            DataBinderTrojan.setBuildSdkInt(Build.VERSION.SDK_INT);
+        }
+    }
+}
diff --git a/tools/data-binding/TestApp/src/main/java/com/android/databinding/testapp/TestActivity.java b/tools/data-binding/TestApp/src/main/java/com/android/databinding/testapp/TestActivity.java
index c5a5b17..fa9dd42 100644
--- a/tools/data-binding/TestApp/src/main/java/com/android/databinding/testapp/TestActivity.java
+++ b/tools/data-binding/TestApp/src/main/java/com/android/databinding/testapp/TestActivity.java
@@ -1,9 +1,12 @@
 /*
  * 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.
diff --git a/tools/data-binding/TestApp/src/main/res/layout/new_api_layout.xml b/tools/data-binding/TestApp/src/main/res/layout/new_api_layout.xml
new file mode 100644
index 0000000..686676b
--- /dev/null
+++ b/tools/data-binding/TestApp/src/main/res/layout/new_api_layout.xml
@@ -0,0 +1,27 @@
+<?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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/myContainer"
+              android:addChildrenForAccessibility="@{children}"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+    <variable name="elevation" type="float"/>
+    <variable name="name" type="java.lang.String"/>
+    <variable name="children" type="java.util.ArrayList&lt;android.view.View>"/>
+    <TextView android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:id="@+id/textView"
+              android:text="@{name}" android:elevation="@{elevation}"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/ProcessExpressions.java b/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/ProcessExpressions.java
index e12ad3e..2b047bb 100644
--- a/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/ProcessExpressions.java
+++ b/tools/data-binding/annotationprocessor/src/main/java/com/android/databinding/annotationprocessor/ProcessExpressions.java
@@ -1,7 +1,25 @@
+/*
+ * 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 com.android.databinding.annotationprocessor;
 
 import com.android.databinding.CompilerChef;
+import com.android.databinding.reflection.SdkUtil;
 import com.android.databinding.store.ResourceBundle;
+import com.android.databinding.util.L;
 import com.android.databinding.writer.AnnotationJavaFileWriter;
 
 import org.apache.commons.codec.binary.Base64;
@@ -11,6 +29,7 @@
 import android.binding.BindingAppInfo;
 
 import java.io.ByteArrayInputStream;
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
@@ -37,12 +56,12 @@
 
     public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
         ResourceBundle resourceBundle = null;
-
         for (Element element : roundEnv.getElementsAnnotatedWith(BindingAppInfo.class)) {
             final BindingAppInfo appInfo = element.getAnnotation(BindingAppInfo.class);
             if (appInfo == null) {
                 continue; // It gets confused between BindingAppInfo and BinderBundle
             }
+            SdkUtil.initialize(appInfo.minSdk(), new File(appInfo.sdkRoot()));
             if (element.getKind() != ElementKind.CLASS) {
                 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
                         "BindingAppInfo associated with wrong type. Should be a class.", element);
diff --git a/tools/data-binding/baseLibrary/src/main/java/android/binding/Bindable.java b/tools/data-binding/baseLibrary/src/main/java/android/binding/Bindable.java
index 80464b4..09bcde9 100644
--- a/tools/data-binding/baseLibrary/src/main/java/android/binding/Bindable.java
+++ b/tools/data-binding/baseLibrary/src/main/java/android/binding/Bindable.java
@@ -21,5 +21,6 @@
 import java.lang.annotation.Target;
 
 @Target({ElementType.FIELD, ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME) // this is necessary for java analyzer to work
 public @interface Bindable {
 }
diff --git a/tools/data-binding/baseLibrary/src/main/java/android/binding/BindingAppInfo.java b/tools/data-binding/baseLibrary/src/main/java/android/binding/BindingAppInfo.java
index 42a6f3d..a90f332 100644
--- a/tools/data-binding/baseLibrary/src/main/java/android/binding/BindingAppInfo.java
+++ b/tools/data-binding/baseLibrary/src/main/java/android/binding/BindingAppInfo.java
@@ -25,4 +25,6 @@
 public @interface BindingAppInfo {
     String buildId();
     String applicationPackage();
+    String sdkRoot();
+    int minSdk();
 }
diff --git a/tools/data-binding/compiler/build.gradle b/tools/data-binding/compiler/build.gradle
index 96917ae..8ebb27a 100644
--- a/tools/data-binding/compiler/build.gradle
+++ b/tools/data-binding/compiler/build.gradle
@@ -40,6 +40,7 @@
     compile project(":baseLibrary")
     compile project(":grammarBuilder")
     compile project(":xmlGrammar")
+    testCompile "com.android.databinding:library:$version@jar"
 }
 uploadArchives {
     repositories {
diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/Binding.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/Binding.java
index 9ccf188..4425e0f 100644
--- a/tools/data-binding/compiler/src/main/java/com/android/databinding/Binding.java
+++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/Binding.java
@@ -18,6 +18,7 @@
 
 import com.android.databinding.reflection.ModelAnalyzer;
 import com.android.databinding.reflection.ModelClass;
+import com.android.databinding.reflection.SdkUtil;
 import com.android.databinding.store.SetterStore;
 import com.android.databinding.expr.Expr;
 
@@ -26,7 +27,7 @@
     private final String mName;
     private final Expr mExpr;
     private final BindingTarget mTarget;
-    private String mJavaCode = null;
+    private SetterStore.SetterCall mSetterCall;
 
     public Binding(BindingTarget target, String name, Expr expr) {
         mTarget = target;
@@ -34,15 +35,31 @@
         mExpr = expr;
     }
 
+    private SetterStore.SetterCall getSetterCall() {
+        if (mSetterCall == null) {
+            ModelClass viewType = mTarget.getResolvedType();
+            mSetterCall = SetterStore.get(ModelAnalyzer.getInstance()).getSetterCall(mName,
+                    viewType, mExpr.getResolvedType(), mExpr.getModel().getImports());
+        }
+        return mSetterCall;
+    }
+
     public BindingTarget getTarget() {
         return mTarget;
     }
 
     public String toJavaCode(String targetViewName, String expressionCode) {
-        ModelClass viewType = mTarget.getResolvedType();
-        return SetterStore.get(ModelAnalyzer.getInstance()).getSetterCall(mName, viewType,
-                mExpr.getResolvedType(), targetViewName, expressionCode,
-                mExpr.getModel().getImports());
+        return getSetterCall().toJava(targetViewName, expressionCode);
+    }
+
+    /**
+     * The min api level in which this binding should be executed.
+     * <p>
+     * This should be the minimum value among the dependencies of this binding. For now, we only
+     * check the setter.
+     */
+    public int getMinApi() {
+        return getSetterCall().getMinApi();
     }
 
 //    private String resolveJavaCode(ModelAnalyzer modelAnalyzer) {
diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/DataBinder.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/DataBinder.java
index fc29b9e..1514a9e 100644
--- a/tools/data-binding/compiler/src/main/java/com/android/databinding/DataBinder.java
+++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/DataBinder.java
@@ -62,6 +62,7 @@
     
     public void writeBinders() {
         for (LayoutBinder layoutBinder : mLayoutBinders) {
+            L.d("writing data binder %s", layoutBinder.getClassName());
             mFileWriter.writeToFile(layoutBinder.getPackage() + "." + layoutBinder.getClassName(),
                     layoutBinder.writeViewBinder());
         }
diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/LayoutXmlProcessor.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/LayoutXmlProcessor.java
index cb4860e..9bca851 100644
--- a/tools/data-binding/compiler/src/main/java/com/android/databinding/LayoutXmlProcessor.java
+++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/LayoutXmlProcessor.java
@@ -21,6 +21,7 @@
 import com.android.databinding.writer.JavaFileWriter;
 
 import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang3.StringEscapeUtils;
 import org.xml.sax.SAXException;
 
 import java.io.File;
@@ -48,16 +49,19 @@
     public static final String APPLICATION_INFO_CLASS = "ApplicationBindingInfo";
     private final JavaFileWriter mFileWriter;
     private final ResourceBundle mResourceBundle;
+    private final int mMinSdk;
+
     private boolean mProcessingComplete;
     private boolean mWritten;
     private final String mBuildId = UUID.randomUUID().toString();
     private final List<File> mResourceFolders;
 
     public LayoutXmlProcessor(String applicationPackage, List<File> resourceFolders,
-            JavaFileWriter fileWriter) {
+            JavaFileWriter fileWriter, int minSdk) {
         mFileWriter = fileWriter;
         mResourceBundle = new ResourceBundle(applicationPackage);
         mResourceFolders = resourceFolders;
+        mMinSdk = minSdk;
     }
 
     public boolean processResources()
@@ -84,13 +88,13 @@
         return true;
     }
 
-    public void writeIntermediateFile() throws JAXBException {
+    public void writeIntermediateFile(File sdkDir) throws JAXBException {
         if (mWritten) {
             return;
         }
         JAXBContext context = JAXBContext.newInstance(ResourceBundle.LayoutFileBundle.class);
         Marshaller marshaller = context.createMarshaller();
-        writeAppInfo(marshaller);
+        writeAppInfo(marshaller, sdkDir);
         for (List<ResourceBundle.LayoutFileBundle> layouts : mResourceBundle.getLayoutBundles()
                 .values()) {
             for (ResourceBundle.LayoutFileBundle layout : layouts) {
@@ -124,10 +128,13 @@
         mFileWriter.writeToFile(RESOURCE_BUNDLE_PACKAGE + className, classString);
     }
 
-    private void writeAppInfo(Marshaller marshaller) {
+    private void writeAppInfo(Marshaller marshaller, File sdkDir) {
+        final String sdkPath = StringEscapeUtils.escapeJava(sdkDir.getAbsolutePath());
         String classString = "import android.binding.BindingAppInfo;\n\n" +
-                "@BindingAppInfo(buildId=\"" + mBuildId + "\", applicationPackage=\"" +
-                mResourceBundle.getAppPackage() + "\")\n" +
+                "@BindingAppInfo(buildId=\"" + mBuildId + "\", " +
+                "applicationPackage=\"" + mResourceBundle.getAppPackage() + "\", " +
+                "sdkRoot=\"" + sdkPath + "\", " +
+                "minSdk=" + mMinSdk + ")\n" +
                 "public class " + APPLICATION_INFO_CLASS + " {}\n";
         mFileWriter.writeToFile(RESOURCE_BUNDLE_PACKAGE + APPLICATION_INFO_CLASS, classString);
     }
diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/ModelAnalyzer.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/ModelAnalyzer.java
index eb40113..b0e66c8 100644
--- a/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/ModelAnalyzer.java
+++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/ModelAnalyzer.java
@@ -16,16 +16,61 @@
 package com.android.databinding.reflection;
 
 import com.google.common.base.Preconditions;
+import com.android.databinding.reflection.annotation.AnnotationAnalyzer;
+import com.android.databinding.util.L;
 
 import java.net.URL;
 import java.util.List;
 import java.util.Map;
-
 import javax.annotation.processing.ProcessingEnvironment;
 
 public abstract class ModelAnalyzer {
+
+    public static final String[] LIST_CLASS_NAMES = {
+            "java.util.List",
+            "android.util.SparseArray",
+            "android.util.SparseBooleanArray",
+            "android.util.SparseIntArray",
+            "android.util.SparseLongArray",
+            "android.util.LongSparseArray",
+            "android.support.v4.util.LongSparseArray",
+    };
+
+    public static final String MAP_CLASS_NAME = "java.util.Map";
+
+    public static final String STRING_CLASS_NAME = "java.lang.String";
+
+    public static final String OBJECT_CLASS_NAME = "java.lang.Object";
+
+    static ModelAnalyzer instance;
+
+    public static final String OBSERVABLE_CLASS_NAME = "android.binding.Observable";
+
+    public static final String OBSERVABLE_LIST_CLASS_NAME = "android.binding.ObservableList";
+
+    public static final String OBSERVABLE_MAP_CLASS_NAME = "android.binding.ObservableMap";
+
+    public static final String[] OBSERVABLE_FIELDS = {
+            "com.android.databinding.library.ObservableBoolean",
+            "com.android.databinding.library.ObservableByte",
+            "com.android.databinding.library.ObservableChar",
+            "com.android.databinding.library.ObservableShort",
+            "com.android.databinding.library.ObservableInt",
+            "com.android.databinding.library.ObservableLong",
+            "com.android.databinding.library.ObservableFloat",
+            "com.android.databinding.library.ObservableDouble",
+            "com.android.databinding.library.ObservableField",
+    };
+
+    public static final String I_VIEW_DATA_BINDER
+            = "com.android.databinding.library.IViewDataBinder";
+
     private static ModelAnalyzer sAnalyzer;
 
+    protected void setInstance(ModelAnalyzer analyzer) {
+        sAnalyzer = analyzer;
+    }
+
     public abstract boolean isDataBinder(ModelClass modelClass);
 
     public abstract Callable findMethod(ModelClass modelClass, String name,
@@ -68,27 +113,32 @@
     }
 
     public static void setProcessingEnvironment(ProcessingEnvironment processingEnvironment) {
+        if (sAnalyzer != null) {
+            throw new IllegalStateException("processing env is already created, you cannot "
+                    + "change class loader after that");
+        }
+        L.d("setting processing env to %s", processingEnvironment);
         AnnotationAnalyzer annotationAnalyzer = new AnnotationAnalyzer(processingEnvironment);
         sAnalyzer = annotationAnalyzer;
     }
 
     public String getDefaultValue(String className) {
-        if("int".equals(className)) {
+        if ("int".equals(className)) {
             return "0";
         }
-        if("short".equals(className)) {
+        if ("short".equals(className)) {
             return "0";
         }
-        if("long".equals(className)) {
+        if ("long".equals(className)) {
             return "0L";
         }
-        if("float".equals(className)) {
+        if ("float".equals(className)) {
             return "0f";
         }
-        if("double".equals(className)) {
+        if ("double".equals(className)) {
             return "0.0";
         }
-        if("boolean".equals(className)) {
+        if ("boolean".equals(className)) {
             return "false";
         }
         if ("char".equals(className)) {
@@ -105,4 +155,6 @@
     public abstract List<URL> getResources(String name);
 
     public abstract ModelClass findClass(Class classType);
+
+    public abstract TypeUtil createTypeUtil();
 }
diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/ModelClass.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/ModelClass.java
index 6e5f41b..4011535 100644
--- a/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/ModelClass.java
+++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/ModelClass.java
@@ -62,4 +62,21 @@
     ModelMethod[] getMethods(String name, int numParameters);
 
     ModelClass getSuperclass();
+
+    String getCanonicalName();
+
+    /**
+     * Since when this class is available. Important for Binding expressions so that we don't
+     * call non-existing APIs when setting UI.
+     *
+     * @return The SDK_INT where this method was added. If it is not a framework method, should
+     * return 1.
+     */
+    int getMinApi();
+
+    /**
+     * Returns the JNI description of the method which can be used to lookup it in SDK.
+     * @see com.android.databinding.reflection.TypeUtil
+     */
+    String getJniDescription();
 }
diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/ModelMethod.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/ModelMethod.java
index 822f598..cea57c7 100644
--- a/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/ModelMethod.java
+++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/ModelMethod.java
@@ -29,4 +29,19 @@
     boolean isPublic();
 
     boolean isStatic();
+
+    /**
+     * Since when this method is available. Important for Binding expressions so that we don't
+     * call non-existing APIs when setting UI.
+     *
+     * @return The SDK_INT where this method was added. If it is not a framework method, should
+     * return 1.
+     */
+    int getMinApi();
+
+    /**
+     * Returns the JNI description of the method which can be used to lookup it in SDK.
+     * @see com.android.databinding.reflection.TypeUtil
+     */
+    String getJniDescription();
 }
diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/SdkUtil.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/SdkUtil.java
new file mode 100644
index 0000000..6efe2bd
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/SdkUtil.java
@@ -0,0 +1,157 @@
+/*
+ * 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 com.android.databinding.reflection;
+
+import com.google.common.base.Preconditions;
+import com.android.databinding.util.L;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+/**
+ * Class that is used for SDK related stuff.
+ * <p>
+ * Must be initialized with the sdk location to work properly
+ */
+public class SdkUtil {
+
+    static File mSdkPath;
+
+    static ApiChecker mApiChecker;
+
+    static int mMinSdk;
+
+    public static void initialize(int minSdk, File sdkPath) {
+        mSdkPath = sdkPath;
+        mMinSdk = minSdk;
+        mApiChecker = new ApiChecker(new File(sdkPath.getAbsolutePath()
+                + "/platform-tools/api/api-versions.xml"));
+        L.d("SdkUtil init, minSdk: %s", minSdk);
+    }
+
+    public static int getMinApi(ModelClass modelClass) {
+        return mApiChecker.getMinApi(modelClass.getJniDescription(), null);
+    }
+
+    public static int getMinApi(ModelMethod modelMethod) {
+        ModelClass declaringClass = modelMethod.getDeclaringClass();
+        Preconditions.checkNotNull(mApiChecker, "should've initialized api checker");
+        while (declaringClass != null) {
+            String classDesc = declaringClass.getJniDescription();
+            String methodDesc = modelMethod.getJniDescription();
+            int result = mApiChecker.getMinApi(classDesc, methodDesc);
+            L.d("checking method api for %s, class:%s method:%s. result: %d", modelMethod.getName(),
+                    classDesc, methodDesc, result);
+            if (result > 1) {
+                return result;
+            }
+            declaringClass = declaringClass.getSuperclass();
+        }
+        return 1;
+    }
+
+    private static class ApiChecker {
+
+        private Map<String, Integer> mFullLookup = new HashMap<>();
+
+        private Document mDoc;
+
+        private XPath mXPath;
+
+        public ApiChecker(File apiFile) {
+            try {
+                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+                DocumentBuilder builder = factory.newDocumentBuilder();
+                mDoc = builder.parse(apiFile);
+                XPathFactory xPathFactory = XPathFactory.newInstance();
+                mXPath = xPathFactory.newXPath();
+                buildFullLookup();
+            } catch (Throwable t) {
+                L.e(t, "cannot load api descriptions from %s", apiFile);
+            }
+        }
+
+        private void buildFullLookup() throws XPathExpressionException {
+            NodeList allClasses = mDoc.getChildNodes().item(0).getChildNodes();
+            for (int j = 0; j < allClasses.getLength(); j++) {
+                Node node = allClasses.item(j);
+                if (node.getNodeType() != Node.ELEMENT_NODE || !"class"
+                        .equals(node.getNodeName())) {
+                    continue;
+                }
+                //L.d("checking node %s", node.getAttributes().getNamedItem("name").getNodeValue());
+                int classSince = getSince(node);
+                String classDesc = node.getAttributes().getNamedItem("name").getNodeValue();
+
+                final NodeList childNodes = node.getChildNodes();
+                for (int i = 0; i < childNodes.getLength(); i++) {
+                    Node child = childNodes.item(i);
+                    if (child.getNodeType() != Node.ELEMENT_NODE || !"method"
+                            .equals(child.getNodeName())) {
+                        continue;
+                    }
+                    int methodSince = getSince(child);
+                    int since = Math.max(classSince, methodSince);
+                    if (since > SdkUtil.mMinSdk) {
+                        String methodDesc = child.getAttributes().getNamedItem("name")
+                                .getNodeValue();
+                        String key = cacheKey(classDesc, methodDesc);
+                        L.d("adding method lookup %s as %s", key, since);
+                        mFullLookup.put(key, since);
+                    }
+                }
+            }
+        }
+
+        public int getMinApi(String classDesc, String methodOrFieldDesc) {
+            if (mDoc == null || mXPath == null) {
+                return 1;
+            }
+            if (classDesc == null || classDesc.isEmpty()) {
+                return 1;
+            }
+            final String key = cacheKey(classDesc, methodOrFieldDesc);
+            Integer since = mFullLookup.get(key);
+            return since == null ? 1 : since;
+        }
+
+        private static String cacheKey(String classDesc, String methodOrFieldDesc) {
+            return classDesc + "~" + methodOrFieldDesc;
+        }
+
+        private static int getSince(Node node) {
+            final Node since = node.getAttributes().getNamedItem("since");
+            if (since != null) {
+                final String nodeValue = since.getNodeValue();
+                if (nodeValue != null && !nodeValue.isEmpty()) {
+                    try {
+                        return Integer.parseInt(nodeValue);
+                    } catch (Throwable t) {
+                    }
+                }
+            }
+
+            return 1;
+        }
+    }
+}
diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/TypeUtil.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/TypeUtil.java
new file mode 100644
index 0000000..50ae3c2
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/TypeUtil.java
@@ -0,0 +1,57 @@
+/*
+ * 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 com.android.databinding.reflection;
+
+public abstract class TypeUtil {
+
+    public static final String BYTE = "B";
+
+    public static final String CHAR = "C";
+
+    public static final String DOUBLE = "D";
+
+    public static final String FLOAT = "F";
+
+    public static final String INT = "I";
+
+    public static final String LONG = "J";
+
+    public static final String SHORT = "S";
+
+    public static final String VOID = "V";
+
+    public static final String BOOLEAN = "Z";
+
+    public static final String ARRAY = "[";
+
+    public static final String CLASS_PREFIX = "L";
+
+    public static final String CLASS_SUFFIX = ";";
+
+    private static TypeUtil sInstance;
+
+    abstract public String getDescription(ModelClass modelClass);
+
+    abstract public String getDescription(ModelMethod modelMethod);
+
+    public static TypeUtil getInstance() {
+        if (sInstance == null) {
+            sInstance = ModelAnalyzer.getInstance().createTypeUtil();
+        }
+        return sInstance;
+    }
+}
diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/AnnotationAnalyzer.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/annotation/AnnotationAnalyzer.java
similarity index 85%
rename from tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/AnnotationAnalyzer.java
rename to tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/annotation/AnnotationAnalyzer.java
index 6c1dd76..d3e32ed 100644
--- a/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/AnnotationAnalyzer.java
+++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/annotation/AnnotationAnalyzer.java
@@ -13,10 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.databinding.reflection;
+package com.android.databinding.reflection.annotation;
 
 import com.google.common.collect.ImmutableMap;
 
+import com.android.databinding.reflection.Callable;
+import com.android.databinding.reflection.ModelAnalyzer;
+import com.android.databinding.reflection.ModelClass;
+import com.android.databinding.reflection.ModelField;
+import com.android.databinding.reflection.ModelMethod;
+import com.android.databinding.reflection.TypeUtil;
 import com.android.databinding.util.L;
 
 import org.apache.commons.lang3.StringUtils;
@@ -45,39 +51,7 @@
 
 public class AnnotationAnalyzer extends ModelAnalyzer {
 
-    public static final String[] LIST_CLASS_NAMES = {
-            "java.util.List",
-            "android.util.SparseArray",
-            "android.util.SparseBooleanArray",
-            "android.util.SparseIntArray",
-            "android.util.SparseLongArray",
-            "android.util.LongSparseArray",
-            "android.support.v4.util.LongSparseArray",
-    };
-
-    public static final String MAP_CLASS_NAME = "java.util.Map";
-
-    public static final String STRING_CLASS_NAME = "java.lang.String";
-
-    public static final String OBJECT_CLASS_NAME = "java.lang.Object";
-
-    static AnnotationAnalyzer instance;
-    private static final String OBSERVABLE_CLASS_NAME = "android.binding.Observable";
-    private static final String OBSERVABLE_LIST_CLASS_NAME = "android.binding.ObservableList";
-    private static final String OBSERVABLE_MAP_CLASS_NAME = "android.binding.ObservableMap";
-    private static final String[] OBSERVABLE_FIELDS = {
-            "com.android.databinding.library.ObservableBoolean",
-            "com.android.databinding.library.ObservableByte",
-            "com.android.databinding.library.ObservableChar",
-            "com.android.databinding.library.ObservableShort",
-            "com.android.databinding.library.ObservableInt",
-            "com.android.databinding.library.ObservableLong",
-            "com.android.databinding.library.ObservableFloat",
-            "com.android.databinding.library.ObservableDouble",
-            "com.android.databinding.library.ObservableField",
-    };
-    private static final String I_VIEW_DATA_BINDER = "com.android.databinding.library.IViewDataBinder";
-    private static final Map<String, TypeKind> PRIMITIVE_TYPES =
+    public static final Map<String, TypeKind> PRIMITIVE_TYPES =
             new ImmutableMap.Builder<String, TypeKind>()
                     .put("boolean", TypeKind.BOOLEAN)
                     .put("byte", TypeKind.BYTE)
@@ -89,7 +63,7 @@
                     .put("double", TypeKind.DOUBLE)
                     .build();
 
-    public final ProcessingEnvironment processingEnv;
+    public final ProcessingEnvironment mProcessingEnv;
 
     private AnnotationClass[] mListTypes;
     private AnnotationClass mMapType;
@@ -102,8 +76,8 @@
     private AnnotationClass mIViewDataBinderType;
 
     public AnnotationAnalyzer(ProcessingEnvironment processingEnvironment) {
-        processingEnv = processingEnvironment;
-        instance = this;
+        mProcessingEnv = processingEnvironment;
+        setInstance(this);
     }
 
     public AnnotationClass[] getListTypes() {
@@ -120,6 +94,10 @@
         return mListTypes;
     }
 
+    public static AnnotationAnalyzer get() {
+        return (AnnotationAnalyzer) getInstance();
+    }
+
     public AnnotationClass getMapType() {
         if (mMapType == null) {
             mMapType = loadClassErasure(MAP_CLASS_NAME);
@@ -154,7 +132,7 @@
         }
         return mObservableListType;
     }
-    
+
     private AnnotationClass getObservableMapType() {
         if (mObservableMapType == null) {
             mObservableMapType = loadClassErasure(OBSERVABLE_MAP_CLASS_NAME);
@@ -185,7 +163,7 @@
     }
 
     private TypeElement findType(String type) {
-        return processingEnv.getElementUtils().getTypeElement(type);
+        return mProcessingEnv.getElementUtils().getTypeElement(type);
     }
 
     @Override
@@ -196,9 +174,9 @@
     @Override
     public Callable findMethod(ModelClass modelClass, String name,
             List<ModelClass> args, boolean staticAccess) {
-        AnnotationClass clazz = (AnnotationClass)modelClass;
+        AnnotationClass clazz = (AnnotationClass) modelClass;
         // TODO implement properly
-        for (String methodName :  new String[]{"set" + StringUtils.capitalize(name), name}) {
+        for (String methodName : new String[]{"set" + StringUtils.capitalize(name), name}) {
             for (ModelMethod method : clazz.getMethods(methodName, args.size())) {
                 if (method.isStatic() == staticAccess) {
                     ModelClass[] parameters = method.getParameterTypes();
@@ -226,13 +204,14 @@
             }
         }
         String message = "cannot find method '" + name + "' in class " + clazz.toJavaCode();
-        printMessage(Diagnostic.Kind.ERROR, message);
-        throw new IllegalArgumentException(message);
+        IllegalArgumentException e = new IllegalArgumentException(message);
+        L.e(e, "cannot find method %s in class %s", name, clazz.toJavaCode());
+        throw e;
     }
 
     @Override
     public boolean isObservable(ModelClass modelClass) {
-        AnnotationClass annotationClass = (AnnotationClass)modelClass;
+        AnnotationClass annotationClass = (AnnotationClass) modelClass;
         return getObservableType().isAssignableFrom(annotationClass) ||
                 getObservableListType().isAssignableFrom(annotationClass) ||
                 getObservableMapType().isAssignableFrom(annotationClass);
@@ -240,8 +219,9 @@
 
     @Override
     public boolean isObservableField(ModelClass modelClass) {
-        AnnotationClass annotationClass = (AnnotationClass)modelClass;
-        AnnotationClass erasure = new AnnotationClass(getTypeUtils().erasure(annotationClass.mTypeMirror));
+        AnnotationClass annotationClass = (AnnotationClass) modelClass;
+        AnnotationClass erasure = new AnnotationClass(
+                getTypeUtils().erasure(annotationClass.mTypeMirror));
         for (AnnotationClass observableField : getObservableFieldTypes()) {
             if (observableField.isAssignableFrom(erasure)) {
                 return true;
@@ -264,7 +244,7 @@
 
     @Override
     public Callable findMethodOrField(ModelClass modelClass, String name, boolean staticAccess) {
-        AnnotationClass annotationClass = (AnnotationClass)modelClass;
+        AnnotationClass annotationClass = (AnnotationClass) modelClass;
         for (String methodName :
                 new String[]{"get" + StringUtils.capitalize(name),
                         "is" + StringUtils.capitalize(name), name}) {
@@ -375,13 +355,21 @@
 
             String baseClassName = className.substring(0, templateOpenIndex);
             TypeElement typeElement = getTypeElement(baseClassName, imports);
+            if (typeElement == null) {
+                L.e("cannot find type element for %s", baseClassName);
+                return null;
+            }
 
             ArrayList<String> templateParameters = splitTemplateParameters(paramStr);
             TypeMirror[] typeArgs = new TypeMirror[templateParameters.size()];
             for (int i = 0; i < typeArgs.length; i++) {
                 typeArgs[i] = findClass(templateParameters.get(i), imports).mTypeMirror;
+                if (typeArgs[i] == null) {
+                    L.e("cannot find type argument for %s in %s", templateParameters.get(i),
+                            baseClassName);
+                    return null;
+                }
             }
-
             Types typeUtils = getTypeUtils();
             declaredType = typeUtils.getDeclaredType(typeElement, typeArgs);
         }
@@ -457,8 +445,7 @@
                 urls.add(resources.nextElement());
             }
         } catch (IOException e) {
-            printMessage(Diagnostic.Kind.ERROR, "IOException while getting resources: " +
-                    e.getLocalizedMessage());
+            L.e(e, "IOException while getting resources:");
         }
 
         return urls;
@@ -470,14 +457,19 @@
     }
 
     public Types getTypeUtils() {
-        return processingEnv.getTypeUtils();
+        return mProcessingEnv.getTypeUtils();
     }
 
     public Elements getElementUtils() {
-        return processingEnv.getElementUtils();
+        return mProcessingEnv.getElementUtils();
     }
 
-    public void printMessage(Diagnostic.Kind kind, String message) {
-        processingEnv.getMessager().printMessage(kind, message);
+    public ProcessingEnvironment getProcessingEnv() {
+        return mProcessingEnv;
+    }
+
+    @Override
+    public TypeUtil createTypeUtil() {
+        return new AnnotationTypeUtil(this);
     }
 }
diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/AnnotationClass.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/annotation/AnnotationClass.java
similarity index 88%
rename from tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/AnnotationClass.java
rename to tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/annotation/AnnotationClass.java
index 00be90f..de54044 100644
--- a/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/AnnotationClass.java
+++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/annotation/AnnotationClass.java
@@ -13,9 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.databinding.reflection;
+package com.android.databinding.reflection.annotation;
 
-import com.android.databinding.store.SetterStore;
+import com.android.databinding.reflection.ModelClass;
+import com.android.databinding.reflection.ModelMethod;
+import com.android.databinding.reflection.SdkUtil;
+import com.android.databinding.reflection.TypeUtil;
+import com.android.databinding.util.L;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -33,7 +37,7 @@
 import javax.lang.model.util.Types;
 import javax.tools.Diagnostic;
 
-public class AnnotationClass implements ModelClass {
+class AnnotationClass implements ModelClass {
 
     final TypeMirror mTypeMirror;
 
@@ -100,15 +104,13 @@
                 }
             }
             if (foundInterface == null) {
-                printMessage(Diagnostic.Kind.ERROR,
-                        "Detected " + interfaceType + " type for " + mTypeMirror +
+                L.e("Detected " + interfaceType + " type for " + mTypeMirror +
                                 ", but not able to find the implemented interface.");
                 return null;
             }
         }
         if (foundInterface.getKind() != TypeKind.DECLARED) {
-            printMessage(Diagnostic.Kind.ERROR,
-                    "Found " + interfaceType + " type for " + mTypeMirror +
+            L.e("Found " + interfaceType + " type for " + mTypeMirror +
                             ", but it isn't a declared type: " + foundInterface);
             return null;
         }
@@ -286,6 +288,21 @@
     }
 
     @Override
+    public String getCanonicalName() {
+        return getTypeUtils().erasure(mTypeMirror).toString();
+    }
+
+    @Override
+    public int getMinApi() {
+        return SdkUtil.getMinApi(this);
+    }
+
+    @Override
+    public String getJniDescription() {
+        return TypeUtil.getInstance().getDescription(this);
+    }
+
+    @Override
     public boolean equals(Object obj) {
         if (obj instanceof AnnotationClass) {
             return getTypeUtils().isSameType(mTypeMirror, ((AnnotationClass) obj).mTypeMirror);
@@ -300,31 +317,27 @@
     }
 
     private static Types getTypeUtils() {
-        return AnnotationAnalyzer.instance.processingEnv.getTypeUtils();
+        return AnnotationAnalyzer.get().mProcessingEnv.getTypeUtils();
     }
 
     private static Elements getElementUtils() {
-        return AnnotationAnalyzer.instance.processingEnv.getElementUtils();
+        return AnnotationAnalyzer.get().mProcessingEnv.getElementUtils();
     }
 
     private static AnnotationClass[] getListTypes() {
-        return AnnotationAnalyzer.instance.getListTypes();
+        return AnnotationAnalyzer.get().getListTypes();
     }
 
     private static AnnotationClass getMapType() {
-        return AnnotationAnalyzer.instance.getMapType();
+        return AnnotationAnalyzer.get().getMapType();
     }
 
     private static AnnotationClass getStringType() {
-        return AnnotationAnalyzer.instance.getStringType();
+        return AnnotationAnalyzer.get().getStringType();
     }
 
     private static AnnotationClass getObjectType() {
-        return AnnotationAnalyzer.instance.getObjectType();
-    }
-
-    private static void printMessage(Diagnostic.Kind kind, String message) {
-        AnnotationAnalyzer.instance.printMessage(kind, message);
+        return AnnotationAnalyzer.get().getObjectType();
     }
 
     @Override
diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/AnnotationField.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/annotation/AnnotationField.java
similarity index 88%
rename from tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/AnnotationField.java
rename to tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/annotation/AnnotationField.java
index a268853..f283366 100644
--- a/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/AnnotationField.java
+++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/annotation/AnnotationField.java
@@ -13,12 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.databinding.reflection;
+package com.android.databinding.reflection.annotation;
+
+import com.android.databinding.reflection.ModelField;
 
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.element.VariableElement;
 
-public class AnnotationField implements ModelField {
+class AnnotationField implements ModelField {
 
     final VariableElement mField;
 
@@ -38,7 +40,7 @@
     public boolean equals(Object obj) {
         if (obj instanceof AnnotationField) {
             AnnotationField that = (AnnotationField) obj;
-            return mDeclaredClass.equals(that.mDeclaredClass) && AnnotationAnalyzer.instance
+            return mDeclaredClass.equals(that.mDeclaredClass) && AnnotationAnalyzer.get()
                     .getTypeUtils().isSameType(mField.asType(), that.mField.asType());
         } else {
             return false;
diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/AnnotationMethod.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/annotation/AnnotationMethod.java
similarity index 70%
rename from tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/AnnotationMethod.java
rename to tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/annotation/AnnotationMethod.java
index 0096759..33dbab9 100644
--- a/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/AnnotationMethod.java
+++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/annotation/AnnotationMethod.java
@@ -13,27 +13,32 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.databinding.reflection;
+package com.android.databinding.reflection.annotation;
+
+import com.android.databinding.reflection.ModelClass;
+import com.android.databinding.reflection.ModelMethod;
+import com.android.databinding.reflection.SdkUtil;
+import com.android.databinding.reflection.TypeUtil;
 
 import java.util.List;
 
 import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.Modifier;
-import javax.lang.model.element.VariableElement;
 import javax.lang.model.type.DeclaredType;
 import javax.lang.model.type.ExecutableType;
 import javax.lang.model.type.TypeMirror;
 import javax.lang.model.util.Types;
 
-public class AnnotationMethod implements ModelMethod {
+class AnnotationMethod implements ModelMethod {
     final ExecutableType mMethod;
     final DeclaredType mDeclaringType;
     final ExecutableElement mExecutableElement;
+    int mApiLevel = -1; // calculated on demand
 
     public AnnotationMethod(DeclaredType declaringType, ExecutableElement executableElement) {
         mDeclaringType = declaringType;
         mExecutableElement = executableElement;
-        Types typeUtils = AnnotationAnalyzer.instance.getTypeUtils();
+        Types typeUtils = AnnotationAnalyzer.get().getTypeUtils();
         mMethod = (ExecutableType) typeUtils.asMemberOf(declaringType, executableElement);
     }
 
@@ -74,4 +79,27 @@
     public boolean isStatic() {
         return mExecutableElement.getModifiers().contains(Modifier.STATIC);
     }
+
+    @Override
+    public int getMinApi() {
+        if (mApiLevel == -1) {
+            mApiLevel = SdkUtil.getMinApi(this);
+        }
+        return mApiLevel;
+    }
+
+    @Override
+    public String getJniDescription() {
+        return TypeUtil.getInstance().getDescription(this);
+    }
+
+    @Override
+    public String toString() {
+        return "AnnotationMethod{" +
+                "mMethod=" + mMethod +
+                ", mDeclaringType=" + mDeclaringType +
+                ", mExecutableElement=" + mExecutableElement +
+                ", mApiLevel=" + mApiLevel +
+                '}';
+    }
 }
diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/annotation/AnnotationTypeUtil.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/annotation/AnnotationTypeUtil.java
new file mode 100644
index 0000000..03f93dc
--- /dev/null
+++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/reflection/annotation/AnnotationTypeUtil.java
@@ -0,0 +1,105 @@
+/*
+ * 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 com.android.databinding.reflection.annotation;
+
+import com.android.databinding.reflection.ModelClass;
+import com.android.databinding.reflection.ModelMethod;
+import com.android.databinding.reflection.TypeUtil;
+
+import java.util.List;
+
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.ExecutableType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.type.TypeVariable;
+
+public class AnnotationTypeUtil extends TypeUtil {
+    javax.lang.model.util.Types mTypes;
+
+    public AnnotationTypeUtil(
+            AnnotationAnalyzer annotationAnalyzer) {
+        mTypes = annotationAnalyzer.getTypeUtils();
+    }
+
+    @Override
+    public String getDescription(ModelClass modelClass) {
+        // TODO use interface
+        return modelClass.getCanonicalName().replace('.', '/');
+    }
+
+    @Override
+    public String getDescription(ModelMethod modelMethod) {
+        // TODO use interface
+        return modelMethod.getName() + getDescription(
+                ((AnnotationMethod) modelMethod).mExecutableElement.asType());
+    }
+
+    private String getDescription(TypeMirror typeMirror) {
+        if (typeMirror == null) {
+            throw new UnsupportedOperationException();
+        }
+        switch (typeMirror.getKind()) {
+            case BOOLEAN:
+                return BOOLEAN;
+            case BYTE:
+                return BYTE;
+            case SHORT:
+                return SHORT;
+            case INT:
+                return INT;
+            case LONG:
+                return LONG;
+            case CHAR:
+                return CHAR;
+            case FLOAT:
+                return FLOAT;
+            case DOUBLE:
+                return DOUBLE;
+            case DECLARED:
+                return CLASS_PREFIX + mTypes.erasure(typeMirror).toString().replace('.', '/') + CLASS_SUFFIX;
+            case VOID:
+                return VOID;
+            case ARRAY:
+                final ArrayType arrayType = (ArrayType) typeMirror;
+                final String componentType = getDescription(arrayType.getComponentType());
+                return ARRAY + componentType;
+            case TYPEVAR:
+                final TypeVariable typeVariable = (TypeVariable) typeMirror;
+                final String name = typeVariable.toString();
+                return CLASS_PREFIX + name.replace('.', '/') + CLASS_SUFFIX;
+            case EXECUTABLE:
+                final ExecutableType executableType = (ExecutableType) typeMirror;
+                final int argStart = mTypes.erasure(executableType).toString().indexOf('(');
+                final String methodName = executableType.toString().substring(0, argStart);
+                final String args = joinArgs(executableType.getParameterTypes());
+                // TODO detect constructor?
+                return methodName + "(" + args + ")" + getDescription(
+                        executableType.getReturnType());
+            default:
+                throw new UnsupportedOperationException("cannot understand type "
+                        + typeMirror.toString() + ", kind:" + typeMirror.getKind().name());
+        }
+    }
+
+    private String joinArgs(List<? extends TypeMirror> mirrorList) {
+        StringBuilder result = new StringBuilder();
+        for (TypeMirror mirror : mirrorList) {
+            result.append(getDescription(mirror));
+        }
+        return result.toString();
+    }
+}
diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/store/SetterStore.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/store/SetterStore.java
index 008f2da..77e1e34 100644
--- a/tools/data-binding/compiler/src/main/java/com/android/databinding/store/SetterStore.java
+++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/store/SetterStore.java
@@ -15,10 +15,12 @@
  */
 package com.android.databinding.store;
 
-import com.android.databinding.reflection.AnnotationAnalyzer;
 import com.android.databinding.reflection.ModelAnalyzer;
 import com.android.databinding.reflection.ModelClass;
 import com.android.databinding.reflection.ModelMethod;
+import com.android.databinding.util.L;
+
+import org.apache.commons.lang3.StringUtils;
 
 import java.io.File;
 import java.io.IOException;
@@ -43,7 +45,6 @@
 import javax.lang.model.type.ArrayType;
 import javax.lang.model.type.DeclaredType;
 import javax.lang.model.type.TypeMirror;
-import javax.tools.Diagnostic;
 import javax.tools.FileObject;
 import javax.tools.StandardLocation;
 
@@ -111,13 +112,9 @@
             }
             return new SetterStore(modelAnalyzer, store);
         } catch (IOException e) {
-            printMessage(Diagnostic.Kind.ERROR, "Could not read SetterStore intermediate file: " +
-                    e.getLocalizedMessage());
-            e.printStackTrace();
+            L.e(e, "Could not read SetterStore intermediate file");
         } catch (ClassNotFoundException e) {
-            printMessage(Diagnostic.Kind.ERROR, "Could not read SetterStore intermediate file: " +
-                    e.getLocalizedMessage());
-            e.printStackTrace();
+            L.e(e, "Could not read SetterStore intermediate file");
         }
         return new SetterStore(modelAnalyzer, store);
     }
@@ -255,8 +252,7 @@
         Filer filer = processingEnvironment.getFiler();
         FileObject resource = filer.createResource(StandardLocation.CLASS_OUTPUT,
                 SetterStore.class.getPackage().getName(), "setter_store.bin");
-        printMessage(Diagnostic.Kind.NOTE, "============= Writing intermediate file: " +
-                resource.getName());
+        L.d("============= Writing intermediate file: %s", resource.getName());
         ObjectOutputStream out = null;
         try {
             out = new ObjectOutputStream(resource.openOutputStream());
@@ -269,17 +265,17 @@
         }
     }
 
-    public String getSetterCall(String attribute, ModelClass viewType,
-            ModelClass valueType, String viewExpression, String valueExpression,
-            Map<String, String> imports) {
+    public SetterCall getSetterCall(String attribute, ModelClass viewType,
+            ModelClass valueType, Map<String, String> imports) {
         if (!attribute.startsWith("android:")) {
             int colon = attribute.indexOf(':');
             if (colon >= 0) {
                 attribute = attribute.substring(colon + 1);
             }
         }
+        SetterCall setterCall = null;
         MethodDescription adapter = null;
-        String setterName = null;
+        MethodDescription conversionMethod = null;
         if (viewType != null) {
             HashMap<AccessorKey, MethodDescription> adapters = mStore.adapterMethods.get(attribute);
             ModelMethod bestSetterMethod = getBestSetter(viewType, valueType, attribute, imports);
@@ -288,7 +284,7 @@
             if (bestSetterMethod != null) {
                 bestViewType = bestSetterMethod.getDeclaringClass();
                 bestValueType = bestSetterMethod.getParameterTypes()[0];
-                setterName = bestSetterMethod.getName();
+                setterCall = new ModelMethodSetter(bestSetterMethod);
             }
 
             if (adapters != null) {
@@ -299,8 +295,7 @@
                         if (adapterViewType.isAssignableFrom(viewType)) {
                             try {
                                 ModelClass adapterValueType = mClassAnalyzer
-                                        .findClass(key.valueType,
-                                                imports);
+                                        .findClass(key.valueType, imports);
                                 boolean isBetterView = bestViewType == null ||
                                         bestValueType.isAssignableFrom(adapterValueType);
                                 if (isBetterParameter(valueType, adapterValueType, bestValueType,
@@ -308,35 +303,29 @@
                                     bestViewType = adapterViewType;
                                     bestValueType = adapterValueType;
                                     adapter = adapters.get(key);
+                                    setterCall = new AdapterSetter(adapter);
                                 }
 
                             } catch (Exception e) {
-                                printMessage(Diagnostic.Kind.NOTE,
-                                        "Unknown class: " + key.valueType);
+                                L.e(e, "Unknown class: %s", key.valueType);
                             }
                         }
                     } catch (Exception e) {
-                        printMessage(Diagnostic.Kind.NOTE, "Unknown class: " + key.viewType);
+                        L.e(e, "Unknown class: %s", key.viewType);
                     }
                 }
             }
 
-            MethodDescription conversionMethod = getConversionMethod(valueType, bestValueType,
-                    imports);
-            if (conversionMethod != null) {
-                valueExpression = conversionMethod.type + "." + conversionMethod.method + "(" +
-                        valueExpression + ")";
-            }
+            conversionMethod = getConversionMethod(valueType, bestValueType, imports);
         }
-        if (adapter == null) {
-            if (setterName == null) {
-                setterName = getDefaultSetter(attribute);
-            }
-            return viewExpression + "." + setterName + "(" + valueExpression + ")";
-        } else {
-            return adapter.type + "." + adapter.method + "(" + viewExpression + ", " +
-                    valueExpression + ")";
+        if (setterCall == null) {
+            setterCall = new DummySetter(getDefaultSetter(attribute));
+            // might be an include tag etc. just note it and continue.
+            L.d("Cannot find the setter for attribute " + attribute + ". might be an include file,"
+                    + " moving on.");
         }
+        setterCall.setConverter(conversionMethod);
+        return setterCall;
     }
 
     public boolean isUntaggable(String viewType) {
@@ -345,15 +334,14 @@
 
     private ModelMethod getBestSetter(ModelClass viewType, ModelClass argumentType,
             String attribute, Map<String, String> imports) {
-        String setterName = null;
-
+        List<String> setterCandidates = new ArrayList<>();
         HashMap<String, MethodDescription> renamed = mStore.renamedMethods.get(attribute);
         if (renamed != null) {
             for (String className : renamed.keySet()) {
                 try {
                     ModelClass renamedViewType = mClassAnalyzer.findClass(className, imports);
                     if (renamedViewType.isAssignableFrom(viewType)) {
-                        setterName = renamed.get(className).method;
+                        setterCandidates.add(renamed.get(className).method);
                         break;
                     }
                 } catch (Exception e) {
@@ -361,34 +349,40 @@
                 }
             }
         }
-        if (setterName == null) {
-            setterName = getDefaultSetter(attribute);
-        }
-        ModelMethod[] methods = viewType.getMethods(setterName, 1);
+        setterCandidates.add(getDefaultSetter(attribute));
+        setterCandidates.add(trimAttributeNamespace(attribute));
 
-        ModelClass bestParameterType = null;
         ModelMethod bestMethod = null;
-        List<ModelClass> args = new ArrayList<>();
-        args.add(argumentType);
-        for (ModelMethod method : methods) {
-            ModelClass[] parameterTypes = method.getParameterTypes();
-            if (method.getReturnType(args).isVoid() && !method.isStatic() && method.isPublic()) {
-                ModelClass param = parameterTypes[0];
-                if (isBetterParameter(argumentType, param, bestParameterType, true, imports)) {
-                    bestParameterType = param;
-                    bestMethod = method;
+        for (String name : setterCandidates) {
+            ModelMethod[] methods = viewType.getMethods(name, 1);
+            ModelClass bestParameterType = null;
+
+            List<ModelClass> args = new ArrayList<>();
+            args.add(argumentType);
+            for (ModelMethod method : methods) {
+                ModelClass[] parameterTypes = method.getParameterTypes();
+                if (method.getReturnType(args).isVoid() && !method.isStatic() && method
+                        .isPublic()) {
+                    ModelClass param = parameterTypes[0];
+                    if (isBetterParameter(argumentType, param, bestParameterType, true, imports)) {
+                        bestParameterType = param;
+                        bestMethod = method;
+
+                    }
                 }
             }
         }
         return bestMethod;
+
+    }
+
+    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) {
-        int colonIndex = attribute.indexOf(':');
-        String propertyName;
-        propertyName = Character.toUpperCase(attribute.charAt(colonIndex + 1)) +
-                attribute.substring(colonIndex + 2);
-        return "set" + propertyName;
+        return "set" + StringUtils.capitalize(trimAttributeNamespace(attribute));
     }
 
     private boolean isBetterParameter(ModelClass argument, ModelClass parameter,
@@ -463,12 +457,12 @@
                                     return conversion.get(toClassName);
                                 }
                             } catch (Exception e) {
-                                printMessage(Diagnostic.Kind.NOTE, "Unknown class: " + toClassName);
+                                L.d(e, "Unknown class: %s", toClassName);
                             }
                         }
                     }
                 } catch (Exception e) {
-                    printMessage(Diagnostic.Kind.NOTE, "Unknown class: " + fromClassName);
+                    L.d(e, "Unknown class: %s", fromClassName);
                 }
             }
         }
@@ -549,15 +543,6 @@
         }
     }
 
-    private static void printMessage(Diagnostic.Kind kind, String message) {
-        ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
-        if (modelAnalyzer instanceof AnnotationAnalyzer) {
-            ((AnnotationAnalyzer) modelAnalyzer).printMessage(kind, message);
-        } else {
-            System.out.println(message);
-        }
-    }
-
     private static class MethodDescription implements Serializable {
 
         private static final long serialVersionUID = 1;
@@ -569,12 +554,14 @@
         public MethodDescription(String type, String method) {
             this.type = type;
             this.method = method;
+            L.d("BINARY created method desc 1 %s %s", type, method );
         }
 
         public MethodDescription(ExecutableElement method) {
             TypeElement enclosingClass = (TypeElement) method.getEnclosingElement();
             this.type = enclosingClass.getQualifiedName().toString();
             this.method = method.getSimpleName().toString();
+            L.d("BINARY created method desc 2 %s %s, %s", type, this.method, method);
         }
 
         @Override
@@ -654,4 +641,85 @@
             return this;
         }
     }
+
+    public static class DummySetter extends SetterCall {
+        private String mMethodName;
+
+        public DummySetter(String methodName) {
+            mMethodName = methodName;
+        }
+
+        @Override
+        public String toJavaInternal(String viewExpression, String valueExpression) {
+            return viewExpression + "." + mMethodName + "(" + valueExpression + ")";
+        }
+
+        @Override
+        public int getMinApi() {
+            return 1;
+        }
+
+
+    }
+
+    public static class AdapterSetter extends SetterCall {
+        final MethodDescription mAdapter;
+
+        public AdapterSetter(MethodDescription adapter) {
+            mAdapter = adapter;
+        }
+
+        @Override
+        public String toJavaInternal(String viewExpression, String valueExpression) {
+            return mAdapter.type + "." + mAdapter.method + "(" + viewExpression + ", " +
+                    valueExpression + ")";
+        }
+
+        @Override
+        public int getMinApi() {
+            return 1;
+        }
+    }
+
+    public static class ModelMethodSetter extends SetterCall {
+        final ModelMethod mModelMethod;
+
+        public ModelMethodSetter(ModelMethod modelMethod) {
+            mModelMethod = modelMethod;
+        }
+
+        @Override
+        public String toJavaInternal(String viewExpression, String valueExpression) {
+            return viewExpression + "." + mModelMethod.getName() + "(" + valueExpression + ")";
+        }
+
+        @Override
+        public int getMinApi() {
+            return mModelMethod.getMinApi();
+        }
+    }
+
+    public static abstract class SetterCall {
+        private MethodDescription mConverter;
+
+        public SetterCall() {
+        }
+
+        public void setConverter(MethodDescription converter) {
+            mConverter = converter;
+        }
+
+        protected abstract String toJavaInternal(String viewExpression, String converted);
+
+        public final String toJava(String viewExpression, String valueExpression) {
+            return toJavaInternal(viewExpression, convertValue(valueExpression));
+        }
+
+        protected String convertValue(String valueExpression) {
+            return mConverter == null ? valueExpression :
+                    mConverter.type + "." + mConverter.method + "(" + valueExpression + ")";
+        }
+
+        abstract public int getMinApi();
+    }
 }
diff --git a/tools/data-binding/compiler/src/main/java/com/android/databinding/util/L.java b/tools/data-binding/compiler/src/main/java/com/android/databinding/util/L.java
index c59e3a7..eea89a2 100644
--- a/tools/data-binding/compiler/src/main/java/com/android/databinding/util/L.java
+++ b/tools/data-binding/compiler/src/main/java/com/android/databinding/util/L.java
@@ -16,19 +16,48 @@
 
 package com.android.databinding.util;
 
+import com.android.databinding.reflection.annotation.AnnotationAnalyzer;
+import com.android.databinding.reflection.ModelAnalyzer;
+
 import org.apache.commons.lang3.exception.ExceptionUtils;
 
+import javax.tools.Diagnostic;
+
 public class L {
 
     public static void d(String msg, Object... args) {
-        System.out.println("[LDEBUG] " + String.format(msg, args));
+        printMessage(Diagnostic.Kind.NOTE, String.format(msg, args));
+    }
+
+    public static void d(Throwable t, String msg, Object... args) {
+        printMessage(Diagnostic.Kind.NOTE,
+                String.format(msg, args) + " " + ExceptionUtils.getStackTrace(t));
     }
 
     public static void e(String msg, Object... args) {
-        System.out.println("[LERROR] " + String.format(msg, args));
+        printMessage(Diagnostic.Kind.ERROR, String.format(msg, args));
     }
+
     public static void e(Throwable t, String msg, Object... args) {
-        System.out
-                .println("[LERROR]" + String.format(msg, args) + " " + ExceptionUtils.getStackTrace(t));
+        printMessage(Diagnostic.Kind.ERROR,
+                String.format(msg, args) + " " + ExceptionUtils.getStackTrace(t));
     }
+
+    private static void printMessage(Diagnostic.Kind kind, String message) {
+        ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
+        System.out.println("[" + kind.name() + "]: " + message);
+        if (modelAnalyzer instanceof AnnotationAnalyzer) {
+            ((AnnotationAnalyzer) modelAnalyzer).getProcessingEnv().getMessager()
+                    .printMessage(kind, message);
+            if (kind == Diagnostic.Kind.ERROR) {
+                throw new RuntimeException("failure, see logs for details.\n" + message);
+            }
+        } else {
+
+            if (kind == Diagnostic.Kind.ERROR) {
+                throw new RuntimeException(message);
+            }
+        }
+    }
+
 }
diff --git a/tools/data-binding/compiler/src/main/kotlin/com/android/databinding/writer/LayoutBinderWriter.kt b/tools/data-binding/compiler/src/main/kotlin/com/android/databinding/writer/LayoutBinderWriter.kt
index d62abe9..b4b5986 100644
--- a/tools/data-binding/compiler/src/main/kotlin/com/android/databinding/writer/LayoutBinderWriter.kt
+++ b/tools/data-binding/compiler/src/main/kotlin/com/android/databinding/writer/LayoutBinderWriter.kt
@@ -620,7 +620,16 @@
                         }.joinToString(" || ")
                         }) {") {
                             it.value.forEach { binding ->
-                                tab("${binding.toJavaCode(binding.getTarget().fieldName, binding.getExpr().toCode().generate())};")
+                                tab("// api target ${binding.getMinApi()}")
+                                val bindingCode = binding.toJavaCode(binding.getTarget().fieldName, binding.getExpr().toCode().generate())
+                                if (binding.getMinApi() > 1) {
+                                    tab("if(com.android.databinding.library.DataBinder.getBuildSdkInt() >= ${binding.getMinApi()}) {") {
+                                        tab("$bindingCode;")
+                                    }
+                                    tab("}")
+                                } else {
+                                    tab("$bindingCode;")
+                                }
                             }
                         }
                         tab("}")
diff --git a/tools/data-binding/compiler/src/test/java/com/android/databinding/ExpressionVisitorTest.java b/tools/data-binding/compiler/src/test/java/com/android/databinding/ExpressionVisitorTest.java
index 8237aee..abf8f9a 100644
--- a/tools/data-binding/compiler/src/test/java/com/android/databinding/ExpressionVisitorTest.java
+++ b/tools/data-binding/compiler/src/test/java/com/android/databinding/ExpressionVisitorTest.java
@@ -27,6 +27,8 @@
 import com.android.databinding.expr.TernaryExpr;
 import com.android.databinding.reflection.Callable;
 import com.android.databinding.reflection.ModelAnalyzer;
+import com.android.databinding.reflection.java.JavaAnalyzer;
+import com.android.databinding.reflection.java.JavaClass;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -43,7 +45,7 @@
 
     @Before
     public void setUp() throws Exception {
-        ModelAnalyzer.initForTests();
+        JavaAnalyzer.initForTests();
     }
 
     private <T extends Expr> T parse(String input, Class<T> klass) {
@@ -57,7 +59,7 @@
         final SymbolExpr res = parse("null", SymbolExpr.class);
         assertEquals(1, mParser.getModel().size());
         assertEquals("null", res.getText());
-        assertSame(res.getResolvedType(), Object.class);
+        assertEquals(new JavaClass(Object.class),res.getResolvedType());
         assertEquals(0, res.getDependencies().size());
     }
 
@@ -69,7 +71,7 @@
 
         @Before
         public void setUp() throws Exception {
-            ModelAnalyzer.initForTests();
+            JavaAnalyzer.initForTests();
         }
 
         @Parameterized.Parameters
@@ -115,7 +117,7 @@
         assertEquals(1, mParser.mModel.size());
         assertEquals("myStr", id.getName());
         id.setUserDefinedType("java.lang.String");
-        assertSame(String.class, id.getResolvedType());
+        assertEquals(new JavaClass(String.class), id.getResolvedType());
     }
 
     @Test
@@ -142,7 +144,7 @@
         assertTrue(parsed.getChild() instanceof IdentifierExpr);
         final IdentifierExpr id = (IdentifierExpr) parsed.getChild();
         id.setUserDefinedType("java.lang.String");
-        assertSame(int.class, parsed.getResolvedType());
+        assertEquals(new JavaClass(int.class), parsed.getResolvedType());
         Callable getter = parsed.getGetter();
         assertEquals(Callable.Type.METHOD, getter.type);
         assertEquals("length", getter.name);
@@ -158,7 +160,7 @@
         assertTrue(parsed.getChild() instanceof IdentifierExpr);
         final IdentifierExpr id = (IdentifierExpr) parsed.getChild();
         id.setUserDefinedType("java.lang.String");
-        assertSame(byte[].class, parsed.getResolvedType());
+        assertEquals(new JavaClass(byte[].class), parsed.getResolvedType());
         Callable getter = parsed.getGetter();
         assertEquals(Callable.Type.METHOD, getter.type);
         assertEquals("getBytes", getter.name);
diff --git a/tools/data-binding/compiler/src/test/java/com/android/databinding/LayoutBinderTest.java b/tools/data-binding/compiler/src/test/java/com/android/databinding/LayoutBinderTest.java
index 4342f03..8dfda2f 100644
--- a/tools/data-binding/compiler/src/test/java/com/android/databinding/LayoutBinderTest.java
+++ b/tools/data-binding/compiler/src/test/java/com/android/databinding/LayoutBinderTest.java
@@ -21,6 +21,7 @@
 import com.android.databinding.expr.StaticIdentifierExpr;
 import com.android.databinding.reflection.Callable;
 import com.android.databinding.reflection.ModelAnalyzer;
+import com.android.databinding.reflection.java.JavaClass;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -35,8 +36,7 @@
     ExprModel mExprModel;
     @Before
     public void setUp() throws Exception {
-        ModelAnalyzer.initForTests();
-        mLayoutBinder = new LayoutBinder(null);
+        mLayoutBinder = new MockLayoutBinder();
         mExprModel = mLayoutBinder.getModel();
     }
 
@@ -49,7 +49,7 @@
         assertEquals(value.getClass(), IdentifierExpr.class);
         final IdentifierExpr id = (IdentifierExpr) value;
         assertEquals("test", id.getName());
-        assertEquals(String.class, id.getResolvedType());
+        assertEquals(new JavaClass(String.class), id.getResolvedType());
         assertTrue(id.isDynamic());
     }
 
@@ -62,7 +62,7 @@
         assertEquals(value.getClass(), StaticIdentifierExpr.class);
         final IdentifierExpr id = (IdentifierExpr) value;
         assertEquals("test", id.getName());
-        assertEquals(String.class, id.getResolvedType());
+        assertEquals(new JavaClass(String.class), id.getResolvedType());
         assertFalse(id.isDynamic());
     }
 
diff --git a/tools/data-binding/compiler/src/test/java/com/android/databinding/MockIViewDataBinder.java b/tools/data-binding/compiler/src/test/java/com/android/databinding/MockLayoutBinder.java
similarity index 71%
rename from tools/data-binding/compiler/src/test/java/com/android/databinding/MockIViewDataBinder.java
rename to tools/data-binding/compiler/src/test/java/com/android/databinding/MockLayoutBinder.java
index db8404c..dcdad423c 100644
--- a/tools/data-binding/compiler/src/test/java/com/android/databinding/MockIViewDataBinder.java
+++ b/tools/data-binding/compiler/src/test/java/com/android/databinding/MockLayoutBinder.java
@@ -13,6 +13,12 @@
 
 package com.android.databinding;
 
-public interface MockIViewDataBinder {
+import com.android.databinding.store.ResourceBundle;
 
+public class MockLayoutBinder extends LayoutBinder {
+
+    public MockLayoutBinder() {
+        super(new ResourceBundle("com.test"),
+                new ResourceBundle.LayoutFileBundle("blah.xml", 1, "."));
+    }
 }
diff --git a/tools/data-binding/compiler/src/test/java/com/android/databinding/MockObservableLsit.java b/tools/data-binding/compiler/src/test/java/com/android/databinding/MockObservableLsit.java
deleted file mode 100644
index 41c8abc..0000000
--- a/tools/data-binding/compiler/src/test/java/com/android/databinding/MockObservableLsit.java
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * 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 com.android.databinding;
-
-public class MockObservableLsit {
-
-}
diff --git a/tools/data-binding/compiler/src/test/java/com/android/databinding/MockObservableMap.java b/tools/data-binding/compiler/src/test/java/com/android/databinding/MockObservableMap.java
deleted file mode 100644
index 1d7add8..0000000
--- a/tools/data-binding/compiler/src/test/java/com/android/databinding/MockObservableMap.java
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * 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 com.android.databinding;
-
-public class MockObservableMap {
-
-}
diff --git a/tools/data-binding/compiler/src/test/java/com/android/databinding/SdkVersionTest.java b/tools/data-binding/compiler/src/test/java/com/android/databinding/SdkVersionTest.java
new file mode 100644
index 0000000..2c85f69
--- /dev/null
+++ b/tools/data-binding/compiler/src/test/java/com/android/databinding/SdkVersionTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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 com.android.databinding;
+
+import com.android.databinding.expr.ExprModel;
+import com.android.databinding.reflection.ModelAnalyzer;
+import com.android.databinding.reflection.ModelClass;
+import com.android.databinding.reflection.ModelMethod;
+import com.android.databinding.reflection.SdkUtil;
+import com.android.databinding.reflection.java.JavaAnalyzer;
+
+import static org.junit.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class SdkVersionTest {
+
+    @Before
+    public void setUp() throws Exception {
+        JavaAnalyzer.initForTests();
+    }
+
+    @Test
+    public void testNewApiMethod() {
+        ModelClass view = ModelAnalyzer.getInstance().findClass("android.view.View", null);
+        ModelMethod setElevation = view.getMethods("setElevation", 1)[0];
+        assertEquals(21, SdkUtil.getMinApi(setElevation));
+    }
+
+    @Test
+    public void testCustomCode() {
+        ModelClass view = ModelAnalyzer.getInstance()
+                .findClass("com.android.databinding.SdkVersionTest", null);
+        ModelMethod setElevation = view.getMethods("testCustomCode", 0)[0];
+        assertEquals(1, SdkUtil.getMinApi(setElevation));
+    }
+}
diff --git a/tools/data-binding/compiler/src/test/java/com/android/databinding/expr/ExprModelTest.java b/tools/data-binding/compiler/src/test/java/com/android/databinding/expr/ExprModelTest.java
index 5fb3802..9dded3a 100644
--- a/tools/data-binding/compiler/src/test/java/com/android/databinding/expr/ExprModelTest.java
+++ b/tools/data-binding/compiler/src/test/java/com/android/databinding/expr/ExprModelTest.java
@@ -19,8 +19,12 @@
 import com.google.common.base.Predicate;
 import com.google.common.collect.Iterables;
 
+import com.android.databinding.MockLayoutBinder;
 import com.android.databinding.reflection.ModelAnalyzer;
 import com.android.databinding.LayoutBinder;
+import com.android.databinding.reflection.ModelClass;
+import com.android.databinding.reflection.java.JavaAnalyzer;
+import com.android.databinding.store.ResourceBundle;
 import com.android.databinding.util.L;
 
 import org.apache.commons.lang3.ArrayUtils;
@@ -31,6 +35,7 @@
 import org.junit.rules.TestWatcher;
 import org.junit.runner.Description;
 
+import java.io.File;
 import java.util.BitSet;
 import java.util.List;
 
@@ -45,8 +50,8 @@
         }
 
         @Override
-        protected Class resolveType(ModelAnalyzer modelAnalyzer) {
-            return Integer.class;
+        protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
+            return modelAnalyzer.findClass(Integer.class);
         }
 
         @Override
@@ -77,7 +82,7 @@
 
     @Before
     public void setUp() throws Exception {
-        ModelAnalyzer.initForTests();
+        JavaAnalyzer.initForTests();
         mExprModel = new ExprModel();
     }
 
@@ -118,7 +123,7 @@
 
     @Test
     public void testShouldRead() {
-        LayoutBinder lb = new LayoutBinder(null);
+        LayoutBinder lb = new MockLayoutBinder();
         mExprModel = lb.getModel();
         IdentifierExpr a = lb.addVariable("a", "java.lang.String");
         IdentifierExpr b = lb.addVariable("b", "java.lang.String");
@@ -140,7 +145,7 @@
 
     @Test
     public void testTernaryInsideTernary() {
-        LayoutBinder lb = new LayoutBinder(null);
+        LayoutBinder lb = new MockLayoutBinder();
         mExprModel = lb.getModel();
         IdentifierExpr cond1 = lb.addVariable("cond1", "boolean");
         IdentifierExpr cond2 = lb.addVariable("cond2", "boolean");
@@ -185,7 +190,7 @@
 
     @Test
     public void testRequirementFlags() {
-        LayoutBinder lb = new LayoutBinder(null);
+        LayoutBinder lb = new MockLayoutBinder();
         mExprModel = lb.getModel();
         IdentifierExpr a = lb.addVariable("a", "java.lang.String");
         IdentifierExpr b = lb.addVariable("b", "java.lang.String");
@@ -256,7 +261,7 @@
 
     @Test
     public void testPostConditionalDependencies() {
-        LayoutBinder lb = new LayoutBinder(null);
+        LayoutBinder lb = new MockLayoutBinder();
         mExprModel = lb.getModel();
 
 
@@ -273,7 +278,7 @@
         Expr bcCmp = bcTernary.getPred();
         Expr u1GetCondD = ((TernaryExpr) bcTernary.getIfTrue()).getPred();
         final MathExpr xxPlusU2getCondE = (MathExpr) bcTernary.getIfFalse();
-        Expr u2GetCondE = xxPlusU2getCondE.mRight;
+        Expr u2GetCondE = xxPlusU2getCondE.getRight();
         Expr u1Name = abTernary.getIfTrue();
         Expr u2Name = abTernary.getIfFalse();
         Expr u1LastName = ((TernaryExpr) bcTernary.getIfTrue()).getIfTrue();
@@ -338,7 +343,7 @@
 
     @Test
     public void testCircularDependency() {
-        LayoutBinder lb = new LayoutBinder(null);
+        LayoutBinder lb = new MockLayoutBinder();
         mExprModel = lb.getModel();
         IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName());
         IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName());
@@ -354,7 +359,7 @@
 
     @Test
     public void testNestedCircularDependency() {
-        LayoutBinder lb = new LayoutBinder(null);
+        LayoutBinder lb = new MockLayoutBinder();
         mExprModel = lb.getModel();
         IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName());
         IdentifierExpr b = lb.addVariable("b", int.class.getCanonicalName());
@@ -374,7 +379,7 @@
 
     @Test
     public void testNoFlagsForNonBindingStatic() {
-        LayoutBinder lb = new LayoutBinder(null);
+        LayoutBinder lb = new MockLayoutBinder();
         mExprModel = lb.getModel();
         lb.addVariable("a", int.class.getCanonicalName());
         final MathExpr parsed = parse(lb, "a * (3 + 2)", MathExpr.class);
@@ -386,7 +391,7 @@
 
     @Test
     public void testFlagsForBindingStatic() {
-        LayoutBinder lb = new LayoutBinder(null);
+        LayoutBinder lb = new MockLayoutBinder();
         mExprModel = lb.getModel();
         lb.addVariable("a", int.class.getCanonicalName());
         final Expr staticParsed = parse(lb, "3 + 2", MathExpr.class);
@@ -401,7 +406,6 @@
 
     @Test
     public void testPartialNeededRead() {
-
         throw new NotImplementedException("create a test that has a variable which can be read for "
                 + "some flags and also may be read for some condition. Try both must match and"
                 + " partial match and none-match in conditionals");
diff --git a/tools/data-binding/compiler/src/test/java/com/android/databinding/expr/ExprTest.java b/tools/data-binding/compiler/src/test/java/com/android/databinding/expr/ExprTest.java
index 32a8ef4..a3618e3 100644
--- a/tools/data-binding/compiler/src/test/java/com/android/databinding/expr/ExprTest.java
+++ b/tools/data-binding/compiler/src/test/java/com/android/databinding/expr/ExprTest.java
@@ -17,6 +17,8 @@
 package com.android.databinding.expr;
 
 import com.android.databinding.reflection.ModelAnalyzer;
+import com.android.databinding.reflection.ModelClass;
+import com.android.databinding.reflection.java.JavaAnalyzer;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -37,8 +39,8 @@
         }
 
         @Override
-        protected Class resolveType(ModelAnalyzer modelAnalyzer) {
-            return Integer.class;
+        protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
+            return modelAnalyzer.findClass(Integer.class);
         }
 
         @Override
@@ -59,15 +61,15 @@
 
     @Before
     public void setUp() throws Exception {
-        ModelAnalyzer.initForTests();
+        JavaAnalyzer.initForTests();
     }
 
     @Test(expected=IllegalStateException.class)
     public void testBadExpr() {
         Expr expr = new Expr() {
             @Override
-            protected Class resolveType(ModelAnalyzer modelAnalyzer) {
-                return Integer.class;
+            protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
+                return modelAnalyzer.findClass(Integer.class);
             }
 
             @Override
diff --git a/tools/data-binding/compiler/src/test/java/com/android/databinding/reflection/java/JavaAnalyzer.java b/tools/data-binding/compiler/src/test/java/com/android/databinding/reflection/java/JavaAnalyzer.java
new file mode 100644
index 0000000..f03d77c
--- /dev/null
+++ b/tools/data-binding/compiler/src/test/java/com/android/databinding/reflection/java/JavaAnalyzer.java
@@ -0,0 +1,361 @@
+/*
+ * 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 com.android.databinding.reflection.java;
+
+import com.google.common.collect.ImmutableMap;
+import com.android.databinding.reflection.Callable;
+import com.android.databinding.reflection.ModelAnalyzer;
+import com.android.databinding.reflection.ModelClass;
+import com.android.databinding.reflection.ModelField;
+import com.android.databinding.reflection.ModelMethod;
+import com.android.databinding.reflection.SdkUtil;
+import com.android.databinding.reflection.TypeUtil;
+import com.android.databinding.util.L;
+
+import org.apache.commons.lang3.StringUtils;
+
+import android.binding.Bindable;
+import android.binding.Observable;
+import android.binding.ObservableList;
+import android.binding.ObservableMap;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class JavaAnalyzer extends ModelAnalyzer {
+    public static final Map<String, Class> PRIMITIVE_TYPES =
+            new ImmutableMap.Builder<String, Class>()
+                    .put("boolean", boolean.class)
+                    .put("byte", byte.class)
+                    .put("short", short.class)
+                    .put("char", char.class)
+                    .put("int", int.class)
+                    .put("long", long.class)
+                    .put("float", float.class)
+                    .put("double", double.class)
+                    .build();
+    private static final String BINDABLE_ANNOTATION_NAME = "android.binding.Bindable";
+
+    private HashMap<String, JavaClass> mClassCache = new HashMap<>();
+
+    private final ClassLoader mClassLoader;
+
+    private final Class mObservable;
+
+    private final Class mObservableList;
+
+    private final Class mObservableMap;
+
+    private final Class[] mObservableFields;
+
+    private final Class mBindable;
+
+    private final boolean mTestMode;
+
+    private final Class mIViewDataBinder;
+
+    public JavaAnalyzer(ClassLoader classLoader, boolean testMode) {
+        setInstance(this);
+        mClassLoader = classLoader;
+        mTestMode = testMode;
+        try {
+            mIViewDataBinder = classLoader.loadClass(I_VIEW_DATA_BINDER);
+            mObservable = Observable.class;
+            mObservableList = ObservableList.class;
+            mObservableMap = ObservableMap.class;
+            mBindable = Bindable.class;
+            mObservableFields = new Class[OBSERVABLE_FIELDS.length];
+            for (int i = 0; i < OBSERVABLE_FIELDS.length; i++) {
+                mObservableFields[i] = classLoader.loadClass(getClassName(OBSERVABLE_FIELDS[i]));
+            }
+
+        } catch (ClassNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private String getClassName(String name) {
+        return name;
+    }
+
+    @Override
+    public boolean isDataBinder(ModelClass reflectionClass) {
+        JavaClass javaClass = (JavaClass) reflectionClass;
+        return mIViewDataBinder.isAssignableFrom(javaClass.mClass);
+    }
+
+    @Override
+    public Callable findMethod(ModelClass modelClass, String name, List<ModelClass> argClasses,
+            boolean staticAccess) {
+        Class klass = ((JavaClass) modelClass).mClass;
+        ArrayList<Class> args = new ArrayList<>(argClasses.size());
+        for (int i = 0; i < argClasses.size(); i++) {
+            args.add(((JavaClass) argClasses.get(i)).mClass);
+        }
+        // TODO implement properly
+        for (String methodName :  new String[]{"set" + StringUtils.capitalize(name), name}) {
+            for (Method method : klass.getMethods()) {
+                if (methodName.equals(method.getName()) && args.size() == method
+                        .getParameterTypes().length) {
+                    return new Callable(Callable.Type.METHOD, methodName,
+                            new JavaClass(method.getReturnType()), true, false);
+                }
+            }
+        }
+        L.e(new Exception(), "cannot find method %s in %s", name, klass);
+        throw new IllegalArgumentException(
+                "cannot find method " + name + " at class " + klass.getSimpleName());
+    }
+
+    @Override
+    public boolean isObservable(ModelClass modelClass) {
+        Class klass = ((JavaClass) modelClass).mClass;
+        return isObservable(klass);
+    }
+
+    private boolean isObservable(Class klass) {
+        return mObservable.isAssignableFrom(klass) || mObservableList.isAssignableFrom(klass) ||
+                mObservableMap.isAssignableFrom(klass);
+    }
+
+    @Override
+    public boolean isObservableField(ModelClass reflectionClass) {
+        Class klass = ((JavaClass) reflectionClass).mClass;
+        for (Class observableField : mObservableFields) {
+            if (observableField.isAssignableFrom(klass)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isBindable(ModelField reflectionField) {
+        Field field = ((JavaField) reflectionField).mField;
+        return isBindable(field);
+    }
+
+    @Override
+    public boolean isBindable(ModelMethod reflectionMethod) {
+        Method method = ((JavaMethod) reflectionMethod).mMethod;
+        return isBindable(method);
+    }
+
+    private boolean isBindable(Field field) {
+        return field.getAnnotation(mBindable) != null;
+    }
+
+    private boolean isBindable(Method method) {
+        return method.getAnnotation(mBindable) != null;
+    }
+
+    @Override
+    public Callable findMethodOrField(ModelClass modelClass, String name, boolean staticAccess) {
+        final Class klass = ((JavaClass) modelClass).mClass;
+        for (String methodName :
+                new String[]{"get" + StringUtils.capitalize(name),
+                        "is" + StringUtils.capitalize(name), name}) {
+            try {
+                Method method = klass.getMethod(methodName);
+                Field backingField = findField(klass, name, true);
+                if (Modifier.isPublic(method.getModifiers())) {
+                    final Callable result = new Callable(Callable.Type.METHOD, methodName,
+                            new JavaClass(method.getReturnType()), true,
+                            isBindable(method) || (backingField != null && isBindable(backingField)) );
+                    L.d("backing field for %s is %s", result, backingField);
+                    return result;
+                }
+            } catch (Throwable t) {
+
+            }
+        }
+        try {
+            Field field = findField(klass, name, false);
+            if (Modifier.isPublic(field.getModifiers())) {
+                return new Callable(Callable.Type.FIELD, name, new JavaClass(field.getType()),
+                        !Modifier.isFinal(field.getModifiers())
+                                || isObservable(field.getType()), isBindable(field));
+            }
+        } catch (Throwable t) {
+
+        }
+        throw new IllegalArgumentException(
+                "cannot find " + name + " in " + klass.getCanonicalName());
+    }
+
+    private Field findField(Class klass, String name, boolean allowNonPublic) {
+        try {
+            return getField(klass, name, allowNonPublic);
+        } catch (NoSuchFieldException e) {
+
+        }
+        String capitalizedName = StringUtils.capitalize(name);
+
+        try {
+            return getField(klass, "m" + capitalizedName, allowNonPublic);
+        } catch (Throwable t){}
+        try {
+            return getField(klass, "_" + name, allowNonPublic);
+        } catch (Throwable t){}
+        try {
+            return getField(klass, "_" + capitalizedName, allowNonPublic);
+        } catch (Throwable t){}
+        try {
+            return getField(klass, "m_" + name, allowNonPublic);
+        } catch (Throwable t){}
+        try {
+            return getField(klass, "m_" + capitalizedName, allowNonPublic);
+        } catch (Throwable t){}
+        return null;
+    }
+
+    private Field getField(Class klass, String exactName, boolean allowNonPublic)
+            throws NoSuchFieldException {
+        try {
+            return klass.getField(exactName);
+        } catch (NoSuchFieldException e) {
+            if (allowNonPublic) {
+                return klass.getDeclaredField(exactName);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Override
+    public JavaClass loadPrimitive(String className) {
+        Class clazz = PRIMITIVE_TYPES.get(className);
+        if (clazz == null) {
+            return null;
+        } else {
+            return new JavaClass(clazz);
+        }
+    }
+
+    @Override
+    public ModelClass findClass(String className, Map<String, String> imports) {
+        // TODO handle imports
+        JavaClass loaded = mClassCache.get(className);
+        if (loaded != null) {
+            return loaded;
+        }
+        L.d("trying to load class %s from %s", className, mClassLoader.toString());
+        loaded = loadPrimitive(className);
+        if (loaded == null) {
+            try {
+                if (className.startsWith("[") && className.contains("L")) {
+                    int indexOfL = className.indexOf('L');
+                    JavaClass baseClass = (JavaClass) findClass(
+                            className.substring(indexOfL + 1, className.length() - 1), null);
+                    String realClassName = className.substring(0, indexOfL + 1) +
+                            baseClass.mClass.getCanonicalName() + ';';
+                    loaded = new JavaClass(Class.forName(realClassName, false, mClassLoader));
+                    mClassCache.put(className, loaded);
+                } else {
+                    loaded = loadRecursively(className);
+                    mClassCache.put(className, loaded);
+                }
+
+            } catch (Throwable t) {
+//                L.e(t, "cannot load class " + className);
+            }
+        }
+        // expr visitor may call this to resolve statics. Sometimes, it is OK not to find a class.
+        if (loaded == null) {
+            return null;
+        }
+        L.d("loaded class %s", loaded.mClass.getCanonicalName());
+        return loaded;
+    }
+
+    @Override
+    public ModelClass findClass(Class classType) {
+        return new JavaClass(classType);
+    }
+
+    @Override
+    public TypeUtil createTypeUtil() {
+        return new JavaTypeUtil();
+    }
+
+    private JavaClass loadRecursively(String className) throws ClassNotFoundException {
+        try {
+            L.d("recursively checking %s", className);
+            return new JavaClass(mClassLoader.loadClass(className));
+        } catch (ClassNotFoundException ex) {
+            int lastIndexOfDot = className.lastIndexOf(".");
+            if (lastIndexOfDot == -1) {
+                throw ex;
+            }
+            return loadRecursively(className.substring(0, lastIndexOfDot) + "$" + className
+                    .substring(lastIndexOfDot + 1));
+        }
+    }
+
+    @Override
+    public List<URL> getResources(String name) {
+        List<URL> urlList = new ArrayList<URL>();
+        Enumeration<URL> urls = null;
+        try {
+            urls = mClassLoader.getResources(name);
+            if (urls != null) {
+                while (urls.hasMoreElements()) {
+                    urlList.add(urls.nextElement());
+                }
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return urlList;
+    }
+
+    public static void initForTests() {
+        Map<String, String> env = System.getenv();
+        for (Map.Entry<String, String> entry : env.entrySet()) {
+            L.d("%s %s", entry.getKey(), entry.getValue());
+        }
+        String androidHome = env.get("ANDROID_HOME");
+        if (androidHome == null) {
+            throw new IllegalStateException("you need to have ANDROID_HOME set in your environment"
+                    + " to run compiler tests");
+        }
+        File androidJar = new File(androidHome + "/platforms/android-21/android.jar");
+        if (!androidJar.exists() || !androidJar.canRead()) {
+            throw new IllegalStateException(
+                    "cannot find android jar at " + androidJar.getAbsolutePath());
+        }
+        // now load android data binding library as well
+
+        try {
+            ClassLoader classLoader = new URLClassLoader(new URL[]{androidJar.toURI().toURL()},
+                    ModelAnalyzer.class.getClassLoader());
+            new JavaAnalyzer(classLoader, true);
+        } catch (MalformedURLException e) {
+            throw new RuntimeException("cannot create class loader", e);
+        }
+
+        SdkUtil.initialize(8, new File(androidHome));
+    }
+}
diff --git a/tools/data-binding/compiler/src/test/java/com/android/databinding/reflection/java/JavaClass.java b/tools/data-binding/compiler/src/test/java/com/android/databinding/reflection/java/JavaClass.java
new file mode 100644
index 0000000..2ea2d2d
--- /dev/null
+++ b/tools/data-binding/compiler/src/test/java/com/android/databinding/reflection/java/JavaClass.java
@@ -0,0 +1,245 @@
+/*
+ * 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 com.android.databinding.reflection.java;
+
+import com.android.databinding.reflection.ModelClass;
+import com.android.databinding.reflection.ModelMethod;
+import com.android.databinding.reflection.SdkUtil;
+import com.android.databinding.reflection.TypeUtil;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class JavaClass implements ModelClass {
+    public final Class mClass;
+
+    public JavaClass(Class clazz) {
+        mClass = clazz;
+    }
+
+    @Override
+    public String toJavaCode() {
+        return toJavaCode(mClass);
+    }
+
+    private static String toJavaCode(Class aClass) {
+        if (aClass.isArray()) {
+            Class component = aClass.getComponentType();
+            return toJavaCode(component) + "[]";
+        } else {
+            return aClass.getCanonicalName().replace('$', '.');
+        }
+    }
+
+    @Override
+    public boolean isArray() {
+        return mClass.isArray();
+    }
+
+    @Override
+    public ModelClass getComponentType() {
+        if (mClass.isArray()) {
+            return new JavaClass(mClass.getComponentType());
+        } else if (isList() || isMap()) {
+            return new JavaClass(Object.class);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public boolean isList() {
+        return List.class.isAssignableFrom(mClass);
+    }
+
+    @Override
+    public boolean isMap() {
+        return Map.class.isAssignableFrom(mClass);
+    }
+
+    @Override
+    public boolean isString() {
+        return String.class.equals(mClass);
+    }
+
+    @Override
+    public boolean isNullable() {
+        return Object.class.isAssignableFrom(mClass);
+    }
+
+    @Override
+    public boolean isPrimitive() {
+        return mClass.isPrimitive();
+    }
+
+    @Override
+    public boolean isBoolean() {
+        return boolean.class.equals(mClass);
+    }
+
+    @Override
+    public boolean isChar() {
+        return char.class.equals(mClass);
+    }
+
+    @Override
+    public boolean isByte() {
+        return byte.class.equals(mClass);
+    }
+
+    @Override
+    public boolean isShort() {
+        return short.class.equals(mClass);
+    }
+
+    @Override
+    public boolean isInt() {
+        return int.class.equals(mClass);
+    }
+
+    @Override
+    public boolean isLong() {
+        return long.class.equals(mClass);
+    }
+
+    @Override
+    public boolean isFloat() {
+        return float.class.equals(mClass);
+    }
+
+    @Override
+    public boolean isDouble() {
+        return double.class.equals(mClass);
+    }
+
+    @Override
+    public boolean isObject() {
+        return Object.class.equals(mClass);
+    }
+
+    @Override
+    public boolean isVoid() {
+        return void.class.equals(mClass);
+    }
+
+    @Override
+    public ModelClass unbox() {
+        if (mClass.isPrimitive()) {
+            return this;
+        }
+        if (Integer.class.equals(mClass)) {
+            return new JavaClass(int.class);
+        } else if (Long.class.equals(mClass)) {
+            return new JavaClass(long.class);
+        } else if (Short.class.equals(mClass)) {
+            return new JavaClass(short.class);
+        } else if (Byte.class.equals(mClass)) {
+            return new JavaClass(byte.class);
+        } else if (Character.class.equals(mClass)) {
+            return new JavaClass(char.class);
+        } else if (Double.class.equals(mClass)) {
+            return new JavaClass(double.class);
+        } else if (Float.class.equals(mClass)) {
+            return new JavaClass(float.class);
+        } else if (Boolean.class.equals(mClass)) {
+            return new JavaClass(boolean.class);
+        } else {
+            // not a boxed type
+            return this;
+        }
+
+    }
+
+    @Override
+    public JavaClass box() {
+        if (!mClass.isPrimitive()) {
+            return this;
+        }
+        if (int.class.equals(mClass)) {
+            return new JavaClass(Integer.class);
+        } else if (long.class.equals(mClass)) {
+            return new JavaClass(Long.class);
+        } else if (short.class.equals(mClass)) {
+            return new JavaClass(Short.class);
+        } else if (byte.class.equals(mClass)) {
+            return new JavaClass(Byte.class);
+        } else if (char.class.equals(mClass)) {
+            return new JavaClass(Character.class);
+        } else if (double.class.equals(mClass)) {
+            return new JavaClass(Double.class);
+        } else if (float.class.equals(mClass)) {
+            return new JavaClass(Float.class);
+        } else if (boolean.class.equals(mClass)) {
+            return new JavaClass(Boolean.class);
+        } else {
+            // not a valid type?
+            return this;
+        }
+    }
+
+    @Override
+    public boolean isAssignableFrom(ModelClass that) {
+        Class thatClass = ((JavaClass) that).mClass;
+        return mClass.isAssignableFrom(thatClass);
+    }
+
+    @Override
+    public ModelMethod[] getMethods(String name, int numParameters) {
+        Method[] methods = mClass.getMethods();
+        ArrayList<JavaMethod> matching = new ArrayList<>();
+        for (Method method : methods) {
+            if (method.getName().equals(name) &&
+                    method.getParameterTypes().length == numParameters) {
+                matching.add(new JavaMethod(method));
+            }
+        }
+        return matching.toArray(new JavaMethod[matching.size()]);
+    }
+
+    @Override
+    public ModelClass getSuperclass() {
+        return new JavaClass(mClass.getSuperclass());
+    }
+
+    @Override
+    public String getCanonicalName() {
+        return mClass.getCanonicalName();
+    }
+
+    @Override
+    public int getMinApi() {
+        return SdkUtil.getMinApi(this);
+    }
+
+    @Override
+    public String getJniDescription() {
+        return TypeUtil.getInstance().getDescription(this);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof JavaClass) {
+            return mClass.equals(((JavaClass) obj).mClass);
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return mClass.hashCode();
+    }
+}
diff --git a/tools/data-binding/compiler/src/test/java/com/android/databinding/MockBindable.java b/tools/data-binding/compiler/src/test/java/com/android/databinding/reflection/java/JavaField.java
similarity index 68%
rename from tools/data-binding/compiler/src/test/java/com/android/databinding/MockBindable.java
rename to tools/data-binding/compiler/src/test/java/com/android/databinding/reflection/java/JavaField.java
index 9d78f0b..122bf32 100644
--- a/tools/data-binding/compiler/src/test/java/com/android/databinding/MockBindable.java
+++ b/tools/data-binding/compiler/src/test/java/com/android/databinding/reflection/java/JavaField.java
@@ -11,11 +11,16 @@
  * limitations under the License.
  */
 
-package com.android.databinding;
+package com.android.databinding.reflection.java;
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Target;
+import com.android.databinding.reflection.ModelField;
 
-@Target({ElementType.FIELD, ElementType.METHOD})
-public @interface MockBindable {
+import java.lang.reflect.Field;
+
+public class JavaField implements ModelField {
+    public final Field mField;
+
+    public JavaField(Field field) {
+        mField = field;
+    }
 }
diff --git a/tools/data-binding/compiler/src/test/java/com/android/databinding/reflection/java/JavaMethod.java b/tools/data-binding/compiler/src/test/java/com/android/databinding/reflection/java/JavaMethod.java
new file mode 100644
index 0000000..901b045
--- /dev/null
+++ b/tools/data-binding/compiler/src/test/java/com/android/databinding/reflection/java/JavaMethod.java
@@ -0,0 +1,78 @@
+/*
+ * 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 com.android.databinding.reflection.java;
+
+
+import com.android.databinding.reflection.ModelClass;
+import com.android.databinding.reflection.ModelMethod;
+import com.android.databinding.reflection.SdkUtil;
+import com.android.databinding.reflection.TypeUtil;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.List;
+
+public class JavaMethod implements ModelMethod {
+    public final Method mMethod;
+
+    public JavaMethod(Method method) {
+        mMethod = method;
+    }
+
+
+    @Override
+    public ModelClass getDeclaringClass() {
+        return new JavaClass(mMethod.getDeclaringClass());
+    }
+
+    @Override
+    public ModelClass[] getParameterTypes() {
+        Class[] parameterTypes = mMethod.getParameterTypes();
+        ModelClass[] parameterClasses = new ModelClass[parameterTypes.length];
+        for (int i = 0; i < parameterTypes.length; i++) {
+            parameterClasses[i] = new JavaClass(parameterTypes[i]);
+        }
+        return parameterClasses;
+    }
+
+    @Override
+    public String getName() {
+        return mMethod.getName();
+    }
+
+    @Override
+    public ModelClass getReturnType(List<ModelClass> args) {
+        return new JavaClass(mMethod.getReturnType());
+    }
+
+    @Override
+    public boolean isPublic() {
+        return Modifier.isPublic(mMethod.getModifiers());
+    }
+
+    @Override
+    public boolean isStatic() {
+        return Modifier.isStatic(mMethod.getModifiers());
+    }
+
+    @Override
+    public int getMinApi() {
+        return SdkUtil.getMinApi(this);
+    }
+
+    @Override
+    public String getJniDescription() {
+        return TypeUtil.getInstance().getDescription(this);
+    }
+}
diff --git a/tools/data-binding/compiler/src/test/java/com/android/databinding/reflection/java/JavaTypeUtil.java b/tools/data-binding/compiler/src/test/java/com/android/databinding/reflection/java/JavaTypeUtil.java
new file mode 100644
index 0000000..50205ef
--- /dev/null
+++ b/tools/data-binding/compiler/src/test/java/com/android/databinding/reflection/java/JavaTypeUtil.java
@@ -0,0 +1,89 @@
+/*
+ * 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 com.android.databinding.reflection.java;
+
+import com.android.databinding.reflection.ModelClass;
+import com.android.databinding.reflection.ModelMethod;
+import com.android.databinding.reflection.TypeUtil;
+import com.android.databinding.util.L;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Method;
+
+public class JavaTypeUtil extends TypeUtil {
+
+    @Override
+    public String getDescription(ModelClass modelClass) {
+        return modelClass.getCanonicalName().replace('.', '/');
+    }
+
+    @Override
+    public String getDescription(ModelMethod modelMethod) {
+        Method method = ((JavaMethod) modelMethod).mMethod;
+        StringBuilder sb  = new StringBuilder();
+        sb.append(method.getName());
+        sb.append("(");
+        for (Class param : method.getParameterTypes()) {
+            sb.append(getDescription(param));
+        }
+        sb.append(")");
+        sb.append(getDescription(method.getReturnType()));
+        return sb.toString();
+    }
+
+    private String getDescription(Class klass) {
+        if (klass == null) {
+            throw new UnsupportedOperationException();
+        }
+        if (boolean.class.equals(klass)) {
+            return BOOLEAN;
+        }
+        if (byte.class.equals(klass)) {
+            return BYTE;
+        }
+        if (short.class.equals(klass)) {
+            return SHORT;
+        }
+        if (int.class.equals(klass)) {
+            return INT;
+        }
+        if (long.class.equals(klass)) {
+            return LONG;
+        }
+        if (char.class.equals(klass)) {
+            return CHAR;
+        }
+        if (float.class.equals(klass)) {
+            return FLOAT;
+        }
+        if (double.class.equals(klass)) {
+            return DOUBLE;
+        }
+        if (void.class.equals(klass)) {
+            return VOID;
+        }
+        if (Object.class.isAssignableFrom(klass)) {
+            return CLASS_PREFIX + klass.getCanonicalName().replace('.', '/') + CLASS_SUFFIX;
+        }
+        if (Array.class.isAssignableFrom(klass)) {
+            return ARRAY + getDescription(klass.getComponentType());
+        }
+
+        UnsupportedOperationException ex
+                = new UnsupportedOperationException("cannot understand type "
+                + klass.toString() + ", kind:");
+        L.e(ex, "cannot create JNI type for %s", klass.getCanonicalName());
+        throw ex;
+    }
+}
diff --git a/tools/data-binding/gradlePlugin/src/main/kotlin/plugin.kt b/tools/data-binding/gradlePlugin/src/main/kotlin/plugin.kt
index 2b7a20c..5913a55 100644
--- a/tools/data-binding/gradlePlugin/src/main/kotlin/plugin.kt
+++ b/tools/data-binding/gradlePlugin/src/main/kotlin/plugin.kt
@@ -51,6 +51,7 @@
 import java.io.FileWriter
 import java.io.ByteArrayOutputStream
 import org.apache.commons.codec.binary.Base64
+import com.android.builder.model.ApiVersion
 
 class DataBinderPlugin : Plugin<Project> {
 
@@ -78,6 +79,8 @@
 
     var viewBinderSource : File by Delegates.notNull()
 
+    var sdkDir : File by Delegates.notNull()
+
     val viewBinderSourceRoot by Delegates.lazy {
         File(project.getBuildDir(), "databinder")
     }
@@ -118,6 +121,8 @@
 
     fun createXmlProcessor(p: Project): LayoutXmlProcessor {
         val ss = p.getExtensions().getByName("android") as AppExtension
+        sdkDir = ss.getSdkDirectory()
+        val minSdkVersion = ss.getDefaultConfig().getMinSdkVersion()
         androidJar = File(ss.getSdkDirectory().getAbsolutePath() + "/platforms/${ss.getCompileSdkVersion()}/android.jar")
         log("creating parser!")
         log("project build dir:${p.getBuildDir()}")
@@ -128,10 +133,7 @@
         variantData = field.get(appVariant) as ApplicationVariantData
 
 
-        // TODO
         val packageName = variantData.generateRClassTask.getPackageForR()
-                //"com.com.android.databinding.android.databinding.libraryGen"//variantData.getPackageName()
-        //
         val sources = variantData.getJavaSources()
         sources.forEach({
             log("source: ${it}");
@@ -168,7 +170,7 @@
         dexTask.doFirst(MethodClosure(this, "preDexAnalysis"))
         val writerOutBase = codeGenTargetFolder.getAbsolutePath();
         fileWriter = GradleFileWriter(writerOutBase)
-        return LayoutXmlProcessor(packageName, resourceFolders, fileWriter)
+        return LayoutXmlProcessor(packageName, resourceFolders, fileWriter, minSdkVersion.getApiLevel())
     }
 
 
@@ -195,6 +197,6 @@
     }
 
     fun generateIntermediateFile(o: Any?) {
-        xmlProcessor.writeIntermediateFile()
+        xmlProcessor.writeIntermediateFile(sdkDir)
     }
 }
diff --git a/tools/data-binding/library/build.gradle b/tools/data-binding/library/build.gradle
index 452bc0e..229d3e1 100644
--- a/tools/data-binding/library/build.gradle
+++ b/tools/data-binding/library/build.gradle
@@ -69,17 +69,14 @@
 android.libraryVariants.all { variant ->
     def name = variant.buildType.name
 
-    if (name.equals("debug")) {
+    if (name.equals(com.android.builder.core.BuilderConstants.DEBUG)) {
         return; // Skip debug builds.
     }
-    def suffix = name.capitalize()
-
-//    def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
-//        classifier = 'sources'
-//        from android.sourceSets.main.java
-//    }
-//
-//    artifacts.add('archives', sourcesJarTask);
+    // @Jar version is needed to run compiler tests
+    def task = project.tasks.create "jar${name.capitalize()}", Jar
+    task.dependsOn variant.javaCompile
+    task.from variant.javaCompile.destinationDir
+    artifacts.add('archives', task);
 }
 uploadArchives {
     repositories {
diff --git a/tools/data-binding/library/src/main/java/com/android/databinding/library/DataBinder.java b/tools/data-binding/library/src/main/java/com/android/databinding/library/DataBinder.java
index 92549c7..4ebecef 100644
--- a/tools/data-binding/library/src/main/java/com/android/databinding/library/DataBinder.java
+++ b/tools/data-binding/library/src/main/java/com/android/databinding/library/DataBinder.java
@@ -17,6 +17,7 @@
 package com.android.databinding.library;
 
 import android.content.Context;
+import android.os.Build;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.LayoutInflater;
@@ -30,6 +31,12 @@
 
     static DataBinderMapper sMapper;
 
+    /**
+     * Instead of directly accessing Build.VERSION.SDK_INT, generated code uses this value so that
+     * we can test API dependent behavior.
+     */
+    static int SDK_INT = Build.VERSION.SDK_INT;
+
     private WeakHashMap<View, ViewDataBinder> mDataBinderMap = new WeakHashMap<>();
 
     private SparseArray<WeakReference<ViewDataBinder>> mDataBinderById = new SparseArray<>();
@@ -49,6 +56,10 @@
         return sMapper;
     }
 
+    public static int getBuildSdkInt() {
+        return SDK_INT;
+    }
+
     public static int convertToId(String key) {
         return getMapper().getId(key);
     }