blob: 8c226aa8f9449023587505e89a2169e2a17f2f59 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2019 Christian Pontesegger and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Christian Pontesegger - initial API and implementation
*******************************************************************************/
package org.eclipse.ease.helpgenerator.docletapi;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.NoType;
import javax.lang.model.util.ElementScanner9;
import org.eclipse.ease.helpgenerator.model.AbstractClassModel;
import org.eclipse.ease.helpgenerator.model.Description;
import org.eclipse.ease.helpgenerator.model.ExceptionValue;
import org.eclipse.ease.helpgenerator.model.Field;
import org.eclipse.ease.helpgenerator.model.Method;
import org.eclipse.ease.helpgenerator.model.Parameter;
import org.eclipse.ease.helpgenerator.model.ReturnValue;
import org.eclipse.ease.helpgenerator.model.ScriptExample;
import org.eclipse.ease.modules.ScriptParameter;
import org.eclipse.ease.modules.WrapToScript;
import com.sun.source.doctree.DeprecatedTree;
import com.sun.source.doctree.DocCommentTree;
import com.sun.source.doctree.DocTree;
import com.sun.source.doctree.ParamTree;
import com.sun.source.doctree.ReturnTree;
import com.sun.source.doctree.ThrowsTree;
import com.sun.source.doctree.UnknownBlockTagTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.util.SimpleDocTreeVisitor;
import com.sun.source.util.SimpleTreeVisitor;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Type;
import jdk.javadoc.doclet.DocletEnvironment;
public class Jep221ClassModel extends AbstractClassModel {
private final ClassSymbol fClassSymbol;
private final DocletEnvironment fEnvironment;
public Jep221ClassModel(DocletEnvironment environment, ClassSymbol classSymbol) {
fEnvironment = environment;
fClassSymbol = classSymbol;
}
@Override
public void populateModel() {
setClassName(fClassSymbol.className());
setClassDocumentation(new Description(getCommentFor(fClassSymbol)));
final DocCommentTree tree = fEnvironment.getDocTrees().getDocCommentTree(fClassSymbol);
String deprecationText = new DeprecationMessageScanner().visit(tree, null);
if ((deprecationText == null) && (fClassSymbol.getAnnotation(Deprecated.class) != null))
deprecationText = "Module deprecated";
setDeprecationMessage(deprecationText);
final boolean hasAnnotation = hasWrapToScriptAnnotation();
setExportedFields(new FieldScanner().scan(fClassSymbol, hasAnnotation));
setExportedMethods(new MethodScanner().scan(fClassSymbol, hasAnnotation));
setImportedClasses(Collections.emptyList());
}
private String getCommentFor(Element element) {
final DocCommentTree elementComment = fEnvironment.getDocTrees().getDocCommentTree(element);
if (elementComment != null) {
final List<? extends DocTree> body = elementComment.getFullBody();
return body.stream().map(e -> e.toString()).collect(Collectors.joining());
}
return null;
}
private boolean hasWrapToScriptAnnotation() {
return new WrapToScriptScanner().scan(fClassSymbol);
}
private class ImportScanner extends SimpleTreeVisitor<Void, Void> {
@Override
public Void visitImport(ImportTree node, Void p) {
// TODO Auto-generated method stub
return super.visitImport(node, p);
}
}
private abstract class RecursiveScanner<R, P> extends ElementScanner9<R, P> {
private final List<Object> fScannedElementsCache = new ArrayList<>();
@Override
public R scan(Element e, P p) {
super.scan(e, p);
if (needsScanning(e)) {
addToScanCache((ClassSymbol) e);
for (final Type iface : ((ClassSymbol) e).getInterfaces())
scan(iface.asElement(), p);
final Type superclass = ((ClassSymbol) e).getSuperclass();
if (!(superclass instanceof NoType))
scan(superclass.asElement(), p);
}
return getResult();
}
private boolean needsScanning(Element e) {
return (e instanceof ClassSymbol) && (!isContainedInScanCache((ClassSymbol) e));
}
private void addToScanCache(ClassSymbol classSymbol) {
fScannedElementsCache.add(classSymbol.getQualifiedName());
}
private boolean isContainedInScanCache(ClassSymbol classSymbol) {
return fScannedElementsCache.contains(classSymbol.getQualifiedName());
}
protected abstract R getResult();
}
private class WrapToScriptScanner extends RecursiveScanner<Boolean, Void> {
private boolean fHasAnnotation = false;
@Override
public Boolean visitExecutable(ExecutableElement e, Void p) {
final WrapToScript annotation = e.getAnnotation(WrapToScript.class);
fHasAnnotation |= (annotation != null);
return getResult();
}
@Override
public Boolean visitVariable(VariableElement e, Void p) {
final WrapToScript annotation = e.getAnnotation(WrapToScript.class);
fHasAnnotation |= (annotation != null);
return getResult();
}
@Override
protected Boolean getResult() {
return fHasAnnotation;
}
}
private class FieldScanner extends RecursiveScanner<List<Field>, Boolean> {
private final List<Field> fFields = new ArrayList<>();
@Override
public List<Field> visitVariable(VariableElement e, Boolean p) {
if (!p || (e.getAnnotation(WrapToScript.class) != null)) {
if ((e.getModifiers().contains(Modifier.STATIC)) && (e.getModifiers().contains(Modifier.PUBLIC))
&& (e.getModifiers().contains(Modifier.FINAL))) {
final DocCommentTree tree = fEnvironment.getDocTrees().getDocCommentTree(e);
String deprecationText = new DeprecationMessageScanner().visit(tree, getDeprecationMessage());
if ((deprecationText == null) && (e.getAnnotation(Deprecated.class) != null))
deprecationText = "";
fFields.add(new Field(e.getSimpleName().toString(), getCommentFor(e), deprecationText));
}
}
return super.visitVariable(e, p);
}
@Override
protected List<Field> getResult() {
return fFields;
}
}
private class MethodScanner extends RecursiveScanner<List<Method>, Boolean> {
private final List<Method> fMethods = new ArrayList<>();
@Override
public List<Method> visitExecutable(ExecutableElement e, Boolean hasWrapToScriptAnnotation) {
final Method registeredMethod = findRegisteredMethod(e.getSimpleName().toString());
final WrapToScript wrapAnnotation = e.getAnnotation(WrapToScript.class);
if (!hasWrapToScriptAnnotation || (wrapAnnotation != null) || (registeredMethod != null)) {
if (e.getModifiers().contains(Modifier.PUBLIC)) {
final DocCommentTree tree = fEnvironment.getDocTrees().getDocCommentTree(e);
String deprecationText = new DeprecationMessageScanner().visit(tree, getDeprecationMessage());
if ((deprecationText == null) && (e.getAnnotation(Deprecated.class) != null))
deprecationText = "";
final Collection<String> aliases = new HashSet<>();
if (wrapAnnotation != null) {
final String candidates = wrapAnnotation.alias();
for (final String token : candidates.split("[,;]")) {
if (!token.trim().isEmpty())
aliases.add(token.trim());
}
}
final ReturnValue returnValue = new ReturnValue(e.getReturnType().toString(), new ReturnTypeMessageScanner().visit(tree, null));
final List<Parameter> parameters = e.getParameters().stream().map(param -> {
final ScriptParameter scriptParameter = param.getAnnotation(ScriptParameter.class);
final String defaultValue = (scriptParameter != null) ? scriptParameter.defaultValue() : null;
final String comment = new ParameterCommentScanner().visit(tree, param.getSimpleName().toString());
return new Parameter(param.getSimpleName().toString(), param.asType().toString(), comment, defaultValue);
}).collect(Collectors.toList());
final List<ExceptionValue> exceptions = e.getThrownTypes().stream().map(type -> {
return new ExceptionValue(type.toString(), new ExceptionMessageScanner().visit(tree, type.toString()));
}).collect(Collectors.toList());
final List<ScriptExample> examples = new ScriptExampleScanner().visit(tree, null);
addMethod(new Method(e.getSimpleName().toString(), getCommentFor(e), deprecationText, aliases, returnValue, parameters, exceptions,
examples));
}
}
return getResult();
}
private Method findRegisteredMethod(String methodName) {
return fMethods.stream().filter(m -> methodName.equals(m.getName())).findFirst().orElse(null);
}
private void addMethod(Method method) {
final Method existingMethod = findRegisteredMethod(method.getName());
if (existingMethod != null)
existingMethod.fetchDetailsFrom(method);
else
fMethods.add(method);
}
@Override
protected List<Method> getResult() {
return fMethods;
}
}
private class ScriptExampleScanner extends SimpleDocTreeVisitor<List<ScriptExample>, Void> {
private final List<ScriptExample> fScriptExamples = new ArrayList<>();
@Override
public List<ScriptExample> visitDocComment(DocCommentTree node, Void p) {
visit(node.getBlockTags(), p);
return fScriptExamples;
}
@Override
public List<ScriptExample> visitUnknownBlockTag(UnknownBlockTagTree node, Void p) {
final String content = node.getContent().stream().map(e -> e.toString()).collect(Collectors.joining());
fScriptExamples.add(new ScriptExample(content));
return fScriptExamples;
}
}
private class ParameterCommentScanner extends SimpleDocTreeVisitor<String, String> {
private String fComment;
@Override
public String visitDocComment(DocCommentTree node, String p) {
visit(node.getBlockTags(), p);
return fComment;
}
@Override
public String visitParam(ParamTree node, String p) {
final List<? extends DocTree> body = node.getDescription();
if (p.equals(node.getName().toString()))
fComment = body.stream().map(e -> e.toString()).collect(Collectors.joining());
return super.visitParam(node, p);
}
}
private class ExceptionMessageScanner extends SimpleDocTreeVisitor<String, String> {
private String fComment;
@Override
public String visitDocComment(DocCommentTree node, String p) {
visit(node.getBlockTags(), p);
return fComment;
}
@Override
public String visitThrows(ThrowsTree node, String p) {
final List<? extends DocTree> body = node.getDescription();
if (p.endsWith(node.getExceptionName().toString()))
fComment = body.stream().map(e -> e.toString()).collect(Collectors.joining());
return super.visitThrows(node, p);
}
}
private class ReturnTypeMessageScanner extends SimpleDocTreeVisitor<String, Void> {
String fDocumentation;
@Override
public String visitDocComment(DocCommentTree node, Void p) {
visit(node.getBlockTags(), p);
return fDocumentation;
}
@Override
public String visitReturn(ReturnTree node, Void p) {
final List<? extends DocTree> body = node.getDescription();
fDocumentation = body.stream().map(e -> e.toString()).collect(Collectors.joining());
return fDocumentation;
}
}
private class DeprecationMessageScanner extends SimpleDocTreeVisitor<String, String> {
String fDocumentation;
@Override
public String visitDocComment(DocCommentTree node, String p) {
visit(node.getBlockTags(), p);
return (fDocumentation != null) ? fDocumentation : p;
}
@Override
public String visitDeprecated(DeprecatedTree node, String p) {
final List<? extends DocTree> body = node.getBody();
fDocumentation = body.stream().map(e -> e.toString()).collect(Collectors.joining());
return fDocumentation;
}
}
}