Merge "Print encoded errors only if data binding is invoked from the IDE" into mnc-dev
diff --git a/baseLibrary/src/main/java/android/databinding/BindingBuildInfo.java b/baseLibrary/src/main/java/android/databinding/BindingBuildInfo.java
index 2561d73..5b0c0b4 100644
--- a/baseLibrary/src/main/java/android/databinding/BindingBuildInfo.java
+++ b/baseLibrary/src/main/java/android/databinding/BindingBuildInfo.java
@@ -39,4 +39,5 @@
     String exportClassListTo();
     boolean isLibrary();
     boolean enableDebugLogs() default false;
+    boolean printEncodedError() default false;
 }
diff --git a/compilationTests/src/test/java/android/databinding/compilationTest/BaseCompilationTest.java b/compilationTests/src/test/java/android/databinding/compilationTest/BaseCompilationTest.java
index 41aa12e..c9cf2a0 100644
--- a/compilationTests/src/test/java/android/databinding/compilationTest/BaseCompilationTest.java
+++ b/compilationTests/src/test/java/android/databinding/compilationTest/BaseCompilationTest.java
@@ -50,6 +50,9 @@
 
 
 public class BaseCompilationTest {
+
+    private static final String PRINT_ENCODED_ERRORS_PROPERTY
+            = "android.databinding.injected.print.encoded.errors";
     @Rule
     public TestName name = new TestName();
     static Pattern VARIABLES = Pattern.compile("!@\\{([A-Za-z0-9_-]*)}");
@@ -103,8 +106,9 @@
 
     /**
      * 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
+     * @param location  The location to extract
      * @return The string that is contained in the given location
      * @throws IOException If file is invalid.
      */
@@ -113,7 +117,7 @@
         assertTrue(file.exists());
         StringBuilder result = new StringBuilder();
         List<String> lines = FileUtils.readLines(file);
-        for (int i = location.startLine; i <= location.endLine; i ++) {
+        for (int i = location.startLine; i <= location.endLine; i++) {
             if (i > location.startLine) {
                 result.append("\n");
             }
@@ -144,7 +148,7 @@
     protected static Map<String, String> toMap(String... keysAndValues) {
         assertEquals(0, keysAndValues.length % 2);
         Map<String, String> map = new HashMap<>();
-        for (int i = 0; i < keysAndValues.length; i+=2) {
+        for (int i = 0; i < keysAndValues.length; i += 2) {
             map.put(keysAndValues[i], keysAndValues[i + 1]);
         }
         return map;
@@ -203,7 +207,8 @@
         replacements = addDefaults(replacements);
         // how to get build folder, pass from gradle somehow ?
         FileUtils.forceMkdir(testFolder);
-        copyResourceTo("/AndroidManifest.xml", new File(testFolder, "app/src/main/AndroidManifest.xml"), replacements);
+        copyResourceTo("/AndroidManifest.xml",
+                new File(testFolder, "app/src/main/AndroidManifest.xml"), replacements);
         copyResourceTo("/project_build.gradle", new File(testFolder, "build.gradle"), replacements);
         copyResourceTo("/app_build.gradle", new File(testFolder, "app/build.gradle"), replacements);
         copyResourceTo("/settings.gradle", new File(testFolder, "settings.gradle"), replacements);
@@ -226,14 +231,17 @@
         FileUtils.forceMkdir(moduleFolder);
         copyResourceTo("/AndroidManifest.xml",
                 new File(moduleFolder, "src/main/AndroidManifest.xml"), replacements);
-        copyResourceTo("/module_build.gradle", new File(moduleFolder, "build.gradle"), replacements);
+        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");
         List<String> args = new ArrayList<>();
         args.add(pathToExecutable.getAbsolutePath());
+        args.add("-P" + PRINT_ENCODED_ERRORS_PROPERTY + "=true");
         args.add("--project-cache-dir");
         args.add(new File("../.caches/", name.getMethodName()).getAbsolutePath());
         Collections.addAll(args, params);
@@ -244,7 +252,7 @@
             builder.environment().put("JAVA_HOME", javaHome);
         }
         builder.directory(testFolder);
-        Process process =  builder.start();
+        Process process = builder.start();
         String output = IOUtils.toString(process.getInputStream());
         String error = IOUtils.toString(process.getErrorStream());
         int result = process.waitFor();
@@ -261,7 +269,8 @@
         perms.add(PosixFilePermission.GROUP_READ);
         //add others permissions
         perms.add(PosixFilePermission.OTHERS_READ);
-        Files.setPosixFilePermissions(Paths.get(new File(testFolder, "gradlew").getAbsolutePath()), perms);
+        Files.setPosixFilePermissions(Paths.get(new File(testFolder, "gradlew").getAbsolutePath()),
+                perms);
     }
 
 
diff --git a/compiler/src/main/java/android/databinding/annotationprocessor/BuildInfoUtil.java b/compiler/src/main/java/android/databinding/annotationprocessor/BuildInfoUtil.java
index 95eb675..b74e6e5 100644
--- a/compiler/src/main/java/android/databinding/annotationprocessor/BuildInfoUtil.java
+++ b/compiler/src/main/java/android/databinding/annotationprocessor/BuildInfoUtil.java
@@ -17,6 +17,7 @@
 package android.databinding.annotationprocessor;
 
 import android.databinding.BindingBuildInfo;
+import android.databinding.tool.processing.ScopedException;
 import android.databinding.tool.util.L;
 import android.databinding.tool.util.Preconditions;
 
@@ -32,6 +33,7 @@
             sCached = extractNotNull(roundEnvironment, BindingBuildInfo.class);
             if (sCached != null) {
                 L.setDebugLog(sCached.enableDebugLogs());
+                ScopedException.encodeOutput(sCached.printEncodedError());
             }
         }
         return sCached;
diff --git a/compilerCommon/src/main/java/android/databinding/tool/LayoutXmlProcessor.java b/compilerCommon/src/main/java/android/databinding/tool/LayoutXmlProcessor.java
index f48c002..046bddb 100644
--- a/compilerCommon/src/main/java/android/databinding/tool/LayoutXmlProcessor.java
+++ b/compilerCommon/src/main/java/android/databinding/tool/LayoutXmlProcessor.java
@@ -163,11 +163,11 @@
 
     public void writeInfoClass(/*Nullable*/ File sdkDir, File xmlOutDir,
             /*Nullable*/ File exportClassListTo) {
-        writeInfoClass(sdkDir, xmlOutDir, exportClassListTo, false);
+        writeInfoClass(sdkDir, xmlOutDir, exportClassListTo, false, false);
     }
 
     public void writeInfoClass(/*Nullable*/ File sdkDir, File xmlOutDir, File exportClassListTo,
-            boolean enableDebugLogs) {
+            boolean enableDebugLogs, boolean printEncodedErrorLogs) {
         final String sdkPath = sdkDir == null ? null : StringEscapeUtils.escapeJava(sdkDir.getAbsolutePath());
         final Class annotation = BindingBuildInfo.class;
         final String layoutInfoPath = StringEscapeUtils.escapeJava(xmlOutDir.getAbsolutePath());
@@ -182,7 +182,8 @@
                 "exportClassListTo=\"" + exportClassListToPath + "\"," +
                 "isLibrary=" + mIsLibrary + "," +
                 "minSdk=" + mMinSdk + "," +
-                "enableDebugLogs=" + enableDebugLogs + ")\n" +
+                "enableDebugLogs=" + enableDebugLogs + "," +
+                "printEncodedError=" + printEncodedErrorLogs + ")\n" +
                 "public class " + CLASS_NAME + " {}\n";
         mFileWriter.writeToFile(RESOURCE_BUNDLE_PACKAGE + "." + CLASS_NAME, classString);
     }
diff --git a/compilerCommon/src/main/java/android/databinding/tool/processing/ScopedException.java b/compilerCommon/src/main/java/android/databinding/tool/processing/ScopedException.java
index 73f42a9..238a895 100644
--- a/compilerCommon/src/main/java/android/databinding/tool/processing/ScopedException.java
+++ b/compilerCommon/src/main/java/android/databinding/tool/processing/ScopedException.java
@@ -35,6 +35,7 @@
     public static final String MSG_KEY = "msg:";
     public static final String LOCATION_KEY = "loc:";
     public static final String FILE_KEY = "file:";
+    private static boolean sEncodeOutput = false;
     private ScopedErrorReport mScopedErrorReport;
     private String mScopeLog;
 
@@ -55,6 +56,23 @@
 
     @Override
     public String getMessage() {
+        return sEncodeOutput ? createEncodedMessage() : createHumanReadableMessage();
+    }
+
+    private String createHumanReadableMessage() {
+        ScopedErrorReport scopedError = getScopedErrorReport();
+        StringBuilder sb = new StringBuilder();
+        sb.append(super.getMessage()).append("\n")
+                .append("file://").append(scopedError.getFilePath());
+        if (scopedError.getLocations() != null && scopedError.getLocations().size() > 0) {
+            sb.append(" Line:");
+            sb.append(scopedError.getLocations().get(0).startLine);
+        }
+        sb.append("\n");
+        return sb.toString();
+    }
+
+    private String createEncodedMessage() {
         ScopedErrorReport scopedError = getScopedErrorReport();
         StringBuilder sb = new StringBuilder();
         sb.append(ERROR_LOG_PREFIX)
@@ -135,4 +153,8 @@
         }
         return errors;
     }
+
+    public static void encodeOutput(boolean encodeOutput) {
+        sEncodeOutput = encodeOutput;
+    }
 }
diff --git a/gradlePlugin/src/main/java/android/databinding/tool/DataBinderPlugin.java b/gradlePlugin/src/main/java/android/databinding/tool/DataBinderPlugin.java
index 600edcf..22e1056 100644
--- a/gradlePlugin/src/main/java/android/databinding/tool/DataBinderPlugin.java
+++ b/gradlePlugin/src/main/java/android/databinding/tool/DataBinderPlugin.java
@@ -47,6 +47,7 @@
 import org.gradle.api.tasks.bundling.Jar;
 import org.gradle.api.tasks.compile.AbstractCompile;
 
+import android.databinding.tool.processing.ScopedException;
 import android.databinding.tool.util.L;
 import android.databinding.tool.writer.JavaFileWriter;
 
@@ -63,7 +64,11 @@
 
 public class DataBinderPlugin implements Plugin<Project> {
 
+    private static final String INVOKED_FROM_IDE_PROPERTY = "android.injected.invoked.from.ide";
+    private static final String PRINT_ENCODED_ERRORS_PROPERTY
+            = "android.databinding.injected.print.encoded.errors";
     private Logger logger;
+    private boolean printEncodedErrors = false;
 
     class GradleFileWriter extends JavaFileWriter {
 
@@ -93,6 +98,26 @@
         }
     }
 
+    private boolean safeGetBooleanProperty(Project project, String property) {
+        boolean hasProperty = project.hasProperty(property);
+        if (!hasProperty) {
+            return false;
+        }
+        try {
+            if (Boolean.parseBoolean(String.valueOf(project.getProperties().get(property)))) {
+                return true;
+            }
+        } catch (Throwable t) {
+            L.w("unable to read property %s", project);
+        }
+        return false;
+    }
+
+    private boolean resolvePrintEncodedErrors(Project project) {
+        return safeGetBooleanProperty(project, INVOKED_FROM_IDE_PROPERTY) ||
+                safeGetBooleanProperty(project, PRINT_ENCODED_ERRORS_PROPERTY);
+    }
+
     @Override
     public void apply(Project project) {
         if (project == null) {
@@ -105,7 +130,8 @@
         if (StringUtils.isEmpty(myVersion)) {
             throw new IllegalStateException("cannot read version of the plugin :/");
         }
-
+        printEncodedErrors = resolvePrintEncodedErrors(project);
+        ScopedException.encodeOutput(printEncodedErrors);
         project.getDependencies().add("compile", "com.android.databinding:library:" + myVersion);
         boolean addAdapters = true;
         if (project.hasProperty("ext")) {
@@ -308,7 +334,9 @@
                         task.setSdkDir(sdkDir);
                         task.setXmlOutFolder(xmlOutDir);
                         task.setExportClassListTo(generatedClassListOut);
+                        task.setPrintEncodedErrors(printEncodedErrors);
                         task.setEnableDebugLogs(logger.isEnabled(LogLevel.DEBUG));
+
                         variantData.registerJavaGeneratingTask(task, codeGenTargetFolder);
                     }
                 });
diff --git a/gradlePlugin/src/main/java/android/databinding/tool/DataBindingExcludeGeneratedTask.java b/gradlePlugin/src/main/java/android/databinding/tool/DataBindingExcludeGeneratedTask.java
index cfc7a98..a58d97a 100644
--- a/gradlePlugin/src/main/java/android/databinding/tool/DataBindingExcludeGeneratedTask.java
+++ b/gradlePlugin/src/main/java/android/databinding/tool/DataBindingExcludeGeneratedTask.java
@@ -25,6 +25,7 @@
 import org.gradle.api.tasks.TaskAction;
 import org.gradle.api.tasks.bundling.Jar;
 
+import android.databinding.tool.processing.Scope;
 import android.databinding.tool.util.L;
 
 import java.io.File;
@@ -92,6 +93,7 @@
                 exclude(klass.replace('.', '/') + ".class");
             }
         }
+        Scope.assertNoError();
         L.d("Excluding generated classes from library jar is done.");
     }
 
diff --git a/gradlePlugin/src/main/java/android/databinding/tool/DataBindingExportInfoTask.java b/gradlePlugin/src/main/java/android/databinding/tool/DataBindingExportInfoTask.java
index 4eb06b2..768fe68 100644
--- a/gradlePlugin/src/main/java/android/databinding/tool/DataBindingExportInfoTask.java
+++ b/gradlePlugin/src/main/java/android/databinding/tool/DataBindingExportInfoTask.java
@@ -15,9 +15,11 @@
  */
 package android.databinding.tool;
 
-import android.databinding.tool.util.L;
 import org.gradle.api.DefaultTask;
 import org.gradle.api.tasks.TaskAction;
+
+import android.databinding.tool.processing.Scope;
+
 import java.io.File;
 
 /**
@@ -28,10 +30,13 @@
     private File sdkDir;
     private File xmlOutFolder;
     private File exportClassListTo;
+    private boolean printEncodedErrors;
     private boolean enableDebugLogs = false;
     @TaskAction
     public void exportInfo() {
-        xmlProcessor.writeInfoClass(sdkDir, xmlOutFolder, exportClassListTo, enableDebugLogs);
+        xmlProcessor.writeInfoClass(sdkDir, xmlOutFolder, exportClassListTo, enableDebugLogs,
+                printEncodedErrors);
+        Scope.assertNoError();
     }
 
     public LayoutXmlProcessor getXmlProcessor() {
@@ -66,6 +71,14 @@
         this.exportClassListTo = exportClassListTo;
     }
 
+    public boolean isPrintEncodedErrors() {
+        return printEncodedErrors;
+    }
+
+    public void setPrintEncodedErrors(boolean printEncodedErrors) {
+        this.printEncodedErrors = printEncodedErrors;
+    }
+
     public boolean isEnableDebugLogs() {
         return enableDebugLogs;
     }
diff --git a/gradlePlugin/src/main/java/android/databinding/tool/DataBindingProcessLayoutsTask.java b/gradlePlugin/src/main/java/android/databinding/tool/DataBindingProcessLayoutsTask.java
index 7a6100d..320d4dc 100644
--- a/gradlePlugin/src/main/java/android/databinding/tool/DataBindingProcessLayoutsTask.java
+++ b/gradlePlugin/src/main/java/android/databinding/tool/DataBindingProcessLayoutsTask.java
@@ -15,6 +15,7 @@
  */
 package android.databinding.tool;
 
+import android.databinding.tool.processing.Scope;
 import android.databinding.tool.util.L;
 
 import org.gradle.api.DefaultTask;
@@ -47,6 +48,7 @@
             IOException {
         L.d("running process layouts task %s", getName());
         xmlProcessor.processResources(minSdk);
+        Scope.assertNoError();
     }
 
     public void writeLayoutXmls() throws JAXBException {