Skip to content

Commit 2d89dc6

Browse files
Support for reading in an apiview_properties.json file in the sources.jar file, for things like typespec back-referencing. (#7078)
1 parent fbff3bf commit 2d89dc6

7 files changed

Lines changed: 156 additions & 239 deletions

File tree

src/java/apiview-java-processor/src/main/java/com/azure/tools/apiview/processor/Main.java

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,7 @@
33
import com.azure.tools.apiview.processor.analysers.JavaASTAnalyser;
44
import com.azure.tools.apiview.processor.analysers.Analyser;
55
import com.azure.tools.apiview.processor.analysers.XMLASTAnalyser;
6-
import com.azure.tools.apiview.processor.model.APIListing;
7-
import com.azure.tools.apiview.processor.model.Diagnostic;
8-
import com.azure.tools.apiview.processor.model.DiagnosticKind;
9-
import com.azure.tools.apiview.processor.model.LanguageVariant;
10-
import com.azure.tools.apiview.processor.model.Token;
6+
import com.azure.tools.apiview.processor.model.*;
117
import com.azure.tools.apiview.processor.model.maven.Pom;
128
import com.fasterxml.jackson.annotation.JsonInclude;
139
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -16,6 +12,7 @@
1612
import java.io.File;
1713
import java.io.FileInputStream;
1814
import java.io.IOException;
15+
import java.net.URL;
1916
import java.nio.file.FileSystem;
2017
import java.nio.file.FileSystems;
2118
import java.nio.file.Files;
@@ -167,11 +164,26 @@ private static void processJavaSourcesJar(File inputFile, APIListing apiListing)
167164
}
168165
System.out.println(" Using '" + apiListing.getLanguageVariant() + "' for the language variant");
169166

170-
final Analyser analyser = new JavaASTAnalyser(inputFile, apiListing);
167+
final Analyser analyser = new JavaASTAnalyser(apiListing);
171168

172169
// Read all files within the jar file so that we can create a list of files to analyse
173170
final List<Path> allFiles = new ArrayList<>();
174171
try (FileSystem fs = FileSystems.newFileSystem(inputFile.toPath(), Main.class.getClassLoader())) {
172+
173+
try {
174+
// we eagerly load the apiview_properties.json file into an ApiViewProperties object, so that it can
175+
// be used throughout the analysis process, as required
176+
URL apiViewPropertiesFile = fs.getPath("/META-INF/apiview_properties.json").toUri().toURL();
177+
final ObjectMapper objectMapper = new ObjectMapper();
178+
ApiViewProperties properties = objectMapper.readValue(apiViewPropertiesFile, ApiViewProperties.class);
179+
apiListing.setApiViewProperties(properties);
180+
System.out.println(" Found apiview_properties.json file in jar file");
181+
System.out.println(" - Found " + properties.getCrossLanguageDefinitionIds().size() + " cross-language definition IDs");
182+
} catch (Exception e) {
183+
// this is fine, we just won't have any APIView properties to read in
184+
System.out.println(" No apiview_properties.json file found in jar file - continuing...");
185+
}
186+
175187
fs.getRootDirectories().forEach(root -> {
176188
try (Stream<Path> paths = Files.walk(root)) {
177189
paths.forEach(allFiles::add);
@@ -183,6 +195,8 @@ private static void processJavaSourcesJar(File inputFile, APIListing apiListing)
183195

184196
// Do the analysis while the filesystem is still represented in memory
185197
analyser.analyse(allFiles);
198+
} catch (Exception e) {
199+
e.printStackTrace();
186200
}
187201
}
188202

src/java/apiview-java-processor/src/main/java/com/azure/tools/apiview/processor/analysers/JavaASTAnalyser.java

Lines changed: 59 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import com.github.javaparser.ast.expr.NormalAnnotationExpr;
4040
import com.github.javaparser.ast.modules.ModuleDeclaration;
4141
import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations;
42+
import com.github.javaparser.ast.nodeTypes.NodeWithSimpleName;
4243
import com.github.javaparser.ast.nodeTypes.NodeWithType;
4344
import com.github.javaparser.ast.type.ClassOrInterfaceType;
4445
import com.github.javaparser.ast.type.ReferenceType;
@@ -73,13 +74,7 @@
7374

7475
import org.apache.commons.lang.StringEscapeUtils;
7576

76-
import static com.azure.tools.apiview.processor.analysers.util.ASTUtils.attemptToFindJavadocComment;
77-
import static com.azure.tools.apiview.processor.analysers.util.ASTUtils.getPackageName;
78-
import static com.azure.tools.apiview.processor.analysers.util.ASTUtils.isInterfaceType;
79-
import static com.azure.tools.apiview.processor.analysers.util.ASTUtils.isPrivateOrPackagePrivate;
80-
import static com.azure.tools.apiview.processor.analysers.util.ASTUtils.isPublicOrProtected;
81-
import static com.azure.tools.apiview.processor.analysers.util.ASTUtils.isTypeAPublicAPI;
82-
import static com.azure.tools.apiview.processor.analysers.util.ASTUtils.makeId;
77+
import static com.azure.tools.apiview.processor.analysers.util.ASTUtils.*;
8378
import static com.azure.tools.apiview.processor.analysers.util.TokenModifier.INDENT;
8479
import static com.azure.tools.apiview.processor.analysers.util.TokenModifier.NEWLINE;
8580
import static com.azure.tools.apiview.processor.analysers.util.TokenModifier.NOTHING;
@@ -111,19 +106,18 @@ public class JavaASTAnalyser implements Analyser {
111106

112107
private static final Pattern SPLIT_NEWLINE = Pattern.compile(MiscUtils.LINEBREAK);
113108

109+
// This is the model that we build up as the AST of all files are analysed. The APIListing is then output as
110+
// JSON that can be understood by APIView.
114111
private final APIListing apiListing;
115112

116-
private final Map<String, JavadocComment> packageNameToPackageInfoJavaDoc;
113+
private final Map<String, JavadocComment> packageNameToPackageInfoJavaDoc = new HashMap<>();
117114

118-
private final Diagnostics diagnostic;
115+
private final Diagnostics diagnostic = new Diagnostics();
119116

120-
private int indent;
117+
private int indent = 0;
121118

122-
public JavaASTAnalyser(File inputFile, APIListing apiListing) {
119+
public JavaASTAnalyser(APIListing apiListing) {
123120
this.apiListing = apiListing;
124-
this.indent = 0;
125-
this.packageNameToPackageInfoJavaDoc = new HashMap<>();
126-
this.diagnostic = new Diagnostics();
127121
}
128122

129123
@Override
@@ -183,10 +177,6 @@ public ScanClass(Path path, CompilationUnit compilationUnit) {
183177
}
184178
}
185179

186-
public CompilationUnit getCompilationUnit() {
187-
return compilationUnit;
188-
}
189-
190180
public Path getPath() {
191181
return path;
192182
}
@@ -209,7 +199,6 @@ private Optional<ScanClass> scanForTypes(Path path) {
209199
// Set up a minimal type solver that only looks at the classes used to run this sample.
210200
CombinedTypeSolver combinedTypeSolver = new CombinedTypeSolver();
211201
combinedTypeSolver.add(new ReflectionTypeSolver(false));
212-
// combinedTypeSolver.add(new SourceJarTypeSolver(inputFile));
213202

214203
ParserConfiguration parserConfiguration = new ParserConfiguration()
215204
.setStoreTokens(true)
@@ -343,7 +332,7 @@ private void tokeniseMavenPom(Pom mavenPom) {
343332
// we don't care to present test scope dependencies
344333
return;
345334
}
346-
String scope = k.equals("") ? "compile" : k;
335+
String scope = k.isEmpty() ? "compile" : k;
347336

348337
addToken(makeWhitespace());
349338
addToken(new Token(COMMENT, "// " + scope + " scope"), NEWLINE);
@@ -510,6 +499,23 @@ private void visitModuleDeclaration(ModuleDeclaration moduleDeclaration) {
510499
addToken(new Token(TYPE_NAME, moduleDeclaration.getNameAsString(), MODULE_INFO_KEY), SPACE);
511500
addToken(new Token(PUNCTUATION, "{"), NEWLINE);
512501

502+
// Sometimes an exports or opens statement is conditional, so we need to handle that case
503+
// in a single location here, to remove duplication.
504+
Consumer<NodeList<Name>> conditionalExportsToOrOpensToConsumer = names -> {
505+
if (!names.isEmpty()) {
506+
addToken(new Token(WHITESPACE, " "));
507+
addToken(new Token(KEYWORD, "to"), SPACE);
508+
509+
for (int i = 0; i < names.size(); i++) {
510+
addToken(new Token(TYPE_NAME, names.get(i).toString()));
511+
512+
if (i < names.size() - 1) {
513+
addToken(new Token(PUNCTUATION, ","), SPACE);
514+
}
515+
}
516+
}
517+
};
518+
513519
moduleDeclaration.getDirectives().forEach(moduleDirective -> {
514520
indent();
515521
addToken(makeWhitespace());
@@ -532,43 +538,14 @@ private void visitModuleDeclaration(ModuleDeclaration moduleDeclaration) {
532538
moduleDirective.ifModuleExportsStmt(d -> {
533539
addToken(new Token(KEYWORD, "exports"), SPACE);
534540
addToken(new Token(TYPE_NAME, d.getNameAsString(), makeId(MODULE_INFO_KEY + "-exports-" + d.getNameAsString())));
535-
536-
NodeList<Name> names = d.getModuleNames();
537-
538-
if (!names.isEmpty()) {
539-
addToken(new Token(WHITESPACE, " "));
540-
addToken(new Token(KEYWORD, "to"), SPACE);
541-
542-
for (int i = 0; i < names.size(); i++) {
543-
addToken(new Token(TYPE_NAME, names.get(i).toString()));
544-
545-
if (i < names.size() - 1) {
546-
addToken(new Token(PUNCTUATION, ","), SPACE);
547-
}
548-
}
549-
}
550-
541+
conditionalExportsToOrOpensToConsumer.accept(d.getModuleNames());
551542
addToken(new Token(PUNCTUATION, ";"), NEWLINE);
552543
});
553544

554545
moduleDirective.ifModuleOpensStmt(d -> {
555546
addToken(new Token(KEYWORD, "opens"), SPACE);
556547
addToken(new Token(TYPE_NAME, d.getNameAsString(), makeId(MODULE_INFO_KEY + "-opens-" + d.getNameAsString())));
557-
558-
NodeList<Name> names = d.getModuleNames();
559-
if (names.size() > 0) {
560-
addToken(new Token(WHITESPACE, " "));
561-
addToken(new Token(KEYWORD, "to"), SPACE);
562-
563-
for (int i = 0; i < names.size(); i++) {
564-
addToken(new Token(TYPE_NAME, names.get(i).toString()));
565-
566-
if (i < names.size() - 1) {
567-
addToken(new Token(PUNCTUATION, ","), SPACE);
568-
}
569-
}
570-
}
571-
548+
conditionalExportsToOrOpensToConsumer.accept(d.getModuleNames());
572549
addToken(new Token(PUNCTUATION, ";"), NEWLINE);
573550
});
574551

@@ -694,7 +671,13 @@ private void getTypeDeclaration(TypeDeclaration<?> typeDeclaration) {
694671
addToken(new Token(DEPRECATED_RANGE_START));
695672
}
696673

697-
addToken(new Token(TYPE_NAME, className, classId));
674+
// setting the class name. We need to look up to see if the apiview_properties.json file specified a
675+
// cross language definition id for this type. If it did, we will use that. The apiview_properties.json
676+
// file uses fully-qualified type names and method names, so we need to ensure that it what we are using
677+
// when we look for a match.
678+
Token typeNameToken = new Token(TYPE_NAME, className, classId);
679+
checkForCrossLanguageDefinitionId(typeNameToken, typeDeclaration);
680+
addToken(typeNameToken);
698681

699682
if (isDeprecated) {
700683
addToken(new Token(DEPRECATED_RANGE_END));
@@ -755,6 +738,25 @@ private void getTypeDeclaration(TypeDeclaration<?> typeDeclaration) {
755738
addToken(SPACE, new Token(PUNCTUATION, "{"), NEWLINE);
756739
}
757740

741+
/*
742+
* This method is used to add 'cross language definition id' to the token if it is defined in the
743+
* apiview_properties.json file. This is used most commonly in conjunction with TypeSpec-generated libraries,
744+
* so that we may review cross languages with some level of confidence that the types and methods are the same.
745+
*/
746+
private void checkForCrossLanguageDefinitionId(Token typeNameToken, NodeWithSimpleName<?> node) {
747+
Optional<String> fqn;
748+
if (node instanceof TypeDeclaration) {
749+
fqn = ((TypeDeclaration<?>) node).getFullyQualifiedName();
750+
} else if (node instanceof CallableDeclaration) {
751+
fqn = Optional.of(getNodeFullyQualifiedName((CallableDeclaration<?>) node));
752+
} else {
753+
fqn = Optional.empty();
754+
}
755+
756+
fqn.flatMap(_fqn -> apiListing.getApiViewProperties().getCrossLanguageDefinitionId(_fqn))
757+
.ifPresent(typeNameToken::setCrossLanguageDefinitionId);
758+
}
759+
758760
private void tokeniseAnnotationMember(AnnotationDeclaration annotationDeclaration) {
759761
indent();
760762
// Member methods in the annotation declaration
@@ -1125,7 +1127,9 @@ private void getDeclarationNameAndParameters(CallableDeclaration<?> callableDecl
11251127
addToken(new Token(DEPRECATED_RANGE_START));
11261128
}
11271129

1128-
addToken(new Token(MEMBER_NAME, name, definitionId));
1130+
Token nameToken = new Token(MEMBER_NAME, name, definitionId);
1131+
checkForCrossLanguageDefinitionId(nameToken, callableDeclaration);
1132+
addToken(nameToken);
11291133

11301134
if (isDeprecated) {
11311135
addToken(new Token(DEPRECATED_RANGE_END));
@@ -1188,7 +1192,7 @@ private void getGenericTypeParameter(TypeParameter typeParameter) {
11881192

11891193
private void getThrowException(CallableDeclaration<?> callableDeclaration) {
11901194
final NodeList<ReferenceType> thrownExceptions = callableDeclaration.getThrownExceptions();
1191-
if (thrownExceptions.size() == 0) {
1195+
if (thrownExceptions.isEmpty()) {
11921196
return;
11931197
}
11941198

@@ -1425,7 +1429,7 @@ private void visitJavaDoc(JavadocComment javadoc) {
14251429
// convert http/s links to external clickable links
14261430
Matcher urlMatch = MiscUtils.URL_MATCH.matcher(line2);
14271431
int currentIndex = 0;
1428-
while(urlMatch.find(currentIndex) == true) {
1432+
while(urlMatch.find(currentIndex)) {
14291433
int start = urlMatch.start();
14301434
int end = urlMatch.end();
14311435

src/java/apiview-java-processor/src/main/java/com/azure/tools/apiview/processor/analysers/util/ASTUtils.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -333,28 +333,37 @@ public static boolean isInterfaceType(TypeDeclaration<?> type) {
333333
return false;
334334
}
335335

336-
public static boolean isTypeImplementingInterface(TypeDeclaration type, String interfaceName) {
336+
public static boolean isTypeImplementingInterface(TypeDeclaration<?> type, String interfaceName) {
337337
return type.asClassOrInterfaceDeclaration().getImplementedTypes().stream()
338338
.anyMatch(_interface -> _interface.getNameAsString().equals(interfaceName));
339339
}
340340

341-
private static String getNodeFullyQualifiedName(Optional<Node> nodeOptional) {
342-
if (!nodeOptional.isPresent()) {
341+
public static String getNodeFullyQualifiedName(Node node) {
342+
if (node == null) {
343343
return "";
344344
}
345345

346-
Node node = nodeOptional.get();
347346
if (node instanceof TypeDeclaration<?>) {
348347
TypeDeclaration<?> type = (TypeDeclaration<?>) node;
349348
return type.getFullyQualifiedName().get();
350349
} else if (node instanceof CallableDeclaration) {
351-
CallableDeclaration callableDeclaration = (CallableDeclaration) node;
352-
return getNodeFullyQualifiedName(node.getParentNode()) + "." + callableDeclaration.getNameAsString();
350+
CallableDeclaration<?> callableDeclaration = (CallableDeclaration<?>) node;
351+
String fqn = getNodeFullyQualifiedName(node.getParentNode()) + "." + callableDeclaration.getNameAsString();
352+
353+
if (callableDeclaration.isConstructorDeclaration()) {
354+
fqn += ".ctor";
355+
}
356+
357+
return fqn;
353358
} else {
354359
return "";
355360
}
356361
}
357362

363+
private static String getNodeFullyQualifiedName(Optional<Node> nodeOptional) {
364+
return nodeOptional.map(ASTUtils::getNodeFullyQualifiedName).orElse("");
365+
}
366+
358367
/**
359368
* Attempts to retrieve the {@link JavadocComment} for a given {@link BodyDeclaration}.
360369
* <p>

0 commit comments

Comments
 (0)