Support javadoc copying in eclipse

This commit is contained in:
Rawi01 committed 2020-07-29 13:39:42 +02:00
1 parent 7dfbe4323c
commit 2b4b5c9835
20 files changed
+585 -65

No files matched your search

@@ -27,6 +27,7 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.AllArgsConstructor;
@@ -756,4 +757,64 @@ public class HandlerUtil {
if (PRIMITIVE_WRAPPER_TYPE_NAME_PATTERN.matcher(typeName).matches()) return 800;
return 0;
}
private static final Pattern SECTION_FINDER = Pattern.compile("^\\s*\\**\\s*[-*][-*]+\\s*([GS]ETTER|WITH(?:ER)?)\\s*[-*][-*]+\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
public static String stripLinesWithTagFromJavadoc(String javadoc, String regexpFragment) {
Pattern p = Pattern.compile("^\\s*\\**\\s*" + regexpFragment + "\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(javadoc);
return m.replaceAll("");
}
public static String stripSectionsFromJavadoc(String javadoc) {
Matcher m = SECTION_FINDER.matcher(javadoc);
if (!m.find()) return javadoc;
return javadoc.substring(0, m.start());
}
public static String getJavadocSection(String javadoc, String sectionNameSpec) {
String[] sectionNames = sectionNameSpec.split("\\|");
Matcher m = SECTION_FINDER.matcher(javadoc);
int sectionStart = -1;
int sectionEnd = -1;
while (m.find()) {
boolean found = false;
for (String sectionName : sectionNames) if (m.group(1).equalsIgnoreCase(sectionName)) {
found = true;
break;
}
if (found) {
sectionStart = m.end() + 1;
} else if (sectionStart != -1) {
sectionEnd = m.start();
}
}
if (sectionStart != -1) {
if (sectionEnd != -1) return javadoc.substring(sectionStart, sectionEnd);
return javadoc.substring(sectionStart);
}
return null;
}
private static final Pattern FIND_RETURN = Pattern.compile("^\\s*\\**\\s*@returns?\\s+.*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
public static String addReturnsThisIfNeeded(String in) {
if (FIND_RETURN.matcher(in).find()) return in;
return addJavadocLine(in, "@return {@code this}.");
}
public static String addReturnsUpdatedSelfIfNeeded(String in) {
if (FIND_RETURN.matcher(in).find()) return in;
return addJavadocLine(in, "@return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed).");
}
public static String addJavadocLine(String in, String line) {
if (in.endsWith("\n")) return in + line + "\n";
return in + "\n" + line;
}
}
@@ -21,9 +21,12 @@
*/
package lombok.eclipse;
import java.util.Map;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.core.CompilationUnit;
import lombok.core.FieldAugment;
@@ -36,4 +39,5 @@ public final class EclipseAugments {
public static final FieldAugment<ASTNode, Boolean> ASTNode_handled = FieldAugment.augment(ASTNode.class, boolean.class, "lombok$handled");
public static final FieldAugment<ASTNode, ASTNode> ASTNode_generatedBy = FieldAugment.augment(ASTNode.class, ASTNode.class, "$generatedBy");
public static final FieldAugment<Annotation, Boolean> Annotation_applied = FieldAugment.augment(Annotation.class, boolean.class, "lombok$applied");
public static final FieldAugment<CompilationUnit, Map<String, String>> CompilationUnit_javadoc = FieldAugment.augment(CompilationUnit.class, Map.class, "$javadoc");
}
@@ -39,6 +39,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration;
@@ -94,6 +96,7 @@ import org.eclipse.jdt.internal.compiler.ast.TypeParameter;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.ast.Wildcard;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.CaptureBinding;
import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding;
@@ -103,6 +106,7 @@ import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.eclipse.jdt.internal.compiler.lookup.WildcardBinding;
import org.eclipse.jdt.internal.core.CompilationUnit;
import lombok.AccessLevel;
import lombok.ConfigurationKeys;
@@ -2635,4 +2639,150 @@ public class EclipseHandlerUtil {
setGeneratedBy(ref, source);
return ref;
}
public static String getDocComment(CompilationUnitDeclaration cud, ASTNode node) {
ICompilationUnit compilationUnit = cud.compilationResult.compilationUnit;
if (node instanceof FieldDeclaration) {
FieldDeclaration fieldDeclaration = (FieldDeclaration) node;
char[] rawContent = CharOperation.subarray(compilationUnit.getContents(), fieldDeclaration.declarationSourceStart, fieldDeclaration.declarationSourceEnd);
String rawContentString = new String(rawContent);
int startIndex = rawContentString.indexOf("/**");
int endIndex = rawContentString.indexOf("*/");
if (startIndex != -1 && endIndex != -1) {
/* Remove all leading asterisks */
return rawContentString.substring(startIndex + 3, endIndex).replaceAll("(?m)^\\s*\\* ?", "").trim();
}
}
return null;
}
public static void setDocComment(CompilationUnitDeclaration cud, EclipseNode eclipseNode, String doc) {
setDocComment(cud, (TypeDeclaration) upToTypeNode(eclipseNode).get(), eclipseNode.get(), doc);
}
public static void setDocComment(CompilationUnitDeclaration cud, TypeDeclaration type, ASTNode node, String doc) {
if (cud.compilationResult.compilationUnit instanceof CompilationUnit) {
CompilationUnit compilationUnit = (CompilationUnit) cud.compilationResult.compilationUnit;
Map<String, String> docs = CompilationUnit_javadoc.setIfAbsent(compilationUnit, new HashMap<String, String>());
if (node instanceof AbstractMethodDeclaration) {
AbstractMethodDeclaration methodDeclaration = (AbstractMethodDeclaration) node;
String signature = getSignature(type, methodDeclaration);
/* Add javadoc start marker, add leading asterisks to each line, add javadoc end marker */
docs.put(signature, String.format("/**%n%s%n */", doc.replaceAll("(?m)^", " * ")));
}
}
}
public static String getSignature(TypeDeclaration type, AbstractMethodDeclaration methodDeclaration) {
StringBuilder sb = new StringBuilder();
sb.append(type.name);
sb.append(".");
sb.append(methodDeclaration.selector);
sb.append("(");
Argument[] arguments = methodDeclaration.arguments;
if (arguments != null) {
for (Argument argument : arguments) {
String signature = Signature.createTypeSignature(argument.type.getLastToken(), false);
sb.append(signature);
}
}
sb.append(")");
return sb.toString();
}
public static enum CopyJavadoc {
VERBATIM {
@Override public String apply(final CompilationUnitDeclaration cu, final EclipseNode node) {
return getDocComment(cu, node.get());
}
},
GETTER {
@Override public String apply(final CompilationUnitDeclaration cu, final EclipseNode node) {
final ASTNode n = node.get();
String javadoc = getDocComment(cu, n);
// step 1: Check if there is a 'GETTER' section. If yes, that becomes the new method's javadoc.
String out = getJavadocSection(javadoc, "GETTER");
final boolean sectionBased = out != null;
if (!sectionBased) {
out = stripLinesWithTagFromJavadoc(stripSectionsFromJavadoc(javadoc), "@param(?:eter)?\\s+.*");
}
return out;
}
},
SETTER {
@Override public String apply(final CompilationUnitDeclaration cu, final EclipseNode node) {
return applySetter(cu, node, "SETTER");
}
},
WITH {
@Override public String apply(final CompilationUnitDeclaration cu, final EclipseNode node) {
return addReturnsUpdatedSelfIfNeeded(applySetter(cu, node, "WITH|WITHER"));
}
},
WITH_BY {
@Override public String apply(final CompilationUnitDeclaration cu, final EclipseNode node) {
return applySetter(cu, node, "WITHBY|WITH_BY");
}
};
public abstract String apply(final CompilationUnitDeclaration cu, final EclipseNode node);
private static String applySetter(final CompilationUnitDeclaration cu, EclipseNode node, String sectionName) {
final ASTNode n = node.get();
String javadoc = getDocComment(cu, n);
// step 1: Check if there is a 'SETTER' section. If yes, that becomes the new method's javadoc.
String out = getJavadocSection(javadoc, sectionName);
final boolean sectionBased = out != null;
if (!sectionBased) {
out = stripLinesWithTagFromJavadoc(stripSectionsFromJavadoc(javadoc), "@returns?\\s+.*");
}
return shouldReturnThis(node) ? addReturnsThisIfNeeded(out) : out;
}
}
/**
* Copies javadoc on one node to the other.
*
* This one is a shortcut for {@link EclipseHandlerUtil#copyJavadoc(EclipseNode, ASTNode, TypeDeclaration, CopyJavadoc, boolean)}
* if source and target node are in the same type.
*/
public static void copyJavadoc(EclipseNode from, ASTNode to, CopyJavadoc copyMode) {
copyJavadoc(from, to, (TypeDeclaration) upToTypeNode(from).get(), copyMode, false);
}
/**
* Copies javadoc on one node to the other.
*
* This one is a shortcut for {@link EclipseHandlerUtil#copyJavadoc(EclipseNode, ASTNode, TypeDeclaration, CopyJavadoc, boolean)}
* if source and target node are in the same type.
*/
public static void copyJavadoc(EclipseNode from, ASTNode to, CopyJavadoc copyMode, boolean forceAddReturn) {
copyJavadoc(from, to, (TypeDeclaration) upToTypeNode(from).get(), copyMode, forceAddReturn);
}
public static void copyJavadoc(EclipseNode from, ASTNode to, TypeDeclaration type, CopyJavadoc copyMode) {
copyJavadoc(from, to, type, copyMode, false);
}
/**
* Copies javadoc on one node to the other.
*
* in 'GETTER' copyMode, first a 'GETTER' segment is searched for. If it exists, that will become the javadoc for the 'to' node, and this section is
* stripped out of the 'from' node. If no 'GETTER' segment is found, then the entire javadoc is taken minus any {@code @param} lines and other sections.
* any {@code @return} lines are stripped from 'from'.
*
* in 'SETTER' mode, stripping works similarly to 'GETTER' mode, except {@code param} are copied and stripped from the original and {@code @return} are skipped.
*/
public static void copyJavadoc(EclipseNode from, ASTNode to, TypeDeclaration type, CopyJavadoc copyMode, boolean forceAddReturn) {
if (copyMode == null) copyMode = CopyJavadoc.VERBATIM;
try {
CompilationUnitDeclaration cud = ((CompilationUnitDeclaration) from.top().get());
String newJavadoc = copyMode.apply(cud, from);
if (newJavadoc != null) {
if (forceAddReturn) newJavadoc = addReturnsThisIfNeeded(newJavadoc);
setDocComment(cud, type, to, newJavadoc);
}
} catch (Exception ignore) {}
}
}
@@ -21,8 +21,8 @@
*/
package lombok.eclipse.handlers;
import static lombok.eclipse.Eclipse.*;
import static lombok.core.handlers.HandlerUtil.*;
import static lombok.eclipse.Eclipse.*;
import static lombok.eclipse.handlers.EclipseHandlerUtil.*;
import java.lang.reflect.Modifier;
@@ -30,6 +30,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
@@ -84,14 +86,16 @@ import lombok.ConfigurationKeys;
import lombok.Singular;
import lombok.ToString;
import lombok.core.AST.Kind;
import lombok.core.handlers.HandlerUtil;
import lombok.core.handlers.InclusionExclusionUtils.Included;
import lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.core.configuration.CheckerFrameworkVersion;
import lombok.core.handlers.HandlerUtil;
import lombok.core.handlers.HandlerUtil.FieldAccess;
import lombok.core.handlers.InclusionExclusionUtils.Included;
import lombok.eclipse.Eclipse;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
import lombok.eclipse.handlers.EclipseHandlerUtil.CopyJavadoc;
import lombok.eclipse.handlers.EclipseHandlerUtil.MemberExistsResult;
import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer;
import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData;
@@ -965,9 +969,29 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
typeReference.annotations[0] = new Annotation[] {ann};
setter.receiver = new Receiver(new char[] { 't', 'h', 'i', 's' }, 0, typeReference, null, Modifier.FINAL);
}
if (sourceNode.up().getKind() == Kind.METHOD) {
copyJavadocFromParam(originalFieldNode.up(), setter, td, paramName.toString());
} else {
copyJavadoc(originalFieldNode, setter, td, CopyJavadoc.SETTER, true);
}
injectMethod(builderType, setter);
}
private void copyJavadocFromParam(EclipseNode from, MethodDeclaration to, TypeDeclaration type, String param) {
try {
CompilationUnitDeclaration cud = (CompilationUnitDeclaration) from.top().get();
String methodComment = getDocComment(cud, from.get());
if (methodComment == null) return;
Pattern pattern = Pattern.compile("@param " + param + " (\\S|\\s)+?(?=^ ?@)", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(methodComment);
if (matcher.find()) {
String newJavadoc = addReturnsThisIfNeeded(matcher.group());
setDocComment(cud, type, to, newJavadoc);
}
} catch (Exception ignore) {}
}
public EclipseNode makeBuilderClass(boolean isStatic, EclipseNode tdParent, String builderClassName, TypeParameter[] typeParams, ASTNode source, AccessLevel access) {
TypeDeclaration parent = (TypeDeclaration) tdParent.get();
TypeDeclaration builder = new TypeDeclaration(parent.compilationResult);
@@ -296,6 +296,7 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> {
}
method.traverse(new SetGeneratedByVisitor(source), parent.scope);
copyJavadoc(fieldNode, method, CopyJavadoc.GETTER);
return method;
}
@@ -263,6 +263,7 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> {
if (returnType != null && returnStatement != null) createRelevantNonNullAnnotation(sourceNode, method);
method.traverse(new SetGeneratedByVisitor(source), parent.scope);
copyJavadoc(fieldNode, method, CopyJavadoc.SETTER, returnStatement != null);
return method;
}
}
@@ -290,6 +290,7 @@ public class HandleWith extends EclipseAnnotationHandler<With> {
EclipseHandlerUtil.createRelevantNonNullAnnotation(fieldNode, method);
method.traverse(new SetGeneratedByVisitor(source), parent.scope);
copyJavadoc(fieldNode, method, CopyJavadoc.WITH);
return method;
}
}
@@ -373,6 +373,7 @@ public class HandleWithBy extends EclipseAnnotationHandler<WithBy> {
createRelevantNonNullAnnotation(fieldNode, method);
method.traverse(new SetGeneratedByVisitor(source), parent.scope);
copyJavadoc(fieldNode, method, CopyJavadoc.WITH_BY);
return method;
}
}
@@ -35,7 +35,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.lang.model.element.Element;
@@ -1963,47 +1962,6 @@ public class JavacHandlerUtil {
return (JCExpression) in;
}
private static final Pattern SECTION_FINDER = Pattern.compile("^\\s*\\**\\s*[-*][-*]+\\s*([GS]ETTER|WITH(?:ER)?)\\s*[-*][-*]+\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
public static String stripLinesWithTagFromJavadoc(String javadoc, String regexpFragment) {
Pattern p = Pattern.compile("^\\s*\\**\\s*" + regexpFragment + "\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(javadoc);
return m.replaceAll("");
}
public static String stripSectionsFromJavadoc(String javadoc) {
Matcher m = SECTION_FINDER.matcher(javadoc);
if (!m.find()) return javadoc;
return javadoc.substring(0, m.start());
}
public static String getJavadocSection(String javadoc, String sectionNameSpec) {
String[] sectionNames = sectionNameSpec.split("\\|");
Matcher m = SECTION_FINDER.matcher(javadoc);
int sectionStart = -1;
int sectionEnd = -1;
while (m.find()) {
boolean found = false;
for (String sectionName : sectionNames) if (m.group(1).equalsIgnoreCase(sectionName)) {
found = true;
break;
}
if (found) {
sectionStart = m.end() + 1;
} else if (sectionStart != -1) {
sectionEnd = m.start();
}
}
if (sectionStart != -1) {
if (sectionEnd != -1) return javadoc.substring(sectionStart, sectionEnd);
return javadoc.substring(sectionStart);
}
return null;
}
public static enum CopyJavadoc {
VERBATIM {
@Override public String apply(final JCCompilationUnit cu, final JavacNode node) {
@@ -2100,24 +2058,6 @@ public class JavacHandlerUtil {
} catch (Exception ignore) {}
}
private static final Pattern FIND_RETURN = Pattern.compile("^\\s*\\**\\s*@returns?\\s+.*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
static String addReturnsThisIfNeeded(String in) {
if (FIND_RETURN.matcher(in).find()) return in;
return addJavadocLine(in, "@return {@code this}.");
}
static String addReturnsUpdatedSelfIfNeeded(String in) {
if (FIND_RETURN.matcher(in).find()) return in;
return addJavadocLine(in, "@return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed).");
}
static String addJavadocLine(String in, String line) {
if (in.endsWith("\n")) return in + line + "\n";
return in + "\n" + line;
}
public static boolean isDirectDescendantOfObject(JavacNode typeNode) {
if (!(typeNode.get() instanceof JCClassDecl)) throw new IllegalArgumentException("not a type node");
JCTree extending = Javac.getExtendsClause((JCClassDecl) typeNode.get());
@@ -126,6 +126,7 @@ public class EclipsePatcher implements AgentLauncher.AgentLaunchable {
patchExtensionMethod(sm, ecjOnly);
patchRenameField(sm);
patchNullCheck(sm);
patchJavadoc(sm);
if (reloadExistingClasses) sm.reloadClasses(instrumentation);
}
@@ -830,5 +831,37 @@ public class EclipsePatcher implements AgentLauncher.AgentLaunchable {
.request(StackRequest.PARAM1)
.transplant().build());
}
private static void patchJavadoc(ScriptManager sm) {
sm.addScript(ScriptBuilder.wrapMethodCall()
.target(new MethodTarget("org.eclipse.jdt.internal.ui.text.javadoc.JavadocContentAccess2", "getHTMLContent", "java.lang.String", "org.eclipse.jdt.core.IJavaElement", "boolean"))
.methodToWrap(new Hook("org.eclipse.jdt.internal.ui.text.javadoc.JavadocContentAccess2", "getHTMLContentFromSource", "java.lang.String", "org.eclipse.jdt.core.IJavaElement"))
.wrapMethod(new Hook("lombok.launch.PatchFixesHider$Javadoc", "getHTMLContentFromSource", "java.lang.String", "java.lang.String", "org.eclipse.jdt.core.IJavaElement"))
.requestExtra(StackRequest.PARAM1)
.build());
/* This is an older version that uses IMember instead of IJavaElement */
sm.addScript(ScriptBuilder.wrapMethodCall()
.target(new MethodTarget("org.eclipse.jdt.internal.ui.text.javadoc.JavadocContentAccess2", "getHTMLContent", "java.lang.String", "org.eclipse.jdt.core.IMember", "boolean"))
.methodToWrap(new Hook("org.eclipse.jdt.internal.ui.text.javadoc.JavadocContentAccess2", "getHTMLContentFromSource", "java.lang.String", "org.eclipse.jdt.core.IMember"))
.wrapMethod(new Hook("lombok.launch.PatchFixesHider$Javadoc", "getHTMLContentFromSource", "java.lang.String", "java.lang.String", "org.eclipse.jdt.core.IJavaElement"))
.requestExtra(StackRequest.PARAM1)
.build());
sm.addScript(ScriptBuilder.replaceMethodCall()
.target(new MethodTarget("org.eclipse.jdt.internal.compiler.ast.TypeDeclaration", "printBody", "java.lang.StringBuffer", "int", "java.lang.StringBuffer"))
.methodToReplace(new Hook("org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration", "print", "java.lang.StringBuffer", "int", "java.lang.StringBuffer"))
.replacementMethod(new Hook("lombok.launch.PatchFixesHider$Javadoc", "printMethod", "java.lang.StringBuffer", "org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration", "int", "java.lang.StringBuffer", "org.eclipse.jdt.internal.compiler.ast.TypeDeclaration"))
.requestExtra(StackRequest.THIS)
.build());
sm.addScript(ScriptBuilder.addField()
.fieldName("$javadoc")
.fieldType("Ljava/util/Map;")
.setPublic()
.setTransient()
.targetClass("org.eclipse.jdt.internal.core.CompilationUnit")
.build());
}
}
@@ -0,0 +1,138 @@
/*
* Copyright (C) 2020 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package lombok.eclipse.agent;
import static lombok.eclipse.EclipseAugments.CompilationUnit_javadoc;
import java.lang.reflect.Method;
import java.util.Map;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.core.CompilationUnit;
import org.eclipse.jdt.internal.core.SourceMethod;
import org.eclipse.jdt.internal.ui.text.javadoc.JavadocContentAccess2;
import lombok.eclipse.EclipseAugments;
import lombok.eclipse.handlers.EclipseHandlerUtil;
import lombok.permit.Permit;
public class PatchJavadoc {
public static String getHTMLContentFromSource(String original, IJavaElement member) {
if (original != null) {
return original;
}
if (member instanceof SourceMethod) {
SourceMethod sourceMethod = (SourceMethod) member;
ICompilationUnit iCompilationUnit = sourceMethod.getCompilationUnit();
if (iCompilationUnit instanceof CompilationUnit) {
CompilationUnit compilationUnit = (CompilationUnit) iCompilationUnit;
Map<String, String> docs = EclipseAugments.CompilationUnit_javadoc.get(compilationUnit);
String signature = getSignature(sourceMethod);
String rawJavadoc = docs.get(signature);
if (rawJavadoc == null) return null;
return Reflection.javadoc2HTML((IMember) member, member, rawJavadoc);
}
}
return null;
}
public static StringBuffer printMethod(AbstractMethodDeclaration methodDeclaration, Integer tab, StringBuffer output, TypeDeclaration type) {
if (methodDeclaration.compilationResult.compilationUnit instanceof CompilationUnit) {
Map<String, String> docs = CompilationUnit_javadoc.get((CompilationUnit) methodDeclaration.compilationResult.compilationUnit);
if (docs != null) {
String signature = EclipseHandlerUtil.getSignature(type, methodDeclaration);
String rawJavadoc = docs.get(signature);
if (rawJavadoc != null) {
for (String line : rawJavadoc.split("\r?\n")) {
ASTNode.printIndent(tab, output).append(line).append("\n");
}
}
}
}
return methodDeclaration.print(tab, output);
}
private static String getSignature(SourceMethod sourceMethod) {
StringBuilder sb = new StringBuilder();
sb.append(sourceMethod.getParent().getElementName());
sb.append(".");
sb.append(sourceMethod.getElementName());
sb.append("(");
for (String type : sourceMethod.getParameterTypes()) {
sb.append(type);
}
sb.append(")");
return sb.toString();
}
/**
* The method <code>javadoc2HTML</code> changed 2014-12 to accept an
* additional IJavaElement parameter. To support older versions, try to
* find that one too.
*/
private static class Reflection {
private static final Method javadoc2HTML;
private static final Method oldJavadoc2HTML;
static {
Method a = null, b = null;
try {
a = Permit.getMethod(JavadocContentAccess2.class, "javadoc2HTML", IMember.class, IJavaElement.class, String.class);
} catch (Throwable t) {}
try {
b = Permit.getMethod(JavadocContentAccess2.class, "javadoc2HTML", IMember.class, String.class);
} catch (Throwable t) {}
javadoc2HTML = a;
oldJavadoc2HTML = b;
}
private static String javadoc2HTML(IMember member, IJavaElement element, String rawJavadoc) {
if (javadoc2HTML != null) {
try {
return (String) javadoc2HTML.invoke(null, member, element, rawJavadoc);
} catch (Throwable t) {
return null;
}
}
if (oldJavadoc2HTML != null) {
try {
return (String) oldJavadoc2HTML.invoke(null, member, rawJavadoc);
} catch (Throwable t) {
return null;
}
}
return null;
}
}
}
@@ -35,6 +35,7 @@ import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IAnnotatable;
import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
@@ -50,6 +51,7 @@ import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ForeachStatement;
import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
import org.eclipse.jdt.internal.compiler.ast.MessageSend;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
@@ -297,6 +299,26 @@ final class PatchFixesHider {
}
}
/** Contains patch code to support Javadoc for generated methods */
public static final class Javadoc {
private static final Method GET_HTML;
private static final Method PRINT_METHOD;
static {
Class<?> shadowed = Util.shadowLoadClass("lombok.eclipse.agent.PatchJavadoc");
GET_HTML = Util.findMethod(shadowed, "getHTMLContentFromSource", String.class, IJavaElement.class);
PRINT_METHOD = Util.findMethod(shadowed, "printMethod", AbstractMethodDeclaration.class, Integer.class, StringBuffer.class, TypeDeclaration.class);
}
public static String getHTMLContentFromSource(String original, IJavaElement member) {
return (String) Util.invokeMethod(GET_HTML, original, member);
}
public static StringBuffer printMethod(AbstractMethodDeclaration methodDeclaration, int tab, StringBuffer output, TypeDeclaration type) {
return (StringBuffer) Util.invokeMethod(PRINT_METHOD, methodDeclaration, tab, output, type);
}
}
/**
* Contains a mix of methods: ecj only, ecj+eclipse, and eclipse only. As a consequence, _EVERY_ method from here used for ecj MUST be
* transplanted, as ecj itself cannot load this class (signatures refer to things that don't exist in ecj-only mode).
+41 -2
View File
@@ -36,16 +36,17 @@ import lombok.eclipse.Eclipse;
import lombok.javac.CapturingDiagnosticListener.CompilerMessage;
import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.Compiler;
import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.batch.CompilationUnit;
import org.eclipse.jdt.internal.compiler.batch.FileSystem;
import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
import org.eclipse.jdt.internal.core.CompilationUnit;
public class RunTestsViaEcj extends AbstractRunTests {
protected CompilerOptions ecjCompilerOptions() {
@@ -103,7 +104,7 @@ public class RunTestsViaEcj extends AbstractRunTests {
};
String source = readFile(file);
final CompilationUnit sourceUnit = new CompilationUnit(source.toCharArray(), file.getName(), encoding == null ? "UTF-8" : encoding);
final CompilationUnit sourceUnit = new TestCompilationUnit(file.getName(), source);
Compiler ecjCompiler = new Compiler(createFileSystem(file, minVersion), ecjErrorHandlingPolicy(), ecjCompilerOptions(), bitbucketRequestor, new DefaultProblemFactory(Locale.ENGLISH)) {
@Override protected synchronized void addCompilationUnit(ICompilationUnit inUnit, CompilationUnitDeclaration parsedUnit) {
@@ -150,4 +151,42 @@ public class RunTestsViaEcj extends AbstractRunTests {
}
return new FileSystem(classpath.toArray(new String[0]), new String[] {file.getAbsolutePath()}, "UTF-8");
}
private static final class TestCompilationUnit extends CompilationUnit {
private final char[] source;
private final char[] mainTypeName;
private TestCompilationUnit(String name, String source) {
super(null, name, null);
this.source = source.toCharArray();
char[] fileNameCharArray = getFileName();
int start = CharOperation.lastIndexOf(File.separatorChar, fileNameCharArray) + 1;
int end = CharOperation.lastIndexOf('.', fileNameCharArray);
if (end == -1) {
end = fileNameCharArray.length;
}
mainTypeName = CharOperation.subarray(fileNameCharArray, start, end);
}
@Override public char[] getContents() {
return source;
}
@Override public char[] getMainTypeName() {
return mainTypeName;
}
@Override public boolean ignoreOptionalProblems() {
return false;
}
@Override public char[][] getPackageName() {
return null;
}
@Override public char[] getModuleName() {
return null;
}
}
}
@@ -16,10 +16,21 @@ import java.util.List;
@java.lang.SuppressWarnings("all") BuilderJavadocBuilder() {
super();
}
/**
* basic gets only a builder setter.
* @see #getsetwith
* @param tag is moved to the setter.
* @return {@code this}.
*/
public @java.lang.SuppressWarnings("all") BuilderJavadoc.BuilderJavadocBuilder<T> basic(final int basic) {
this.basic = basic;
return this;
}
/**
* getsetwith gets a builder setter, an instance getter and setter, and a wither.
* @param tag is moved to the setters and wither.
* @return {@code this}.
*/
public @java.lang.SuppressWarnings("all") BuilderJavadoc.BuilderJavadocBuilder<T> getsetwith(final int getsetwith) {
this.getsetwith = getsetwith;
return this;
@@ -45,12 +56,26 @@ import java.util.List;
public static @java.lang.SuppressWarnings("all") <T>BuilderJavadoc.BuilderJavadocBuilder<T> builder() {
return new BuilderJavadoc.BuilderJavadocBuilder<T>();
}
/**
* getsetwith gets a builder setter, an instance getter and setter, and a wither.
*
* @return tag is moved to the getter.
*/
public @java.lang.SuppressWarnings("all") int getGetsetwith() {
return this.getsetwith;
}
/**
* getsetwith gets a builder setter, an instance getter and setter, and a wither.
* @param tag is moved to the setters and wither.
*/
public @java.lang.SuppressWarnings("all") void setGetsetwith(final int getsetwith) {
this.getsetwith = getsetwith;
}
/**
* getsetwith gets a builder setter, an instance getter and setter, and a wither.
* @param tag is moved to the setters and wither.
* @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed).
*/
public @java.lang.SuppressWarnings("all") BuilderJavadoc<T> withGetsetwith(final int getsetwith) {
return ((this.getsetwith == getsetwith) ? this : new BuilderJavadoc<T>(this.basic, getsetwith, this.predef, this.predefWithJavadoc));
}
@@ -10,6 +10,10 @@ public @Builder class BuilderWithDeprecated {
@java.lang.SuppressWarnings("all") BuilderWithDeprecatedBuilder() {
super();
}
/**
* @deprecated since always
* @return {@code this}.
*/
public @java.lang.Deprecated @java.lang.SuppressWarnings("all") BuilderWithDeprecated.BuilderWithDeprecatedBuilder dep1(final String dep1) {
this.dep1 = dep1;
return this;
@@ -8,6 +8,9 @@ class GetterDeprecated {
public @java.lang.Deprecated @java.lang.SuppressWarnings("all") int getAnnotation() {
return this.annotation;
}
/**
* @deprecated
*/
public @java.lang.Deprecated @java.lang.SuppressWarnings("all") int getJavadoc() {
return this.javadoc;
}
@@ -1,8 +1,18 @@
@lombok.Data class GetterSetterJavadoc1 {
private int fieldName;
/**
* Getter section
*
* @return Sky is blue1
*/
public @java.lang.SuppressWarnings("all") int getFieldName() {
return this.fieldName;
}
/**
* Some text
*
* @param fieldName Hello, World1
*/
public @java.lang.SuppressWarnings("all") void setFieldName(final int fieldName) {
this.fieldName = fieldName;
}
@@ -39,9 +49,19 @@ class GetterSetterJavadoc2 {
GetterSetterJavadoc2() {
super();
}
/**
* Some text
*
* @return Sky is blue2
*/
public @java.lang.SuppressWarnings("all") int getFieldName() {
return this.fieldName;
}
/**
* Some text
*
* @param fieldName Hello, World2
*/
public @java.lang.SuppressWarnings("all") void setFieldName(final int fieldName) {
this.fieldName = fieldName;
}
@@ -51,9 +71,17 @@ class GetterSetterJavadoc3 {
GetterSetterJavadoc3() {
super();
}
/**
* Getter section
* @return Sky is blue3
*/
public @java.lang.SuppressWarnings("all") int getFieldName() {
return this.fieldName;
}
/**
* Setter section
* @param fieldName Hello, World3
*/
public @java.lang.SuppressWarnings("all") void setFieldName(final int fieldName) {
this.fieldName = fieldName;
}
@@ -63,9 +91,20 @@ class GetterSetterJavadoc3 {
GetterSetterJavadoc4() {
super();
}
/**
* Some text
*
* @return Sky is blue4
*/
public @java.lang.SuppressWarnings("all") int fieldName() {
return this.fieldName;
}
/**
* Some text
*
* @param fieldName Hello, World4
* @return {@code this}.
*/
public @java.lang.SuppressWarnings("all") GetterSetterJavadoc4 fieldName(final int fieldName) {
this.fieldName = fieldName;
return this;
@@ -76,9 +115,18 @@ class GetterSetterJavadoc3 {
GetterSetterJavadoc5() {
super();
}
/**
* Getter section
* @return Sky is blue5
*/
public @java.lang.SuppressWarnings("all") int fieldName() {
return this.fieldName;
}
/**
* Setter section
* @param fieldName Hello, World5
* @return Sky is blue5
*/
public @java.lang.SuppressWarnings("all") GetterSetterJavadoc5 fieldName(final int fieldName) {
this.fieldName = fieldName;
return this;
@@ -7,15 +7,33 @@ class SetterAndWithMethodJavadoc {
this.i = i;
this.j = j;
}
/**
* Some value.
* @param the new value
*/
public @java.lang.SuppressWarnings("all") void setI(final int i) {
this.i = i;
}
/**
* Some value.
* @param the new value
* @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed).
*/
public @java.lang.SuppressWarnings("all") SetterAndWithMethodJavadoc withI(final int i) {
return ((this.i == i) ? this : new SetterAndWithMethodJavadoc(i, this.j));
}
/**
* Set some other value.
* @param the new other value
*/
public @java.lang.SuppressWarnings("all") void setJ(final int j) {
this.j = j;
}
/**
* Reinstantiate with some other value.
* @param the other new other value
* @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed).
*/
public @java.lang.SuppressWarnings("all") SetterAndWithMethodJavadoc withJ(final int j) {
return ((this.j == j) ? this : new SetterAndWithMethodJavadoc(this.i, j));
}
@@ -8,6 +8,9 @@ class SetterDeprecated {
public @java.lang.Deprecated @java.lang.SuppressWarnings("all") void setAnnotation(final int annotation) {
this.annotation = annotation;
}
/**
* @deprecated
*/
public @java.lang.Deprecated @java.lang.SuppressWarnings("all") void setJavadoc(final int javadoc) {
this.javadoc = javadoc;
}
@@ -8,6 +8,10 @@ class WithMethodMarkedDeprecated {
public @java.lang.Deprecated @java.lang.SuppressWarnings("all") WithMethodMarkedDeprecated withAnnotation(final int annotation) {
return ((this.annotation == annotation) ? this : new WithMethodMarkedDeprecated(annotation, this.javadoc));
}
/**
* @deprecated
* @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed).
*/
public @java.lang.Deprecated @java.lang.SuppressWarnings("all") WithMethodMarkedDeprecated withJavadoc(final int javadoc) {
return ((this.javadoc == javadoc) ? this : new WithMethodMarkedDeprecated(this.annotation, javadoc));
}