feat(java_indexer): emit refs to JVM graph for externally defined nodes (#3800)

Experimental feature to support cross-language references for JVM-based
languages.  Currently, the indexer will be highly conservative when
deciding whether to emit references to the JVM language vs Java-native
nodes.  Only nodes within the same source file as the enclosing class
and JDK symbols will be considered Java-native when
--emit_jvm_references is enabled.

See: https://github.com/kythe/kythe/blob/master/kythe/java/com/google/devtools/kythe/analyzers/jvm/DESIGN.md#relations-with-higher-level-jvm-languages
diff --git a/kythe/java/com/google/devtools/kythe/analyzers/java/JavaEntrySets.java b/kythe/java/com/google/devtools/kythe/analyzers/java/JavaEntrySets.java
index 7310c63..8594935 100644
--- a/kythe/java/com/google/devtools/kythe/analyzers/java/JavaEntrySets.java
+++ b/kythe/java/com/google/devtools/kythe/analyzers/java/JavaEntrySets.java
@@ -345,7 +345,7 @@
     return sourceFile.toUri().getHost();
   }
 
-  private static boolean fromJDK(@Nullable Symbol sym) {
+  static boolean fromJDK(@Nullable Symbol sym) {
     if (sym == null || sym.enclClass() == null) {
       return false;
     }
diff --git a/kythe/java/com/google/devtools/kythe/analyzers/java/JavaIndexerConfig.java b/kythe/java/com/google/devtools/kythe/analyzers/java/JavaIndexerConfig.java
index c4ad586..06cfebb 100644
--- a/kythe/java/com/google/devtools/kythe/analyzers/java/JavaIndexerConfig.java
+++ b/kythe/java/com/google/devtools/kythe/analyzers/java/JavaIndexerConfig.java
@@ -60,6 +60,13 @@
   private JvmMode jvmMode = JvmMode.SEMANTIC;
 
   @Parameter(
+      names = "--emit_jvm_references",
+      description =
+          "Whether to reference the JVM graph when encountering nodes from outside the analyzed"
+              + " compilation unit")
+  private boolean jvmReferences = false;
+
+  @Parameter(
       names = "--emit_anchor_scopes",
       description =
           "Whether to emit childof edges from anchors to their lexical scope's semantic node")
@@ -94,6 +101,10 @@
     return jvmMode;
   }
 
+  public boolean getEmitJvmReferences() {
+    return jvmReferences;
+  }
+
   public boolean getEmitAnchorScopes() {
     return emitAnchorScopes;
   }
@@ -118,6 +129,11 @@
     return this;
   }
 
+  public JavaIndexerConfig setEmitJvmReferences(boolean jvmReferences) {
+    this.jvmReferences = jvmReferences;
+    return this;
+  }
+
   public JavaIndexerConfig setEmitAnchorScopes(boolean emitAnchorScopes) {
     this.emitAnchorScopes = emitAnchorScopes;
     return this;
diff --git a/kythe/java/com/google/devtools/kythe/analyzers/java/KytheTreeScanner.java b/kythe/java/com/google/devtools/kythe/analyzers/java/KytheTreeScanner.java
index 2d2169c..67b9502 100644
--- a/kythe/java/com/google/devtools/kythe/analyzers/java/KytheTreeScanner.java
+++ b/kythe/java/com/google/devtools/kythe/analyzers/java/KytheTreeScanner.java
@@ -992,12 +992,38 @@
 
   /** Returns the {@link JavaNode} associated with a {@link Symbol} or {@code null}. */
   private JavaNode getJavaNode(Symbol sym) {
+    if (jvmGraph != null && config.getEmitJvmReferences() && isExternal(sym)) {
+      // Symbol is external to the analyzed compilation and may not be defined in Java.  Return the
+      // related JVM node to accomodate cross-language references.
+      Type type = externalType(sym);
+      if (type instanceof Type.MethodType) {
+        JvmGraph.Type.MethodType methodJvmType = toMethodJvmType((Type.MethodType) type);
+        ReferenceType parentClass = referenceType(externalType(sym.enclClass()));
+        String methodName = sym.getQualifiedName().toString();
+        return new JavaNode(jvmGraph.getMethodVName(parentClass, methodName, methodJvmType));
+      } else if (type instanceof Type.ClassType) {
+        return new JavaNode(jvmGraph.getReferenceVName(referenceType(sym.type)));
+      } else if (sym instanceof Symbol.VarSymbol
+          && ((Symbol.VarSymbol) sym).getKind() == ElementKind.FIELD) {
+        ReferenceType parentClass = referenceType(externalType(sym.enclClass()));
+        String fieldName = sym.getSimpleName().toString();
+        return new JavaNode(jvmGraph.getFieldVName(parentClass, fieldName));
+      }
+    }
+
     return signatureGenerator
         .getSignature(sym)
         .map(sig -> new JavaNode(entrySets.getNode(signatureGenerator, sym, sig, null)))
         .orElse(null);
   }
 
+  private boolean isExternal(Symbol sym) {
+    // TODO(schroederc): check if Symbol comes from any source file in compilation
+    // TODO(schroederc): research other methods to hueristically determine if a Symbol is defined in
+    //                   a Java compilation (vs. some other JVM language)
+    return sym.enclClass().sourcefile != filePositions.getSourceFile() && !entrySets.fromJDK(sym);
+  }
+
   private void visitAnnotations(
       VName owner, List<JCAnnotation> annotations, TreeContext ownerContext) {
     for (JCAnnotation annotation : annotations) {
diff --git a/kythe/java/com/google/devtools/kythe/common/BUILD b/kythe/java/com/google/devtools/kythe/common/BUILD
index 31d9628..e4925d5 100644
--- a/kythe/java/com/google/devtools/kythe/common/BUILD
+++ b/kythe/java/com/google/devtools/kythe/common/BUILD
@@ -18,7 +18,10 @@
 java_library(
     name = "autovalue",
     exported_plugins = [":auto_plugin"],
-    exports = ["@com_google_auto_value_auto_value//jar"],
+    exports = [
+        "@com_google_auto_value_auto_value//jar",
+        "@javax_annotation_jsr250_api//jar",
+    ],
 )
 
 java_plugin(
@@ -34,5 +37,8 @@
 java_library(
     name = "autoservice",
     exported_plugins = [":auto_service_plugin"],
-    exports = ["@com_google_auto_service_auto_service//jar"],
+    exports = [
+        "@com_google_auto_service_auto_service//jar",
+        "@javax_annotation_jsr250_api//jar",
+    ],
 )
diff --git a/kythe/javatests/com/google/devtools/kythe/analyzers/java/testdata/pkg/BUILD b/kythe/javatests/com/google/devtools/kythe/analyzers/java/testdata/pkg/BUILD
index 9d2235b..e6dd6af 100644
--- a/kythe/javatests/com/google/devtools/kythe/analyzers/java/testdata/pkg/BUILD
+++ b/kythe/javatests/com/google/devtools/kythe/analyzers/java/testdata/pkg/BUILD
@@ -323,7 +323,6 @@
     size = "small",
     srcs = ["Jvm.java"],
     extra_goals = ["//kythe/javatests/com/google/devtools/kythe/analyzers/jvm/testdata:jvm_nodes.kythe_verifier.txt"],
-    indexer_opts = ["--emit_jvm=semantic"],
     verifier_opts = [
         "--ignore_dups",
         "--convert_marked_source",
@@ -333,6 +332,14 @@
 )
 
 java_verifier_test(
+    name = "jvm_reference_tests",
+    size = "small",
+    srcs = ["JvmCrossFile.java"],
+    indexer_opts = ["--emit_jvm_references"],
+    verifier_deps = [":files_tests"],
+)
+
+java_verifier_test(
     name = "release9_test",
     size = "small",
     srcs = [
diff --git a/kythe/javatests/com/google/devtools/kythe/analyzers/java/testdata/pkg/JvmCrossFile.java b/kythe/javatests/com/google/devtools/kythe/analyzers/java/testdata/pkg/JvmCrossFile.java
new file mode 100644
index 0000000..5fe3296
--- /dev/null
+++ b/kythe/javatests/com/google/devtools/kythe/analyzers/java/testdata/pkg/JvmCrossFile.java
@@ -0,0 +1,49 @@
+package pkg;
+
+// Check that nodes unify across file/compilation boundaries
+
+//- @CONSTANT ref/imports JvmConstantMember
+import static pkg.Files.CONSTANT;
+//- @Inner ref/imports JvmInnerClass
+import static pkg.Files.Inner;
+//- @staticMethod ref/imports JvmStaticMethod
+import static pkg.Files.staticMethod;
+
+import pkg.Files.Inter;
+import pkg.Files.OtherDecl;
+
+//- ConstantMember generates JvmConstantMember
+//- FilesClass generates JvmFilesClass
+//- InnerClass generates JvmInnerClass
+//- Inter generates JvmInter
+//- ODecl generates JvmODecl
+//- StaticMethod generates JvmStaticMethod
+
+public class JvmCrossFile {
+  //- @Files ref JvmFilesClass
+  Files f1;
+
+  //- @Files ref JvmFilesClass
+  //- @Inner ref JvmInnerClass
+  Files.Inner f2;
+
+  //- @OtherDecl ref JvmODecl
+  OtherDecl f3;
+
+  //- @Inter ref JvmInter
+  Inter i;
+
+  //- @InterSub defines/binding InterSub
+  //- InterSub.node/kind interface
+  //- InterSub extends JvmInter
+  interface InterSub extends Inter {}
+
+  public static void main(String[] args) {
+    //- @staticMethod ref JvmStaticMethod
+    Files.staticMethod();
+    //- @staticMethod ref JvmStaticMethod
+    staticMethod();
+    //- @CONSTANT ref JvmConstantMember
+    System.out.println(Files.CONSTANT);
+  }
+}