Merge "Handle overrideSourcePosition in unsupportedappusageprocessor."
diff --git a/core/java/android/annotation/UnsupportedAppUsage.java b/core/java/android/annotation/UnsupportedAppUsage.java
index 204d71d..1af48cb 100644
--- a/core/java/android/annotation/UnsupportedAppUsage.java
+++ b/core/java/android/annotation/UnsupportedAppUsage.java
@@ -148,6 +148,18 @@
     String publicAlternatives() default "";
 
     /**
+     * Override the default source position when generating an index of the annotations.
+     *
+     * <p>This is intended for use by tools that generate java source code, to point to the
+     * original source position of the annotation, rather than the position within the generated
+     * code. It should never be set manually.
+     *
+     * <p>The format of the value is "path/to/file:startline:startcol:endline:endcol" indicating
+     * the position of the annotation itself.
+     */
+    String overrideSourcePosition() default "";
+
+    /**
      * Container for {@link UnsupportedAppUsage} that allows it to be applied repeatedly to types.
      */
     @Retention(CLASS)
diff --git a/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/SourcePosition.java b/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/SourcePosition.java
new file mode 100644
index 0000000..4ae093c
--- /dev/null
+++ b/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/SourcePosition.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 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.processor.unsupportedappusage;
+
+/**
+ * Represents a source position within a source file
+ */
+public class SourcePosition {
+    public final String filename;
+    public final int startLine;
+    public final int startCol;
+    public final int endLine;
+    public final int endCol;
+
+    public SourcePosition(String filename, int startLine, int startCol, int endLine, int endCol) {
+        this.filename = filename;
+        this.startLine = startLine;
+        this.startCol = startCol;
+        this.endLine = endLine;
+        this.endCol = endCol;
+    }
+}
diff --git a/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessor.java b/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessor.java
index 5bb956a..ca2c275 100644
--- a/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessor.java
+++ b/tools/processors/unsupportedappusage/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessor.java
@@ -43,6 +43,7 @@
 import javax.lang.model.element.Element;
 import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.TypeElement;
+import javax.tools.Diagnostic;
 
 /**
  * Annotation processor for {@link UnsupportedAppUsage} annotations.
@@ -68,6 +69,7 @@
     private static final ImmutableSet<String> SUPPORTED_ANNOTATION_NAMES =
             SUPPORTED_ANNOTATIONS.stream().map(annotation -> annotation.getCanonicalName()).collect(
                     ImmutableSet.toImmutableSet());
+    private static final String OVERRIDE_SOURCE_POSITION_PROPERTY = "overrideSourcePosition";
 
     @Override
     public SourceVersion getSupportedSourceVersion() {
@@ -126,6 +128,9 @@
         StringBuilder sb = new StringBuilder();
         for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> e
                 : annotation.getElementValues().entrySet()) {
+            if (e.getKey().getSimpleName().toString().equals(OVERRIDE_SOURCE_POSITION_PROPERTY)) {
+                continue;
+            }
             if (sb.length() > 0) {
                 sb.append("&");
             }
@@ -136,6 +141,34 @@
         return sb.toString();
     }
 
+    private SourcePosition getSourcePositionOverride(
+            Element annotatedElement, AnnotationMirror annotation) {
+        for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> e
+                : annotation.getElementValues().entrySet()) {
+            if (e.getKey().getSimpleName().toString().equals(OVERRIDE_SOURCE_POSITION_PROPERTY)) {
+                String[] position = e.getValue().getValue().toString().split(":");
+                if (position.length != 5) {
+                    processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format(
+                            "Expected %s to have format file:startLine:startCol:endLine:endCol",
+                            OVERRIDE_SOURCE_POSITION_PROPERTY), annotatedElement, annotation);
+                    return null;
+                }
+                try {
+                    return new SourcePosition(position[0], Integer.parseInt(position[1]),
+                            Integer.parseInt(position[2]), Integer.parseInt(position[3]),
+                            Integer.parseInt(position[4]));
+                } catch (NumberFormatException nfe) {
+                    processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format(
+                            "Expected %s to have format file:startLine:startCol:endLine:endCol; "
+                            + "error parsing integer: %s", OVERRIDE_SOURCE_POSITION_PROPERTY,
+                            nfe.getMessage()), annotatedElement, annotation);
+                    return null;
+                }
+            }
+        }
+        return null;
+    }
+
     /**
      * Maps an annotated element to the source position of the @UnsupportedAppUsage annotation
      * attached to it. It returns CSV in the format:
@@ -152,16 +185,25 @@
         JavacElements javacElem = (JavacElements) processingEnv.getElementUtils();
         AnnotationMirror unsupportedAppUsage =
                 getUnsupportedAppUsageAnnotationMirror(annotatedElement);
-        Pair<JCTree, JCTree.JCCompilationUnit> pair =
-                javacElem.getTreeAndTopLevel(annotatedElement, unsupportedAppUsage, null);
-        Position.LineMap lines = pair.snd.lineMap;
+        SourcePosition position = getSourcePositionOverride(annotatedElement, unsupportedAppUsage);
+        if (position == null) {
+            Pair<JCTree, JCTree.JCCompilationUnit> pair =
+                    javacElem.getTreeAndTopLevel(annotatedElement, unsupportedAppUsage, null);
+            Position.LineMap lines = pair.snd.lineMap;
+            position = new SourcePosition(
+                    pair.snd.getSourceFile().getName(),
+                    lines.getLineNumber(pair.fst.pos().getStartPosition()),
+                    lines.getColumnNumber(pair.fst.pos().getStartPosition()),
+                    lines.getLineNumber(pair.fst.pos().getEndPosition(pair.snd.endPositions)),
+                    lines.getColumnNumber(pair.fst.pos().getEndPosition(pair.snd.endPositions)));
+        }
         return Joiner.on(",").join(
                 signature,
-                pair.snd.getSourceFile().getName(),
-                lines.getLineNumber(pair.fst.pos().getStartPosition()),
-                lines.getColumnNumber(pair.fst.pos().getStartPosition()),
-                lines.getLineNumber(pair.fst.pos().getEndPosition(pair.snd.endPositions)),
-                lines.getColumnNumber(pair.fst.pos().getEndPosition(pair.snd.endPositions)),
+                position.filename,
+                position.startLine,
+                position.startCol,
+                position.endLine,
+                position.endCol,
                 encodeAnnotationProperties(unsupportedAppUsage));
     }
 
diff --git a/tools/processors/unsupportedappusage/test/Android.bp b/tools/processors/unsupportedappusage/test/Android.bp
index 49ea3d4..e10fd47 100644
--- a/tools/processors/unsupportedappusage/test/Android.bp
+++ b/tools/processors/unsupportedappusage/test/Android.bp
@@ -18,7 +18,7 @@
     srcs: ["src/**/*.java"],
 
     static_libs: [
-        "libjavac",
+        "compile-testing-prebuilt",
         "unsupportedappusage-annotation-processor-lib",
         "truth-host-prebuilt",
         "mockito-host",
diff --git a/tools/processors/unsupportedappusage/test/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessorTest.java b/tools/processors/unsupportedappusage/test/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessorTest.java
index 012e88f..75158ee 100644
--- a/tools/processors/unsupportedappusage/test/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessorTest.java
+++ b/tools/processors/unsupportedappusage/test/src/android/processor/unsupportedappusage/UnsupportedAppUsageProcessorTest.java
@@ -18,61 +18,67 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import com.android.javac.Javac;
+import com.google.testing.compile.Compilation;
+import com.google.testing.compile.CompilationSubject;
+import com.google.testing.compile.Compiler;
+import com.google.testing.compile.JavaFileObjects;
 
-import com.google.common.base.Joiner;
-
-import org.junit.Before;
 import org.junit.Test;
 
 import java.io.IOException;
 import java.util.Map;
+import java.util.Optional;
+
+import javax.tools.JavaFileObject;
+import javax.tools.StandardLocation;
 
 public class UnsupportedAppUsageProcessorTest {
 
-    private Javac mJavac;
+    private static final JavaFileObject ANNOTATION = JavaFileObjects.forSourceLines(
+            "dalvik.dalvik.annotation.compat.UnsupportedAppUsage",
+            "package dalvik.annotation.compat;",
+            "public @interface UnsupportedAppUsage {",
+            "    String expectedSignature() default \"\";\n",
+            "    String someProperty() default \"\";",
+            "    String overrideSourcePosition() default \"\";",
+            "}");
 
-    @Before
-    public void setup() throws IOException {
-        mJavac = new Javac();
-        mJavac.addSource("dalvik.annotation.compat.UnsupportedAppUsage", Joiner.on('\n').join(
-                "package dalvik.annotation.compat;",
-                "public @interface UnsupportedAppUsage {",
-                "    String expectedSignature() default \"\";\n",
-                "    String someProperty() default \"\";",
-                "}"));
-    }
+    private CsvReader compileAndReadCsv(JavaFileObject source) throws IOException {
+        Compilation compilation =
+                Compiler.javac().withProcessors(new UnsupportedAppUsageProcessor())
+                .compile(ANNOTATION, source);
+        CompilationSubject.assertThat(compilation).succeeded();
+        Optional<JavaFileObject> csv = compilation.generatedFile(StandardLocation.CLASS_OUTPUT,
+                "unsupportedappusage/unsupportedappusage_index.csv");
+        assertThat(csv.isPresent()).isTrue();
 
-    private CsvReader compileAndReadCsv() throws IOException {
-        mJavac.compileWithAnnotationProcessor(new UnsupportedAppUsageProcessor());
-        return new CsvReader(
-                mJavac.getOutputFile("unsupportedappusage/unsupportedappusage_index.csv"));
+        return new CsvReader(csv.get().openInputStream());
     }
 
     @Test
     public void testSignatureFormat() throws Exception {
-        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+        JavaFileObject src = JavaFileObjects.forSourceLines("a.b.Class",
                 "package a.b;",
                 "import dalvik.annotation.compat.UnsupportedAppUsage;",
                 "public class Class {",
                 "  @UnsupportedAppUsage",
                 "  public void method() {}",
-                "}"));
-        assertThat(compileAndReadCsv().getContents().get(0)).containsEntry(
+                "}");
+        assertThat(compileAndReadCsv(src).getContents().get(0)).containsEntry(
                 "signature", "La/b/Class;->method()V"
         );
     }
 
     @Test
     public void testSourcePosition() throws Exception {
-        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+        JavaFileObject src = JavaFileObjects.forSourceLines("a.b.Class",
                 "package a.b;", // 1
                 "import dalvik.annotation.compat.UnsupportedAppUsage;", // 2
                 "public class Class {", // 3
                 "  @UnsupportedAppUsage", // 4
                 "  public void method() {}", // 5
-                "}"));
-        Map<String, String> row = compileAndReadCsv().getContents().get(0);
+                "}");
+        Map<String, String> row = compileAndReadCsv(src).getContents().get(0);
         assertThat(row).containsEntry("startline", "4");
         assertThat(row).containsEntry("startcol", "3");
         assertThat(row).containsEntry("endline", "4");
@@ -81,16 +87,68 @@
 
     @Test
     public void testAnnotationProperties() throws Exception {
-        mJavac.addSource("a.b.Class", Joiner.on('\n').join(
+        JavaFileObject src = JavaFileObjects.forSourceLines("a.b.Class",
                 "package a.b;", // 1
                 "import dalvik.annotation.compat.UnsupportedAppUsage;", // 2
                 "public class Class {", // 3
                 "  @UnsupportedAppUsage(someProperty=\"value\")", // 4
                 "  public void method() {}", // 5
-                "}"));
-        assertThat(compileAndReadCsv().getContents().get(0)).containsEntry(
+                "}");
+        assertThat(compileAndReadCsv(src).getContents().get(0)).containsEntry(
                 "properties", "someProperty=%22value%22");
     }
 
+    @Test
+    public void testSourcePositionOverride() throws Exception {
+        JavaFileObject src = JavaFileObjects.forSourceLines("a.b.Class",
+                "package a.b;", // 1
+                "import dalvik.annotation.compat.UnsupportedAppUsage;", // 2
+                "public class Class {", // 3
+                "  @UnsupportedAppUsage(overrideSourcePosition=\"otherfile.aidl:30:10:31:20\")",
+                "  public void method() {}", // 5
+                "}");
+        Map<String, String> row = compileAndReadCsv(src).getContents().get(0);
+        assertThat(row).containsEntry("file", "otherfile.aidl");
+        assertThat(row).containsEntry("startline", "30");
+        assertThat(row).containsEntry("startcol", "10");
+        assertThat(row).containsEntry("endline", "31");
+        assertThat(row).containsEntry("endcol", "20");
+        assertThat(row).containsEntry("properties", "");
+    }
+
+    @Test
+    public void testSourcePositionOverrideWrongFormat() throws Exception {
+        JavaFileObject src = JavaFileObjects.forSourceLines("a.b.Class",
+                "package a.b;", // 1
+                "import dalvik.annotation.compat.UnsupportedAppUsage;", // 2
+                "public class Class {", // 3
+                "  @UnsupportedAppUsage(overrideSourcePosition=\"invalid\")", // 4
+                "  public void method() {}", // 5
+                "}");
+        Compilation compilation =
+                Compiler.javac().withProcessors(new UnsupportedAppUsageProcessor())
+                        .compile(ANNOTATION, src);
+        CompilationSubject.assertThat(compilation).failed();
+        CompilationSubject.assertThat(compilation).hadErrorContaining(
+                "Expected overrideSourcePosition to have format "
+                + "file:startLine:startCol:endLine:endCol").inFile(src).onLine(4);
+    }
+
+    @Test
+    public void testSourcePositionOverrideInvalidInt() throws Exception {
+        JavaFileObject src = JavaFileObjects.forSourceLines("a.b.Class",
+                "package a.b;", // 1
+                "import dalvik.annotation.compat.UnsupportedAppUsage;", // 2
+                "public class Class {", // 3
+                "  @UnsupportedAppUsage(overrideSourcePosition=\"otherfile.aidl:a:b:c:d\")", // 4
+                "  public void method() {}", // 5
+                "}");
+        Compilation compilation =
+                Compiler.javac().withProcessors(new UnsupportedAppUsageProcessor())
+                        .compile(ANNOTATION, src);
+        CompilationSubject.assertThat(compilation).failed();
+        CompilationSubject.assertThat(compilation).hadErrorContaining(
+                "error parsing integer").inFile(src).onLine(4);
+    }
 
 }