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;