();
int i = 0;
for (JCTree def : defs) {
if (i++ != idx) out.append(def);
}
return out.toList();
}
/**
* In javac, dotted access of any kind, from {@code java.lang.String} to {@code var.methodName}
* is represented by a fold-left of {@code Select} nodes with the leftmost string represented by
* a {@code Ident} node. This method generates such an expression.
*
* The position of the generated node(s) will be unpositioned (-1).
*
* For example, maker.Select(maker.Select(maker.Ident(NAME[java]), NAME[lang]), NAME[String]).
*
* @see com.sun.tools.javac.tree.JCTree.JCIdent
* @see com.sun.tools.javac.tree.JCTree.JCFieldAccess
*/
public static JCExpression chainDots(JavacNode node, String elem1, String elem2, String... elems) {
return chainDots(node, -1, elem1, elem2, elems);
}
public static JCExpression chainDots(JavacNode node, String[] elems) {
return chainDots(node, -1, null, null, elems);
}
public static JCExpression chainDots(JavacNode node, LombokImmutableList elems) {
assert elems != null;
JavacTreeMaker maker = node.getTreeMaker();
JCExpression e = null;
for (String elem : elems) {
if (e == null) e = maker.Ident(node.toName(elem));
else e = maker.Select(e, node.toName(elem));
}
return e;
}
/**
* In javac, dotted access of any kind, from {@code java.lang.String} to {@code var.methodName}
* is represented by a fold-left of {@code Select} nodes with the leftmost string represented by
* a {@code Ident} node. This method generates such an expression.
*
* The position of the generated node(s) will be equal to the {@code pos} parameter.
*
* For example, maker.Select(maker.Select(maker.Ident(NAME[java]), NAME[lang]), NAME[String]).
*
* @see com.sun.tools.javac.tree.JCTree.JCIdent
* @see com.sun.tools.javac.tree.JCTree.JCFieldAccess
*/
public static JCExpression chainDots(JavacNode node, int pos, String elem1, String elem2, String... elems) {
assert elems != null;
JavacTreeMaker maker = node.getTreeMaker();
if (pos != -1) maker = maker.at(pos);
JCExpression e = null;
if (elem1 != null) e = maker.Ident(node.toName(elem1));
if (elem2 != null) e = e == null ? maker.Ident(node.toName(elem2)) : maker.Select(e, node.toName(elem2));
for (int i = 0 ; i < elems.length ; i++) {
e = e == null ? maker.Ident(node.toName(elems[i])) : maker.Select(e, node.toName(elems[i]));
}
assert e != null;
return e;
}
/**
* In javac, dotted access of any kind, from {@code java.lang.String} to {@code var.methodName}
* is represented by a fold-left of {@code Select} nodes with the leftmost string represented by
* a {@code Ident} node. This method generates such an expression.
*
* For example, maker.Select(maker.Select(maker.Ident(NAME[java]), NAME[lang]), NAME[String]).
*
* @see com.sun.tools.javac.tree.JCTree.JCIdent
* @see com.sun.tools.javac.tree.JCTree.JCFieldAccess
*/
public static JCExpression chainDotsString(JavacNode node, String elems) {
return chainDots(node, null, null, elems.split("\\."));
}
/**
* Searches the given field node for annotations and returns each one that matches the provided regular expression pattern.
*
* Only the simple name is checked - the package and any containing class are ignored.
*/
public static List findAnnotations(JavacNode fieldNode, Pattern namePattern) {
ListBuffer result = new ListBuffer();
for (JavacNode child : fieldNode.down()) {
if (child.getKind() == Kind.ANNOTATION) {
JCAnnotation annotation = (JCAnnotation) child.get();
String name = annotation.annotationType.toString();
int idx = name.lastIndexOf(".");
String suspect = idx == -1 ? name : name.substring(idx + 1);
if (namePattern.matcher(suspect).matches()) {
result.append(annotation);
}
}
}
return result.toList();
}
public static String scanForNearestAnnotation(JavacNode node, String... anns) {
while (node != null) {
for (JavacNode ann : node.down()) {
if (ann.getKind() != Kind.ANNOTATION) continue;
JCAnnotation a = (JCAnnotation) ann.get();
for (String annToFind : anns) if (typeMatches(annToFind, node, a.annotationType)) return annToFind;
}
node = node.up();
}
return null;
}
public static boolean hasNonNullAnnotations(JavacNode node) {
for (JavacNode child : node.down()) {
if (child.getKind() == Kind.ANNOTATION) {
JCAnnotation annotation = (JCAnnotation) child.get();
for (String nn : NONNULL_ANNOTATIONS) if (typeMatches(nn, node, annotation.annotationType)) return true;
}
}
return false;
}
public static boolean hasNonNullAnnotations(JavacNode node, List anns) {
if (anns == null) return false;
for (JCAnnotation ann : anns) {
for (String nn : NONNULL_ANNOTATIONS) if (typeMatches(nn, node, ann)) return true;
}
return false;
}
/**
* Searches the given field node for annotations and returns each one that is 'copyable' (either via configuration or from the base list).
*/
public static List findCopyableAnnotations(JavacNode node) {
JCAnnotation anno = null;
String annoName = null;
for (JavacNode child : node.down()) {
if (child.getKind() == Kind.ANNOTATION) {
if (anno != null) {
annoName = "";
break;
}
JCAnnotation annotation = (JCAnnotation) child.get();
annoName = annotation.annotationType.toString();
anno = annotation;
}
}
if (annoName == null) return List.nil();
java.util.List configuredCopyable = node.getAst().readConfiguration(ConfigurationKeys.COPYABLE_ANNOTATIONS);
if (!annoName.isEmpty()) {
for (TypeName cn : configuredCopyable) if (cn != null && typeMatches(cn.toString(), node, anno.annotationType)) return List.of(anno);
for (String bn : BASE_COPYABLE_ANNOTATIONS) if (typeMatches(bn, node, anno.annotationType)) return List.of(anno);
}
ListBuffer result = new ListBuffer();
for (JavacNode child : node.down()) {
if (child.getKind() == Kind.ANNOTATION) {
JCAnnotation annotation = (JCAnnotation) child.get();
boolean match = false;
for (TypeName cn : configuredCopyable) if (cn != null && typeMatches(cn.toString(), node, annotation.annotationType)) {
result.append(annotation);
match = true;
break;
}
if (!match) for (String bn : BASE_COPYABLE_ANNOTATIONS) if (typeMatches(bn, node, annotation.annotationType)) {
result.append(annotation);
break;
}
}
}
return result.toList();
}
/**
* Searches the given field node for annotations that are specifically intentioned to be copied to the setter.
*/
public static List findCopyableToSetterAnnotations(JavacNode node) {
return findAnnotationsInList(node, COPY_TO_SETTER_ANNOTATIONS);
}
/**
* Searches the given field node for annotations that are specifically intentioned to be copied to the builder's singular method.
*/
public static List findCopyableToBuilderSingularSetterAnnotations(JavacNode node) {
return findAnnotationsInList(node, COPY_TO_BUILDER_SINGULAR_SETTER_ANNOTATIONS);
}
/**
* Searches the given field node for annotations that are in the given list, and returns those.
*/
private static List findAnnotationsInList(JavacNode node, java.util.List annotationsToFind) {
JCAnnotation anno = null;
String annoName = null;
for (JavacNode child : node.down()) {
if (child.getKind() == Kind.ANNOTATION) {
if (anno != null) {
annoName = "";
break;
}
JCAnnotation annotation = (JCAnnotation) child.get();
annoName = annotation.annotationType.toString();
anno = annotation;
}
}
if (annoName == null) return List.nil();
if (!annoName.isEmpty()) {
for (String bn : annotationsToFind) if (typeMatches(bn, node, anno.annotationType)) return List.of(anno);
}
ListBuffer result = new ListBuffer();
for (JavacNode child : node.down()) {
if (child.getKind() == Kind.ANNOTATION) {
JCAnnotation annotation = (JCAnnotation) child.get();
boolean match = false;
if (!match) for (String bn : annotationsToFind) if (typeMatches(bn, node, annotation.annotationType)) {
result.append(annotation);
break;
}
}
}
return result.toList();
}
/**
* Generates a new statement that checks if the given variable is null, and if so, throws a configured exception with the
* variable name as message.
*/
public static JCStatement generateNullCheck(JavacTreeMaker maker, JavacNode variable, JavacNode source) {
return generateNullCheck(maker, (JCVariableDecl) variable.get(), source);
}
/**
* Generates a new statement that checks if the given local is null, and if so, throws a configured exception with the
* local variable name as message.
*/
public static JCStatement generateNullCheck(JavacTreeMaker maker, JCExpression typeNode, Name varName, JavacNode source, String customMessage) {
NullCheckExceptionType exceptionType = source.getAst().readConfiguration(ConfigurationKeys.NON_NULL_EXCEPTION_TYPE);
if (exceptionType == null) exceptionType = NullCheckExceptionType.NULL_POINTER_EXCEPTION;
if (typeNode != null && isPrimitive(typeNode)) return null;
JCLiteral message = maker.Literal(exceptionType.toExceptionMessage(varName.toString(), customMessage));
LombokImmutableList method = exceptionType.getMethod();
if (method != null) {
return maker.Exec(maker.Apply(List.nil(), chainDots(source, method), List.of(maker.Ident(varName), message)));
}
if (exceptionType == NullCheckExceptionType.ASSERTION) {
return maker.Assert(maker.Binary(CTC_NOT_EQUAL, maker.Ident(varName), maker.Literal(CTC_BOT, null)), message);
}
JCExpression exType = genTypeRef(source, exceptionType.getExceptionType());
JCExpression exception = maker.NewClass(null, List.nil(), exType, List.of(message), null);
JCStatement throwStatement = maker.Throw(exception);
JCBlock throwBlock = maker.Block(0, List.of(throwStatement));
return maker.If(maker.Binary(CTC_EQUAL, maker.Ident(varName), maker.Literal(CTC_BOT, null)), throwBlock, null);
}
/**
* Generates a new statement that checks if the given variable is null, and if so, throws a configured exception with the
* variable name as message.
*
* This is a special case method reserved for use when the provided declaration differs from the
* variable's declaration, i.e. in a constructor or setter where the local parameter is named the same but with the prefix
* stripped as a result of @Accessors.prefix.
*/
public static JCStatement generateNullCheck(JavacTreeMaker maker, JCVariableDecl varDecl, JavacNode source) {
return generateNullCheck(maker, varDecl.vartype, varDecl.name, source, null);
}
/**
* Given a list of field names and a node referring to a type, finds each name in the list that does not match a field within the type.
*/
public static List createListOfNonExistentFields(List list, JavacNode type, boolean excludeStandard, boolean excludeTransient) {
boolean[] matched = new boolean[list.size()];
for (JavacNode child : type.down()) {
if (list.isEmpty()) break;
if (child.getKind() != Kind.FIELD) continue;
JCVariableDecl field = (JCVariableDecl)child.get();
if (excludeStandard) {
if ((field.mods.flags & Flags.STATIC) != 0) continue;
if (field.name.toString().startsWith("$")) continue;
}
if (excludeTransient && (field.mods.flags & Flags.TRANSIENT) != 0) continue;
int idx = list.indexOf(child.getName());
if (idx > -1) matched[idx] = true;
}
ListBuffer problematic = new ListBuffer();
for (int i = 0 ; i < list.size() ; i++) {
if (!matched[i]) problematic.append(i);
}
return problematic.toList();
}
static List unboxAndRemoveAnnotationParameter(JCAnnotation ast, String parameterName, String errorName, JavacNode annotationNode) {
ListBuffer params = new ListBuffer();
ListBuffer result = new ListBuffer();
outer:
for (JCExpression param : ast.args) {
boolean allowRaw;
String nameOfParam = "value";
JCExpression valueOfParam = null;
if (param instanceof JCAssign) {
JCAssign assign = (JCAssign) param;
if (assign.lhs instanceof JCIdent) {
JCIdent ident = (JCIdent) assign.lhs;
nameOfParam = ident.name.toString();
}
valueOfParam = assign.rhs;
}
/* strip trailing underscores */ {
int lastIdx;
for (lastIdx = nameOfParam.length() ; lastIdx > 0; lastIdx--) {
if (nameOfParam.charAt(lastIdx - 1) != '_') break;
}
allowRaw = lastIdx < nameOfParam.length();
nameOfParam = nameOfParam.substring(0, lastIdx);
}
if (!parameterName.equals(nameOfParam)) {
params.append(param);
continue outer;
}
int endPos = Javac.getEndPosition(param.pos(), (JCCompilationUnit) annotationNode.top().get());
annotationNode.getAst().removeFromDeferredDiagnostics(param.pos, endPos);
if (valueOfParam instanceof JCAnnotation) {
String dummyAnnotationName = ((JCAnnotation) valueOfParam).annotationType.toString();
dummyAnnotationName = dummyAnnotationName.replace("_", "").replace("$", "").replace("x", "").replace("X", "");
if (dummyAnnotationName.length() > 0) {
if (allowRaw) {
result.append((JCAnnotation) valueOfParam);
} else {
addError(errorName, annotationNode);
continue outer;
}
} else {
for (JCExpression expr : ((JCAnnotation) valueOfParam).args) {
if (expr instanceof JCAssign && ((JCAssign) expr).lhs instanceof JCIdent) {
JCIdent id = (JCIdent) ((JCAssign) expr).lhs;
if ("value".equals(id.name.toString())) {
expr = ((JCAssign) expr).rhs;
} else {
addError(errorName, annotationNode);
}
}
if (expr instanceof JCAnnotation) {
result.append((JCAnnotation) expr);
} else if (expr instanceof JCNewArray) {
for (JCExpression expr2 : ((JCNewArray) expr).elems) {
if (expr2 instanceof JCAnnotation) {
result.append((JCAnnotation) expr2);
} else {
addError(errorName, annotationNode);
continue outer;
}
}
} else {
addError(errorName, annotationNode);
continue outer;
}
}
}
} else if (valueOfParam instanceof JCNewArray) {
JCNewArray arr = (JCNewArray) valueOfParam;
if (arr.elems.isEmpty()) {
// Just remove it, this is always fine.
} else if (allowRaw) {
for (JCExpression jce : arr.elems) {
if (jce instanceof JCAnnotation) result.append((JCAnnotation) jce);
else addError(errorName, annotationNode);
}
} else {
addError(errorName, annotationNode);
}
} else {
addError(errorName, annotationNode);
}
}
for (JCAnnotation annotation : result) {
clearTypes(annotation);
}
ast.args = params.toList();
return result.toList();
}
/**
* Removes all type information from the provided tree.
*/
private static void clearTypes(JCTree tree) {
tree.accept(new TreeScanner() {
@Override public void scan(JCTree tree) {
tree.type = null;
super.scan(tree);
}
@Override public void visitClassDef(JCClassDecl tree) {
tree.sym = null;
super.visitClassDef(tree);
}
@Override public void visitMethodDef(JCMethodDecl tree) {
tree.sym = null;
super.visitMethodDef(tree);
}
@Override public void visitVarDef(JCVariableDecl tree) {
tree.sym = null;
super.visitVarDef(tree);
}
@Override public void visitSelect(JCFieldAccess tree) {
tree.sym = null;
super.visitSelect(tree);
}
@Override public void visitIdent(JCIdent tree) {
tree.sym = null;
super.visitIdent(tree);
}
@Override public void visitAnnotation(JCAnnotation tree) {
JCAnnotationReflect.setAttribute(tree, null);
super.visitAnnotation(tree);
}
});
}
private static void addError(String errorName, JavacNode node) {
if (node.getLatestJavaSpecSupported() < 8) {
node.addError("The correct format up to JDK7 is " + errorName + "=@__({@SomeAnnotation, @SomeOtherAnnotation}))");
} else {
node.addError("The correct format for JDK8+ is " + errorName + "_={@SomeAnnotation, @SomeOtherAnnotation})");
}
}
public static List copyTypeParams(JavacNode source, List params) {
if (params == null || params.isEmpty()) return params;
ListBuffer out = new ListBuffer();
JavacTreeMaker maker = source.getTreeMaker();
for (JCTypeParameter tp : params) {
List bounds = tp.bounds;
if (bounds != null && !bounds.isEmpty()) {
ListBuffer boundsCopy = new ListBuffer();
for (JCExpression expr : tp.bounds) {
boundsCopy.append(cloneType(maker, expr, source));
}
bounds = boundsCopy.toList();
}
out.append(maker.TypeParameter(tp.name, bounds));
}
return out.toList();
}
public static List getTypeUseAnnotations(JCExpression from) {
if (!JCAnnotatedTypeReflect.is(from)) return List.nil();
return JCAnnotatedTypeReflect.getAnnotations(from);
}
public static JCExpression removeTypeUseAnnotations(JCExpression from) {
if (!JCAnnotatedTypeReflect.is(from)) return from;
return JCAnnotatedTypeReflect.getUnderlyingType(from);
}
public static JCExpression namePlusTypeParamsToTypeReference(JavacTreeMaker maker, JavacNode type, List params) {
JCClassDecl td = (JCClassDecl) type.get();
boolean instance = (td.mods.flags & Flags.STATIC) == 0;
return namePlusTypeParamsToTypeReference(maker, type.up(), td.name, instance, params, List.nil());
}
public static JCExpression namePlusTypeParamsToTypeReference(JavacTreeMaker maker, JavacNode type, List params, List annotations) {
JCClassDecl td = (JCClassDecl) type.get();
boolean instance = (td.mods.flags & Flags.STATIC) == 0;
return namePlusTypeParamsToTypeReference(maker, type.up(), td.name, instance, params, annotations);
}
public static JCExpression namePlusTypeParamsToTypeReference(JavacTreeMaker maker, JavacNode parentType, Name typeName, boolean instance, List params) {
return namePlusTypeParamsToTypeReference(maker, parentType, typeName, instance, params, List.nil());
}
public static JCExpression namePlusTypeParamsToTypeReference(JavacTreeMaker maker, JavacNode parentType, Name typeName, boolean instance, List params, List annotations) {
JCExpression r = null;
if (parentType != null && parentType.getKind() == Kind.TYPE && !parentType.getName().isEmpty()) {
JCClassDecl td = (JCClassDecl) parentType.get();
boolean outerInstance = instance && ((td.mods.flags & Flags.STATIC) == 0);
List outerParams = instance ? td.typarams : List.nil();
r = namePlusTypeParamsToTypeReference(maker, parentType.up(), td.name, outerInstance, outerParams, List.nil());
}
r = r == null ? maker.Ident(typeName) : maker.Select(r, typeName);
if (!annotations.isEmpty()) r = JCAnnotatedTypeReflect.create(annotations, r);
if (!params.isEmpty()) r = maker.TypeApply(r, typeParameterNames(maker, params));
return r;
}
public static List typeParameterNames(JavacTreeMaker maker, List params) {
ListBuffer typeArgs = new ListBuffer();
for (JCTypeParameter param : params) {
typeArgs.append(maker.Ident(param.name));
}
return typeArgs.toList();
}
public static void sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(JavacNode typeNode, JavacNode errorNode) {
List disallowed = List.nil();
for (JavacNode child : typeNode.down()) {
for (String annType : INVALID_ON_BUILDERS) {
if (annotationTypeMatches(annType, child)) {
int lastIndex = annType.lastIndexOf('.');
disallowed = disallowed.append(lastIndex == -1 ? annType : annType.substring(lastIndex + 1));
}
}
}
int size = disallowed.size();
if (size == 0) return;
if (size == 1) {
errorNode.addError("@" + disallowed.head + " is not allowed on builder classes.");
return;
}
StringBuilder out = new StringBuilder();
for (String a : disallowed) out.append("@").append(a).append(", ");
out.setLength(out.length() - 2);
errorNode.addError(out.append(" are not allowed on builder classes.").toString());
}
static List copyAnnotations(List extends JCExpression> in) {
ListBuffer out = new ListBuffer();
for (JCExpression expr : in) {
if (!(expr instanceof JCAnnotation)) continue;
out.append((JCAnnotation) expr.clone());
}
return out.toList();
}
static List mergeAnnotations(List a, List b) {
if (a == null || a.isEmpty()) return b;
if (b == null || b.isEmpty()) return a;
ListBuffer out = new ListBuffer();
for (JCAnnotation ann : a) out.append(ann);
for (JCAnnotation ann : b) out.append(ann);
return out.toList();
}
/**
* Returns {@code true} if the provided node is an actual class and not some other type declaration (so, not an annotation definition, interface, enum, or record).
*/
public static boolean isClass(JavacNode typeNode) {
return isClassAndDoesNotHaveFlags(typeNode, Flags.INTERFACE | Flags.ENUM | Flags.ANNOTATION | RECORD);
}
/**
* Returns {@code true} if the provided node is an actual class or enum and not some other type declaration (so, not an annotation definition, interface, or record).
*/
public static boolean isClassOrEnum(JavacNode typeNode) {
return isClassAndDoesNotHaveFlags(typeNode, Flags.INTERFACE | Flags.ANNOTATION | RECORD);
}
/**
* Returns {@code true} if the provided node is an actual class, an enum or a record and not some other type declaration (so, not an annotation definition or interface).
*/
public static boolean isClassEnumOrRecord(JavacNode typeNode) {
return isClassAndDoesNotHaveFlags(typeNode, Flags.INTERFACE | Flags.ANNOTATION);
}
/**
* Returns {@code true} if the provided node is a record declaration (so, not an annotation definition, interface, enum, or plain class).
*/
public static boolean isRecord(JavacNode typeNode) {
return typeNode.getKind() == Kind.TYPE && (((JCClassDecl) typeNode.get()).mods.flags & RECORD) != 0;
}
public static boolean isClassAndDoesNotHaveFlags(JavacNode typeNode, long flags) {
JCClassDecl typeDecl = null;
if (typeNode.get() instanceof JCClassDecl) typeDecl = (JCClassDecl) typeNode.get();
else return false;
long typeDeclflags = typeDecl == null ? 0 : typeDecl.mods.flags;
return (typeDeclflags & flags) == 0;
}
/**
* Returns {@code true} if the provided node supports static methods and types (top level or static class)
*/
public static boolean isStaticAllowed(JavacNode typeNode) {
boolean staticAllowed = true;
while (typeNode.getKind() != Kind.COMPILATION_UNIT) {
if (!staticAllowed) return false;
staticAllowed = typeNode.isStatic();
typeNode = typeNode.up();
}
return true;
}
public static JavacNode upToTypeNode(JavacNode node) {
if (node == null) throw new NullPointerException("node");
while ((node != null) && !(node.get() instanceof JCClassDecl)) node = node.up();
return node;
}
public static List cloneTypes(JavacTreeMaker maker, List in, JavacNode source) {
if (in.isEmpty()) return List.nil();
if (in.size() == 1) return List.of(cloneType(maker, in.get(0), source));
ListBuffer lb = new ListBuffer();
for (JCExpression expr : in) lb.append(cloneType(maker, expr, source));
return lb.toList();
}
/**
* Creates a full clone of a given javac AST type node. Every part is cloned (every identifier, every select, every wildcard, every type apply, every type_use annotation).
*
* If there's any node in the tree that we don't know how to clone, that part isn't cloned. However, we wouldn't know what could possibly show up that we
* can't currently clone; that's just a safeguard.
*
* This should be used if the type looks the same in the code, but resolves differently. For example, a static method that has some generics in it named after
* the class's own parameter, but as its a static method, the static method's notion of {@code T} is different from the class notion of {@code T}. If you're duplicating
* a type used in the class context, you need to use this method.
*/
public static JCExpression cloneType(JavacTreeMaker maker, JCExpression in, JavacNode source) {
JCExpression out = cloneType0(maker, in);
if (out != null) recursiveSetGeneratedBy(out, source);
return out;
}
private static JCExpression cloneType0(JavacTreeMaker maker, JCTree in) {
if (in == null) return null;
if (in instanceof JCPrimitiveTypeTree) {
return maker.TypeIdent(TypeTag.typeTag(in));
}
if (in instanceof JCIdent) {
return maker.Ident(((JCIdent) in).name);
}
if (in instanceof JCFieldAccess) {
JCFieldAccess fa = (JCFieldAccess) in;
return maker.Select(cloneType0(maker, fa.selected), fa.name);
}
if (in instanceof JCArrayTypeTree) {
JCArrayTypeTree att = (JCArrayTypeTree) in;
return maker.TypeArray(cloneType0(maker, att.elemtype));
}
if (in instanceof JCTypeApply) {
JCTypeApply ta = (JCTypeApply) in;
ListBuffer lb = new ListBuffer();
for (JCExpression typeArg : ta.arguments) {
lb.append(cloneType0(maker, typeArg));
}
return maker.TypeApply(cloneType0(maker, ta.clazz), lb.toList());
}
if (in instanceof JCWildcard) {
JCWildcard w = (JCWildcard) in;
JCExpression newInner = cloneType0(maker, w.inner);
TypeBoundKind newKind;
switch (w.getKind()) {
case SUPER_WILDCARD:
newKind = maker.TypeBoundKind(BoundKind.SUPER);
break;
case EXTENDS_WILDCARD:
newKind = maker.TypeBoundKind(BoundKind.EXTENDS);
break;
default:
case UNBOUNDED_WILDCARD:
newKind = maker.TypeBoundKind(BoundKind.UNBOUND);
break;
}
return maker.Wildcard(newKind, newInner);
}
if (JCAnnotatedTypeReflect.is(in)) {
JCExpression underlyingType = cloneType0(maker, JCAnnotatedTypeReflect.getUnderlyingType(in));
List anns = copyAnnotations(JCAnnotatedTypeReflect.getAnnotations(in));
return JCAnnotatedTypeReflect.create(anns, underlyingType);
}
// This is somewhat unsafe, but it's better than outright throwing an exception here. Returning null will just cause an exception down the pipeline.
return (JCExpression) in;
}
public static enum CopyJavadoc {
VERBATIM {
@Override public String apply(final JCCompilationUnit cu, final JavacNode node) {
return Javac.getDocComment(cu, node.get());
}
},
GETTER {
@Override public String apply(final JCCompilationUnit cu, final JavacNode node) {
final JCTree n = node.get();
String javadoc = Javac.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), JavadocTag.PARAM);
}
node.getAst().cleanupTask("javadocfilter-getter", n, new CleanupTask() {
@Override public void cleanup() {
String javadoc = Javac.getDocComment(cu, n);
if (javadoc == null || javadoc.isEmpty()) return;
javadoc = stripSectionsFromJavadoc(javadoc);
if (!sectionBased) {
javadoc = stripLinesWithTagFromJavadoc(stripSectionsFromJavadoc(javadoc), JavadocTag.RETURN);
}
Javac.setDocComment(cu, n, javadoc);
}
});
return out;
}
},
SETTER {
@Override public String apply(final JCCompilationUnit cu, final JavacNode node) {
return applySetter(cu, node, "SETTER");
}
},
WITH {
@Override public String apply(final JCCompilationUnit cu, final JavacNode node) {
return addReturnsUpdatedSelfIfNeeded(applySetter(cu, node, "WITH|WITHER"));
}
},
WITH_BY {
@Override public String apply(final JCCompilationUnit cu, final JavacNode node) {
return applySetter(cu, node, "WITHBY|WITH_BY");
}
};
public abstract String apply(final JCCompilationUnit cu, final JavacNode node);
private static String applySetter(final JCCompilationUnit cu, JavacNode node, String sectionName) {
final JCTree n = node.get();
String javadoc = Javac.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), JavadocTag.RETURN);
}
node.getAst().cleanupTask("javadocfilter-setter", n, new CleanupTask() {
@Override public void cleanup() {
String javadoc = Javac.getDocComment(cu, n);
if (javadoc == null || javadoc.isEmpty()) return;
javadoc = stripSectionsFromJavadoc(javadoc);
if (!sectionBased) {
javadoc = stripLinesWithTagFromJavadoc(stripSectionsFromJavadoc(javadoc), JavadocTag.PARAM);
}
Javac.setDocComment(cu, n, javadoc);
}
});
return shouldReturnThis(node) ? addReturnsThisIfNeeded(out) : out;
}
}
public static void copyJavadoc(JavacNode from, JCTree to, CopyJavadoc copyMode) {
copyJavadoc(from, to, 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(JavacNode from, JCTree to, CopyJavadoc copyMode, boolean forceAddReturn) {
if (copyMode == null) copyMode = CopyJavadoc.VERBATIM;
try {
JCCompilationUnit cu = ((JCCompilationUnit) from.top().get());
String newJavadoc = copyMode.apply(cu, from);
if (forceAddReturn) {
newJavadoc = addReturnsThisIfNeeded(newJavadoc);
}
Javac.setDocComment(cu, to, newJavadoc);
} catch (Exception ignore) {}
}
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());
if (extending == null) return true;
String p = extending.toString();
return p.equals("Object") || p.equals("java.lang.Object");
}
public static void createRelevantNullableAnnotation(JavacNode typeNode, JCMethodDecl mth) {
NullAnnotationLibrary lib = typeNode.getAst().readConfiguration(ConfigurationKeys.ADD_NULL_ANNOTATIONS);
if (lib == null) return;
applyAnnotationToMethodDecl(typeNode, mth, lib.getNullableAnnotation(), lib.isTypeUse());
}
public static void createRelevantNonNullAnnotation(JavacNode typeNode, JCMethodDecl mth) {
NullAnnotationLibrary lib = typeNode.getAst().readConfiguration(ConfigurationKeys.ADD_NULL_ANNOTATIONS);
if (lib == null) return;
applyAnnotationToMethodDecl(typeNode, mth, lib.getNonNullAnnotation(), lib.isTypeUse());
}
public static void createRelevantNonNullAnnotation(JavacNode typeNode, JCVariableDecl arg) {
NullAnnotationLibrary lib = typeNode.getAst().readConfiguration(ConfigurationKeys.ADD_NULL_ANNOTATIONS);
if (lib == null) return;
applyAnnotationToVarDecl(typeNode, arg, lib.getNonNullAnnotation(), lib.isTypeUse());
}
public static void createRelevantNullableAnnotation(JavacNode typeNode, JCVariableDecl arg) {
NullAnnotationLibrary lib = typeNode.getAst().readConfiguration(ConfigurationKeys.ADD_NULL_ANNOTATIONS);
if (lib == null) return;
applyAnnotationToVarDecl(typeNode, arg, lib.getNullableAnnotation(), lib.isTypeUse());
}
private static void applyAnnotationToMethodDecl(JavacNode typeNode, JCMethodDecl mth, String annType, boolean typeUse) {
if (annType == null) return;
JavacTreeMaker maker = typeNode.getTreeMaker();
JCAnnotation m = maker.Annotation(genTypeRef(typeNode, annType), List.nil());
if (typeUse) {
JCExpression resType = mth.restype;
if (resType instanceof JCTypeApply) {
JCTypeApply ta = (JCTypeApply) resType;
if (ta.clazz instanceof JCFieldAccess) {
mth.restype = maker.TypeApply(maker.AnnotatedType(List.of(m), ta.clazz), ta.arguments);
return;
}
resType = ta.clazz;
}
if (resType instanceof JCFieldAccess || resType instanceof JCArrayTypeTree) {
mth.restype = maker.AnnotatedType(List.of(m), resType);
return;
}
if (JCAnnotatedTypeReflect.is(resType)) {
List annotations = JCAnnotatedTypeReflect.getAnnotations(resType);
JCAnnotatedTypeReflect.setAnnotations(resType, annotations.prepend(m));
return;
}
if (resType instanceof JCPrimitiveTypeTree || resType instanceof JCIdent) {
mth.mods.annotations = mth.mods.annotations == null ? List.of(m) : mth.mods.annotations.prepend(m);
}
} else {
mth.mods.annotations = mth.mods.annotations == null ? List.of(m) : mth.mods.annotations.prepend(m);
}
}
private static void applyAnnotationToVarDecl(JavacNode typeNode, JCVariableDecl arg, String annType, boolean typeUse) {
if (annType == null) return;
JavacTreeMaker maker = typeNode.getTreeMaker();
JCAnnotation m = maker.Annotation(genTypeRef(typeNode, annType), List.nil());
if (typeUse) {
JCExpression varType = arg.vartype;
JCTypeApply ta = null;
if (varType instanceof JCTypeApply) {
ta = (JCTypeApply) varType;
varType = ta.clazz;
}
if (varType instanceof JCFieldAccess || varType instanceof JCArrayTypeTree) {
varType = maker.AnnotatedType(List.of(m), varType);
if (ta != null) ta.clazz = varType;
else arg.vartype = varType;
return;
}
if (JCAnnotatedTypeReflect.is(varType)) {
List annotations = JCAnnotatedTypeReflect.getAnnotations(varType);
JCAnnotatedTypeReflect.setAnnotations(varType, annotations.prepend(m));
return;
}
if (varType instanceof JCPrimitiveTypeTree || varType instanceof JCIdent) {
arg.mods.annotations = arg.mods.annotations == null ? List.of(m) : arg.mods.annotations.prepend(m);
}
} else {
arg.mods.annotations = arg.mods.annotations == null ? List.of(m) : arg.mods.annotations.prepend(m);
}
}
}