blob: 2c744982ddace9d9d9a23ec685be9b310aa3bb6f [file] [log] [blame]
/*
* Copyright 2014 The Kythe Authors. All rights reserved.
*
* 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 com.google.devtools.kythe.analyzers.java;
import com.google.common.base.Ascii;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.Lists;
import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import com.google.common.io.ByteStreams;
import com.google.devtools.kythe.analyzers.base.EdgeKind;
import com.google.devtools.kythe.analyzers.base.EntrySet;
import com.google.devtools.kythe.analyzers.java.KytheDocTreeScanner.DocCommentVisitResult;
import com.google.devtools.kythe.analyzers.java.SourceText.Comment;
import com.google.devtools.kythe.analyzers.java.SourceText.Keyword;
import com.google.devtools.kythe.analyzers.java.SourceText.Positions;
import com.google.devtools.kythe.analyzers.jvm.JvmGraph;
import com.google.devtools.kythe.analyzers.jvm.JvmGraph.Type.ReferenceType;
import com.google.devtools.kythe.platform.java.filemanager.JavaFileStoreBasedFileManager;
import com.google.devtools.kythe.platform.java.helpers.JCTreeScanner;
import com.google.devtools.kythe.platform.java.helpers.JavacUtil;
import com.google.devtools.kythe.platform.java.helpers.SignatureGenerator;
import com.google.devtools.kythe.platform.shared.Metadata;
import com.google.devtools.kythe.platform.shared.MetadataLoaders;
import com.google.devtools.kythe.platform.shared.StatisticsCollector;
import com.google.devtools.kythe.proto.Diagnostic;
import com.google.devtools.kythe.proto.MarkedSource;
import com.google.devtools.kythe.proto.Storage.VName;
import com.google.devtools.kythe.util.Span;
import com.sun.source.tree.MemberReferenceTree.ReferenceMode;
import com.sun.source.tree.Scope;
import com.sun.source.tree.Tree.Kind;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.PackageSymbol;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.TypeTag;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree;
import com.sun.tools.javac.tree.JCTree.JCAssert;
import com.sun.tools.javac.tree.JCTree.JCAssign;
import com.sun.tools.javac.tree.JCTree.JCAssignOp;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
import com.sun.tools.javac.tree.JCTree.JCFunctionalExpression;
import com.sun.tools.javac.tree.JCTree.JCIdent;
import com.sun.tools.javac.tree.JCTree.JCImport;
import com.sun.tools.javac.tree.JCTree.JCLambda;
import com.sun.tools.javac.tree.JCTree.JCLiteral;
import com.sun.tools.javac.tree.JCTree.JCMemberReference;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
import com.sun.tools.javac.tree.JCTree.JCModifiers;
import com.sun.tools.javac.tree.JCTree.JCNewClass;
import com.sun.tools.javac.tree.JCTree.JCPackageDecl;
import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree;
import com.sun.tools.javac.tree.JCTree.JCReturn;
import com.sun.tools.javac.tree.JCTree.JCThrow;
import com.sun.tools.javac.tree.JCTree.JCTypeApply;
import com.sun.tools.javac.tree.JCTree.JCTypeParameter;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.tree.JCTree.JCWildcard;
import com.sun.tools.javac.util.Context;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.NestingKind;
import javax.tools.FileObject;
import javax.tools.JavaFileObject;
/** {@link JCTreeScanner} that emits Kythe nodes and edges. */
public class KytheTreeScanner extends JCTreeScanner<JavaNode, TreeContext> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/** Maximum allowed text size for variable {@link MarkedSource.Kind.INITIALIZER}s */
private static final int MAX_INITIALIZER_LENGTH = 80;
/** Name for special source file containing package annotations and documentation. */
private static final String PACKAGE_INFO_NAME = "package-info";
private final JavaIndexerConfig config;
private final JavaEntrySets entrySets;
private final StatisticsCollector statistics;
// TODO(schroederc): refactor SignatureGenerator for new schema names
private final SignatureGenerator signatureGenerator;
private final Positions filePositions;
private final Map<Integer, List<Comment>> comments = new HashMap<>();
private final Map<Integer, Integer> commentClaims = new HashMap<>();
private final BiConsumer<JCTree, VName> nodeConsumer;
private final Context javaContext;
private final JavaFileStoreBasedFileManager fileManager;
private final MetadataLoaders metadataLoaders;
private final JvmGraph jvmGraph;
private List<Metadata> metadata;
private KytheDocTreeScanner docScanner;
private KytheTreeScanner(
JavaEntrySets entrySets,
StatisticsCollector statistics,
SignatureGenerator signatureGenerator,
SourceText src,
Context javaContext,
BiConsumer<JCTree, VName> nodeConsumer,
JavaFileStoreBasedFileManager fileManager,
MetadataLoaders metadataLoaders,
JvmGraph jvmGraph,
JavaIndexerConfig config) {
this.entrySets = entrySets;
this.statistics = statistics;
this.signatureGenerator = signatureGenerator;
this.filePositions = src.getPositions();
this.javaContext = javaContext;
this.nodeConsumer = nodeConsumer;
this.fileManager = fileManager;
this.metadataLoaders = metadataLoaders;
this.jvmGraph = jvmGraph;
this.config = config;
for (Comment comment : src.getComments()) {
for (int line = comment.lineSpan.getStart(); line <= comment.lineSpan.getEnd(); line++) {
if (comments.containsKey(line)) {
comments.get(line).add(comment);
} else {
comments.put(line, Lists.newArrayList(comment));
}
}
}
}
public static void emitEntries(
Context javaContext,
StatisticsCollector statistics,
JavaEntrySets entrySets,
SignatureGenerator signatureGenerator,
JCCompilationUnit compilation,
BiConsumer<JCTree, VName> nodeConsumer,
SourceText src,
JavaFileStoreBasedFileManager fileManager,
MetadataLoaders metadataLoaders,
JavaIndexerConfig config)
throws IOException {
new KytheTreeScanner(
entrySets,
statistics,
signatureGenerator,
src,
javaContext,
nodeConsumer,
fileManager,
metadataLoaders,
config.getJvmMode() == JavaIndexerConfig.JvmMode.SEMANTIC
? new JvmGraph(statistics, entrySets.getEmitter())
: null,
config)
.scan(compilation, null);
}
/** Returns the {@link Symtab} (symbol table) for the compilation currently being processed. */
public Symtab getSymbols() {
return Symtab.instance(javaContext);
}
@Override
public JavaNode scan(JCTree tree, TreeContext owner) {
JavaNode node = super.scan(tree, owner);
if (node != null && nodeConsumer != null) {
nodeConsumer.accept(tree, node.getVName());
}
return node;
}
@Override
public JavaNode visitTopLevel(JCCompilationUnit compilation, TreeContext owner) {
if (compilation.docComments != null) {
docScanner = new KytheDocTreeScanner(this, javaContext);
}
TreeContext ctx = new TreeContext(filePositions, compilation);
metadata = new ArrayList<>();
EntrySet fileNode = entrySets.newFileNodeAndEmit(filePositions);
List<JavaNode> decls = scanList(compilation.getTypeDecls(), ctx);
decls.removeAll(Collections.singleton(null));
JavaNode pkgNode = scan(compilation.getPackage(), ctx);
if (pkgNode != null) {
for (JavaNode n : decls) {
entrySets.emitEdge(n.getVName(), EdgeKind.CHILDOF, pkgNode.getVName());
}
}
scan(compilation.getImports(), ctx);
return new JavaNode(fileNode);
}
@Override
public JavaNode visitPackage(JCPackageDecl pkg, TreeContext owner) {
TreeContext ctx = owner.down(pkg.pid);
VName pkgNode = entrySets.newPackageNodeAndEmit(pkg.packge).getVName();
boolean isPkgInfo =
filePositions
.getSourceFile()
.isNameCompatible(PACKAGE_INFO_NAME, JavaFileObject.Kind.SOURCE);
EdgeKind anchorKind = isPkgInfo ? EdgeKind.DEFINES_BINDING : EdgeKind.REF;
emitAnchor(ctx, anchorKind, pkgNode);
visitDocComment(pkgNode, null, /* modifiers= */ null);
visitAnnotations(pkgNode, pkg.getAnnotations(), ctx);
return new JavaNode(pkgNode);
}
@Override
public JavaNode visitImport(JCImport imprt, TreeContext owner) {
return scan(imprt.qualid, owner.downAsSnippet(imprt));
}
@Override
public JavaNode visitIdent(JCIdent ident, TreeContext owner) {
TreeContext ctx = owner.down(ident);
if (ident.sym == null) {
return emitDiagnostic(ctx, "missing identifier symbol", null, null);
}
return emitSymUsage(ctx, ident.sym);
}
@Override
public JavaNode visitClassDef(JCClassDecl classDef, TreeContext owner) {
loadAnnotationsFromClassDecl(classDef);
TreeContext ctx = owner.down(classDef);
Optional<String> signature = signatureGenerator.getSignature(classDef.sym);
if (!signature.isPresent()) {
// TODO(schroederc): details
return emitDiagnostic(ctx, "missing class signature", null, null);
}
MarkedSource.Builder markedSource = MarkedSource.newBuilder();
VName classNode =
entrySets.getNode(signatureGenerator, classDef.sym, signature.get(), markedSource, null);
// Emit the fact that the class is a child of its containing class or method.
// Note that for a nested/inner class, we already emitted the fact that it's a
// child of the containing class when we scanned the containing class's members.
// However we can't restrict ourselves to just classes contained in methods here,
// because that would miss the case of local/anonymous classes in static/member
// initializers. But there's no harm in emitting the same fact twice!
getScope(ctx).ifPresent(scope -> entrySets.emitEdge(classNode, EdgeKind.CHILDOF, scope));
NestingKind nestingKind = classDef.sym.getNestingKind();
if (nestingKind != NestingKind.LOCAL && nestingKind != NestingKind.ANONYMOUS) {
if (jvmGraph != null) {
// Emit corresponding JVM node
JvmGraph.Type.ReferenceType referenceType = referenceType(classDef.sym.type);
VName jvmNode = jvmGraph.emitClassNode(referenceType);
entrySets.emitEdge(classNode, EdgeKind.GENERATES, jvmNode);
} else {
// Emit NAME nodes for the jvm binary name of classes.
VName nameNode = entrySets.getJvmNameAndEmit(classDef.sym.flatname.toString()).getVName();
entrySets.emitEdge(classNode, EdgeKind.NAMED, nameNode);
}
}
Span classIdent = filePositions.findIdentifier(classDef.name, classDef.getPreferredPosition());
if (!classDef.name.isEmpty() && classIdent == null) {
logger.atWarning().log("Missing span for class identifier: %s", classDef.sym);
}
// Generic classes record the source range of the class name for the abs node, regular
// classes record the source range of the class name for the record node.
EntrySet absNode =
defineTypeParameters(
ctx,
classNode,
classDef.getTypeParameters(),
ImmutableList.<VName>of(), /* There are no wildcards in class definitions */
markedSource.build());
boolean documented = visitDocComment(classNode, absNode, classDef.getModifiers());
if (absNode != null) {
if (classIdent != null) {
EntrySet absAnchor =
entrySets.newAnchorAndEmit(filePositions, classIdent, ctx.getSnippet());
emitDefinesBindingEdge(classIdent, absAnchor, absNode.getVName(), getScope(ctx));
}
if (!documented) {
emitComment(classDef, absNode.getVName());
}
}
if (absNode == null && classIdent != null) {
EntrySet anchor = entrySets.newAnchorAndEmit(filePositions, classIdent, ctx.getSnippet());
emitDefinesBindingEdge(classIdent, anchor, classNode, getScope(ctx));
}
emitAnchor(ctx, EdgeKind.DEFINES, classNode);
if (!documented) {
emitComment(classDef, classNode);
}
visitAnnotations(classNode, classDef.getModifiers().getAnnotations(), ctx);
JavaNode superClassNode = scan(classDef.getExtendsClause(), ctx);
if (superClassNode == null) {
// Use the implicit superclass.
switch (classDef.getKind()) {
case CLASS:
superClassNode = getJavaLangObjectNode();
break;
case ENUM:
superClassNode = getJavaLangEnumNode(classNode);
break;
case ANNOTATION_TYPE:
// TODO(schroederc): handle annotation superclass
break;
case INTERFACE:
break; // Interfaces have no implicit superclass.
default:
logger.atWarning().log("Unexpected JCClassDecl kind: %s", classDef.getKind());
break;
}
}
if (superClassNode != null) {
entrySets.emitEdge(classNode, EdgeKind.EXTENDS, superClassNode.getVName());
}
for (JCExpression implClass : classDef.getImplementsClause()) {
JavaNode implNode = scan(implClass, ctx);
if (implNode == null) {
statistics.incrementCounter("warning-missing-implements-node");
logger.atWarning().log(
"Missing 'implements' node for %s: %s", implClass.getClass(), implClass);
continue;
}
entrySets.emitEdge(classNode, EdgeKind.EXTENDS, implNode.getVName());
}
// Set the resulting node for the class before recursing through its members. Setting the node
// first is necessary to correctly add childof edges from local/anonymous classes defined
// directly in the class body (in static initializers or member initializers).
JavaNode node = ctx.setNode(new JavaNode(classNode));
for (JCTree member : classDef.getMembers()) {
JavaNode n = scan(member, ctx);
if (n != null) {
entrySets.emitEdge(n.getVName(), EdgeKind.CHILDOF, classNode);
}
}
return node;
}
@Override
public JavaNode visitMethodDef(JCMethodDecl methodDef, TreeContext owner) {
TreeContext ctx = owner.down(methodDef);
scan(methodDef.getThrows(), ctx);
scan(methodDef.getDefaultValue(), ctx);
scan(methodDef.getReceiverParameter(), ctx);
JavaNode returnType = scan(methodDef.getReturnType(), ctx);
List<JavaNode> params = new ArrayList<>();
List<JavaNode> paramTypes = new ArrayList<>();
List<VName> wildcards = new ArrayList<>();
for (JCVariableDecl param : methodDef.getParameters()) {
JavaNode n = scan(param, ctx);
params.add(n);
JavaNode typeNode = n.getType();
if (typeNode == null) {
logger.atWarning().log(
"Missing parameter type (method: %s; parameter: %s)", methodDef.getName(), param);
wildcards.addAll(n.childWildcards);
continue;
}
wildcards.addAll(typeNode.childWildcards);
paramTypes.add(typeNode);
}
Optional<String> signature = signatureGenerator.getSignature(methodDef.sym);
if (!signature.isPresent()) {
// Try to scan method body even if signature could not be generated.
scan(methodDef.getBody(), ctx);
// TODO(schroederc): details
return emitDiagnostic(ctx, "missing method signature", null, null);
}
MarkedSource.Builder markedSource = MarkedSource.newBuilder();
VName methodNode =
entrySets.getNode(signatureGenerator, methodDef.sym, signature.get(), markedSource, null);
visitAnnotations(methodNode, methodDef.getModifiers().getAnnotations(), ctx);
EntrySet absNode =
defineTypeParameters(
ctx, methodNode, methodDef.getTypeParameters(), wildcards, markedSource.build());
boolean documented = visitDocComment(methodNode, absNode, methodDef.getModifiers());
// Emit corresponding JVM node
if (jvmGraph != null) {
JvmGraph.Type.MethodType methodJvmType =
toMethodJvmType((Type.MethodType) externalType(methodDef.sym));
ReferenceType parentClass = referenceType(externalType(owner.getTree().type.tsym));
String methodName = methodDef.name.toString();
VName jvmNode = jvmGraph.emitMethodNode(parentClass, methodName, methodJvmType);
entrySets.emitEdge(methodNode, EdgeKind.GENERATES, jvmNode);
for (int i = 0; i < params.size(); i++) {
JavaNode param = params.get(i);
VName paramJvmNode = jvmGraph.emitParameterNode(parentClass, methodName, methodJvmType, i);
entrySets.emitEdge(param.getVName(), EdgeKind.GENERATES, paramJvmNode);
entrySets.emitEdge(jvmNode, EdgeKind.PARAM, paramJvmNode, i);
}
}
VName ret = null;
EntrySet bindingAnchor = null;
if (methodDef.sym.isConstructor()) {
// Implicit constructors (those without syntactic definition locations) share the same
// preferred position as their owned class. We can differentiate them from other constructors
// by checking if its position is ahead of the owner's position.
if (methodDef.getPreferredPosition() > owner.getTree().getPreferredPosition()) {
// Explicit constructor: use the owner's name (the class name) to find the definition
// anchor's location because constructors are internally named "<init>".
bindingAnchor =
emitDefinesBindingAnchorEdge(
ctx, methodDef.sym.owner.name, methodDef.getPreferredPosition(), methodNode);
} else {
// Implicit constructor: generate a zero-length implicit anchor
emitAnchor(ctx, EdgeKind.DEFINES, methodNode);
}
// Likewise, constructors don't have return types in the Java AST, but
// Kythe models all functions with return types. As a solution, we use
// the class type as the return type for all constructors.
ret = getNode(methodDef.sym.owner);
} else {
bindingAnchor =
emitDefinesBindingAnchorEdge(
ctx, methodDef.name, methodDef.getPreferredPosition(), methodNode);
ret = returnType.getVName();
}
if (bindingAnchor != null) {
if (!documented) {
emitComment(methodDef, methodNode);
}
if (absNode != null) {
emitAnchor(bindingAnchor, EdgeKind.DEFINES_BINDING, absNode.getVName(), getScope(ctx));
Span span = filePositions.findIdentifier(methodDef.name, methodDef.getPreferredPosition());
if (span != null) {
emitMetadata(span, absNode.getVName());
}
if (!documented) {
emitComment(methodDef, absNode.getVName());
}
}
emitAnchor(ctx, EdgeKind.DEFINES, methodNode);
}
emitOrdinalEdges(methodNode, EdgeKind.PARAM, params);
VName recv = null;
if (!methodDef.getModifiers().getFlags().contains(Modifier.STATIC)) {
recv = owner.getNode().getVName();
}
EntrySet fnTypeNode =
entrySets.newFunctionTypeAndEmit(
ret,
recv == null ? entrySets.newBuiltinAndEmit("void").getVName() : recv,
toVNames(paramTypes),
recv == null ? MarkedSources.FN_TAPP : MarkedSources.METHOD_TAPP);
entrySets.emitEdge(methodNode, EdgeKind.TYPED, fnTypeNode.getVName());
JavacUtil.visitSuperMethods(
javaContext,
methodDef.sym,
(sym, kind) ->
entrySets.emitEdge(
methodNode,
kind == JavacUtil.OverrideKind.DIRECT
? EdgeKind.OVERRIDES
: EdgeKind.OVERRIDES_TRANSITIVE,
getNode(sym)));
// Set the resulting node for the method and then recurse through its body. Setting the node
// first is necessary to correctly add childof edges in the callgraph.
JavaNode node = ctx.setNode(new JavaNode(methodNode));
scan(methodDef.getBody(), ctx);
for (JavaNode param : params) {
entrySets.emitEdge(param.getVName(), EdgeKind.CHILDOF, node.getVName());
}
return node;
}
@Override
public JavaNode visitLambda(JCLambda lambda, TreeContext owner) {
TreeContext ctx = owner.down(lambda);
VName lambdaNode = entrySets.newLambdaAndEmit(filePositions, lambda).getVName();
emitAnchor(ctx, EdgeKind.DEFINES, lambdaNode);
for (Type target : getTargets(lambda)) {
VName targetNode = getNode(target.asElement());
entrySets.emitEdge(lambdaNode, EdgeKind.EXTENDS, targetNode);
}
scan(lambda.body, ctx);
scanList(lambda.params, ctx);
return new JavaNode(lambdaNode);
}
private static Iterable<Type> getTargets(JCFunctionalExpression node) {
try {
@SuppressWarnings("unchecked")
Iterable<Type> targets =
(Iterable<Type>) JCFunctionalExpression.class.getField("targets").get(node);
return targets != null ? targets : ImmutableList.of();
} catch (ReflectiveOperationException e) {
// continue below
}
try {
// Work with the field rename in JDK 11: http://hg.openjdk.java.net/jdk/jdk11/rev/f854b76b6a0c
return com.sun.tools.javac.util.List.of(
(Type) JCFunctionalExpression.class.getField("target").get(node));
} catch (ReflectiveOperationException e) {
throw new LinkageError(e.getMessage(), e);
}
}
@Override
public JavaNode visitVarDef(JCVariableDecl varDef, TreeContext owner) {
TreeContext ctx = owner.downAsSnippet(varDef);
Optional<String> signature = signatureGenerator.getSignature(varDef.sym);
if (!signature.isPresent()) {
// TODO(schroederc): details
return emitDiagnostic(ctx, "missing variable signature", null, null);
}
List<MarkedSource> markedSourceChildren = new ArrayList<>();
if (varDef.getInitializer() != null) {
String initializer = varDef.getInitializer().toString();
if (initializer.length() <= MAX_INITIALIZER_LENGTH) {
markedSourceChildren.add(
MarkedSource.newBuilder()
.setKind(MarkedSource.Kind.INITIALIZER)
.setPreText(initializer)
.build());
}
}
scan(varDef.getInitializer(), ctx);
VName varNode =
entrySets.getNode(
signatureGenerator, varDef.sym, signature.get(), null, markedSourceChildren);
boolean documented = visitDocComment(varNode, null, varDef.getModifiers());
emitDefinesBindingAnchorEdge(ctx, varDef.name, varDef.getStartPosition(), varNode);
emitAnchor(ctx, EdgeKind.DEFINES, varNode);
if (varDef.sym.getKind().isField() && !documented) {
// emit comments for fields and enumeration constants
emitComment(varDef, varNode);
}
// Emit corresponding JVM node
if (jvmGraph != null && varDef.sym.getKind().isField()) {
VName jvmNode =
jvmGraph.emitFieldNode(
referenceType(externalType(owner.getTree().type.tsym)), varDef.name.toString());
entrySets.emitEdge(varNode, EdgeKind.GENERATES, jvmNode);
}
getScope(ctx).ifPresent(scope -> entrySets.emitEdge(varNode, EdgeKind.CHILDOF, scope));
visitAnnotations(varNode, varDef.getModifiers().getAnnotations(), ctx);
if (varDef.getModifiers().getFlags().contains(Modifier.STATIC)) {
entrySets.getEmitter().emitFact(varNode, "/kythe/tag/static", "");
}
JavaNode typeNode = scan(varDef.getType(), ctx);
if (typeNode != null) {
entrySets.emitEdge(varNode, EdgeKind.TYPED, typeNode.getVName());
return new JavaNode(varNode, typeNode.childWildcards).setType(typeNode);
}
return new JavaNode(varNode);
}
@Override
public JavaNode visitTypeApply(JCTypeApply tApply, TreeContext owner) {
TreeContext ctx = owner.down(tApply);
JavaNode typeCtorNode = scan(tApply.getType(), ctx);
if (typeCtorNode == null) {
logger.atWarning().log("Missing type constructor: %s", tApply.getType());
return emitDiagnostic(ctx, "missing type constructor", null, null);
}
List<JavaNode> arguments = scanList(tApply.getTypeArguments(), ctx);
List<VName> argVNames = new ArrayList<>();
Builder<VName> childWildcards = ImmutableList.builder();
for (JavaNode n : arguments) {
argVNames.add(n.getVName());
childWildcards.addAll(n.childWildcards);
}
EntrySet typeNode =
entrySets.newTApplyAndEmit(typeCtorNode.getVName(), argVNames, MarkedSources.GENERIC_TAPP);
// TODO(salguarnieri) Think about removing this since it isn't something that we have a use for.
emitAnchor(ctx, EdgeKind.REF, typeNode.getVName());
return new JavaNode(typeNode, childWildcards.build());
}
@Override
public JavaNode visitSelect(JCFieldAccess field, TreeContext owner) {
TreeContext ctx = owner.down(field);
JCImport imprt = null;
if (owner.getTree() instanceof JCImport) {
imprt = (JCImport) owner.getTree();
}
Symbol sym = field.sym;
if (sym == null && imprt != null && imprt.isStatic()) {
// Static imports don't have their symbol populated so we search for the symbol.
ClassSymbol cls = JavacUtil.getClassSymbol(javaContext, field.selected + "." + field.name);
if (cls != null) {
// Import was a inner class import
sym = cls;
} else {
cls = JavacUtil.getClassSymbol(javaContext, field.selected.toString());
if (cls != null) {
// Import is a class member; emit usages for all matching (by name) class members.
ctx = ctx.down(field);
JavacTrees trees = JavacTrees.instance(javaContext);
Type.ClassType classType = (Type.ClassType) cls.asType();
Scope scope = trees.getScope(treePath);
JavaNode lastMember = null;
for (Symbol member : cls.members().getSymbolsByName(field.name)) {
try {
if (!member.isStatic() || !trees.isAccessible(scope, member, classType)) {
continue;
}
// Ensure member symbol's type is complete. If the extractor finds that a static
// member isn't used (due to overloads), the symbol's dependent type classes won't
// be saved in the CompilationUnit and this will throw an exception.
if (member.type != null) {
member.type.tsym.complete();
member.type.getParameterTypes().forEach(t -> t.tsym.complete());
Type returnType = member.type.getReturnType();
if (returnType != null) {
returnType.tsym.complete();
}
}
lastMember = emitNameUsage(ctx, member, field.name, EdgeKind.REF_IMPORTS);
} catch (Symbol.CompletionFailure e) {
// Symbol resolution failed (see above comment). Ignore and continue with other
// class members matching static import.
}
}
scan(field.getExpression(), ctx);
return lastMember;
}
}
}
if (sym == null) {
scan(field.getExpression(), ctx);
if (!field.name.toString().equals("*")) {
String msg = "Could not determine selected Symbol for " + field;
if (config.getVerboseLogging()) {
logger.atWarning().log(msg);
}
return emitDiagnostic(ctx, msg, null, null);
}
return null;
} else if (sym.getKind() == ElementKind.PACKAGE) {
EntrySet pkgNode = entrySets.newPackageNodeAndEmit((PackageSymbol) sym);
emitAnchor(ctx, EdgeKind.REF, pkgNode.getVName());
return new JavaNode(pkgNode);
} else {
scan(field.getExpression(), ctx);
return emitNameUsage(
ctx, sym, field.name, imprt != null ? EdgeKind.REF_IMPORTS : EdgeKind.REF);
}
}
@Override
public JavaNode visitReference(JCMemberReference reference, TreeContext owner) {
TreeContext ctx = owner.down(reference);
scan(reference.getQualifierExpression(), ctx);
return emitNameUsage(
ctx,
reference.sym,
reference.getMode() == ReferenceMode.NEW ? Keyword.of("new") : reference.name);
}
@Override
public JavaNode visitApply(JCMethodInvocation invoke, TreeContext owner) {
TreeContext ctx = owner.down(invoke);
scan(invoke.getArguments(), ctx);
scan(invoke.getTypeArguments(), ctx);
JavaNode method = scan(invoke.getMethodSelect(), ctx);
if (method == null) {
// TODO details
return emitDiagnostic(ctx, "error analyzing method", null, null);
}
emitAnchor(ctx, EdgeKind.REF_CALL, method.getVName());
return method;
}
@Override
public JavaNode visitNewClass(JCNewClass newClass, TreeContext owner) {
TreeContext ctx = owner.down(newClass);
VName ctorNode = getNode(newClass.constructor);
if (ctorNode == null) {
return emitDiagnostic(ctx, "error analyzing class", null, null);
}
// Span over "new Class"
Span refSpan =
new Span(filePositions.getStart(newClass), filePositions.getEnd(newClass.getIdentifier()));
// Span over "new Class(...)"
Span callSpan = new Span(refSpan.getStart(), filePositions.getEnd(newClass));
if (owner.getTree().getTag() == JCTree.Tag.VARDEF) {
JCVariableDecl varDef = (JCVariableDecl) owner.getTree();
if (varDef.sym.getKind() == ElementKind.ENUM_CONSTANT) {
// Handle enum constructors specially.
// Span over "EnumValueName"
refSpan = filePositions.findIdentifier(varDef.name, varDef.getStartPosition());
// Span over "EnumValueName(...)"
callSpan = new Span(refSpan.getStart(), filePositions.getEnd(varDef));
}
}
EntrySet anchor = entrySets.newAnchorAndEmit(filePositions, refSpan, ctx.getSnippet());
emitAnchor(anchor, EdgeKind.REF, ctorNode, getScope(ctx));
EntrySet callAnchor = entrySets.newAnchorAndEmit(filePositions, callSpan, ctx.getSnippet());
emitAnchor(callAnchor, EdgeKind.REF_CALL, ctorNode, getScope(ctx));
scanList(newClass.getTypeArguments(), ctx);
scanList(newClass.getArguments(), ctx);
scan(newClass.getEnclosingExpression(), ctx);
scan(newClass.getClassBody(), ctx);
return scan(newClass.getIdentifier(), ctx);
}
@Override
public JavaNode visitTypeIdent(JCPrimitiveTypeTree primitiveType, TreeContext owner) {
TreeContext ctx = owner.down(primitiveType);
if (config.getVerboseLogging() && primitiveType.typetag == TypeTag.ERROR) {
logger.atWarning().log("found primitive ERROR type: %s", ctx);
}
String name = Ascii.toLowerCase(primitiveType.typetag.toString());
EntrySet node = entrySets.newBuiltinAndEmit(name);
emitAnchor(ctx, EdgeKind.REF, node.getVName());
return new JavaNode(node);
}
@Override
public JavaNode visitTypeArray(JCArrayTypeTree arrayType, TreeContext owner) {
TreeContext ctx = owner.down(arrayType);
JavaNode typeNode = scan(arrayType.getType(), ctx);
EntrySet node =
entrySets.newTApplyAndEmit(
entrySets.newBuiltinAndEmit("array").getVName(),
Arrays.asList(typeNode.getVName()),
MarkedSources.ARRAY_TAPP);
emitAnchor(ctx, EdgeKind.REF, node.getVName());
JavaNode arrayNode = new JavaNode(node);
return arrayNode;
}
@Override
public JavaNode visitAnnotation(JCAnnotation annotation, TreeContext owner) {
TreeContext ctx = owner.down(annotation);
scanList(annotation.getArguments(), ctx);
return scan(annotation.getAnnotationType(), ctx);
}
@Override
public JavaNode visitWildcard(JCWildcard wild, TreeContext owner) {
TreeContext ctx = owner.down(wild);
EntrySet node = entrySets.newWildcardNodeAndEmit(wild, owner.getSourcePath());
Builder<VName> wildcards = ImmutableList.builder();
wildcards.add(node.getVName());
if (wild.getKind() != Kind.UNBOUNDED_WILDCARD) {
JavaNode bound = scan(wild.getBound(), ctx);
emitEdge(
node,
wild.getKind() == Kind.EXTENDS_WILDCARD ? EdgeKind.BOUNDED_UPPER : EdgeKind.BOUNDED_LOWER,
bound);
wildcards.addAll(bound.childWildcards);
}
return new JavaNode(node, wildcards.build());
}
@Override
public JavaNode visitExec(JCExpressionStatement stmt, TreeContext owner) {
return scan(stmt.expr, owner.downAsSnippet(stmt));
}
@Override
public JavaNode visitReturn(JCReturn ret, TreeContext owner) {
return scan(ret.expr, owner.downAsSnippet(ret));
}
@Override
public JavaNode visitThrow(JCThrow thr, TreeContext owner) {
return scan(thr.expr, owner.downAsSnippet(thr));
}
@Override
public JavaNode visitAssert(JCAssert azzert, TreeContext owner) {
return scanAll(owner.downAsSnippet(azzert), azzert.cond, azzert.detail);
}
@Override
public JavaNode visitAssign(JCAssign assgn, TreeContext owner) {
return scanAll(owner.downAsSnippet(assgn), assgn.lhs, assgn.rhs);
}
@Override
public JavaNode visitAssignOp(JCAssignOp assgnOp, TreeContext owner) {
return scanAll(owner.downAsSnippet(assgnOp), assgnOp.lhs, assgnOp.rhs);
}
private boolean visitDocComment(VName node, EntrySet absNode, JCModifiers modifiers) {
// TODO(#1501): always use absNode
Optional<String> deprecation = Optional.empty();
boolean documented = false;
if (docScanner != null) {
DocCommentVisitResult result = docScanner.visitDocComment(treePath, node, absNode);
documented = result.documented();
deprecation = result.deprecation();
}
if (!deprecation.isPresent() && modifiers != null) {
// emit tags/deprecated if a @Deprecated annotation is present even if there isn't @deprecated
// javadoc
if (modifiers.getAnnotations().stream()
.map(a -> a.annotationType.type.tsym.getQualifiedName())
.anyMatch(n -> n.contentEquals("java.lang.Deprecated"))) {
deprecation = Optional.of("");
}
}
emitDeprecated(deprecation, node);
if (absNode != null) {
emitDeprecated(deprecation, absNode.getVName());
}
return documented;
}
// // Utility methods ////
void emitDocReference(Symbol sym, int startChar, int endChar) {
VName node = getNode(sym);
if (node == null) {
if (config.getVerboseLogging()) {
logger.atWarning().log("failed to emit documentation reference to %s", sym);
}
return;
}
Span loc =
new Span(
filePositions.charToByteOffset(startChar), filePositions.charToByteOffset(endChar));
EntrySet anchor = entrySets.newAnchorAndEmit(filePositions, loc);
if (anchor != null) {
emitAnchor(anchor, EdgeKind.REF_DOC, node, Optional.empty());
}
}
int charToLine(int charPosition) {
return filePositions.charToLine(charPosition);
}
boolean emitCommentsOnLine(int line, VName node, int defLine) {
List<Comment> lst = comments.get(line);
if (lst == null || commentClaims.computeIfAbsent(line, l -> defLine) != defLine) {
return false;
}
for (Comment comment : lst) {
String bracketed =
MiniAnchor.bracket(
comment.text.replaceFirst("^(//|/\\*) ?", "").replaceFirst(" ?\\*/$", ""),
pos -> pos,
new ArrayList<>());
emitDoc(DocKind.LINE, bracketed, new ArrayList<>(), node, null);
}
return !lst.isEmpty();
}
private static List<VName> toVNames(Iterable<JavaNode> nodes) {
return Streams.stream(nodes).map(JavaNode::getVName).collect(Collectors.toList());
}
// TODO When we want to refer to a type or method that is generic, we need to point to the abs
// node. The code currently does not have an easy way to access that node but this method might
// offer a way to change that.
// See #1501 for more discussion and detail.
/** Create an abs node if we have type variables or if we have wildcards. */
private EntrySet defineTypeParameters(
TreeContext ownerContext,
VName owner,
List<JCTypeParameter> params,
List<VName> wildcards,
MarkedSource markedSource) {
if (params.isEmpty() && wildcards.isEmpty()) {
return null;
}
List<VName> typeParams = new ArrayList<>();
for (JCTypeParameter tParam : params) {
TreeContext ctx = ownerContext.down(tParam);
VName node = getNode(tParam.type.asElement());
emitDefinesBindingAnchorEdge(ctx, tParam.name, tParam.getStartPosition(), node);
visitAnnotations(node, tParam.getAnnotations(), ctx);
typeParams.add(node);
List<JCExpression> bounds = tParam.getBounds();
List<JavaNode> boundNodes =
bounds.stream().map(expr -> scan(expr, ctx)).collect(Collectors.toList());
if (boundNodes.isEmpty()) {
boundNodes.add(getJavaLangObjectNode());
}
emitOrdinalEdges(node, EdgeKind.BOUNDED_UPPER, boundNodes);
}
// Add all of the wildcards that roll up to this node. For example:
// public static <T> void foo(Ty<?> a, Obj<?, ?> b, Obj<Ty<?>, Ty<?>> c) should declare an abs
// node that has 1 named absvar (T) and 5 unnamed absvars.
typeParams.addAll(wildcards);
return entrySets.newAbstractAndEmit(owner, typeParams, markedSource);
}
/** Returns the node associated with a {@link Symbol} or {@code null}. */
private VName getNode(Symbol sym) {
JavaNode node = getJavaNode(sym);
return node == null ? null : node.getVName();
}
/** Returns the {@link JavaNode} associated with a {@link Symbol} or {@code null}. */
private JavaNode getJavaNode(Symbol sym) {
Optional<String> signature = signatureGenerator.getSignature(sym);
if (!signature.isPresent()) {
return null;
}
return new JavaNode(entrySets.getNode(signatureGenerator, sym, signature.get(), null, null));
}
private void visitAnnotations(
VName owner, List<JCAnnotation> annotations, TreeContext ownerContext) {
for (JCAnnotation annotation : annotations) {
int defPosition = annotation.getPreferredPosition();
int defLine = filePositions.charToLine(defPosition);
// Claim trailing annotation comments, which isn't always right, but
// avoids some confusing comments for method annotations.
// TODO(danielmoy): don't do this for inline field annotations.
commentClaims.put(defLine, defLine);
}
for (JavaNode node : scanList(annotations, ownerContext)) {
entrySets.emitEdge(owner, EdgeKind.ANNOTATED_BY, node.getVName());
}
}
// Emits a node for the given sym, an anchor encompassing the TreeContext, and a REF edge
private JavaNode emitSymUsage(TreeContext ctx, Symbol sym) {
JavaNode node = getRefNode(ctx, sym);
if (node == null) {
// TODO(schroederc): details
return emitDiagnostic(ctx, "failed to resolve symbol reference", null, null);
}
// TODO(schroederc): emit reference to JVM node if `sym.outermostClass()` is not defined in a
// .java source file
emitAnchor(ctx, EdgeKind.REF, node.getVName());
statistics.incrementCounter("symbol-usages-emitted");
return node;
}
// Emits a node for the given sym, an anchor encompassing the name, and a REF edge
private JavaNode emitNameUsage(TreeContext ctx, Symbol sym, Name name) {
return emitNameUsage(ctx, sym, name, EdgeKind.REF);
}
// Emits a node for the given sym, an anchor encompassing the name, and a given edge kind
private JavaNode emitNameUsage(TreeContext ctx, Symbol sym, Name name, EdgeKind edgeKind) {
JavaNode node = getRefNode(ctx, sym);
if (node == null) {
// TODO(schroederc): details
return emitDiagnostic(ctx, "failed to resolve symbol name", null, null);
}
// Ensure the context has a valid source span before searching for the Name. Otherwise, anchors
// may accidentily be emitted for Names that happen to appear after the tree context (e.g.
// lambdas with type-inferred parameters that use the parameter type in the lambda body).
if (filePositions.getSpan(ctx.getTree()).isValidAndNonZero()) {
emitAnchor(
name,
ctx.getTree().getPreferredPosition(),
edgeKind,
node.getVName(),
ctx.getSnippet(),
getScope(ctx));
statistics.incrementCounter("name-usages-emitted");
}
return node;
}
private static Optional<VName> getScope(TreeContext ctx) {
return Optional.ofNullable(ctx.getClassOrMethodParent())
.map(TreeContext::getNode)
.map(JavaNode::getVName);
}
// Returns the reference node for the given symbol.
private JavaNode getRefNode(TreeContext ctx, Symbol sym) {
if (sym.getKind() == ElementKind.PACKAGE) {
return new JavaNode(entrySets.newPackageNodeAndEmit((PackageSymbol) sym).getVName());
}
// If referencing a generic class, distinguish between generic vs. raw use
// (e.g., `List` is in generic context in `List<String> x` but not in `List x`).
boolean inGenericContext = ctx.up().getTree() instanceof JCTypeApply;
try {
if (sym != null
&& SignatureGenerator.isArrayHelperClass(sym.enclClass())
&& ctx.getTree() instanceof JCFieldAccess) {
signatureGenerator.setArrayTypeContext(((JCFieldAccess) ctx.getTree()).selected.type);
}
JavaNode node = getJavaNode(sym);
if (node != null
&& sym instanceof ClassSymbol
&& inGenericContext
&& !sym.getTypeParameters().isEmpty()) {
// Always reference the abs node of a generic class, unless used as a raw type.
node = new JavaNode(entrySets.newAbstractAndEmit(node.getVName()));
}
return node;
} finally {
signatureGenerator.setArrayTypeContext(null);
}
}
// Cached common java.lang.* nodes.
private JavaNode javaLangObjectNode, javaLangEnumNode;
// Returns a JavaNode representing java.lang.Object.
private JavaNode getJavaLangObjectNode() {
if (javaLangObjectNode == null) {
javaLangObjectNode = resolveJavaLangSymbol(getSymbols().objectType.asElement());
}
return javaLangObjectNode;
}
// Returns a JavaNode representing java.lang.Enum<E> where E is a given enum type.
private JavaNode getJavaLangEnumNode(VName enumVName) {
if (javaLangEnumNode == null) {
javaLangEnumNode =
new JavaNode(
entrySets
.newAbstractAndEmit(resolveJavaLangSymbol(getSymbols().enumSym).getVName())
.getVName());
}
EntrySet typeNode =
entrySets.newTApplyAndEmit(
javaLangEnumNode.getVName(),
Collections.singletonList(enumVName),
MarkedSources.GENERIC_TAPP);
return new JavaNode(typeNode);
}
private JavaNode resolveJavaLangSymbol(Symbol sym) {
Optional<String> signature = signatureGenerator.getSignature(sym);
if (!signature.isPresent()) {
// This usually indicates a problem with the compilation's bootclasspath.
return emitDiagnostic(null, "failed to resolve " + sym, null, null);
}
return new JavaNode(entrySets.getNode(signatureGenerator, sym, signature.get(), null, null));
}
// Creates/emits an anchor and an associated edge
private EntrySet emitAnchor(TreeContext anchorContext, EdgeKind kind, VName node) {
return emitAnchor(
entrySets.newAnchorAndEmit(
filePositions, anchorContext.getTreeSpan(), anchorContext.getSnippet()),
kind,
node,
getScope(anchorContext));
}
// Creates/emits an anchor (for an identifier) and an associated edge
private EntrySet emitAnchor(
Name name, int startOffset, EdgeKind kind, VName node, Span snippet, Optional<VName> scope) {
EntrySet anchor = entrySets.newAnchorAndEmit(filePositions, name, startOffset, snippet);
if (anchor == null) {
// TODO(schroederc): Special-case these anchors (most come from visitSelect)
return null;
}
return emitAnchor(anchor, kind, node, scope);
}
private void emitMetadata(Span span, VName node) {
for (Metadata data : metadata) {
for (Metadata.Rule rule : data.getRulesForLocation(span.getStart())) {
if (rule.end == span.getEnd()) {
if (rule.reverseEdge) {
entrySets.emitEdge(rule.vname, rule.edgeOut, node);
} else {
entrySets.emitEdge(node, rule.edgeOut, rule.vname);
}
}
}
}
}
private EntrySet emitDefinesBindingAnchorEdge(
TreeContext ctx, Name name, int startOffset, VName node) {
EntrySet anchor =
emitAnchor(
name, startOffset, EdgeKind.DEFINES_BINDING, node, ctx.getSnippet(), getScope(ctx));
Span span = filePositions.findIdentifier(name, startOffset);
if (span != null) {
emitMetadata(span, node);
}
return anchor;
}
private void emitDefinesBindingEdge(
Span span, EntrySet anchor, VName node, Optional<VName> scope) {
emitMetadata(span, node);
emitAnchor(anchor, EdgeKind.DEFINES_BINDING, node, scope);
}
// Creates/emits an anchor and an associated edge
private EntrySet emitAnchor(EntrySet anchor, EdgeKind kind, VName node, Optional<VName> scope) {
Preconditions.checkArgument(
kind.isAnchorEdge(), "EdgeKind was not intended for ANCHORs: %s", kind);
if (anchor == null) {
return null;
}
entrySets.emitEdge(anchor.getVName(), kind, node);
if (kind == EdgeKind.REF_CALL || config.getEmitAnchorScopes()) {
scope.ifPresent(s -> entrySets.emitEdge(anchor.getVName(), EdgeKind.CHILDOF, s));
}
return anchor;
}
private void emitComment(JCTree defTree, VName node) {
int defPosition = defTree.getPreferredPosition();
int defLine = filePositions.charToLine(defPosition);
emitCommentsOnLine(defLine, node, defLine);
emitCommentsOnLine(defLine - 1, node, defLine);
}
void emitDoc(
DocKind kind, String bracketedText, Iterable<Symbol> params, VName node, VName absNode) {
List<VName> paramNodes = new ArrayList<>();
for (Symbol s : params) {
VName paramNode = getNode(s);
if (paramNode == null) {
return;
}
paramNodes.add(paramNode);
}
EntrySet doc =
entrySets.newDocAndEmit(kind.getDocSubkind(), filePositions, bracketedText, paramNodes);
// TODO(#1501): always use absNode
entrySets.emitEdge(doc.getVName(), EdgeKind.DOCUMENTS, node);
if (absNode != null) {
entrySets.emitEdge(doc.getVName(), EdgeKind.DOCUMENTS, absNode);
}
}
private void emitDeprecated(Optional<String> deprecation, VName node) {
deprecation.ifPresent(d -> entrySets.getEmitter().emitFact(node, "/kythe/tag/deprecated", d));
}
// Unwraps the target EntrySet and emits an edge to it from the sourceNode
private void emitEdge(EntrySet sourceNode, EdgeKind kind, JavaNode target) {
entrySets.emitEdge(sourceNode.getVName(), kind, target.getVName());
}
// Unwraps each target JavaNode and emits an ordinal edge to each from the given source node
private void emitOrdinalEdges(VName node, EdgeKind kind, List<JavaNode> targets) {
entrySets.emitOrdinalEdges(node, kind, toVNames(targets));
}
private JavaNode emitDiagnostic(TreeContext ctx, String message, String details, String context) {
Diagnostic.Builder d = Diagnostic.newBuilder().setMessage(message);
if (details != null) {
d.setDetails(details);
}
if (context != null) {
d.setContextUrl(context);
}
if (ctx != null) {
Span s = ctx.getTreeSpan();
if (s.isValid()) {
d.getSpanBuilder().getStartBuilder().setByteOffset(s.getStart());
d.getSpanBuilder().getEndBuilder().setByteOffset(s.getEnd());
} else if (s.getStart() >= 0) {
// If the span isn't valid but we have a valid start, use the start for a zero-width span.
d.getSpanBuilder().getStartBuilder().setByteOffset(s.getStart());
d.getSpanBuilder().getEndBuilder().setByteOffset(s.getStart());
}
}
EntrySet node = entrySets.emitDiagnostic(filePositions, d.build());
// TODO(schroederc): don't allow any edges to a diagnostic node
return new JavaNode(node);
}
private <T extends JCTree> List<JavaNode> scanList(List<T> trees, TreeContext owner) {
List<JavaNode> nodes = new ArrayList<>();
for (T t : trees) {
nodes.add(scan(t, owner));
}
return nodes;
}
private void loadAnnotationsFile(String path) {
URI uri = filePositions.getSourceFile().toUri();
try {
String fullPath = uri.resolve(path).getPath();
if (fullPath.startsWith("/")) {
fullPath = fullPath.substring(1);
}
FileObject file = fileManager.getJavaFileFromPath(fullPath, JavaFileObject.Kind.OTHER);
if (file == null) {
logger.atWarning().log("Can't find metadata %s for %s at %s", path, uri, fullPath);
return;
}
InputStream stream = file.openInputStream();
Metadata newMetadata = metadataLoaders.parseFile(fullPath, ByteStreams.toByteArray(stream));
if (newMetadata == null) {
logger.atWarning().log("Can't load metadata %s for %s", path, uri);
return;
}
metadata.add(newMetadata);
} catch (IOException | IllegalArgumentException ex) {
logger.atWarning().log("Can't read metadata %s for %s", path, uri);
}
}
private void loadAnnotationsFromClassDecl(JCClassDecl decl) {
for (JCAnnotation annotation : decl.getModifiers().getAnnotations()) {
Symbol annotationSymbol = null;
if (annotation.getAnnotationType() instanceof JCFieldAccess) {
annotationSymbol = ((JCFieldAccess) annotation.getAnnotationType()).sym;
} else if (annotation.getAnnotationType() instanceof JCIdent) {
annotationSymbol = ((JCIdent) annotation.getAnnotationType()).sym;
}
if (annotationSymbol == null
|| !annotationSymbol.toString().equals("javax.annotation.Generated")) {
continue;
}
for (JCExpression arg : annotation.getArguments()) {
if (!(arg instanceof JCAssign)) {
continue;
}
JCAssign assignArg = (JCAssign) arg;
if (!(assignArg.lhs instanceof JCIdent) || !(assignArg.rhs instanceof JCLiteral)) {
continue;
}
JCIdent lhs = (JCIdent) assignArg.lhs;
JCLiteral rhs = (JCLiteral) assignArg.rhs;
if (!lhs.name.contentEquals("comments") || !(rhs.getValue() instanceof String)) {
continue;
}
String comments = (String) rhs.getValue();
if (comments.startsWith(Metadata.ANNOTATION_COMMENT_PREFIX)) {
loadAnnotationsFile(comments.substring(Metadata.ANNOTATION_COMMENT_PREFIX.length()));
}
}
}
}
private Type externalType(Symbol sym) {
return sym.externalType(Types.instance(javaContext));
}
static JvmGraph.Type toJvmType(Type type) {
switch (type.getTag()) {
case ARRAY:
return JvmGraph.Type.arrayType(toJvmType(((Type.ArrayType) type).getComponentType()));
case CLASS:
return referenceType(type);
case METHOD:
return toMethodJvmType(type.asMethodType());
case TYPEVAR:
return referenceType(type);
case BOOLEAN:
return JvmGraph.Type.booleanType();
case BYTE:
return JvmGraph.Type.byteType();
case CHAR:
return JvmGraph.Type.charType();
case DOUBLE:
return JvmGraph.Type.doubleType();
case FLOAT:
return JvmGraph.Type.floatType();
case INT:
return JvmGraph.Type.intType();
case LONG:
return JvmGraph.Type.longType();
case SHORT:
return JvmGraph.Type.shortType();
default:
throw new IllegalStateException("unhandled Java Type: " + type.getTag());
}
}
/** Returns a new JVM class/enum/interface type descriptor to the specified source type. */
private static ReferenceType referenceType(Type referent) {
String qualifiedName = referent.tsym.flatName().toString();
return JvmGraph.Type.referenceType(qualifiedName);
}
private static JvmGraph.VoidableType toJvmReturnType(Type type) {
switch (type.getTag()) {
case VOID:
return JvmGraph.Type.voidType();
default:
return toJvmType(type);
}
}
static JvmGraph.Type.MethodType toMethodJvmType(Type.MethodType type) {
return JvmGraph.Type.methodType(
type.getParameterTypes().stream()
.map(KytheTreeScanner::toJvmType)
.collect(Collectors.toList()),
toJvmReturnType(type.getReturnType()));
}
static enum DocKind {
JAVADOC(Optional.of("javadoc")),
LINE;
private final Optional<String> subkind;
private DocKind(Optional<String> subkind) {
this.subkind = subkind;
}
private DocKind() {
this(Optional.empty());
}
/** Returns the Kythe subkind for this type of document. */
public Optional<String> getDocSubkind() {
return subkind;
}
}
}