Skip to content

Commit

Permalink
Support for client core libraries (#7955)
Browse files Browse the repository at this point in the history
Improving APIView to support non-Azure libraries, in particular the new generic core
  • Loading branch information
JonathanGiles authored Apr 9, 2024
1 parent e99e474 commit bca1ac5
Show file tree
Hide file tree
Showing 36 changed files with 656 additions and 218 deletions.
4 changes: 2 additions & 2 deletions src/java/apiview-java-processor/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.4.2</version>
<version>2.17.0</version>
</dependency>

<dependency>
<groupId>com.github.javaparser</groupId>
<artifactId>javaparser-symbol-solver-core</artifactId>
<version>3.24.2</version>
<version>3.25.9</version>
</dependency>

<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;

import java.io.File;
import java.io.FileInputStream;
Expand Down Expand Up @@ -164,8 +165,6 @@ private static void processJavaSourcesJar(File inputFile, APIListing apiListing)
}
System.out.println(" Using '" + apiListing.getLanguageVariant() + "' for the language variant");

final Analyser analyser = new JavaASTAnalyser(apiListing);

// Read all files within the jar file so that we can create a list of files to analyse
final List<Path> allFiles = new ArrayList<>();
try (FileSystem fs = FileSystems.newFileSystem(inputFile.toPath(), Main.class.getClassLoader())) {
Expand All @@ -179,6 +178,9 @@ private static void processJavaSourcesJar(File inputFile, APIListing apiListing)
apiListing.setApiViewProperties(properties);
System.out.println(" Found apiview_properties.json file in jar file");
System.out.println(" - Found " + properties.getCrossLanguageDefinitionIds().size() + " cross-language definition IDs");
} catch (InvalidFormatException e) {
System.out.println(" ERROR: Unable to parse apiview_properties.json file in jar file");
e.printStackTrace();
} catch (Exception e) {
// this is fine, we just won't have any APIView properties to read in
System.out.println(" No apiview_properties.json file found in jar file - continuing...");
Expand All @@ -194,6 +196,7 @@ private static void processJavaSourcesJar(File inputFile, APIListing apiListing)
});

// Do the analysis while the filesystem is still represented in memory
final Analyser analyser = new JavaASTAnalyser(apiListing);
analyser.analyse(allFiles);
} catch (Exception e) {
e.printStackTrace();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.azure.tools.apiview.processor.analysers;

import com.azure.tools.apiview.processor.analysers.models.AnnotationRendererModel;
import com.azure.tools.apiview.processor.analysers.models.AnnotationRule;
import com.azure.tools.apiview.processor.analysers.util.MiscUtils;
import com.azure.tools.apiview.processor.analysers.util.TokenModifier;
import com.azure.tools.apiview.processor.diagnostics.Diagnostics;
Expand Down Expand Up @@ -51,19 +53,10 @@
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.regex.Matcher;
Expand All @@ -75,6 +68,7 @@
import org.apache.commons.lang.StringEscapeUtils;

import static com.azure.tools.apiview.processor.analysers.util.ASTUtils.*;
import static com.azure.tools.apiview.processor.analysers.util.MiscUtils.*;
import static com.azure.tools.apiview.processor.analysers.util.TokenModifier.INDENT;
import static com.azure.tools.apiview.processor.analysers.util.TokenModifier.NEWLINE;
import static com.azure.tools.apiview.processor.analysers.util.TokenModifier.NOTHING;
Expand All @@ -101,8 +95,22 @@ public class JavaASTAnalyser implements Analyser {
public static final String MODULE_INFO_KEY = "module-info";

private static final boolean SHOW_JAVADOC = true;
private static final Set<String> BLOCKED_ANNOTATIONS =
new HashSet<>(Arrays.asList("ServiceMethod", "SuppressWarnings"));

private static final Map<String, AnnotationRule> ANNOTATION_RULE_MAP;
static {
/*
For some annotations, we want to customise how they are displayed. Sometimes, we don't show them in any
circumstance. Other times, we want to show them but not their attributes. This map is used to define these
customisations. These rules override the default output that APIView will do, based on the location
annotation in the code.
*/
ANNOTATION_RULE_MAP = new HashMap<>();
ANNOTATION_RULE_MAP.put("ServiceMethod", new AnnotationRule().setHidden(true));
ANNOTATION_RULE_MAP.put("SuppressWarnings", new AnnotationRule().setHidden(true));

// we always want @Metadata annotations to be fully expanded, but in a condensed form
ANNOTATION_RULE_MAP.put("Metadata", new AnnotationRule().setShowProperties(true).setCondensed(true));
}

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

Expand All @@ -112,12 +120,13 @@ public class JavaASTAnalyser implements Analyser {

private final Map<String, JavadocComment> packageNameToPackageInfoJavaDoc = new HashMap<>();

private final Diagnostics diagnostic = new Diagnostics();
private final Diagnostics diagnostic;

private int indent = 0;

public JavaASTAnalyser(APIListing apiListing) {
this.apiListing = apiListing;
diagnostic = new Diagnostics(apiListing);
}

@Override
Expand Down Expand Up @@ -1032,34 +1041,24 @@ private void getAnnotations(final NodeWithAnnotations<?> nodeWithAnnotations,
final boolean showAnnotationProperties,
final boolean addNewline) {
Consumer<AnnotationExpr> consumer = annotation -> {
if (addNewline) {
addToken(makeWhitespace());
}

addToken(new Token(TYPE_NAME, "@" + annotation.getName().toString(), makeId(annotation, nodeWithAnnotations)));
if (showAnnotationProperties) {
if (annotation instanceof NormalAnnotationExpr) {
addToken(new Token(PUNCTUATION, "("));
NodeList<MemberValuePair> pairs = ((NormalAnnotationExpr) annotation).getPairs();
for (int i = 0; i < pairs.size(); i++) {
MemberValuePair pair = pairs.get(i);

addToken(new Token(TEXT, pair.getNameAsString()));
addToken(new Token(PUNCTUATION, " = "));
// Check the annotation rules map for any overrides
final String annotationName = annotation.getName().toString();
AnnotationRule annotationRule = ANNOTATION_RULE_MAP.get(annotationName);

Expression valueExpr = pair.getValue();
processAnnotationValueExpression(valueExpr);
AnnotationRendererModel model = new AnnotationRendererModel(
annotation, nodeWithAnnotations, annotationRule, showAnnotationProperties, addNewline);

if (i < pairs.size() - 1) {
addToken(new Token(PUNCTUATION, ", "));
}
}
if (model.isHidden()) {
return;
}

addToken(new Token(PUNCTUATION, ")"));
}
if (model.isAddNewline()) {
addToken(makeWhitespace());
}

if (addNewline) {
renderAnnotation(model).forEach(JavaASTAnalyser.this::addToken);

if (model.isAddNewline()) {
addNewLine();
} else {
addToken(new Token(WHITESPACE, " "));
Expand All @@ -1070,43 +1069,96 @@ private void getAnnotations(final NodeWithAnnotations<?> nodeWithAnnotations,
.stream()
.filter(annotationExpr -> {
String id = annotationExpr.getName().getIdentifier();
return !BLOCKED_ANNOTATIONS.contains(id) && !id.startsWith("Json");
return !id.startsWith("Json");
})
.sorted(Comparator.comparing(a -> a.getName().getIdentifier())) // we sort the annotations alphabetically
.forEach(consumer);
}

private void processAnnotationValueExpression(Expression valueExpr) {
private List<Token> renderAnnotation(AnnotationRendererModel m) {
final AnnotationExpr a = m.getAnnotation();
List<Token> tokens = new ArrayList<>();
tokens.add(new Token(TYPE_NAME, "@" + a.getNameAsString(), makeId(a, m.getAnnotationParent())));
if (m.isShowProperties()) {
if (a instanceof NormalAnnotationExpr) {
tokens.add(new Token(PUNCTUATION, "("));
NodeList<MemberValuePair> pairs = ((NormalAnnotationExpr) a).getPairs();
for (int i = 0; i < pairs.size(); i++) {
MemberValuePair pair = pairs.get(i);

// If the pair is a boolean expression, and we are condensed, we only take the name.
// If we are not a boolean expression, and we are condensed, we only take the value.
// If we are not condensed, we take both.
final Expression valueExpr = pair.getValue();
if (m.isCondensed()) {
if (valueExpr.isBooleanLiteralExpr()) {
tokens.add(new Token(MEMBER_NAME, upperCase(pair.getNameAsString())));
} else {
processAnnotationValueExpression(valueExpr, m.isCondensed(), tokens);
}
} else {
tokens.add(new Token(MEMBER_NAME, pair.getNameAsString()));
tokens.add(new Token(PUNCTUATION, " = "));

processAnnotationValueExpression(valueExpr, m.isCondensed(), tokens);
}

if (i < pairs.size() - 1) {
tokens.add(new Token(PUNCTUATION, ", "));
}
}

tokens.add(new Token(PUNCTUATION, ")"));
}
}
return tokens;
}

private void processAnnotationValueExpression(final Expression valueExpr, final boolean condensed, final List<Token> tokens) {
if (valueExpr.isClassExpr()) {
// lookup to see if the type is known about, if so, make it a link, otherwise leave it as text
String typeName = valueExpr.getChildNodes().get(0).toString();
if (apiListing.getKnownTypes().containsKey(typeName)) {
final Token token = new Token(TYPE_NAME, typeName);
token.setNavigateToId(apiListing.getKnownTypes().get(typeName));
addToken(token);
tokens.add(token);
return;
}
} else if (valueExpr.isArrayInitializerExpr()) {
addToken(new Token(PUNCTUATION, "{ "));
if (!condensed) {
tokens.add(new Token(PUNCTUATION, "{ "));
}
for (int i = 0; i < valueExpr.getChildNodes().size(); i++) {
Node n = valueExpr.getChildNodes().get(i);

if (n instanceof Expression) {
processAnnotationValueExpression((Expression) n);
processAnnotationValueExpression((Expression) n, condensed, tokens);
} else {
addToken(new Token(TEXT, valueExpr.toString()));
tokens.add(new Token(TEXT, valueExpr.toString()));
}

if (i < valueExpr.getChildNodes().size() - 1) {
addToken(new Token(PUNCTUATION, ", "));
tokens.add(new Token(PUNCTUATION, ", "));
}
}
addToken(new Token(PUNCTUATION, " }"));
if (!condensed) {
tokens.add(new Token(PUNCTUATION, " }"));
}
return;
}

// if we fall through to here, just treat it as a string
addToken(new Token(TEXT, valueExpr.toString()));
// if we fall through to here, just treat it as a string.
// If we are in condensed mode, we strip off everything before the last period
String value = valueExpr.toString();
if (condensed) {
int lastPeriod = value.lastIndexOf('.');
if (lastPeriod != -1) {
value = value.substring(lastPeriod + 1);
}
tokens.add(new Token(TEXT, upperCase(value)));
} else {
tokens.add(new Token(TEXT, value));
}
}

private void getModifiers(NodeList<Modifier> modifiers) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.azure.tools.apiview.processor.analysers.models;

import com.github.javaparser.ast.expr.AnnotationExpr;
import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations;

public class AnnotationRendererModel {
private final AnnotationExpr annotation;

private final NodeWithAnnotations<?> annotationParent;

private final AnnotationRule rule;

private final boolean showProperties;

private final boolean addNewline;

public AnnotationRendererModel(AnnotationExpr annotation,
NodeWithAnnotations<?> nodeWithAnnotations,
AnnotationRule rule,
boolean showAnnotationProperties,
boolean addNewline) {
this.annotation = annotation;
this.annotationParent = nodeWithAnnotations;
this.rule = rule;

// we override the showAnnotationProperties flag if the annotation rule specifies it
this.showProperties = rule == null ? showAnnotationProperties : rule.isShowProperties().orElse(showAnnotationProperties);
this.addNewline = rule == null ? addNewline : rule.isShowOnNewline().orElse(addNewline);
}

public boolean isAddNewline() {
return addNewline;
}

public boolean isShowProperties() {
return showProperties;
}

public AnnotationExpr getAnnotation() {
return annotation;
}

public NodeWithAnnotations<?> getAnnotationParent() {
return annotationParent;
}

public boolean isHidden() {
return rule != null && rule.isHidden().orElse(false);
}

public boolean isCondensed() {
return rule != null && rule.isCondensed().orElse(false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.azure.tools.apiview.processor.analysers.models;

import java.util.Optional;

public class AnnotationRule {

private Optional<Boolean> hideAnnotation = Optional.empty();

private Optional<Boolean> showProperties = Optional.empty();

private Optional<Boolean> showOnNewline = Optional.empty();

private Optional<Boolean> condensed = Optional.empty();

public AnnotationRule setHidden(boolean hidden) {
this.hideAnnotation = Optional.of(hidden);
return this;
}

public Optional<Boolean> isHidden() {
return hideAnnotation;
}

public AnnotationRule setShowProperties(boolean showProperties) {
this.showProperties = Optional.of(showProperties);
return this;
}

public Optional<Boolean> isShowProperties() {
return showProperties;
}

public AnnotationRule setShowOnNewline(boolean showOnNewline) {
this.showOnNewline = Optional.of(showOnNewline);
return this;
}

public Optional<Boolean> isShowOnNewline() {
return showOnNewline;
}

public AnnotationRule setCondensed(boolean condensed) {
this.condensed = Optional.of(condensed);
return this;
}

public Optional<Boolean> isCondensed() {
return condensed;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ public static boolean isPublicOrProtected(AccessSpecifier accessSpecifier) {
* @return Whether the access specifier is package-private or private.
*/
public static boolean isPrivateOrPackagePrivate(AccessSpecifier accessSpecifier) {
return (accessSpecifier == AccessSpecifier.PRIVATE) || (accessSpecifier == AccessSpecifier.PACKAGE_PRIVATE);
return (accessSpecifier == AccessSpecifier.PRIVATE) || (accessSpecifier == AccessSpecifier.NONE);
}

public static String makeId(CompilationUnit cu) {
Expand Down
Loading

0 comments on commit bca1ac5

Please sign in to comment.