summaryrefslogtreecommitdiff
path: root/compilerCommon
diff options
context:
space:
mode:
authorYigit Boyar <yboyar@google.com>2015-06-25 10:50:24 -0700
committerYigit Boyar <yboyar@google.com>2015-06-30 10:19:08 -0700
commit731b74f7f44e67312a1fc4161c4e0aae221b2417 (patch)
tree62ff3308d8e30d00568db694469c54caacff90d1 /compilerCommon
parent4df4ba38a62b791bbbc25e923efe8d9c2f9a52e9 (diff)
downloaddata-binding-731b74f7f44e67312a1fc4161c4e0aae221b2417.tar.gz
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
Diffstat (limited to 'compilerCommon')
-rw-r--r--compilerCommon/build.gradle7
-rw-r--r--compilerCommon/src/main/java/android/databinding/tool/processing/ErrorMessages.java28
-rw-r--r--compilerCommon/src/main/java/android/databinding/tool/processing/Scope.java160
-rw-r--r--compilerCommon/src/main/java/android/databinding/tool/processing/ScopedErrorReport.java69
-rw-r--r--compilerCommon/src/main/java/android/databinding/tool/processing/ScopedException.java138
-rw-r--r--compilerCommon/src/main/java/android/databinding/tool/processing/scopes/FileScopeProvider.java24
-rw-r--r--compilerCommon/src/main/java/android/databinding/tool/processing/scopes/LocationScopeProvider.java29
-rw-r--r--compilerCommon/src/main/java/android/databinding/tool/processing/scopes/ScopeProvider.java24
-rw-r--r--compilerCommon/src/main/java/android/databinding/tool/store/LayoutFileParser.java98
-rw-r--r--compilerCommon/src/main/java/android/databinding/tool/store/Location.java115
-rw-r--r--compilerCommon/src/main/java/android/databinding/tool/store/ResourceBundle.java54
-rw-r--r--compilerCommon/src/main/java/android/databinding/tool/util/L.java27
-rw-r--r--compilerCommon/src/test/java/android/databinding/tool/store/LocationTest.java86
13 files changed, 812 insertions, 47 deletions
diff --git a/compilerCommon/build.gradle b/compilerCommon/build.gradle
index 4e3c5d38..bc59c721 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 @@ sourceSets {
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 00000000..d1d103de
--- /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 00000000..723fd0a7
--- /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 00000000..dfd20397
--- /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 00000000..73f42a9d
--- /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 00000000..43452d70
--- /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 00000000..ee7f8a92
--- /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 00000000..7c3cb829
--- /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 1805729e..422cd7e2 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 org.xml.sax.SAXException;
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 @@ public class LayoutFileParser {
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;
+ if (hasMergeInclude(rootView)) {
+ L.e(ErrorMessages.INCLUDE_INSIDE_MERGE);
+ return null;
+ }
+ boolean isMerge = "merge".equals(rootView.elmName.getText());
+
+ 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();
}
- 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;
}
private void parseExpressions(String newTag, final XMLParser.ElementContext rootView,
@@ -215,8 +237,16 @@ public class LayoutFileParser {
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 9fbd2c2c..3342016c 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 class Location {
public int endLine;
@XmlAttribute(name = "endOffset")
public int endOffset;
- @XmlElement
+ @XmlElement(name = "parentLocation")
public Location parentLocation;
// for XML unmarshalling
@@ -48,6 +49,13 @@ public class Location {
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 @@ public class Location {
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 @@ public class Location {
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 @@ public class Location {
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 5e1232f0..be8a4fad 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.XmlAttribute;
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 class ResourceBundle implements Serializable {
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 class ResourceBundle implements Serializable {
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 class ResourceBundle implements Serializable {
public String getModulePackage() {
return mModulePackage;
}
+
+ public String getAbsoluteFilePath() {
+ return mAbsoluteFilePath;
+ }
}
@XmlAccessorType(XmlAccessType.NONE)
@@ -527,7 +537,7 @@ public class ResourceBundle implements Serializable {
}
@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 @@ public class ResourceBundle implements Serializable {
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 @@ public class ResourceBundle implements Serializable {
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 class ResourceBundle implements Serializable {
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 478ff588..fe888ca2 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 @@ package android.databinding.tool.util;
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 @@ public class L {
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 class L {
}
}
+ 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 00000000..195febfd
--- /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());
+ }
+}