Introduce Scopes to track logical stack traces

This CL introduces a static class called Scope, which is
used the logical processing stack for data binding.
These scopes are used to generate meaningful error messages
when an error is detected.

Bug: 21953001
Change-Id: I5470a8c4ad94401d34a140762baae9d53c5a0402
diff --git a/compilationTests/build.gradle b/compilationTests/build.gradle
index abe046a..e8b56dd 100644
--- a/compilationTests/build.gradle
+++ b/compilationTests/build.gradle
@@ -12,4 +12,5 @@
     testCompile 'org.apache.commons:commons-lang3:3.3.2'
     testCompile 'commons-io:commons-io:2.4'
     testCompile 'commons-codec:commons-codec:1.10'
+    testCompile project(':compilerCommon')
 }
\ No newline at end of file
diff --git a/compilationTests/src/test/java/android/databinding/compilationTest/BaseCompilationTest.java b/compilationTests/src/test/java/android/databinding/compilationTest/BaseCompilationTest.java
index c390edc..51925d5 100644
--- a/compilationTests/src/test/java/android/databinding/compilationTest/BaseCompilationTest.java
+++ b/compilationTests/src/test/java/android/databinding/compilationTest/BaseCompilationTest.java
@@ -19,6 +19,10 @@
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
 import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TestName;
+
+import android.databinding.tool.store.Location;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -29,8 +33,11 @@
 import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.nio.file.attribute.PosixFilePermission;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.regex.Matcher;
@@ -42,6 +49,8 @@
 
 
 public class BaseCompilationTest {
+    @Rule
+    public TestName name = new TestName();
     static Pattern VARIABLES = Pattern.compile("!@\\{([A-Za-z0-9_-]*)}");
 
     public static final String KEY_MANIFEST_PACKAGE = "PACKAGE";
@@ -49,7 +58,7 @@
     public static final String KEY_SETTINGS_INCLUDES = "SETTINGS_INCLUDES";
     public static final String DEFAULT_APP_PACKAGE = "com.android.databinding.compilationTest.test";
 
-    File testFolder = new File("./build/build-test");
+    protected final File testFolder = new File("./build/build-test");
 
     protected void copyResourceTo(String name, String path) throws IOException {
         copyResourceTo(name, new File(testFolder, path));
@@ -81,6 +90,36 @@
         }
     }
 
+    /**
+     * Extracts the text in the given location from the the at the given application path.
+     * @param pathInApp The path, relative to the root of the application under test
+     * @param location The location to extract
+     * @return The string that is contained in the given location
+     * @throws IOException If file is invalid.
+     */
+    protected String extract(String pathInApp, Location location) throws IOException {
+        File file = new File(testFolder, pathInApp);
+        assertTrue(file.exists());
+        StringBuilder result = new StringBuilder();
+        List<String> lines = FileUtils.readLines(file);
+        for (int i = location.startLine; i <= location.endLine; i ++) {
+            if (i > location.startLine) {
+                result.append("\n");
+            }
+            String line = lines.get(i);
+            int start = 0;
+            if (i == location.startLine) {
+                start = location.startOffset;
+            }
+            int end = line.length() - 1; // inclusive
+            if (i == location.endLine) {
+                end = location.endOffset;
+            }
+            result.append(line.substring(start, end + 1));
+        }
+        return result.toString();
+    }
+
     protected void copyResourceTo(String name, File targetFile) throws IOException {
         File directory = targetFile.getParentFile();
         FileUtils.forceMkdir(directory);
@@ -179,13 +218,17 @@
         copyResourceTo("/module_build.gradle", new File(moduleFolder, "build.gradle"), replacements);
     }
 
-    protected CompilationResult runGradle(String params) throws IOException, InterruptedException {
+    protected CompilationResult runGradle(String... params) throws IOException, InterruptedException {
         setExecutable();
         File pathToExecutable = new File(testFolder, "gradlew");
-        ProcessBuilder builder = new ProcessBuilder(pathToExecutable.getAbsolutePath(), params);
+        List<String> args = new ArrayList<>();
+        args.add(pathToExecutable.getAbsolutePath());
+        args.add("--project-cache-dir");
+        args.add(new File("../.caches/", name.getMethodName()).getAbsolutePath());
+        Collections.addAll(args, params);
+        ProcessBuilder builder = new ProcessBuilder(args);
         builder.environment().putAll(System.getenv());
         builder.directory(testFolder);
-        //builder.redirectErrorStream(true); // merges error and input streams
         Process process =  builder.start();
         String output = IOUtils.toString(process.getInputStream());
         String error = IOUtils.toString(process.getErrorStream());
diff --git a/compilationTests/src/test/java/android/databinding/compilationTest/CompilationResult.java b/compilationTests/src/test/java/android/databinding/compilationTest/CompilationResult.java
index 496550b..f50755b 100644
--- a/compilationTests/src/test/java/android/databinding/compilationTest/CompilationResult.java
+++ b/compilationTests/src/test/java/android/databinding/compilationTest/CompilationResult.java
@@ -16,6 +16,13 @@
 
 package android.databinding.compilationTest;
 
+import android.databinding.tool.processing.ScopedErrorReport;
+import android.databinding.tool.processing.ScopedException;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
 public class CompilationResult {
     public final int resultCode;
     public final String output;
@@ -34,4 +41,17 @@
     public boolean errorContainsText(String text) {
         return resultCode != 0 && error.indexOf(text) > 0;
     }
+
+    public ScopedException getBindingException() {
+        List<ScopedException> errors = ScopedException.extractErrors(error);
+        if (errors.isEmpty()) {
+            return null;
+        }
+        assertEquals(1, errors.size());
+        return errors.get(0);
+    }
+
+    public List<ScopedException> getBindingExceptions() {
+        return ScopedException.extractErrors(error);
+    }
 }
diff --git a/compilationTests/src/test/java/android/databinding/compilationTest/SimpleCompilationTest.java b/compilationTests/src/test/java/android/databinding/compilationTest/SimpleCompilationTest.java
index 6540e67..877fe80 100644
--- a/compilationTests/src/test/java/android/databinding/compilationTest/SimpleCompilationTest.java
+++ b/compilationTests/src/test/java/android/databinding/compilationTest/SimpleCompilationTest.java
@@ -20,13 +20,21 @@
 import org.apache.commons.lang3.StringUtils;
 import org.junit.Test;
 
+import android.databinding.tool.processing.ErrorMessages;
+import android.databinding.tool.processing.ScopedErrorReport;
+import android.databinding.tool.processing.ScopedException;
+import android.databinding.tool.store.Location;
+
 import java.io.File;
 import java.io.IOException;
 import java.net.URISyntaxException;
+import java.util.List;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 public class SimpleCompilationTest extends BaseCompilationTest {
 
@@ -50,17 +58,95 @@
                 result.resultContainsText("BUILD SUCCESSFUL"));
     }
 
+    private ScopedException singleFileErrorTest(String resource, String targetFile,
+            String expectedExtract, String errorMessage)
+            throws IOException, URISyntaxException, InterruptedException {
+        prepareProject();
+        copyResourceTo(resource, targetFile);
+        CompilationResult result = runGradle("assembleDebug");
+        assertNotEquals(0, result.resultCode);
+        ScopedException scopedException = result.getBindingException();
+        assertNotNull(scopedException);
+        ScopedErrorReport report = scopedException.getScopedErrorReport();
+        assertNotNull(report);
+        assertEquals(1, report.getLocations().size());
+        Location loc = report.getLocations().get(0);
+        if (expectedExtract != null) {
+            String extract = extract(targetFile, loc);
+            assertEquals(expectedExtract, extract);
+        }
+        final File errorFile = new File(report.getFilePath());
+        assertTrue(errorFile.exists());
+        assertEquals(new File(testFolder, targetFile).getCanonicalFile(),
+                errorFile.getCanonicalFile());
+        if (errorMessage != null) {
+            assertEquals(errorMessage, scopedException.getBareMessage());
+        }
+        return scopedException;
+    }
+
     @Test
-    public void testUndefinedVariable() throws IOException, URISyntaxException,
-            InterruptedException {
+    public void testMultipleExceptionsInDifferentFiles()
+            throws IOException, URISyntaxException, InterruptedException {
         prepareProject();
         copyResourceTo("/layout/undefined_variable_binding.xml",
                 "/app/src/main/res/layout/broken.xml");
+        copyResourceTo("/layout/invalid_setter_binding.xml",
+                "/app/src/main/res/layout/invalid_setter.xml");
         CompilationResult result = runGradle("assembleDebug");
         assertNotEquals(0, result.resultCode);
-        assertTrue("Undefined variable",
-                result.errorContainsText(
-                        "Identifiers must have user defined types from the XML file. myVariable is missing it"));
+        List<ScopedException> bindingExceptions = result.getBindingExceptions();
+        assertEquals(2, bindingExceptions.size());
+        File broken = new File(testFolder, "/app/src/main/res/layout/broken.xml");
+        File invalidSetter = new File(testFolder, "/app/src/main/res/layout/invalid_setter.xml");
+        for (ScopedException exception : bindingExceptions) {
+            ScopedErrorReport report = exception.getScopedErrorReport();
+            final File errorFile = new File(report.getFilePath());
+            String message = null;
+            String expectedErrorFile = null;
+            if (errorFile.getCanonicalPath().equals(broken.getCanonicalPath())) {
+                message = String.format(ErrorMessages.UNDEFINED_VARIABLE, "myVariable");
+                expectedErrorFile = "/app/src/main/res/layout/broken.xml";
+            } else if (errorFile.getCanonicalPath().equals(invalidSetter.getCanonicalPath())) {
+                message = String.format(ErrorMessages.CANNOT_FIND_SETTER_CALL, "android:textx",
+                        String.class.getCanonicalName());
+                expectedErrorFile = "/app/src/main/res/layout/invalid_setter.xml";
+            } else {
+                fail("unexpected exception " + exception.getBareMessage());
+            }
+            assertEquals(1, report.getLocations().size());
+            Location loc = report.getLocations().get(0);
+            String extract = extract(expectedErrorFile, loc);
+            assertEquals("myVariable", extract);
+            assertEquals(message, exception.getBareMessage());
+        }
+    }
+
+    @Test
+    public void testUndefinedVariable() throws IOException, URISyntaxException,
+            InterruptedException {
+        ScopedException ex = singleFileErrorTest("/layout/undefined_variable_binding.xml",
+                "/app/src/main/res/layout/broken.xml", "myVariable",
+                String.format(ErrorMessages.UNDEFINED_VARIABLE, "myVariable"));
+    }
+
+    @Test
+    public void testInvalidSetterBinding() throws IOException, URISyntaxException,
+            InterruptedException {
+        prepareProject();
+        ScopedException ex = singleFileErrorTest("/layout/invalid_setter_binding.xml",
+                "/app/src/main/res/layout/invalid_setter.xml", "myVariable",
+                String.format(ErrorMessages.CANNOT_FIND_SETTER_CALL, "android:textx",
+                        String.class.getCanonicalName()));
+    }
+
+    @Test
+    public void testInvalidVariableType() throws IOException, URISyntaxException,
+            InterruptedException {
+        prepareProject();
+        ScopedException ex = singleFileErrorTest("/layout/invalid_variable_type.xml",
+                "/app/src/main/res/layout/invalid_variable.xml", "myVariable",
+                String.format(ErrorMessages.CANNOT_RESOLVE_TYPE, "myVariable~"));
     }
 
     @Test
@@ -96,8 +182,17 @@
         copyResourceTo("/layout/merge_include.xml", "/app/src/main/res/layout/merge_include.xml");
         CompilationResult result = runGradle("assembleDebug");
         assertNotEquals(0, result.resultCode);
-        assertTrue("Merge shouldn't support includes as root. Error message was '" + result.error + "'",
-                result.errorContainsText(
-                        "Data binding does not support include elements as direct children of a merge element"));
+        List<ScopedException> errors = ScopedException.extractErrors(result.error);
+        assertEquals(result.error, 1, errors.size());
+        final ScopedException ex = errors.get(0);
+        final ScopedErrorReport report = ex.getScopedErrorReport();
+        final File errorFile = new File(report.getFilePath());
+        assertTrue(errorFile.exists());
+        assertEquals(
+                new File(testFolder, "/app/src/main/res/layout/merge_include.xml")
+                        .getCanonicalFile(),
+                errorFile.getCanonicalFile());
+        assertEquals("Merge shouldn't support includes as root. Error message was '" + result.error,
+                ErrorMessages.INCLUDE_INSIDE_MERGE, ex.getBareMessage());
     }
 }
diff --git a/compiler/src/main/java/android/databinding/annotationprocessor/ProcessDataBinding.java b/compiler/src/main/java/android/databinding/annotationprocessor/ProcessDataBinding.java
index bf88435..3145a5a 100644
--- a/compiler/src/main/java/android/databinding/annotationprocessor/ProcessDataBinding.java
+++ b/compiler/src/main/java/android/databinding/annotationprocessor/ProcessDataBinding.java
@@ -20,6 +20,7 @@
 
 import android.databinding.BindingBuildInfo;
 import android.databinding.tool.CompilerChef;
+import android.databinding.tool.processing.Scope;
 import android.databinding.tool.reflection.ModelAnalyzer;
 import android.databinding.tool.util.Preconditions;
 import android.databinding.tool.writer.AnnotationJavaFileWriter;
@@ -70,6 +71,7 @@
                 step.onProcessingOver(roundEnv, processingEnv, buildInfo);
             }
         }
+        Scope.assertNoError();
         return done;
     }
 
diff --git a/compiler/src/main/java/android/databinding/tool/Binding.java b/compiler/src/main/java/android/databinding/tool/Binding.java
index 1905af9..842afd5 100644
--- a/compiler/src/main/java/android/databinding/tool/Binding.java
+++ b/compiler/src/main/java/android/databinding/tool/Binding.java
@@ -17,15 +17,21 @@
 package android.databinding.tool;
 
 import android.databinding.tool.expr.Expr;
+import android.databinding.tool.processing.ErrorMessages;
+import android.databinding.tool.processing.Scope;
+import android.databinding.tool.processing.scopes.LocationScopeProvider;
 import android.databinding.tool.reflection.ModelAnalyzer;
 import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.store.Location;
 import android.databinding.tool.store.SetterStore;
 import android.databinding.tool.store.SetterStore.SetterCall;
 import android.databinding.tool.util.L;
 import android.databinding.tool.writer.CodeGenUtil;
 import android.databinding.tool.writer.WriterPackage;
 
-public class Binding {
+import java.util.List;
+
+public class Binding implements LocationScopeProvider {
 
     private final String mName;
     private final Expr mExpr;
@@ -38,6 +44,11 @@
         mExpr = expr;
     }
 
+    @Override
+    public List<Location> provideScopeLocation() {
+        return mExpr.getLocations();
+    }
+
     public void resolveListeners() {
         ModelClass listenerParameter = getListenerParameter();
         if (listenerParameter != null) {
@@ -47,31 +58,41 @@
 
     private SetterStore.BindingSetterCall getSetterCall() {
         if (mSetterCall == null) {
-            ModelClass viewType = mTarget.getResolvedType();
-            if (viewType != null && viewType.extendsViewStub()) {
-                if (isListenerAttribute()) {
-                    ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
-                    ModelClass viewStubProxy = modelAnalyzer.
-                            findClass("android.databinding.ViewStubProxy", null);
-                    mSetterCall = SetterStore.get(modelAnalyzer).getSetterCall(mName,
-                            viewStubProxy, mExpr.getResolvedType(), mExpr.getModel().getImports());
-                } else if (isViewStubAttribute()) {
-                    mSetterCall = new ViewStubDirectCall(mName, viewType, mExpr);
-                } else {
-                    mSetterCall = new ViewStubSetterCall(mName);
+            try {
+                Scope.enter(getTarget());
+                Scope.enter(this);
+                resolveSetterCall();
+                if (mSetterCall == null) {
+                    L.e(ErrorMessages.CANNOT_FIND_SETTER_CALL, mName, mExpr.getResolvedType());
                 }
-            } else {
-                mSetterCall = SetterStore.get(ModelAnalyzer.getInstance()).getSetterCall(mName,
-                        viewType, mExpr.getResolvedType(), mExpr.getModel().getImports());
+            } finally {
+                Scope.exit();
+                Scope.exit();
             }
         }
-        if (mSetterCall == null) {
-            L.e("Cannot find the setter for attribute '%s' on %s with parameter type %s.",
-                    mName, mTarget, mExpr.getResolvedType());
-        }
         return mSetterCall;
     }
 
+    private void resolveSetterCall() {
+        ModelClass viewType = mTarget.getResolvedType();
+        if (viewType != null && viewType.extendsViewStub()) {
+            if (isListenerAttribute()) {
+                ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance();
+                ModelClass viewStubProxy = modelAnalyzer.
+                        findClass("android.databinding.ViewStubProxy", null);
+                mSetterCall = SetterStore.get(modelAnalyzer).getSetterCall(mName,
+                        viewStubProxy, mExpr.getResolvedType(), mExpr.getModel().getImports());
+            } else if (isViewStubAttribute()) {
+                mSetterCall = new ViewStubDirectCall(mName, viewType, mExpr);
+            } else {
+                mSetterCall = new ViewStubSetterCall(mName);
+            }
+        } else {
+            mSetterCall = SetterStore.get(ModelAnalyzer.getInstance()).getSetterCall(mName,
+                    viewType, mExpr.getResolvedType(), mExpr.getModel().getImports());
+        }
+    }
+
     /**
      * Similar to getSetterCall, but assumes an Object parameter to find the best matching listener.
      */
diff --git a/compiler/src/main/java/android/databinding/tool/BindingTarget.java b/compiler/src/main/java/android/databinding/tool/BindingTarget.java
index 34e9371..5c3fac5 100644
--- a/compiler/src/main/java/android/databinding/tool/BindingTarget.java
+++ b/compiler/src/main/java/android/databinding/tool/BindingTarget.java
@@ -18,8 +18,12 @@
 
 import android.databinding.tool.expr.Expr;
 import android.databinding.tool.expr.ExprModel;
+import android.databinding.tool.processing.Scope;
+import android.databinding.tool.processing.scopes.FileScopeProvider;
+import android.databinding.tool.processing.scopes.LocationScopeProvider;
 import android.databinding.tool.reflection.ModelAnalyzer;
 import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.store.Location;
 import android.databinding.tool.store.ResourceBundle;
 import android.databinding.tool.store.SetterStore;
 import android.databinding.tool.util.L;
@@ -31,7 +35,7 @@
 import java.util.List;
 import java.util.Map;
 
-public class BindingTarget {
+public class BindingTarget implements LocationScopeProvider {
     List<Binding> mBindings = new ArrayList<Binding>();
     ExprModel mModel;
     ModelClass mResolvedClass;
@@ -56,6 +60,11 @@
         return mBundle.getInterfaceType() == null ? mBundle.getFullClassName() : mBundle.getInterfaceType();
     }
 
+    @Override
+    public List<Location> provideScopeLocation() {
+        return mBundle.provideScopeLocation();
+    }
+
     public String getId() {
         return mBundle.getId();
     }
@@ -119,8 +128,13 @@
         final ModelClass[] types = new ModelClass[mBindings.size()];
         for (int i = 0; i < mBindings.size(); i ++) {
             Binding binding = mBindings.get(i);
-            attributes[i] = binding.getName();
-            types[i] = binding.getExpr().getResolvedType();
+            try {
+                Scope.enter(binding);
+                attributes[i] = binding.getName();
+                types[i] = binding.getExpr().getResolvedType();
+            } finally {
+                Scope.exit();
+            }
         }
         final List<SetterStore.MultiAttributeSetter> multiAttributeSetterCalls = setterStore
                 .getMultiAttributeSetterCalls(attributes, getResolvedType(), types);
diff --git a/compiler/src/main/java/android/databinding/tool/DataBinder.java b/compiler/src/main/java/android/databinding/tool/DataBinder.java
index 9cb91c7..d560f0f 100644
--- a/compiler/src/main/java/android/databinding/tool/DataBinder.java
+++ b/compiler/src/main/java/android/databinding/tool/DataBinder.java
@@ -16,6 +16,8 @@
 
 package android.databinding.tool;
 
+import android.databinding.tool.processing.Scope;
+import android.databinding.tool.processing.ScopedException;
 import android.databinding.tool.store.ResourceBundle;
 import android.databinding.tool.util.L;
 import android.databinding.tool.writer.JavaFileWriter;
@@ -41,7 +43,11 @@
         for (Map.Entry<String, List<ResourceBundle.LayoutFileBundle>> entry :
                 resourceBundle.getLayoutBundles().entrySet()) {
             for (ResourceBundle.LayoutFileBundle bundle : entry.getValue()) {
-                mLayoutBinders.add(new LayoutBinder(bundle));
+                try {
+                    mLayoutBinders.add(new LayoutBinder(bundle));
+                } catch (ScopedException ex) {
+                    Scope.defer(ex);
+                }
             }
         }
     }
@@ -51,27 +57,41 @@
 
     public void writerBaseClasses(boolean isLibrary) {
         for (LayoutBinder layoutBinder : mLayoutBinders) {
-            if (isLibrary || layoutBinder.hasVariations()) {
-                String className = layoutBinder.getClassName();
-                String canonicalName = layoutBinder.getPackage() + "." + className;
-                if (writtenClasses.contains(canonicalName)) {
-                    continue;
+            try {
+                Scope.enter(layoutBinder);
+                if (isLibrary || layoutBinder.hasVariations()) {
+                    String className = layoutBinder.getClassName();
+                    String canonicalName = layoutBinder.getPackage() + "." + className;
+                    if (writtenClasses.contains(canonicalName)) {
+                        continue;
+                    }
+                    L.d("writing data binder base %s", canonicalName);
+                    mFileWriter.writeToFile(canonicalName,
+                            layoutBinder.writeViewBinderBaseClass(isLibrary));
+                    writtenClasses.add(canonicalName);
                 }
-                L.d("writing data binder base %s", canonicalName);
-                mFileWriter.writeToFile(canonicalName,
-                        layoutBinder.writeViewBinderBaseClass(isLibrary));
-                writtenClasses.add(canonicalName);
+            } catch (ScopedException ex){
+                Scope.defer(ex);
+            } finally {
+                Scope.exit();
             }
         }
     }
 
     public void writeBinders(int minSdk) {
         for (LayoutBinder layoutBinder : mLayoutBinders) {
-            String className = layoutBinder.getImplementationName();
-            String canonicalName = layoutBinder.getPackage() + "." + className;
-            L.d("writing data binder %s", canonicalName);
-            writtenClasses.add(canonicalName);
-            mFileWriter.writeToFile(canonicalName, layoutBinder.writeViewBinder(minSdk));
+            try {
+                Scope.enter(layoutBinder);
+                String className = layoutBinder.getImplementationName();
+                String canonicalName = layoutBinder.getPackage() + "." + className;
+                L.d("writing data binder %s", canonicalName);
+                writtenClasses.add(canonicalName);
+                mFileWriter.writeToFile(canonicalName, layoutBinder.writeViewBinder(minSdk));
+            } catch (ScopedException ex) {
+                Scope.defer(ex);
+            } finally {
+                Scope.exit();
+            }
         }
     }
 
diff --git a/compiler/src/main/java/android/databinding/tool/ExpressionParser.java b/compiler/src/main/java/android/databinding/tool/ExpressionParser.java
index 844100a..467f658 100644
--- a/compiler/src/main/java/android/databinding/tool/ExpressionParser.java
+++ b/compiler/src/main/java/android/databinding/tool/ExpressionParser.java
@@ -82,12 +82,10 @@
                     }
                 }
             });
+            return root.accept(visitor);
         } finally {
             mModel.setCurrentLocationInFile(null);
         }
-        L.d("exp tree: %s", root.toStringTree(parser));
-
-        return root.accept(visitor);
     }
 
     public ExprModel getModel() {
diff --git a/compiler/src/main/java/android/databinding/tool/LayoutBinder.java b/compiler/src/main/java/android/databinding/tool/LayoutBinder.java
index c9efe8f..6144d88 100644
--- a/compiler/src/main/java/android/databinding/tool/LayoutBinder.java
+++ b/compiler/src/main/java/android/databinding/tool/LayoutBinder.java
@@ -23,6 +23,8 @@
 import android.databinding.tool.expr.ExprModel;
 import android.databinding.tool.expr.ExprModel.ResolveListenersCallback;
 import android.databinding.tool.expr.IdentifierExpr;
+import android.databinding.tool.processing.Scope;
+import android.databinding.tool.processing.scopes.FileScopeProvider;
 import android.databinding.tool.store.Location;
 import android.databinding.tool.store.ResourceBundle;
 import android.databinding.tool.store.ResourceBundle.BindingTargetBundle;
@@ -40,7 +42,7 @@
 /**
  * Keeps all information about the bindings per layout file
  */
-public class LayoutBinder implements ResolveListenersCallback {
+public class LayoutBinder implements ResolveListenersCallback, FileScopeProvider {
     private static final Comparator<BindingTarget> COMPARE_FIELD_NAME = new Comparator<BindingTarget>() {
         @Override
         public int compare(BindingTarget first, BindingTarget second) {
@@ -164,33 +166,43 @@
     };
 
     public LayoutBinder(ResourceBundle.LayoutFileBundle layoutBundle) {
-        mExprModel = new ExprModel();
-        mExpressionParser = new ExpressionParser(mExprModel);
-        mBindingTargets = new ArrayList<BindingTarget>();
-        mBundle = layoutBundle;
-        mModulePackage = layoutBundle.getModulePackage();
-        // copy over data.
-        for (ResourceBundle.NameTypeLocation variable : mBundle.getVariables()) {
-            addVariable(variable.name, variable.type, variable.location);
-        }
-
-        for (ResourceBundle.NameTypeLocation userImport : mBundle.getImports()) {
-            mExprModel.addImport(userImport.name, userImport.type, userImport.location);
-        }
-        for (String javaLangClass : sJavaLangClasses) {
-            mExprModel.addImport(javaLangClass, "java.lang." + javaLangClass, null);
-        }
-        for (BindingTargetBundle targetBundle : mBundle.getBindingTargetBundles()) {
-            final BindingTarget bindingTarget = createBindingTarget(targetBundle);
-            for (ResourceBundle.BindingTargetBundle.BindingBundle bindingBundle : targetBundle
-                    .getBindingBundleList()) {
-                bindingTarget.addBinding(bindingBundle.getName(), parse(bindingBundle.getExpr(),
-                        targetBundle.getLocation()));
+        try {
+            Scope.enter(this);
+            mExprModel = new ExprModel();
+            mExpressionParser = new ExpressionParser(mExprModel);
+            mBindingTargets = new ArrayList<BindingTarget>();
+            mBundle = layoutBundle;
+            mModulePackage = layoutBundle.getModulePackage();
+            // copy over data.
+            for (ResourceBundle.NameTypeLocation variable : mBundle.getVariables()) {
+                addVariable(variable.name, variable.type, variable.location);
             }
-            bindingTarget.resolveMultiSetters();
+
+            for (ResourceBundle.NameTypeLocation userImport : mBundle.getImports()) {
+                mExprModel.addImport(userImport.name, userImport.type, userImport.location);
+            }
+            for (String javaLangClass : sJavaLangClasses) {
+                mExprModel.addImport(javaLangClass, "java.lang." + javaLangClass, null);
+            }
+            for (BindingTargetBundle targetBundle : mBundle.getBindingTargetBundles()) {
+                try {
+                    Scope.enter(targetBundle);
+                    final BindingTarget bindingTarget = createBindingTarget(targetBundle);
+                    for (BindingTargetBundle.BindingBundle bindingBundle : targetBundle
+                            .getBindingBundleList()) {
+                        bindingTarget.addBinding(bindingBundle.getName(),
+                                parse(bindingBundle.getExpr(), bindingBundle.getValueLocation()));
+                    }
+                    bindingTarget.resolveMultiSetters();
+                } finally {
+                    Scope.exit();
+                }
+            }
+            mSortedBindingTargets = new ArrayList<BindingTarget>(mBindingTargets);
+            Collections.sort(mSortedBindingTargets, COMPARE_FIELD_NAME);
+        } finally {
+            Scope.exit();
         }
-        mSortedBindingTargets = new ArrayList<BindingTarget>(mBindingTargets);
-        Collections.sort(mSortedBindingTargets, COMPARE_FIELD_NAME);
     }
 
     public void resolveWhichExpressionsAreUsed() {
@@ -321,4 +333,9 @@
             }
         }
     }
+
+    @Override
+    public String provideScopeFilePath() {
+        return mBundle.getAbsoluteFilePath();
+    }
 }
diff --git a/compiler/src/main/java/android/databinding/tool/expr/BracketExpr.java b/compiler/src/main/java/android/databinding/tool/expr/BracketExpr.java
index 2a9df50..a5ea5a7 100644
--- a/compiler/src/main/java/android/databinding/tool/expr/BracketExpr.java
+++ b/compiler/src/main/java/android/databinding/tool/expr/BracketExpr.java
@@ -37,7 +37,7 @@
 
     @Override
     protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
-        ModelClass targetType = getTarget().resolveType(modelAnalyzer);
+        ModelClass targetType = getTarget().getResolvedType();
         if (targetType.isArray()) {
             mAccessor = BracketAccessor.ARRAY;
         } else if (targetType.isList()) {
diff --git a/compiler/src/main/java/android/databinding/tool/expr/Expr.java b/compiler/src/main/java/android/databinding/tool/expr/Expr.java
index c7cf15b..1712bb1 100644
--- a/compiler/src/main/java/android/databinding/tool/expr/Expr.java
+++ b/compiler/src/main/java/android/databinding/tool/expr/Expr.java
@@ -18,9 +18,14 @@
 
 import org.antlr.v4.runtime.misc.Nullable;
 
+import android.databinding.tool.processing.ErrorMessages;
+import android.databinding.tool.processing.Scope;
+import android.databinding.tool.processing.scopes.LocationScopeProvider;
+import android.databinding.tool.processing.scopes.ScopeProvider;
 import android.databinding.tool.reflection.ModelAnalyzer;
 import android.databinding.tool.reflection.ModelClass;
 import android.databinding.tool.store.Location;
+import android.databinding.tool.util.L;
 import android.databinding.tool.util.Preconditions;
 
 import java.util.ArrayList;
@@ -28,7 +33,7 @@
 import java.util.Collections;
 import java.util.List;
 
-abstract public class Expr implements VersionProvider {
+abstract public class Expr implements VersionProvider, LocationScopeProvider {
 
     public static final int NO_ID = -1;
     protected List<Expr> mChildren = new ArrayList<Expr>();
@@ -301,7 +306,15 @@
     public ModelClass getResolvedType() {
         if (mResolvedType == null) {
             // TODO not get instance
-            mResolvedType = resolveType(ModelAnalyzer.getInstance());
+            try {
+                Scope.enter(this);
+                mResolvedType = resolveType(ModelAnalyzer.getInstance());
+                if (mResolvedType == null) {
+                    L.e(ErrorMessages.CANNOT_RESOLVE_TYPE, this);
+                }
+            } finally {
+                Scope.exit();
+            }
         }
         return mResolvedType;
     }
@@ -630,6 +643,11 @@
         return null;
     }
 
+    @Override
+    public List<Location> provideScopeLocation() {
+        return mLocations;
+    }
+
     static class Node {
 
         BitSet mBitSet = new BitSet();
diff --git a/compiler/src/main/java/android/databinding/tool/expr/FieldAccessExpr.java b/compiler/src/main/java/android/databinding/tool/expr/FieldAccessExpr.java
index e1afbe5..220cb54 100644
--- a/compiler/src/main/java/android/databinding/tool/expr/FieldAccessExpr.java
+++ b/compiler/src/main/java/android/databinding/tool/expr/FieldAccessExpr.java
@@ -16,6 +16,7 @@
 
 package android.databinding.tool.expr;
 
+import android.databinding.tool.processing.Scope;
 import android.databinding.tool.reflection.Callable;
 import android.databinding.tool.reflection.Callable.Type;
 import android.databinding.tool.reflection.ModelAnalyzer;
@@ -196,8 +197,13 @@
 
     @Override
     public void updateExpr(ModelAnalyzer modelAnalyzer) {
-        resolveType(modelAnalyzer);
-        super.updateExpr(modelAnalyzer);
+        try {
+            Scope.enter(this);
+            resolveType(modelAnalyzer);
+            super.updateExpr(modelAnalyzer);
+        } finally {
+            Scope.exit();
+        }
     }
 
     @Override
@@ -207,7 +213,7 @@
                 return modelAnalyzer.findClass(Object.class);
             }
             Expr child = getChild();
-            child.resolveType(modelAnalyzer);
+            child.getResolvedType();
             boolean isStatic = child instanceof StaticIdentifierExpr;
             ModelClass resolvedType = child.getResolvedType();
             L.d("resolving %s. Resolved class type: %s", this, resolvedType);
diff --git a/compiler/src/main/java/android/databinding/tool/expr/GroupExpr.java b/compiler/src/main/java/android/databinding/tool/expr/GroupExpr.java
index 8af1d68..bacfe8d 100644
--- a/compiler/src/main/java/android/databinding/tool/expr/GroupExpr.java
+++ b/compiler/src/main/java/android/databinding/tool/expr/GroupExpr.java
@@ -28,7 +28,7 @@
 
     @Override
     protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
-        return getWrapped().resolveType(modelAnalyzer);
+        return getWrapped().getResolvedType();
     }
 
     @Override
diff --git a/compiler/src/main/java/android/databinding/tool/expr/IdentifierExpr.java b/compiler/src/main/java/android/databinding/tool/expr/IdentifierExpr.java
index 9464698..f68e3d6 100644
--- a/compiler/src/main/java/android/databinding/tool/expr/IdentifierExpr.java
+++ b/compiler/src/main/java/android/databinding/tool/expr/IdentifierExpr.java
@@ -16,6 +16,7 @@
 
 package android.databinding.tool.expr;
 
+import android.databinding.tool.processing.ErrorMessages;
 import android.databinding.tool.reflection.ModelAnalyzer;
 import android.databinding.tool.reflection.ModelClass;
 import android.databinding.tool.util.L;
@@ -52,16 +53,6 @@
         return mUserDefinedType;
     }
 
-    public String getExpandedUserDefinedType(ModelAnalyzer modelAnalyzer) {
-        Preconditions.checkNotNull(mUserDefinedType,
-                "Identifiers must have user defined types from the XML file. %s is missing it",
-                mName);
-        final String expanded = modelAnalyzer
-                .applyImports(mUserDefinedType, getModel().getImports());
-        L.d("expanded version of %s is %s", mUserDefinedType, expanded);
-        return expanded;
-    }
-
     @Override
     public boolean isDynamic() {
         return true;
@@ -69,8 +60,7 @@
 
     @Override
     protected ModelClass resolveType(final ModelAnalyzer modelAnalyzer) {
-        Preconditions.checkNotNull(mUserDefinedType,
-                "Identifiers must have user defined types from the XML file. %s is missing it", mName);
+        Preconditions.checkNotNull(mUserDefinedType, ErrorMessages.UNDEFINED_VARIABLE, mName);
         return modelAnalyzer.findClass(mUserDefinedType, getModel().getImports());
     }
 
diff --git a/compiler/src/main/java/android/databinding/tool/expr/MethodCallExpr.java b/compiler/src/main/java/android/databinding/tool/expr/MethodCallExpr.java
index 997f950..a96e8f8 100644
--- a/compiler/src/main/java/android/databinding/tool/expr/MethodCallExpr.java
+++ b/compiler/src/main/java/android/databinding/tool/expr/MethodCallExpr.java
@@ -16,6 +16,7 @@
 
 package android.databinding.tool.expr;
 
+import android.databinding.tool.processing.Scope;
 import android.databinding.tool.reflection.Callable;
 import android.databinding.tool.reflection.Callable.Type;
 import android.databinding.tool.reflection.ModelAnalyzer;
@@ -50,8 +51,13 @@
 
     @Override
     public void updateExpr(ModelAnalyzer modelAnalyzer) {
-        resolveType(modelAnalyzer);
-        super.updateExpr(modelAnalyzer);
+        try {
+            Scope.enter(this);
+            resolveType(modelAnalyzer);
+            super.updateExpr(modelAnalyzer);
+        } finally {
+            Scope.exit();
+        }
     }
 
     @Override
diff --git a/compiler/src/main/java/android/databinding/tool/expr/UnaryExpr.java b/compiler/src/main/java/android/databinding/tool/expr/UnaryExpr.java
index ad57949..1aff945 100644
--- a/compiler/src/main/java/android/databinding/tool/expr/UnaryExpr.java
+++ b/compiler/src/main/java/android/databinding/tool/expr/UnaryExpr.java
@@ -35,7 +35,7 @@
 
     @Override
     protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
-        return getExpr().resolveType(modelAnalyzer);
+        return getExpr().getResolvedType();
     }
 
     @Override
diff --git a/compiler/src/test/java/android/databinding/tool/MockLayoutBinder.java b/compiler/src/test/java/android/databinding/tool/MockLayoutBinder.java
index 36b92fe..3380ffa 100644
--- a/compiler/src/test/java/android/databinding/tool/MockLayoutBinder.java
+++ b/compiler/src/test/java/android/databinding/tool/MockLayoutBinder.java
@@ -15,9 +15,13 @@
 
 import android.databinding.tool.store.ResourceBundle;
 
+import java.io.File;
+
 public class MockLayoutBinder extends LayoutBinder {
 
     public MockLayoutBinder() {
-        super(new ResourceBundle.LayoutFileBundle("blah.xml", "layout", "com.test.submodule", false));
+        super(new ResourceBundle.LayoutFileBundle(new File("./blah.xml"), "blah.xml", "layout",
+                "com.test.submodule",
+                false));
     }
 }
diff --git a/compilerCommon/build.gradle b/compilerCommon/build.gradle
index 4e3c5d3..bc59c72 100644
--- a/compilerCommon/build.gradle
+++ b/compilerCommon/build.gradle
@@ -17,7 +17,6 @@
 apply plugin: 'java'
 
 
-version = '1.0'
 sourceCompatibility = config.javaTargetCompatibility
 targetCompatibility = config.javaSourceCompatibility
 repositories {
@@ -32,6 +31,12 @@
             srcDir 'src/main/grammar-gen'
         }
     }
+    test {
+        java {
+            srcDir 'src/test/java'
+        }
+    }
+
 }
 
 dependencies {
diff --git a/compilerCommon/src/main/java/android/databinding/tool/processing/ErrorMessages.java b/compilerCommon/src/main/java/android/databinding/tool/processing/ErrorMessages.java
new file mode 100644
index 0000000..d1d103d
--- /dev/null
+++ b/compilerCommon/src/main/java/android/databinding/tool/processing/ErrorMessages.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.processing;
+
+public class ErrorMessages {
+    public static final String INCLUDE_INSIDE_MERGE =
+            "Data binding does not support include elements as direct children of a merge element.";
+    public static final String UNDEFINED_VARIABLE =
+            "Identifiers must have user defined types from the XML file. %s is missing it";
+    public static final String CANNOT_FIND_SETTER_CALL =
+            "Cannot find the setter for attribute '%s' with parameter type %s.";
+    public static final String CANNOT_RESOLVE_TYPE =
+            "Cannot resolve type for %s";
+}
diff --git a/compilerCommon/src/main/java/android/databinding/tool/processing/Scope.java b/compilerCommon/src/main/java/android/databinding/tool/processing/Scope.java
new file mode 100644
index 0000000..723fd0a
--- /dev/null
+++ b/compilerCommon/src/main/java/android/databinding/tool/processing/Scope.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.processing;
+
+import android.databinding.tool.processing.scopes.FileScopeProvider;
+import android.databinding.tool.processing.scopes.LocationScopeProvider;
+import android.databinding.tool.processing.scopes.ScopeProvider;
+import android.databinding.tool.store.Location;
+import android.databinding.tool.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Utility class to keep track of "logical" stack traces, which we can use to print better error
+ * reports.
+ */
+public class Scope {
+
+    private static ThreadLocal<ScopeEntry> sScopeItems = new ThreadLocal<ScopeEntry>();
+    static List<ScopedException> sDeferredExceptions = new ArrayList<>();
+
+    public static void enter(ScopeProvider scopeProvider) {
+        ScopeEntry peek = sScopeItems.get();
+        ScopeEntry entry = new ScopeEntry(scopeProvider, peek);
+        sScopeItems.set(entry);
+    }
+
+    public static void exit() {
+        ScopeEntry entry = sScopeItems.get();
+        Preconditions.checkNotNull(entry, "Inconsistent scope exit");
+        sScopeItems.set(entry.mParent);
+    }
+
+    public static void defer(ScopedException exception) {
+        sDeferredExceptions.add(exception);
+    }
+
+    public static void assertNoError() {
+        if (sDeferredExceptions.isEmpty()) {
+            return;
+        }
+        StringBuilder sb = new StringBuilder();
+        for (ScopedException ex : sDeferredExceptions) {
+            sb.append(ex.getMessage()).append("\n");
+        }
+        throw new RuntimeException("Found data binding errors.\n" + sb.toString());
+    }
+
+    static String produceScopeLog() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("full scope log\n");
+        ScopeEntry top = sScopeItems.get();
+        while (top != null) {
+            ScopeProvider provider = top.mProvider;
+            sb.append("---").append(provider).append("\n");
+            if (provider instanceof FileScopeProvider) {
+                sb.append("file:").append(((FileScopeProvider) provider).provideScopeFilePath())
+                        .append("\n");
+            }
+            if (provider instanceof LocationScopeProvider) {
+                LocationScopeProvider loc = (LocationScopeProvider) provider;
+                sb.append("loc:");
+                List<Location> locations = loc.provideScopeLocation();
+                if (locations == null) {
+                    sb.append("null\n");
+                } else {
+                    for (Location location : locations) {
+                        sb.append(location).append("\n");
+                    }
+                }
+            }
+            top = top.mParent;
+        }
+        sb.append("---\n");
+        return sb.toString();
+    }
+
+    static ScopedErrorReport createReport() {
+        ScopeEntry top = sScopeItems.get();
+        String filePath = null;
+        List<Location> locations = null;
+        while (top != null && (filePath == null || locations == null)) {
+            ScopeProvider provider = top.mProvider;
+            if (locations == null && provider instanceof LocationScopeProvider) {
+                locations = findAbsoluteLocationFrom(top, (LocationScopeProvider) provider);
+            }
+            if (filePath == null && provider instanceof FileScopeProvider) {
+                filePath = ((FileScopeProvider) provider).provideScopeFilePath();
+            }
+            top = top.mParent;
+        }
+        return new ScopedErrorReport(filePath, locations);
+    }
+
+    private static List<Location> findAbsoluteLocationFrom(ScopeEntry entry,
+            LocationScopeProvider top) {
+        List<Location> locations = top.provideScopeLocation();
+        if (locations == null || locations.isEmpty()) {
+            return null;
+        }
+        if (locations.size() == 1) {
+            return Arrays.asList(locations.get(0).toAbsoluteLocation());
+        }
+        // We have more than 1 location. Depending on the scope, we may or may not want all of them
+        List<Location> chosen = new ArrayList<>();
+        for (Location location : locations) {
+            Location absLocation = location.toAbsoluteLocation();
+            if (validatedContained(entry.mParent, absLocation)) {
+                chosen.add(absLocation);
+            }
+        }
+        return chosen.isEmpty() ? locations : chosen;
+    }
+
+    private static boolean validatedContained(ScopeEntry parent, Location absLocation) {
+        if (parent == null) {
+            return true;
+        }
+        ScopeProvider provider = parent.mProvider;
+        if (!(provider instanceof LocationScopeProvider)) {
+            return validatedContained(parent.mParent, absLocation);
+        }
+        List<Location> absoluteParents = findAbsoluteLocationFrom(parent,
+                (LocationScopeProvider) provider);
+        for (Location location : absoluteParents) {
+            if (location.contains(absLocation)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static class ScopeEntry {
+
+        ScopeProvider mProvider;
+
+        ScopeEntry mParent;
+
+        public ScopeEntry(ScopeProvider scopeProvider, ScopeEntry parent) {
+            mProvider = scopeProvider;
+            mParent = parent;
+        }
+    }
+}
\ No newline at end of file
diff --git a/compilerCommon/src/main/java/android/databinding/tool/processing/ScopedErrorReport.java b/compilerCommon/src/main/java/android/databinding/tool/processing/ScopedErrorReport.java
new file mode 100644
index 0000000..dfd2039
--- /dev/null
+++ b/compilerCommon/src/main/java/android/databinding/tool/processing/ScopedErrorReport.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.processing;
+
+import org.apache.commons.lang3.StringUtils;
+
+import android.databinding.tool.store.Location;
+
+import java.util.List;
+
+public class ScopedErrorReport {
+
+    private final String mFilePath;
+
+    private final List<Location> mLocations;
+
+    /**
+     * Only created by Scope
+     */
+    ScopedErrorReport(String filePath, List<Location> locations) {
+        mFilePath = filePath;
+        mLocations = locations;
+    }
+
+    public String getFilePath() {
+        return mFilePath;
+    }
+
+    public List<Location> getLocations() {
+        return mLocations;
+    }
+
+    public boolean isValid() {
+        return StringUtils.isNotBlank(mFilePath);
+    }
+
+    public String toUserReadableString() {
+        StringBuilder sb = new StringBuilder();
+        if (mFilePath != null) {
+            sb.append("File:");
+            sb.append(mFilePath);
+        }
+        if (mLocations != null && mLocations.size() > 0) {
+            if (mLocations.size() > 1) {
+                sb.append("Locations:");
+                for (Location location : mLocations) {
+                    sb.append("\n    ").append(location.toUserReadableString());
+                }
+            } else {
+                sb.append("\n    Location: ").append(mLocations.get(0).toUserReadableString());
+            }
+        }
+        return sb.toString();
+    }
+}
diff --git a/compilerCommon/src/main/java/android/databinding/tool/processing/ScopedException.java b/compilerCommon/src/main/java/android/databinding/tool/processing/ScopedException.java
new file mode 100644
index 0000000..73f42a9
--- /dev/null
+++ b/compilerCommon/src/main/java/android/databinding/tool/processing/ScopedException.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.processing;
+
+import org.apache.commons.lang3.StringUtils;
+
+import android.databinding.tool.store.Location;
+import android.databinding.tool.util.L;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * An exception that contains scope information.
+ */
+public class ScopedException extends RuntimeException {
+    public static final String ERROR_LOG_PREFIX = "****/ data binding error ****";
+    public static final String ERROR_LOG_SUFFIX = "****\\ data binding error ****";
+    public static final String MSG_KEY = "msg:";
+    public static final String LOCATION_KEY = "loc:";
+    public static final String FILE_KEY = "file:";
+    private ScopedErrorReport mScopedErrorReport;
+    private String mScopeLog;
+
+    public ScopedException(String message, Object... args) {
+        super(message == null ? "unknown data binding exception" : String.format(message, args));
+        mScopedErrorReport = Scope.createReport();
+        mScopeLog = L.isDebugEnabled() ? Scope.produceScopeLog() : null;
+    }
+
+    ScopedException(String message, ScopedErrorReport scopedErrorReport) {
+        super(message);
+        mScopedErrorReport = scopedErrorReport;
+    }
+
+    public String getBareMessage() {
+        return super.getMessage();
+    }
+
+    @Override
+    public String getMessage() {
+        ScopedErrorReport scopedError = getScopedErrorReport();
+        StringBuilder sb = new StringBuilder();
+        sb.append(ERROR_LOG_PREFIX)
+                .append(MSG_KEY).append(super.getMessage()).append("\n")
+                .append(FILE_KEY).append(scopedError.getFilePath()).append("\n");
+        if (scopedError.getLocations() != null) {
+            for (Location location : scopedError.getLocations()) {
+                sb.append(LOCATION_KEY).append(location.toUserReadableString()).append("\n");
+            }
+        }
+        sb.append(ERROR_LOG_SUFFIX);
+        return StringUtils.join(StringUtils.split(sb.toString(), System.lineSeparator()), " ");
+    }
+
+    public ScopedErrorReport getScopedErrorReport() {
+        return mScopedErrorReport;
+    }
+
+    public boolean isValid() {
+        return mScopedErrorReport.isValid();
+    }
+
+    public static ScopedException createFromOutput(String output) {
+        String message = "";
+        String file = "";
+        List<Location> locations = new ArrayList<>();
+        int msgStart = output.indexOf(MSG_KEY);
+        if (msgStart < 0) {
+            message = output;
+        } else {
+            int fileStart = output.indexOf(FILE_KEY, msgStart + MSG_KEY.length());
+            if (fileStart < 0) {
+                message = output;
+            } else {
+                message = output.substring(msgStart + MSG_KEY.length(), fileStart);
+                int locStart = output.indexOf(LOCATION_KEY, fileStart + FILE_KEY.length());
+                if (locStart < 0) {
+                    file = output.substring(fileStart + FILE_KEY.length());
+                } else {
+                    file = output.substring(fileStart + FILE_KEY.length(), locStart);
+                    int nextLoc = 0;
+                    while(nextLoc >= 0) {
+                        nextLoc = output.indexOf(LOCATION_KEY, locStart + LOCATION_KEY.length());
+                        Location loc;
+                        if (nextLoc < 0) {
+                            loc = Location.fromUserReadableString(
+                                    output.substring(locStart + LOCATION_KEY.length()));
+                        } else {
+                            loc = Location.fromUserReadableString(
+                                    output.substring(locStart + LOCATION_KEY.length(), nextLoc));
+                        }
+                        if (loc != null && loc.isValid()) {
+                            locations.add(loc);
+                        }
+                        locStart = nextLoc;
+                    }
+                }
+            }
+        }
+        return new ScopedException(message.trim(),
+                new ScopedErrorReport(StringUtils.isEmpty(file) ? null : file.trim(), locations));
+    }
+
+    public static List<ScopedException> extractErrors(String output) {
+        List<ScopedException> errors = new ArrayList<>();
+        int index = output.indexOf(ERROR_LOG_PREFIX);
+        final int limit = output.length();
+        while (index >= 0 && index < limit) {
+            int end = output.indexOf(ERROR_LOG_SUFFIX, index + ERROR_LOG_PREFIX.length());
+            if (end == -1) {
+                errors.add(createFromOutput(output.substring(index + ERROR_LOG_PREFIX.length())));
+                break;
+            } else {
+                errors.add(createFromOutput(output.substring(index + ERROR_LOG_PREFIX.length(),
+                        end)));
+                index = output.indexOf(ERROR_LOG_PREFIX, end + ERROR_LOG_SUFFIX.length());
+            }
+        }
+        return errors;
+    }
+}
diff --git a/compilerCommon/src/main/java/android/databinding/tool/processing/scopes/FileScopeProvider.java b/compilerCommon/src/main/java/android/databinding/tool/processing/scopes/FileScopeProvider.java
new file mode 100644
index 0000000..43452d7
--- /dev/null
+++ b/compilerCommon/src/main/java/android/databinding/tool/processing/scopes/FileScopeProvider.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.processing.scopes;
+
+/**
+ * An item that is tight to a source file.
+ */
+public interface FileScopeProvider extends ScopeProvider {
+    String provideScopeFilePath();
+}
diff --git a/compilerCommon/src/main/java/android/databinding/tool/processing/scopes/LocationScopeProvider.java b/compilerCommon/src/main/java/android/databinding/tool/processing/scopes/LocationScopeProvider.java
new file mode 100644
index 0000000..ee7f8a9
--- /dev/null
+++ b/compilerCommon/src/main/java/android/databinding/tool/processing/scopes/LocationScopeProvider.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.processing.scopes;
+
+
+import android.databinding.tool.store.Location;
+
+import java.util.List;
+
+/**
+ * An item that is tight to locations in a source file.
+ */
+public interface LocationScopeProvider extends ScopeProvider {
+    public List<Location> provideScopeLocation();
+}
diff --git a/compilerCommon/src/main/java/android/databinding/tool/processing/scopes/ScopeProvider.java b/compilerCommon/src/main/java/android/databinding/tool/processing/scopes/ScopeProvider.java
new file mode 100644
index 0000000..7c3cb82
--- /dev/null
+++ b/compilerCommon/src/main/java/android/databinding/tool/processing/scopes/ScopeProvider.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.processing.scopes;
+
+/**
+ * Base class for all scopes
+ */
+public interface ScopeProvider {
+
+}
diff --git a/compilerCommon/src/main/java/android/databinding/tool/store/LayoutFileParser.java b/compilerCommon/src/main/java/android/databinding/tool/store/LayoutFileParser.java
index 1805729..422cd7e 100644
--- a/compilerCommon/src/main/java/android/databinding/tool/store/LayoutFileParser.java
+++ b/compilerCommon/src/main/java/android/databinding/tool/store/LayoutFileParser.java
@@ -28,6 +28,9 @@
 import android.databinding.parser.XMLLexer;
 import android.databinding.parser.XMLParser;
 import android.databinding.parser.XMLParserBaseVisitor;
+import android.databinding.tool.processing.ErrorMessages;
+import android.databinding.tool.processing.Scope;
+import android.databinding.tool.processing.scopes.FileScopeProvider;
 import android.databinding.tool.util.L;
 import android.databinding.tool.util.ParserHelper;
 import android.databinding.tool.util.Preconditions;
@@ -64,47 +67,66 @@
 
     private static final String LAYOUT_PREFIX = "@layout/";
 
-    public ResourceBundle.LayoutFileBundle parseXml(File xml, String pkg, int minSdk)
+    public ResourceBundle.LayoutFileBundle parseXml(final File xml, String pkg, int minSdk)
             throws ParserConfigurationException, IOException, SAXException,
             XPathExpressionException {
-        final String xmlNoExtension = ParserHelper.stripExtension(xml.getName());
-        final String newTag = xml.getParentFile().getName() + '/' + xmlNoExtension;
-        File original = stripFileAndGetOriginal(xml, newTag);
-        if (original == null) {
-            L.d("assuming the file is the original for %s", xml.getAbsoluteFile());
-            original = xml;
+        try {
+            Scope.enter(new FileScopeProvider() {
+                @Override
+                public String provideScopeFilePath() {
+                    return xml.getAbsolutePath();
+                }
+            });
+            final String xmlNoExtension = ParserHelper.stripExtension(xml.getName());
+            final String newTag = xml.getParentFile().getName() + '/' + xmlNoExtension;
+            File original = stripFileAndGetOriginal(xml, newTag);
+            if (original == null) {
+                L.d("assuming the file is the original for %s", xml.getAbsoluteFile());
+                original = xml;
+            }
+            return parseXml(original, pkg);
+        } finally {
+            Scope.exit();
         }
-        return parseXml(original, pkg);
     }
 
-    private ResourceBundle.LayoutFileBundle parseXml(File original, String pkg)
+    private ResourceBundle.LayoutFileBundle parseXml(final File original, String pkg)
             throws IOException {
-        final String xmlNoExtension = ParserHelper.stripExtension(original.getName());
-        ANTLRInputStream inputStream = new ANTLRInputStream(new FileReader(original));
-        XMLLexer lexer = new XMLLexer(inputStream);
-        CommonTokenStream tokenStream = new CommonTokenStream(lexer);
-        XMLParser parser = new XMLParser(tokenStream);
-        XMLParser.DocumentContext expr = parser.document();
-        XMLParser.ElementContext root = expr.element();
-        if (!"layout".equals(root.elmName.getText())) {
-            return null;
-        }
-        XMLParser.ElementContext data = getDataNode(root);
-        XMLParser.ElementContext rootView = getViewNode(original, root);
+        try {
+            Scope.enter(new FileScopeProvider() {
+                @Override
+                public String provideScopeFilePath() {
+                    return original.getAbsolutePath();
+                }
+            });
+            final String xmlNoExtension = ParserHelper.stripExtension(original.getName());
+            ANTLRInputStream inputStream = new ANTLRInputStream(new FileReader(original));
+            XMLLexer lexer = new XMLLexer(inputStream);
+            CommonTokenStream tokenStream = new CommonTokenStream(lexer);
+            XMLParser parser = new XMLParser(tokenStream);
+            XMLParser.DocumentContext expr = parser.document();
+            XMLParser.ElementContext root = expr.element();
+            if (!"layout".equals(root.elmName.getText())) {
+                return null;
+            }
+            XMLParser.ElementContext data = getDataNode(root);
+            XMLParser.ElementContext rootView = getViewNode(original, root);
 
-        if (hasMergeInclude(rootView)) {
-            L.e("Data binding does not support include elements as direct children of a " +
-                    "merge element: %s", original.getPath());
-            return null;
-        }
-        boolean isMerge = "merge".equals(rootView.elmName.getText());
+            if (hasMergeInclude(rootView)) {
+                L.e(ErrorMessages.INCLUDE_INSIDE_MERGE);
+                return null;
+            }
+            boolean isMerge = "merge".equals(rootView.elmName.getText());
 
-        ResourceBundle.LayoutFileBundle bundle = new ResourceBundle.LayoutFileBundle(
-                xmlNoExtension, original.getParentFile().getName(), pkg, isMerge);
-        final String newTag = original.getParentFile().getName() + '/' + xmlNoExtension;
-        parseData(original, data, bundle);
-        parseExpressions(newTag, rootView, isMerge, bundle);
-        return bundle;
+            ResourceBundle.LayoutFileBundle bundle = new ResourceBundle.LayoutFileBundle(original,
+                    xmlNoExtension, original.getParentFile().getName(), pkg, isMerge);
+            final String newTag = original.getParentFile().getName() + '/' + xmlNoExtension;
+            parseData(original, data, bundle);
+            parseExpressions(newTag, rootView, isMerge, bundle);
+            return bundle;
+        } finally {
+            Scope.exit();
+        }
     }
 
     private void parseExpressions(String newTag, final XMLParser.ElementContext rootView,
@@ -215,8 +237,16 @@
                 if (value.charAt(0) == '@' && value.charAt(1) == '{' &&
                         value.charAt(value.length() - 1) == '}') {
                     final String strippedValue = value.substring(2, value.length() - 1);
+                    Location attrLocation = new Location(attr);
+                    Location valueLocation = new Location();
+                    // offset to 0 based
+                    valueLocation.startLine = attr.attrValue.getLine() - 1;
+                    valueLocation.startOffset = attr.attrValue.getCharPositionInLine() +
+                            attr.attrValue.getText().indexOf(strippedValue);
+                    valueLocation.endLine = attrLocation.endLine;
+                    valueLocation.endOffset = attrLocation.endOffset - 2; // account for: "}
                     bindingTargetBundle.addBinding(escapeQuotes(attr.attrName.getText(), false)
-                            , strippedValue);
+                            , strippedValue, attrLocation, valueLocation);
                 }
             }
         }
diff --git a/compilerCommon/src/main/java/android/databinding/tool/store/Location.java b/compilerCommon/src/main/java/android/databinding/tool/store/Location.java
index 9fbd2c2..3342016 100644
--- a/compilerCommon/src/main/java/android/databinding/tool/store/Location.java
+++ b/compilerCommon/src/main/java/android/databinding/tool/store/Location.java
@@ -17,6 +17,7 @@
 package android.databinding.tool.store;
 import org.antlr.v4.runtime.ParserRuleContext;
 import org.antlr.v4.runtime.Token;
+import org.apache.commons.lang3.StringUtils;
 
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
@@ -40,7 +41,7 @@
     public int endLine;
     @XmlAttribute(name = "endOffset")
     public int endOffset;
-    @XmlElement
+    @XmlElement(name = "parentLocation")
     public Location parentLocation;
 
     // for XML unmarshalling
@@ -48,6 +49,13 @@
         startOffset = endOffset = startLine = endLine = NaN;
     }
 
+    public Location(Location other) {
+        startOffset = other.startOffset;
+        endOffset = other.endOffset;
+        startLine = other.startLine;
+        endLine = other.endLine;
+    }
+
     public Location(Token start, Token end) {
         if (start == null) {
             startLine = startOffset = NaN;
@@ -60,7 +68,10 @@
             endLine = endOffset = NaN;
         } else {
             endLine = end.getLine() - 1; // token lines start from 1
-            endOffset = end.getCharPositionInLine();
+            String endText = end.getText();
+            int lastLineStart = endText.lastIndexOf(System.lineSeparator());
+            String lastLine = lastLineStart < 0 ? endText : endText.substring(lastLineStart + 1);
+            endOffset = end.getCharPositionInLine() + lastLine.length() - 1;//end is inclusive
         }
     }
 
@@ -69,6 +80,24 @@
                 context == null ? null : context.getStop());
     }
 
+    public Location(int startLine, int startOffset, int endLine, int endOffset) {
+        this.startOffset = startOffset;
+        this.startLine = startLine;
+        this.endLine = endLine;
+        this.endOffset = endOffset;
+    }
+
+    @Override
+    public String toString() {
+        return "Location{" +
+                "startLine=" + startLine +
+                ", startOffset=" + startOffset +
+                ", endLine=" + endLine +
+                ", endOffset=" + endOffset +
+                ", parentLocation=" + parentLocation +
+                '}';
+    }
+
     public void setParentLocation(Location parentLocation) {
         this.parentLocation = parentLocation;
     }
@@ -112,4 +141,86 @@
         result = 31 * result + endOffset;
         return result;
     }
+
+    public boolean isValid() {
+        return startLine != NaN && endLine != NaN && startOffset != NaN && endOffset != NaN;
+    }
+
+    public boolean contains(Location other) {
+        if (startLine > other.startLine) {
+            return false;
+        }
+        if (startLine == other.startLine && startOffset > other.startOffset) {
+            return false;
+        }
+        if (endLine < other.endLine) {
+            return false;
+        }
+        if (endLine == other.endLine && endOffset < other.endOffset) {
+            return false;
+        }
+        return true;
+    }
+
+    private Location getValidParentAbsoluteLocation() {
+        if (parentLocation == null) {
+            return null;
+        }
+        if (parentLocation.isValid()) {
+            return parentLocation.toAbsoluteLocation();
+        }
+        return parentLocation.getValidParentAbsoluteLocation();
+    }
+
+    public Location toAbsoluteLocation() {
+        Location absoluteParent = getValidParentAbsoluteLocation();
+        if (absoluteParent == null) {
+            return this;
+        }
+        Location copy = new Location(this);
+        boolean sameLine = copy.startLine == copy.endLine;
+        if (copy.startLine == 0) {
+            copy.startOffset += absoluteParent.startOffset;
+        }
+        if (sameLine) {
+            copy.endOffset += absoluteParent.startOffset;
+        }
+
+        copy.startLine += absoluteParent.startLine;
+        copy.endLine += absoluteParent.startLine;
+        return copy;
+    }
+
+    public String toUserReadableString() {
+        return startLine + ":" + startOffset + " - " + endLine + ":" + endOffset;
+    }
+
+    public static Location fromUserReadableString(String str) {
+        int glue = str.indexOf('-');
+        if (glue == -1) {
+            return new Location();
+        }
+        String start = str.substring(0, glue);
+        String end = str.substring(glue + 1);
+        int[] point = new int[]{-1, -1};
+        Location location = new Location();
+        parsePoint(start, point);
+        location.startLine = point[0];
+        location.startOffset = point[1];
+        point[0] = point[1] = -1;
+        parsePoint(end, point);
+        location.endLine = point[0];
+        location.endOffset = point[1];
+        return location;
+    }
+
+    private static boolean parsePoint(String content, int[] into) {
+        int index = content.indexOf(':');
+        if (index == -1) {
+            return false;
+        }
+        into[0] = Integer.parseInt(content.substring(0, index).trim());
+        into[1] = Integer.parseInt(content.substring(index + 1).trim());
+        return true;
+    }
 }
diff --git a/compilerCommon/src/main/java/android/databinding/tool/store/ResourceBundle.java b/compilerCommon/src/main/java/android/databinding/tool/store/ResourceBundle.java
index 5e1232f..be8a4fa 100644
--- a/compilerCommon/src/main/java/android/databinding/tool/store/ResourceBundle.java
+++ b/compilerCommon/src/main/java/android/databinding/tool/store/ResourceBundle.java
@@ -13,14 +13,19 @@
 
 package android.databinding.tool.store;
 
+import org.antlr.v4.runtime.Token;
 import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
 
+import android.databinding.tool.processing.scopes.LocationScopeProvider;
 import android.databinding.tool.util.L;
 import android.databinding.tool.util.ParserHelper;
 import android.databinding.tool.util.Preconditions;
 
+import java.io.File;
 import java.io.Serializable;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -33,8 +38,6 @@
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlElementWrapper;
 import javax.xml.bind.annotation.XmlRootElement;
-import javax.xml.bind.annotation.adapters.XmlAdapter;
-import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 
 /**
  * This is a serializable class that can keep the result of parsing layout files.
@@ -253,6 +256,8 @@
         public String mFileName;
         @XmlAttribute(name="modulePackage", required = true)
         public String mModulePackage;
+        @XmlAttribute(name="absoluteFilePath", required = true)
+        public String mAbsoluteFilePath;
         private String mConfigName;
 
         // The binding class as given by the user
@@ -289,12 +294,13 @@
         public LayoutFileBundle() {
         }
 
-        public LayoutFileBundle(String fileName, String directory, String modulePackage,
-                boolean isMerge) {
+        public LayoutFileBundle(File file, String fileName, String directory,
+                String modulePackage, boolean isMerge) {
             mFileName = fileName;
             mDirectory = directory;
             mModulePackage = modulePackage;
             mIsMerge = isMerge;
+            mAbsoluteFilePath = file.getAbsolutePath();
         }
 
         public void addVariable(String name, String type, Location location) {
@@ -449,6 +455,10 @@
         public String getModulePackage() {
             return mModulePackage;
         }
+
+        public String getAbsoluteFilePath() {
+            return mAbsoluteFilePath;
+        }
     }
 
     @XmlAccessorType(XmlAccessType.NONE)
@@ -527,7 +537,7 @@
     }
 
     @XmlAccessorType(XmlAccessType.NONE)
-    public static class BindingTargetBundle implements Serializable {
+    public static class BindingTargetBundle implements Serializable, LocationScopeProvider {
         // public for XML serialization
 
         @XmlAttribute(name="id")
@@ -562,8 +572,8 @@
             mLocation = location;
         }
 
-        public void addBinding(String name, String expr) {
-            mBindingBundleList.add(new BindingBundle(name, expr));
+        public void addBinding(String name, String expr, Location location, Location valueLocation) {
+            mBindingBundleList.add(new BindingBundle(name, expr, location, valueLocation));
         }
 
         public void setIncludedLayout(String includedLayout) {
@@ -637,17 +647,27 @@
             return mInterfaceType;
         }
 
+        @Override
+        public List<Location> provideScopeLocation() {
+            return mLocation == null ? null : Arrays.asList(mLocation);
+        }
+
         @XmlAccessorType(XmlAccessType.NONE)
         public static class BindingBundle implements Serializable {
 
             private String mName;
             private String mExpr;
+            private Location mLocation;
+            private Location mValueLocation;
 
             public BindingBundle() {}
 
-            public BindingBundle(String name, String expr) {
+            public BindingBundle(String name, String expr, Location location,
+                    Location valueLocation) {
                 mName = name;
                 mExpr = expr;
+                mLocation = location;
+                mValueLocation = valueLocation;
             }
 
             @XmlAttribute(name="attribute", required=true)
@@ -667,6 +687,24 @@
             public void setExpr(String expr) {
                 mExpr = expr;
             }
+
+            @XmlElement(name="Location")
+            public Location getLocation() {
+                return mLocation;
+            }
+
+            public void setLocation(Location location) {
+                mLocation = location;
+            }
+
+            @XmlElement(name="ValueLocation")
+            public Location getValueLocation() {
+                return mValueLocation;
+            }
+
+            public void setValueLocation(Location valueLocation) {
+                mValueLocation = valueLocation;
+            }
         }
     }
 }
diff --git a/compilerCommon/src/main/java/android/databinding/tool/util/L.java b/compilerCommon/src/main/java/android/databinding/tool/util/L.java
index 478ff58..fe888ca 100644
--- a/compilerCommon/src/main/java/android/databinding/tool/util/L.java
+++ b/compilerCommon/src/main/java/android/databinding/tool/util/L.java
@@ -18,6 +18,8 @@
 
 import org.apache.commons.lang3.exception.ExceptionUtils;
 
+import android.databinding.tool.processing.ScopedException;
+
 import javax.tools.Diagnostic;
 import javax.tools.Diagnostic.Kind;
 
@@ -66,13 +68,30 @@
                 String.format(msg, args) + " " + ExceptionUtils.getStackTrace(t));
     }
 
+    private static void tryToThrowScoped(Throwable t, String fullMessage) {
+        if (t instanceof ScopedException) {
+            ScopedException ex = (ScopedException) t;
+            if (ex.isValid()) {
+                throw ex;
+            }
+        }
+        ScopedException ex = new ScopedException(fullMessage);
+        if (ex.isValid()) {
+            throw ex;
+        }
+    }
+
     public static void e(String msg, Object... args) {
-        printMessage(Diagnostic.Kind.ERROR, String.format(msg, args));
+        String fullMsg = String.format(msg, args);
+        tryToThrowScoped(null, fullMsg);
+        printMessage(Diagnostic.Kind.ERROR, fullMsg);
     }
 
     public static void e(Throwable t, String msg, Object... args) {
+        String fullMsg = String.format(msg, args);
+        tryToThrowScoped(t, fullMsg);
         printMessage(Diagnostic.Kind.ERROR,
-                String.format(msg, args) + " " + ExceptionUtils.getStackTrace(t));
+                fullMsg + " " + ExceptionUtils.getStackTrace(t));
     }
 
     private static void printMessage(Diagnostic.Kind kind, String message) {
@@ -82,6 +101,10 @@
         }
     }
 
+    public static boolean isDebugEnabled() {
+        return sEnableDebug;
+    }
+
     public static interface Client {
         public void printMessage(Diagnostic.Kind kind, String message);
     }
diff --git a/compilerCommon/src/test/java/android/databinding/tool/store/LocationTest.java b/compilerCommon/src/test/java/android/databinding/tool/store/LocationTest.java
new file mode 100644
index 0000000..195febf
--- /dev/null
+++ b/compilerCommon/src/test/java/android/databinding/tool/store/LocationTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.tool.store;
+
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+
+public class LocationTest {
+    @Test
+    public void testInvalid() {
+        assertFalse(new Location().isValid());
+    }
+
+    @Test
+    public void testValid() {
+        Location location = new Location(0, 0, 1, 1);
+        assertTrue(location.isValid());
+    }
+
+    @Test
+    public void testContains() {
+        Location location1 = new Location(0, 0, 10, 1);
+        Location location2 = new Location(0, 0, 9, 1);
+        assertTrue(location1.contains(location2));
+        location2.endLine = 10;
+        assertTrue(location1.contains(location2));
+        location2.endOffset = 2;
+        assertFalse(location1.contains(location2));
+    }
+
+    @Test
+    public void testAbsolute() {
+        Location loc = new Location(1, 2, 3, 4);
+        assertEquals(loc, loc.toAbsoluteLocation());
+    }
+
+    @Test
+    public void testAbsoluteWithInvalidParent() {
+        Location loc = new Location(1, 2, 3, 4);
+        loc.setParentLocation(new Location());
+        assertEquals(loc, loc.toAbsoluteLocation());
+    }
+
+    @Test
+    public void testAbsoluteWithParent() {
+        Location loc = new Location(1, 2, 3, 4);
+        loc.setParentLocation(new Location(10, 0, 20, 0));
+        assertEquals(new Location(11, 2, 13, 4), loc.toAbsoluteLocation());
+    }
+
+    @Test
+    public void testAbsoluteWith2Parents() {
+        Location loc = new Location(1, 2, 3, 4);
+        Location parent1 = new Location(5, 6, 10, 11);
+        parent1.setParentLocation(new Location(5, 6, 17, 8));
+        loc.setParentLocation(parent1);
+        assertEquals(new Location(10, 6, 15, 11), parent1.toAbsoluteLocation());
+        assertEquals(new Location(11, 2, 13, 4), loc.toAbsoluteLocation());
+    }
+
+    @Test
+    public void testAbsoluteWithSameLine() {
+        Location loc = new Location(0, 2, 0, 4);
+        loc.setParentLocation(new Location(7, 2, 12, 46));
+        assertEquals(new Location(7, 4, 7, 6), loc.toAbsoluteLocation());
+    }
+}
diff --git a/gradlePlugin/src/main/java/android/databinding/tool/DataBinderPlugin.java b/gradlePlugin/src/main/java/android/databinding/tool/DataBinderPlugin.java
index b235b99..600edcf 100644
--- a/gradlePlugin/src/main/java/android/databinding/tool/DataBinderPlugin.java
+++ b/gradlePlugin/src/main/java/android/databinding/tool/DataBinderPlugin.java
@@ -272,6 +272,7 @@
 
                         logD("TASK adding dependency on %s for %s", task, processResTask);
                         processResTask.dependsOn(task);
+                        processResTask.getInputs().dir(xmlOutDir);
                         for (Object dep : processResTask.getDependsOn()) {
                             if (dep == task) {
                                 continue;