All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.twosigma.znai.java.parser.JavaCodeVisitor Maven / Gradle / Ivy
/*
* Copyright 2019 TWO SIGMA OPEN SOURCE, LLC
*
* 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.twosigma.znai.java.parser;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.ast.comments.JavadocComment;
import com.github.javaparser.ast.expr.MarkerAnnotationExpr;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import com.github.javaparser.javadoc.Javadoc;
import com.github.javaparser.javadoc.JavadocBlockTag;
import com.github.javaparser.javadoc.description.JavadocDescription;
import com.github.javaparser.javadoc.description.JavadocDescriptionElement;
import com.github.javaparser.javadoc.description.JavadocInlineTag;
import com.github.javaparser.javadoc.description.JavadocSnippet;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;
import static com.github.javaparser.javadoc.JavadocBlockTag.Type.PARAM;
import static com.github.javaparser.javadoc.JavadocBlockTag.Type.RETURN;
import static com.twosigma.znai.utils.StringUtils.*;
import static java.util.stream.Collectors.*;
public class JavaCodeVisitor extends VoidVisitorAdapter {
private final List lines;
private Map javaTypes;
private List javaMethods;
private List enumEntries;
private Map javaFields;
private String topLevelJavaDoc;
private boolean isAfterInlinedTag;
public JavaCodeVisitor(String code) {
lines = Arrays.asList(code.split("\n"));
javaTypes = new LinkedHashMap<>();
javaMethods = new ArrayList<>();
javaFields = new LinkedHashMap<>();
enumEntries = new ArrayList<>();
}
public boolean hasType(String typeName) {
return javaTypes.containsKey(typeName);
}
public JavaType findTypeDetails(String typeName) {
if (! javaTypes.containsKey(typeName)) {
throw new RuntimeException("no type found: " + typeName);
}
return javaTypes.get(typeName);
}
public JavaMethod findMethodDetails(String methodNameWithOptionalTypes) {
List details = findAllMethodDetails(methodNameWithOptionalTypes);
if (details.isEmpty()) {
throw new RuntimeException("no method found: " + methodNameWithOptionalTypes + "." +
"\nAvailable methods:\n" + renderAllMethods());
}
return details.get(0);
}
public List findAllMethodDetails(String methodNameWithOptionalTypes) {
String nameWithoutSpaces = methodNameWithOptionalTypes.replaceAll("\\s+", "");
return javaMethods.stream()
.filter(
m -> m.getName().equals(methodNameWithOptionalTypes) ||
m.getNameWithTypes().equals(nameWithoutSpaces))
.collect(Collectors.toList());
}
public List getEnumEntries() {
return enumEntries;
}
public JavaField findFieldDetails(String fieldName) {
if (! javaFields.containsKey(fieldName)) {
throw new RuntimeException("no field found: " + fieldName);
}
return javaFields.get(fieldName);
}
public String findJavaDoc(String entryName) {
if (javaFields.containsKey(entryName)) {
return javaFields.get(entryName).getJavaDocText();
}
if (!hasMethodDetails(entryName)) {
throw new RuntimeException("can't find method or field: " + entryName + "." +
"\nAvailable methods:\n" + renderAllMethods() +
"\nAvailable fields:\n" + renderAllFields());
}
return findMethodDetails(entryName).getJavaDocText();
}
public String getTopLevelJavaDoc() {
return topLevelJavaDoc;
}
@Override
public void visit(FieldDeclaration fieldDeclaration, String arg) {
String javaDocText = fieldDeclaration.hasJavaDocComment() ? fieldDeclaration.getJavadocComment().parse().toText() : "";
fieldDeclaration.getVariables().stream().map(vd -> vd.getName().getIdentifier())
.forEach(name -> javaFields.put(name, new JavaField(name, javaDocText)));
}
@Override
public void visit(ClassOrInterfaceDeclaration classOrInterfaceDeclaration, String arg) {
extractTopLevelJavaDoc(classOrInterfaceDeclaration);
registerType(classOrInterfaceDeclaration);
super.visit(classOrInterfaceDeclaration, arg);
}
@Override
public void visit(EnumDeclaration enumDeclaration, String arg) {
extractTopLevelJavaDoc(enumDeclaration);
registerType(enumDeclaration);
List entries = enumDeclaration.getEntries().stream().map(e -> new EnumEntry(e.getName().getIdentifier(),
extractJavaDocDescription(e.getJavadocComment()), extractIsDeprecated(e))).collect(toList());
enumEntries.addAll(entries);
super.visit(enumDeclaration, arg);
}
@Override
public void visit(MethodDeclaration methodDeclaration, String arg) {
String name = methodDeclaration.getName().getIdentifier();
String code = JavaCodeUtils.extractCode(lines, methodDeclaration);
JavadocComment javaDocComment = methodDeclaration.getJavadocComment();
Javadoc javaDoc = javaDocComment != null ? javaDocComment.parse() : null;
String javaDocText = (javaDoc != null) ?
extractJavaDocDescription(javaDoc.getDescription()) :
"";
javaMethods.add(new JavaMethod(name,
stripIndentation(JavaCodeUtils.removeSemicolonAtEnd(code)),
stripIndentation(JavaCodeUtils.removeSemicolonAtEnd(extractInsideCurlyBraces(code))),
JavaCodeUtils.removeSemicolonAtEnd(JavaCodeUtils.extractSignature(code)),
extractParams(methodDeclaration, javaDoc),
extractReturn(methodDeclaration, javaDoc),
javaDocText));
}
private boolean extractIsDeprecated(EnumConstantDeclaration e) {
List annotationNodes = e.getNodesByType(MarkerAnnotationExpr.class);
return annotationNodes.stream().anyMatch(an -> an.getName().getIdentifier().equals("Deprecated"));
}
private String renderAllMethods() {
return " " + javaMethods.stream().map(JavaMethod::getNameWithTypes).collect(joining("\n "));
}
private String renderAllFields() {
return " " + String.join("\n ", javaFields.keySet());
}
private String extractJavaDocDescription(JavadocComment javadocComment) {
if (javadocComment == null) {
return "";
}
Javadoc javadoc = javadocComment.parse();
JavadocDescription description = javadoc.getDescription();
return description == null ? "" : extractJavaDocDescription(description);
}
private String extractJavaDocDescription(JavadocDescription description) {
// TODO check if github java parser lib solved the problem with inlined tags UnsupportedOperation exception
List elements = getPrivateFieldValue(description,"elements");
return elements.stream()
.map(this::elementToText)
.filter(text -> !text.isEmpty())
.collect(joining(" "));
}
private String elementToText(JavadocDescriptionElement el) {
if (el instanceof JavadocSnippet) {
String result = isAfterInlinedTag ? el.toText().substring(1) : el.toText();
isAfterInlinedTag = false;
return result.trim();
}
if (el instanceof JavadocInlineTag) {
return "" + extractTextFromInlinedTag(el).trim() + "
";
}
return el.toText();
}
private String extractTextFromInlinedTag(JavadocDescriptionElement el) {
isAfterInlinedTag = true;
return getPrivateFieldValue(el, "content");
}
@SuppressWarnings("unchecked")
private E getPrivateFieldValue(Object o, String fieldName) {
try {
Field elementsFields = o.getClass().getDeclaredField(fieldName);
elementsFields.setAccessible(true);
return (E) elementsFields.get(o);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
private void extractTopLevelJavaDoc(TypeDeclaration declaration) {
if (topLevelJavaDoc == null) {
JavadocComment javadocComment = declaration.getJavadocComment();
topLevelJavaDoc = (javadocComment != null) ?
extractJavaDocDescription(javadocComment.parse().getDescription()):
"";
}
}
private void registerType(TypeDeclaration declaration) {
String name = declaration.getName().getIdentifier();
String code = JavaCodeUtils.extractCode(lines, declaration);
JavaType javaType = new JavaType(name,
stripIndentation(code),
stripIndentation(extractInsideCurlyBraces(code)));
javaTypes.put(name, javaType);
}
private boolean hasMethodDetails(String methodNameWithOptionalTypes) {
String nameWithoutSpaces = methodNameWithOptionalTypes.replaceAll("\\s+", "");
return javaMethods.stream().anyMatch(m -> m.getName().equals(methodNameWithOptionalTypes) ||
m.getNameWithTypes().equals(nameWithoutSpaces));
}
private List extractParams(MethodDeclaration methodDeclaration, Javadoc javadoc) {
Map typeByName = methodDeclaration.getParameters().stream()
.collect(toMap(p -> p.getName().getIdentifier(), p -> eraseGenericType(p.getType().getElementType().toString())));
List paramNames = methodDeclaration.getParameters().stream().map(p -> p.getName().getIdentifier()).collect(toList());
Map javaDocTextByName = javadoc != null ?
(javadoc.getBlockTags().stream().filter(b -> b.getType() == PARAM)
.collect(toMap(
b -> b.getName().orElse(""),
b -> extractJavaDocDescription(b.getContent())))) : Collections.emptyMap();
return paramNames.stream().map(n -> new JavaMethodParam(n, javaDocTextByName.get(n), typeByName.get(n)))
.collect(toList());
}
private JavaMethodReturn extractReturn(MethodDeclaration methodDeclaration, Javadoc javadoc) {
if (javadoc == null) {
return null;
}
Optional returnBlock = javadoc.getBlockTags().stream().filter(b -> b.getType() == RETURN).findFirst();
if (! returnBlock.isPresent()) {
return null;
}
return new JavaMethodReturn(methodDeclaration.getType().toString(),
returnBlock.map(b -> extractJavaDocDescription(b.getContent())).orElse(""));
}
private static String eraseGenericType(String type) {
return removeContentInsideBracketsInclusive(type);
}
}