org.openrewrite.java.isolated.ReloadableJava21ParserVisitor Maven / Gradle / Ivy
Show all versions of rewrite-java-21 Show documentation
/*
* Copyright 2023 the original author or authors.
*
* 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
*
* https://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 org.openrewrite.java.isolated;
import com.sun.source.tree.*;
import com.sun.source.util.TreePathScanner;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.tree.DCTree;
import com.sun.tools.javac.tree.DocCommentTable;
import com.sun.tools.javac.tree.EndPosTable;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.*;
import com.sun.tools.javac.util.Context;
import org.jspecify.annotations.Nullable;
import org.openrewrite.Cursor;
import org.openrewrite.ExecutionContext;
import org.openrewrite.FileAttributes;
import org.openrewrite.internal.EncodingDetectingInputStream;
import org.openrewrite.internal.ListUtils;
import org.openrewrite.java.JavaParsingException;
import org.openrewrite.java.internal.JavaTypeCache;
import org.openrewrite.java.marker.CompactConstructor;
import org.openrewrite.java.marker.OmitParentheses;
import org.openrewrite.java.tree.*;
import org.openrewrite.marker.Markers;
import org.openrewrite.style.NamedStyles;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.lang.Math.max;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;
import static java.util.stream.StreamSupport.stream;
import static org.openrewrite.Tree.randomId;
import static org.openrewrite.internal.StringUtils.indexOfNextNonWhitespace;
import static org.openrewrite.java.tree.Space.EMPTY;
import static org.openrewrite.java.tree.Space.format;
/**
* Maps the compiler internal AST to the Rewrite {@link J} AST.
*
* This visitor is not thread safe, as it maintains a {@link #cursor} and {@link #endPosTable}
* for each compilation unit visited.
*/
public class ReloadableJava21ParserVisitor extends TreePathScanner {
private final static int SURR_FIRST = 0xD800;
private final static int SURR_LAST = 0xDFFF;
private static final Map MODIFIER_BY_KEYWORD =
Stream.of(Modifier.values()).collect(Collectors.toUnmodifiableMap(Modifier::toString, Function.identity()));
private final Path sourcePath;
@Nullable
private final FileAttributes fileAttributes;
private final String source;
private final Charset charset;
private final boolean charsetBomMarked;
private final Collection styles;
private final ExecutionContext ctx;
private final Context context;
private final ReloadableJava21TypeMapping typeMapping;
@SuppressWarnings("NotNullFieldNotInitialized")
private EndPosTable endPosTable;
@SuppressWarnings("NotNullFieldNotInitialized")
private DocCommentTable docCommentTable;
private int cursor = 0;
private static final Pattern whitespaceSuffixPattern = Pattern.compile("\\s*[^\\s]+(\\s*)");
public ReloadableJava21ParserVisitor(Path sourcePath,
@Nullable FileAttributes fileAttributes,
EncodingDetectingInputStream source,
Collection styles,
JavaTypeCache typeCache,
ExecutionContext ctx,
Context context) {
this.sourcePath = sourcePath;
this.fileAttributes = fileAttributes;
this.source = source.readFully();
this.charset = source.getCharset();
this.charsetBomMarked = source.isCharsetBomMarked();
this.styles = styles;
this.ctx = ctx;
this.context = context;
this.typeMapping = new ReloadableJava21TypeMapping(typeCache);
}
@Override
public J visitAnnotation(AnnotationTree node, Space fmt) {
skip("@");
NameTree name = convert(node.getAnnotationType());
JContainer args = null;
if (!node.getArguments().isEmpty()) {
Space argsPrefix = sourceBefore("(");
List> expressions;
if (node.getArguments().size() == 1) {
ExpressionTree arg = node.getArguments().get(0);
if (arg instanceof JCAssign) {
if (endPos(arg) < 0) {
expressions = singletonList(convert(((JCAssign) arg).rhs, t -> sourceBefore(")")));
} else {
expressions = singletonList(convert(arg, t -> sourceBefore(")")));
}
} else {
expressions = singletonList(convert(arg, t -> sourceBefore(")")));
}
} else {
expressions = convertAll(node.getArguments(), commaDelim, t -> sourceBefore(")"));
}
args = JContainer.build(argsPrefix, expressions, Markers.EMPTY);
} else {
String remaining = source.substring(cursor, endPos(node));
// TODO: technically, if there is code like this, we have a bug, but seems exceedingly unlikely:
// @MyAnnotation /* Comment () that contains parentheses */ ()
if (remaining.contains("(") && remaining.contains(")")) {
args = JContainer.build(
sourceBefore("("),
singletonList(padRight(new J.Empty(randomId(), sourceBefore(")"), Markers.EMPTY), EMPTY)),
Markers.EMPTY
);
}
}
return new J.Annotation(randomId(), fmt, Markers.EMPTY, name, args);
}
@Override
public J visitArrayAccess(ArrayAccessTree node, Space fmt) {
return new J.ArrayAccess(
randomId(),
fmt,
Markers.EMPTY,
convert(node.getExpression()),
new J.ArrayDimension(randomId(), sourceBefore("["), Markers.EMPTY,
convert(node.getIndex(), t -> sourceBefore("]"))),
typeMapping.type(node)
);
}
@Override
public J visitArrayType(ArrayTypeTree node, Space fmt) {
return arrayTypeTree(node, new HashMap<>()).withPrefix(fmt);
}
@Override
public J visitAssert(AssertTree node, Space fmt) {
skip("assert");
JCAssert jcAssert = (JCAssert) node;
return new J.Assert(randomId(), fmt, Markers.EMPTY,
convert(jcAssert.cond),
jcAssert.detail == null ? null : padLeft(sourceBefore(":"), convert(jcAssert.detail)));
}
@Override
public J visitAssignment(AssignmentTree node, Space fmt) {
return new J.Assignment(randomId(), fmt, Markers.EMPTY,
convert(node.getVariable()),
padLeft(sourceBefore("="), convert(node.getExpression())),
typeMapping.type(node));
}
@Override
public J visitBinary(BinaryTree node, Space fmt) {
Expression left = convert(node.getLeftOperand());
Space opPrefix = whitespace();
J.Binary.Type op;
switch (((JCBinary) node).getTag()) {
case PLUS:
skip("+");
op = J.Binary.Type.Addition;
break;
case MINUS:
skip("-");
op = J.Binary.Type.Subtraction;
break;
case DIV:
skip("/");
op = J.Binary.Type.Division;
break;
case MUL:
skip("*");
op = J.Binary.Type.Multiplication;
break;
case MOD:
skip("%");
op = J.Binary.Type.Modulo;
break;
case AND:
skip("&&");
op = J.Binary.Type.And;
break;
case OR:
skip("||");
op = J.Binary.Type.Or;
break;
case BITAND:
skip("&");
op = J.Binary.Type.BitAnd;
break;
case BITOR:
skip("|");
op = J.Binary.Type.BitOr;
break;
case BITXOR:
skip("^");
op = J.Binary.Type.BitXor;
break;
case SL:
skip("<<");
op = J.Binary.Type.LeftShift;
break;
case SR:
skip(">>");
op = J.Binary.Type.RightShift;
break;
case USR:
skip(">>>");
op = J.Binary.Type.UnsignedRightShift;
break;
case LT:
skip("<");
op = J.Binary.Type.LessThan;
break;
case GT:
skip(">");
op = J.Binary.Type.GreaterThan;
break;
case LE:
skip("<=");
op = J.Binary.Type.LessThanOrEqual;
break;
case GE:
skip(">=");
op = J.Binary.Type.GreaterThanOrEqual;
break;
case EQ:
skip("==");
op = J.Binary.Type.Equal;
break;
case NE:
skip("!=");
op = J.Binary.Type.NotEqual;
break;
default:
throw new IllegalArgumentException("Unexpected binary tag " + ((JCBinary) node).getTag());
}
return new J.Binary(randomId(), fmt, Markers.EMPTY, left, padLeft(opPrefix, op),
convert(node.getRightOperand()), typeMapping.type(node));
}
@Override
public J visitBlock(BlockTree node, Space fmt) {
JRightPadded stat;
if ((((JCBlock) node).flags & (long) Flags.STATIC) != 0L) {
skip("static");
stat = new JRightPadded<>(true, sourceBefore("{"), Markers.EMPTY);
} else {
skip("{");
stat = new JRightPadded<>(false, EMPTY, Markers.EMPTY);
}
// filter out synthetic super() invocations and the like
List statementTrees = new ArrayList<>(node.getStatements().size());
for (StatementTree s : node.getStatements()) {
if (endPos(s) > 0) {
statementTrees.add(s);
}
}
return new J.Block(randomId(), fmt, Markers.EMPTY,
stat,
convertStatements(statementTrees),
sourceBefore("}"));
}
@Override
public J visitBreak(BreakTree node, Space fmt) {
skip("break");
J.Identifier label = node.getLabel() == null ? null : new J.Identifier(randomId(),
sourceBefore(node.getLabel().toString()), Markers.EMPTY,
emptyList(), skip(node.getLabel().toString()), null, null);
return new J.Break(randomId(), fmt, Markers.EMPTY, label);
}
@Override
public J visitCase(CaseTree node, Space fmt) {
J.Case.Type type = node.getCaseKind() == CaseTree.CaseKind.RULE ? J.Case.Type.Rule : J.Case.Type.Statement;
return new J.Case(
randomId(),
fmt,
Markers.EMPTY,
type,
null,
JContainer.build(
node.getExpressions().isEmpty() ? EMPTY : sourceBefore("case"),
node.getExpressions().isEmpty() ?
List.of(JRightPadded.build(new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), skip("default"), null, null))) :
convertAll(node.getExpressions(), commaDelim, t -> EMPTY),
Markers.EMPTY
),
JContainer.build(
sourceBefore(type == J.Case.Type.Rule ? "->" : ":"),
convertStatements(node.getStatements()),
Markers.EMPTY
),
type == J.Case.Type.Rule ?
padRight(convert(node.getBody()), statementDelim(node.getBody())) :
null
);
}
@Override
public J visitYield(YieldTree node, Space fmt) {
boolean implicit = !source.startsWith("yield", cursor);
if (!implicit) {
skip("yield");
}
return new J.Yield(randomId(), fmt, Markers.EMPTY, implicit, convert(node.getValue()));
}
@Override
public J visitCatch(CatchTree node, Space fmt) {
skip("catch");
Space paramPrefix = sourceBefore("(");
J.VariableDeclarations paramDecl = convert(node.getParameter());
J.ControlParentheses param = new J.ControlParentheses<>(randomId(), paramPrefix,
Markers.EMPTY, padRight(paramDecl, sourceBefore(")")));
return new J.Try.Catch(randomId(), fmt, Markers.EMPTY, param, convert(node.getBlock()));
}
@Override
public J visitClass(ClassTree node, Space fmt) {
Map annotationPosTable = mapAnnotations(node.getModifiers().getAnnotations(),
new HashMap<>(node.getModifiers().getAnnotations().size()));
ReloadableJava21ModifierResults modifierResults = sortedModifiersAndAnnotations(node.getModifiers(), annotationPosTable);
List kindAnnotations = collectAnnotations(annotationPosTable);
J.ClassDeclaration.Kind kind;
if (hasFlag(node.getModifiers(), Flags.ENUM)) {
kind = new J.ClassDeclaration.Kind(randomId(), sourceBefore("enum"), Markers.EMPTY, kindAnnotations, J.ClassDeclaration.Kind.Type.Enum);
} else if (hasFlag(node.getModifiers(), Flags.ANNOTATION)) {
// note that annotations ALSO have the INTERFACE flag
kind = new J.ClassDeclaration.Kind(randomId(), sourceBefore("@interface"), Markers.EMPTY, kindAnnotations, J.ClassDeclaration.Kind.Type.Annotation);
} else if (hasFlag(node.getModifiers(), Flags.INTERFACE)) {
kind = new J.ClassDeclaration.Kind(randomId(), sourceBefore("interface"), Markers.EMPTY, kindAnnotations, J.ClassDeclaration.Kind.Type.Interface);
} else if (hasFlag(node.getModifiers(), Flags.RECORD)) {
kind = new J.ClassDeclaration.Kind(randomId(), sourceBefore("record"), Markers.EMPTY, kindAnnotations, J.ClassDeclaration.Kind.Type.Record);
} else {
kind = new J.ClassDeclaration.Kind(randomId(), sourceBefore("class"), Markers.EMPTY, kindAnnotations, J.ClassDeclaration.Kind.Type.Class);
}
J.Identifier name = new J.Identifier(randomId(), sourceBefore(node.getSimpleName().toString()),
Markers.EMPTY, emptyList(), ((JCClassDecl) node).getSimpleName().toString(), typeMapping.type(node), null);
JContainer typeParams = node.getTypeParameters().isEmpty() ? null : JContainer.build(
sourceBefore("<"),
convertAll(node.getTypeParameters(), commaDelim, t -> sourceBefore(">")),
Markers.EMPTY);
JContainer primaryConstructor = null;
if (kind.getType() == J.ClassDeclaration.Kind.Type.Record) {
List stateVector = new ArrayList<>();
for (Tree member : node.getMembers()) {
if (member instanceof VariableTree vt) {
if (hasFlag(vt.getModifiers(), Flags.RECORD)) {
stateVector.add(vt);
}
}
}
primaryConstructor = JContainer.build(
sourceBefore("("),
stateVector.isEmpty() ?
singletonList(padRight(new J.Empty(randomId(), sourceBefore(")"), Markers.EMPTY), EMPTY)) :
convertAll(stateVector, commaDelim, t -> sourceBefore(")")),
Markers.EMPTY
);
}
JLeftPadded extendings = node.getExtendsClause() == null ? null :
padLeft(sourceBefore("extends"), convertOrNull(node.getExtendsClause()));
JContainer implementings = null;
if (node.getImplementsClause() != null && !node.getImplementsClause().isEmpty()) {
Space implementsPrefix = sourceBefore(kind.getType() == J.ClassDeclaration.Kind.Type.Interface ?
"extends" : "implements");
implementings = JContainer.build(
implementsPrefix,
convertAll(node.getImplementsClause(), commaDelim, noDelim),
Markers.EMPTY
);
}
JContainer permitting = null;
if (node.getPermitsClause() != null && !node.getPermitsClause().isEmpty()) {
permitting = JContainer.build(
sourceBefore("permits"),
convertAll(node.getPermitsClause(), commaDelim, noDelim),
Markers.EMPTY
);
}
Space bodyPrefix = sourceBefore("{");
// enum values are required by the grammar to occur before any ordinary field, constructor, or method members
List jcEnums = new ArrayList<>(node.getMembers().size());
for (Tree tree : node.getMembers()) {
if (tree instanceof JCVariableDecl) {
if (hasFlag(((JCVariableDecl) tree).getModifiers(), Flags.ENUM)) {
jcEnums.add(tree);
}
}
}
JRightPadded enumSet = null;
if (!jcEnums.isEmpty()) {
AtomicBoolean semicolonPresent = new AtomicBoolean(false);
List> enumValues = convertAll(jcEnums, commaDelim, t -> {
// this semicolon is required when there are non-value members, but can still
// be present when there are not
semicolonPresent.set(positionOfNext(";", '}') > 0);
return semicolonPresent.get() ? sourceBefore(";", '}') : EMPTY;
});
enumSet = padRight(
new J.EnumValueSet(
randomId(),
EMPTY,
Markers.EMPTY,
enumValues,
semicolonPresent.get()
),
EMPTY
);
}
List membersMultiVariablesSeparated = new ArrayList<>(node.getMembers().size());
for (Tree m : node.getMembers()) {
// we don't care about the compiler-inserted default constructor,
// since it will never be subject to refactoring
if (m instanceof JCMethodDecl md && (
hasFlag(md.getModifiers(), Flags.GENERATEDCONSTR) ||
hasFlag(md.getModifiers(), Flags.RECORD))) {
continue;
}
if (m instanceof JCVariableDecl vt &&
(hasFlag(vt.getModifiers(), Flags.ENUM) ||
hasFlag(vt.getModifiers(), Flags.RECORD))) {
continue;
}
membersMultiVariablesSeparated.add(m);
}
List> members = new ArrayList<>(
membersMultiVariablesSeparated.size() + (enumSet == null ? 0 : 1));
if (enumSet != null) {
members.add(enumSet);
}
members.addAll(convertStatements(membersMultiVariablesSeparated));
J.Block body = new J.Block(randomId(), bodyPrefix, Markers.EMPTY, new JRightPadded<>(false, EMPTY, Markers.EMPTY),
members, sourceBefore("}"));
return new J.ClassDeclaration(randomId(), fmt, Markers.EMPTY, modifierResults.getLeadingAnnotations(), modifierResults.getModifiers(), kind, name, typeParams,
primaryConstructor, extendings, implementings, permitting, body, (JavaType.FullyQualified) typeMapping.type(node));
}
@Override
public J visitCompilationUnit(CompilationUnitTree node, Space fmt) {
JCCompilationUnit cu = (JCCompilationUnit) node;
if (node.getTypeDecls().isEmpty() || cu.getPackageName() != null || !node.getImports().isEmpty()) {
// if the package and imports are empty, allow the formatting to apply to the first class declaration.
// in this way, javadoc comments are interpreted as javadocs on that class declaration.
fmt = format(source, 0, cu.getStartPosition());
cursor(cu.getStartPosition());
}
endPosTable = cu.endPositions;
docCommentTable = cu.docComments;
Map annotationPosTable = mapAnnotations(node.getPackageAnnotations(),
new HashMap<>(node.getPackageAnnotations().size()));
List packageAnnotations = collectAnnotations(annotationPosTable);
J.Package packageDecl = null;
if (cu.getPackageName() != null) {
Space packagePrefix = sourceBefore("package");
packageDecl = new J.Package(randomId(), packagePrefix, Markers.EMPTY,
convert(cu.getPackageName()), packageAnnotations);
}
return new J.CompilationUnit(
randomId(),
fmt,
Markers.build(styles),
sourcePath,
fileAttributes,
charset.name(),
charsetBomMarked,
null,
packageDecl == null ? null : padRight(packageDecl, sourceBefore(";")),
convertAll(node.getImports(), this::statementDelim, this::statementDelim),
convertAll(node.getTypeDecls().stream().filter(JCClassDecl.class::isInstance).collect(toList())),
format(source, cursor, source.length())
);
}
@Override
public J visitCompoundAssignment(CompoundAssignmentTree node, Space fmt) {
Expression left = convert(((JCAssignOp) node).lhs);
Space opPrefix = whitespace();
J.AssignmentOperation.Type op;
switch (((JCAssignOp) node).getTag()) {
case PLUS_ASG:
skip("+=");
op = J.AssignmentOperation.Type.Addition;
break;
case MINUS_ASG:
skip("-=");
op = J.AssignmentOperation.Type.Subtraction;
break;
case DIV_ASG:
skip("/=");
op = J.AssignmentOperation.Type.Division;
break;
case MUL_ASG:
skip("*=");
op = J.AssignmentOperation.Type.Multiplication;
break;
case MOD_ASG:
skip("%=");
op = J.AssignmentOperation.Type.Modulo;
break;
case BITAND_ASG:
skip("&=");
op = J.AssignmentOperation.Type.BitAnd;
break;
case BITOR_ASG:
skip("|=");
op = J.AssignmentOperation.Type.BitOr;
break;
case BITXOR_ASG:
skip("^=");
op = J.AssignmentOperation.Type.BitXor;
break;
case SL_ASG:
skip("<<=");
op = J.AssignmentOperation.Type.LeftShift;
break;
case SR_ASG:
skip(">>=");
op = J.AssignmentOperation.Type.RightShift;
break;
case USR_ASG:
skip(">>>=");
op = J.AssignmentOperation.Type.UnsignedRightShift;
break;
default:
throw new IllegalArgumentException("Unexpected compound assignment tag " + ((JCAssignOp) node).getTag());
}
return new J.AssignmentOperation(randomId(), fmt, Markers.EMPTY, left,
padLeft(opPrefix, op), convert(((JCAssignOp) node).rhs), typeMapping.type(node));
}
@Override
public J visitConditionalExpression(ConditionalExpressionTree node, Space fmt) {
return new J.Ternary(randomId(), fmt, Markers.EMPTY,
convert(node.getCondition()),
padLeft(sourceBefore("?"), convert(node.getTrueExpression())),
padLeft(sourceBefore(":"), convert(node.getFalseExpression())),
typeMapping.type(node));
}
@Override
public J visitContinue(ContinueTree node, Space fmt) {
skip("continue");
Name label = node.getLabel();
return new J.Continue(randomId(), fmt, Markers.EMPTY,
label == null ? null : new J.Identifier(randomId(), sourceBefore(label.toString()),
Markers.EMPTY, emptyList(), label.toString(), null, null));
}
@Override
public J visitDoWhileLoop(DoWhileLoopTree node, Space fmt) {
skip("do");
return new J.DoWhileLoop(randomId(), fmt, Markers.EMPTY,
convert(node.getStatement(), this::statementDelim),
padLeft(sourceBefore("while"), convert(node.getCondition())));
}
@Override
public J visitEmptyStatement(EmptyStatementTree node, Space fmt) {
return new J.Empty(randomId(), fmt, Markers.EMPTY);
}
@Override
public J visitEnhancedForLoop(EnhancedForLoopTree node, Space fmt) {
skip("for");
return new J.ForEachLoop(randomId(), fmt, Markers.EMPTY,
new J.ForEachLoop.Control(randomId(), sourceBefore("("), Markers.EMPTY,
convert(node.getVariable(), t -> sourceBefore(":")),
convert(node.getExpression(), t -> sourceBefore(")"))),
convert(node.getStatement(), this::statementDelim));
}
private J visitEnumVariable(VariableTree node, Space fmt) {
List annotations = emptyList();
Space nameSpace = EMPTY;
if (!node.getModifiers().getAnnotations().isEmpty()) {
annotations = convertAll(node.getModifiers().getAnnotations());
nameSpace = sourceBefore(node.getName().toString());
} else {
skip(node.getName().toString());
}
J.Identifier name = new J.Identifier(randomId(), nameSpace, Markers.EMPTY, emptyList(), node.getName().toString(), typeMapping.type(node), null);
J.NewClass initializer = null;
if (source.charAt(endPos(node) - 1) == ')' || source.charAt(endPos(node) - 1) == '}') {
initializer = convert(node.getInitializer());
}
return new J.EnumValue(randomId(), fmt, Markers.EMPTY, annotations, name, initializer);
}
@Override
public J visitForLoop(ForLoopTree node, Space fmt) {
skip("for");
Space ctrlPrefix = sourceBefore("(");
List> init = node.getInitializer().isEmpty() ?
singletonList(padRight(new J.Empty(randomId(), sourceBefore(";"), Markers.EMPTY), EMPTY)) :
convertStatements(node.getInitializer(), t ->
positionOfNext(",", ';') == -1 ?
semiDelim.apply(t) :
commaDelim.apply(t)
);
JRightPadded condition = convertOrNull(node.getCondition(), semiDelim);
if (condition == null) {
condition = padRight(new J.Empty(randomId(), sourceBefore(";"), Markers.EMPTY), EMPTY);
}
List> update;
if (node.getUpdate().isEmpty()) {
update = singletonList(padRight(new J.Empty(randomId(), sourceBefore(")"), Markers.EMPTY), EMPTY));
} else {
List extends ExpressionStatementTree> nodeUpdate = node.getUpdate();
update = new ArrayList<>(nodeUpdate.size());
for (int i = 0; i < nodeUpdate.size(); i++) {
ExpressionStatementTree tree = nodeUpdate.get(i);
update.add(convert(tree, i == nodeUpdate.size() - 1 ? t -> sourceBefore(")") : commaDelim));
}
}
return new J.ForLoop(randomId(), fmt, Markers.EMPTY,
new J.ForLoop.Control(randomId(), ctrlPrefix, Markers.EMPTY, init, condition, update),
convert(node.getStatement(), this::statementDelim));
}
@Override
public J visitIdentifier(IdentifierTree node, Space fmt) {
String name = node.getName().toString();
cursor += name.length();
JCIdent ident = (JCIdent) node;
// no `JavaType.Method` attribution for `super()` and `this()`
JavaType type = ident.sym != null && ident.sym.isConstructor() ? null : typeMapping.type(ident);
return new J.Identifier(randomId(), fmt, Markers.EMPTY, emptyList(), name, type, typeMapping.variableType(ident.sym));
}
@Override
public J visitIf(IfTree node, Space fmt) {
skip("if");
return new J.If(randomId(), fmt, Markers.EMPTY,
convert(node.getCondition()),
convert(node.getThenStatement(), this::statementDelim),
node.getElseStatement() instanceof JCTree.JCStatement ?
new J.If.Else(randomId(), sourceBefore("else"), Markers.EMPTY, convert(node.getElseStatement(), this::statementDelim)) :
null);
}
@Override
public J visitImport(ImportTree node, Space fmt) {
skip("import");
return new J.Import(randomId(), fmt, Markers.EMPTY,
new JLeftPadded<>(node.isStatic() ? sourceBefore("static") : EMPTY,
node.isStatic(), Markers.EMPTY),
convert(node.getQualifiedIdentifier()), null);
}
@Override
public J visitInstanceOf(InstanceOfTree node, Space fmt) {
JavaType type = typeMapping.type(node);
return new J.InstanceOf(randomId(), fmt, Markers.EMPTY,
convert(node.getExpression(), t -> sourceBefore("instanceof")),
convert(node.getType()),
node.getPattern() instanceof JCBindingPattern b ?
new J.Identifier(randomId(), sourceBefore(b.getVariable().getName().toString()), Markers.EMPTY, emptyList(), b.getVariable().getName().toString(),
type, typeMapping.variableType(b.var.sym)) : null,
type);
}
@Override
public J visitIntersectionType(IntersectionTypeTree node, Space fmt) {
JContainer bounds = node.getBounds().isEmpty() ? null :
JContainer.build(EMPTY,
convertAll(node.getBounds(), t -> sourceBefore("&"), noDelim), Markers.EMPTY);
return new J.IntersectionType(randomId(), fmt, Markers.EMPTY, bounds);
}
@Override
public J visitLabeledStatement(LabeledStatementTree node, Space fmt) {
skip(node.getLabel().toString());
return new J.Label(randomId(), fmt, Markers.EMPTY,
padRight(new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), node.getLabel().toString(), null, null), sourceBefore(":")),
convert(node.getStatement()));
}
@Override
public J visitLambdaExpression(LambdaExpressionTree node, Space fmt) {
boolean parenthesized = source.charAt(cursor) == '(';
skip("(");
List> paramList;
if (parenthesized && node.getParameters().isEmpty()) {
paramList = singletonList(padRight(new J.Empty(randomId(), sourceBefore(")"), Markers.EMPTY), EMPTY));
} else {
paramList = convertAll(node.getParameters(), commaDelim, t -> parenthesized ? sourceBefore(")") : EMPTY);
}
J.Lambda.Parameters params = new J.Lambda.Parameters(randomId(), EMPTY, Markers.EMPTY, parenthesized, paramList);
Space arrow = sourceBefore("->");
J body;
if (node.getBody() instanceof JCTree.JCBlock) {
Space prefix = sourceBefore("{");
cursor--;
body = convert(node.getBody());
body = body.withPrefix(prefix);
} else {
body = convert(node.getBody());
}
return new J.Lambda(randomId(), fmt, Markers.EMPTY, params, arrow, body, typeMapping.type(node));
}
@Override
public J visitLiteral(LiteralTree node, Space fmt) {
cursor(endPos(node));
Object value = node.getValue();
String valueSource = source.substring(((JCLiteral) node).getStartPosition(), endPos(node));
JavaType.Primitive type = typeMapping.primitive(((JCTree.JCLiteral) node).typetag);
if (value instanceof Character) {
char c = (Character) value;
if (c >= SURR_FIRST && c <= SURR_LAST) {
return new J.Literal(randomId(), fmt, Markers.EMPTY, null, "''",
singletonList(new J.Literal.UnicodeEscape(1,
valueSource.substring(3, valueSource.length() - 1))), type);
}
} else if (JavaType.Primitive.String.equals(type)) {
StringBuilder valueSourceWithoutSurrogates = new StringBuilder();
List unicodeEscapes = null;
int i = 0;
for (int j = 0; j < valueSource.length(); j++) {
char c = valueSource.charAt(j);
if (c == '\\' && j < valueSource.length() - 1 && (j == 0 || valueSource.charAt(j - 1) != '\\')) {
if (valueSource.charAt(j + 1) == 'u' && j < valueSource.length() - 5) {
String codePoint = valueSource.substring(j + 2, j + 6);
int codePointNumeric = Integer.parseInt(codePoint, 16);
if (codePointNumeric >= SURR_FIRST && codePointNumeric <= SURR_LAST) {
if (unicodeEscapes == null) {
unicodeEscapes = new ArrayList<>(1);
}
unicodeEscapes.add(new J.Literal.UnicodeEscape(i, codePoint));
j += 5;
continue;
}
}
}
valueSourceWithoutSurrogates.append(c);
i++;
}
return new J.Literal(randomId(), fmt, Markers.EMPTY,
unicodeEscapes == null ? value : valueSourceWithoutSurrogates.toString(),
valueSourceWithoutSurrogates.toString(), unicodeEscapes, type);
}
return new J.Literal(randomId(), fmt, Markers.EMPTY, value, valueSource, null, type);
}
@Override
public J visitMemberReference(MemberReferenceTree node, Space fmt) {
JCMemberReference ref = (JCMemberReference) node;
String referenceName;
switch (ref.getMode()) {
case NEW:
referenceName = "new";
break;
case INVOKE:
default:
referenceName = node.getName().toString();
break;
}
JavaType.Method methodReferenceType = null;
if (ref.sym instanceof Symbol.MethodSymbol) {
Symbol.MethodSymbol methodSymbol = (Symbol.MethodSymbol) ref.sym;
methodReferenceType = typeMapping.methodInvocationType(methodSymbol.type, methodSymbol);
}
JavaType.Variable fieldReferenceType = null;
if (ref.sym instanceof Symbol.VarSymbol) {
Symbol.VarSymbol varSymbol = (Symbol.VarSymbol) ref.sym;
fieldReferenceType = typeMapping.variableType(varSymbol);
}
return new J.MemberReference(randomId(),
fmt,
Markers.EMPTY,
padRight(convert(ref.expr), sourceBefore("::")),
convertTypeParameters(node.getTypeArguments()),
padLeft(whitespace(), new J.Identifier(randomId(),
sourceBefore(referenceName),
Markers.EMPTY,
emptyList(),
referenceName,
null, null)),
typeMapping.type(node),
methodReferenceType,
fieldReferenceType
);
}
@Override
public J visitMemberSelect(MemberSelectTree node, Space fmt) {
JCFieldAccess fieldAccess = (JCFieldAccess) node;
JavaType type = typeMapping.type(node);
return new J.FieldAccess(randomId(), fmt, Markers.EMPTY,
convert(fieldAccess.selected),
padLeft(sourceBefore("."), new J.Identifier(randomId(),
sourceBefore(fieldAccess.name.toString()), Markers.EMPTY,
emptyList(), fieldAccess.name.toString(), type, typeMapping.variableType(fieldAccess.sym))),
type);
}
@Override
public J visitMethodInvocation(MethodInvocationTree node, Space fmt) {
JCExpression jcSelect = ((JCTree.JCMethodInvocation) node).getMethodSelect();
JRightPadded select = null;
if (jcSelect instanceof JCFieldAccess) {
select = convert(((JCFieldAccess) jcSelect).selected, t -> sourceBefore("."));
} else if (!(jcSelect instanceof JCIdent)) {
throw new IllegalStateException("Unexpected method select type " + jcSelect.getClass().getSimpleName());
}
// generic type parameters can only exist on qualified targets
JContainer typeParams = null;
if (!node.getTypeArguments().isEmpty()) {
typeParams = JContainer.build(sourceBefore("<"), convertAll(node.getTypeArguments(), commaDelim,
t -> sourceBefore(">")), Markers.EMPTY);
}
J.Identifier name;
if (jcSelect instanceof JCFieldAccess) {
String selectName = ((JCFieldAccess) jcSelect).name.toString();
name = new J.Identifier(randomId(), sourceBefore(selectName), Markers.EMPTY, emptyList(), selectName, null, null);
} else {
name = convert(jcSelect);
}
JContainer args = JContainer.build(sourceBefore("("), node.getArguments().isEmpty() ?
singletonList(padRight(new J.Empty(randomId(), sourceBefore(")"), Markers.EMPTY), EMPTY)) :
convertAll(node.getArguments(), commaDelim, t -> sourceBefore(")")), Markers.EMPTY);
Symbol methodSymbol = (jcSelect instanceof JCFieldAccess) ? ((JCFieldAccess) jcSelect).sym :
((JCIdent) jcSelect).sym;
return new J.MethodInvocation(randomId(), fmt, Markers.EMPTY, select, typeParams, name, args,
typeMapping.methodInvocationType(jcSelect.type, methodSymbol));
}
@Override
public J visitMethod(MethodTree node, Space fmt) {
JCMethodDecl jcMethod = (JCMethodDecl) node;
Map annotationPosTable = mapAnnotations(node.getModifiers().getAnnotations(),
new HashMap<>(node.getModifiers().getAnnotations().size()));
ReloadableJava21ModifierResults modifierResults = sortedModifiersAndAnnotations(node.getModifiers(), annotationPosTable);
J.TypeParameters typeParams;
if (node.getTypeParameters().isEmpty()) {
typeParams = null;
} else {
List typeParamsAnnotations = collectAnnotations(annotationPosTable);
// see https://docs.oracle.com/javase/tutorial/java/generics/methods.html
typeParams = new J.TypeParameters(randomId(), sourceBefore("<"), Markers.EMPTY,
typeParamsAnnotations,
convertAll(node.getTypeParameters(), commaDelim, t -> sourceBefore(">")));
}
List returnTypeAnnotations = collectAnnotations(annotationPosTable);
TypeTree returnType = convertOrNull(node.getReturnType());
if (returnType != null && !returnTypeAnnotations.isEmpty()) {
returnType = new J.AnnotatedType(randomId(), Space.EMPTY, Markers.EMPTY,
returnTypeAnnotations, returnType);
}
Symbol.MethodSymbol nodeSym = jcMethod.sym;
J.MethodDeclaration.IdentifierWithAnnotations name;
if ("".equals(node.getName().toString())) {
String owner = null;
if (nodeSym == null) {
for (Tree tree : getCurrentPath()) {
if (tree instanceof JCClassDecl) {
owner = ((JCClassDecl) tree).getSimpleName().toString();
break;
}
}
if (owner == null) {
throw new IllegalStateException("Should have been able to locate an owner");
}
} else {
owner = jcMethod.sym.owner.name.toString();
}
name = new J.MethodDeclaration.IdentifierWithAnnotations(new J.Identifier(randomId(), sourceBefore(owner),
Markers.EMPTY, emptyList(), owner, null, null), returnType == null ? returnTypeAnnotations : emptyList());
} else {
name = new J.MethodDeclaration.IdentifierWithAnnotations(new J.Identifier(randomId(), sourceBefore(node.getName().toString(), null), Markers.EMPTY,
emptyList(), node.getName().toString(), null, null), returnType == null ? returnTypeAnnotations : emptyList());
}
boolean isCompactConstructor = nodeSym != null && (nodeSym.flags() & Flags.COMPACT_RECORD_CONSTRUCTOR) != 0;
JContainer params = JContainer.empty();
if (!isCompactConstructor) {
Space paramFmt = sourceBefore("(");
params = !node.getParameters().isEmpty() ?
JContainer.build(paramFmt, convertAll(node.getParameters(), commaDelim, t -> sourceBefore(")")),
Markers.EMPTY) :
JContainer.build(paramFmt, singletonList(padRight(new J.Empty(randomId(), sourceBefore(")"),
Markers.EMPTY), EMPTY)), Markers.EMPTY);
}
JContainer throws_ = node.getThrows().isEmpty() ? null :
JContainer.build(sourceBefore("throws"), convertAll(node.getThrows(), commaDelim, noDelim),
Markers.EMPTY);
J.Block body = convertOrNull(node.getBody());
JLeftPadded defaultValue = node.getDefaultValue() == null ? null :
padLeft(sourceBefore("default"), convert(node.getDefaultValue()));
J.MethodDeclaration md = new J.MethodDeclaration(randomId(), fmt, Markers.EMPTY,
modifierResults.getLeadingAnnotations(),
modifierResults.getModifiers(), typeParams,
returnType, name, params, throws_, body, defaultValue,
typeMapping.methodDeclarationType(jcMethod.sym, null));
md = isCompactConstructor ? md.withMarkers(md.getMarkers().addIfAbsent(new CompactConstructor(randomId()))) : md;
return md;
}
@Override
public J visitNewArray(NewArrayTree node, Space fmt) {
skip("new");
JCExpression jcVarType = ((JCNewArray) node).elemtype;
TypeTree typeExpr;
if (jcVarType instanceof JCArrayTypeTree) {
// we'll capture the array dimensions in a bit, just convert the element type
JCExpression elementType = ((JCArrayTypeTree) jcVarType).elemtype;
while (elementType instanceof JCArrayTypeTree) {
elementType = ((JCArrayTypeTree) elementType).elemtype;
}
typeExpr = convertOrNull(elementType);
} else {
typeExpr = convertOrNull(jcVarType);
}
List extends ExpressionTree> nodeDimensions = node.getDimensions();
List dimensions = new ArrayList<>(nodeDimensions.size());
for (ExpressionTree dim : nodeDimensions) {
dimensions.add(new J.ArrayDimension(
randomId(),
sourceBefore("["),
Markers.EMPTY,
convert(dim, t -> sourceBefore("]"))));
}
while (true) {
int beginBracket = indexOfNextNonWhitespace(cursor, source);
if (source.charAt(beginBracket) == '[') {
int endBracket = indexOfNextNonWhitespace(beginBracket + 1, source);
dimensions.add(new J.ArrayDimension(
randomId(),
format(source, cursor, beginBracket),
Markers.EMPTY,
padRight(new J.Empty(randomId(), format(source, beginBracket + 1, endBracket), Markers.EMPTY), EMPTY)));
cursor = endBracket + 1;
} else {
break;
}
}
JContainer initializer = node.getInitializers() == null ? null :
JContainer.build(sourceBefore("{"), node.getInitializers().isEmpty() ?
singletonList(padRight(new J.Empty(randomId(), sourceBefore("}"), Markers.EMPTY), EMPTY)) :
convertAll(node.getInitializers(), commaDelim, t -> sourceBefore("}")), Markers.EMPTY);
return new J.NewArray(randomId(), fmt, Markers.EMPTY, typeExpr, dimensions,
initializer, typeMapping.type(node));
}
@Override
public J visitNewClass(NewClassTree node, Space fmt) {
JRightPadded encl = node.getEnclosingExpression() == null ? null :
convert(node.getEnclosingExpression(), t -> sourceBefore("."));
Space whitespaceBeforeNew = EMPTY;
Tree parent = getCurrentPath().getParentPath().getLeaf();
if (!(parent instanceof JCVariableDecl && ((((JCVariableDecl) parent).mods.flags & Flags.ENUM) != 0))) {
whitespaceBeforeNew = sourceBefore("new");
}
// for enum definitions with anonymous class initializers, endPos of node identifier will be -1
TypeTree clazz = endPos(node.getIdentifier()) >= 0 ? convertOrNull(node.getIdentifier()) : null;
JContainer args;
if (positionOfNext("(", '{') > -1) {
args = JContainer.build(sourceBefore("("),
node.getArguments().isEmpty() ?
singletonList(padRight(new J.Empty(randomId(), sourceBefore(")"), Markers.EMPTY), EMPTY)) :
convertAll(node.getArguments(), commaDelim, t -> sourceBefore(")")), Markers.EMPTY);
} else {
args = JContainer.empty()
.withMarkers(Markers.build(singletonList(new OmitParentheses(randomId()))));
}
J.Block body = null;
if (node.getClassBody() != null) {
Space bodyPrefix = sourceBefore("{");
// we don't care about the compiler-inserted default constructor,
// since it will never be subject to refactoring
List members = new ArrayList<>(node.getClassBody().getMembers().size());
for (Tree m : node.getClassBody().getMembers()) {
if (!(m instanceof JCMethodDecl) || (((JCMethodDecl) m).getModifiers().flags & Flags.GENERATEDCONSTR) == 0L) {
members.add(m);
}
}
body = new J.Block(randomId(), bodyPrefix, Markers.EMPTY, new JRightPadded<>(false, EMPTY, Markers.EMPTY),
convertStatements(members), sourceBefore("}"));
}
JCNewClass jcNewClass = (JCNewClass) node;
JavaType.Method constructorType = typeMapping.methodInvocationType(jcNewClass.constructorType, jcNewClass.constructor);
if (constructorType != null && jcNewClass.clazz.type.isParameterized() && node.getClassBody() == null) {
constructorType = constructorType.withReturnType(typeMapping.type(jcNewClass.clazz.type));
}
return new J.NewClass(randomId(), fmt, Markers.EMPTY, encl, whitespaceBeforeNew,
clazz, args, body, constructorType);
}
@Override
public J visitParameterizedType(ParameterizedTypeTree node, Space fmt) {
return new J.ParameterizedType(randomId(), fmt, Markers.EMPTY, convert(node.getType()), convertTypeParameters(node.getTypeArguments()), typeMapping.type(node));
}
@Override
public J visitParenthesized(ParenthesizedTree node, Space fmt) {
skip("(");
Tree parent = getCurrentPath().getParentPath().getLeaf();
switch (parent.getKind()) {
case CATCH:
case DO_WHILE_LOOP:
case IF:
case SWITCH:
case SWITCH_EXPRESSION:
case SYNCHRONIZED:
case TYPE_CAST:
case WHILE_LOOP:
return new J.ControlParentheses(randomId(), fmt, Markers.EMPTY,
convert(node.getExpression(), t -> sourceBefore(")")));
default:
return new J.Parentheses(randomId(), fmt, Markers.EMPTY,
convert(node.getExpression(), t -> sourceBefore(")")));
}
}
@Override
public J visitPrimitiveType(PrimitiveTypeTree node, Space fmt) {
cursor(endPos(node));
JavaType.Primitive primitiveType;
switch (node.getPrimitiveTypeKind()) {
case BOOLEAN:
primitiveType = JavaType.Primitive.Boolean;
break;
case BYTE:
primitiveType = JavaType.Primitive.Byte;
break;
case CHAR:
primitiveType = JavaType.Primitive.Char;
break;
case DOUBLE:
primitiveType = JavaType.Primitive.Double;
break;
case FLOAT:
primitiveType = JavaType.Primitive.Float;
break;
case INT:
primitiveType = JavaType.Primitive.Int;
break;
case LONG:
primitiveType = JavaType.Primitive.Long;
break;
case SHORT:
primitiveType = JavaType.Primitive.Short;
break;
case VOID:
primitiveType = JavaType.Primitive.Void;
break;
default:
throw new IllegalArgumentException("Unknown primitive type " + node.getPrimitiveTypeKind());
}
return new J.Primitive(randomId(), fmt, Markers.EMPTY, primitiveType);
}
@Override
public J visitReturn(ReturnTree node, Space fmt) {
skip("return");
Expression expression = convertOrNull(node.getExpression());
return new J.Return(randomId(), fmt, Markers.EMPTY, expression);
}
@Override
public J visitSwitch(SwitchTree node, Space fmt) {
skip("switch");
return new J.Switch(randomId(), fmt, Markers.EMPTY,
convert(node.getExpression()),
new J.Block(randomId(), sourceBefore("{"), Markers.EMPTY, new JRightPadded<>(false, EMPTY, Markers.EMPTY),
convertAll(node.getCases(), noDelim, noDelim), sourceBefore("}")));
}
@Override
public J visitSwitchExpression(SwitchExpressionTree node, Space fmt) {
skip("switch");
return new J.SwitchExpression(randomId(), fmt, Markers.EMPTY,
convert(node.getExpression()),
new J.Block(randomId(), sourceBefore("{"), Markers.EMPTY, new JRightPadded<>(false, EMPTY, Markers.EMPTY),
convertAll(node.getCases(), noDelim, noDelim), sourceBefore("}")));
}
@Override
public J visitSynchronized(SynchronizedTree node, Space fmt) {
skip("synchronized");
return new J.Synchronized(randomId(), fmt, Markers.EMPTY, convert(node.getExpression()),
convert(node.getBlock()));
}
@Override
public J visitThrow(ThrowTree node, Space fmt) {
skip("throw");
return new J.Throw(randomId(), fmt, Markers.EMPTY, convert(node.getExpression()));
}
@Override
public J visitTry(TryTree node, Space fmt) {
skip("try");
JContainer resources;
if (node.getResources().isEmpty()) {
resources = null;
} else {
Space before = sourceBefore("(");
List> resourceList = new ArrayList<>(node.getResources().size());
for (int i = 0; i < node.getResources().size(); i++) {
Tree resource = node.getResources().get(i);
J resourceVar = convert(resource);
boolean semicolonPresent = true;
if (i == node.getResources().size() - 1) {
semicolonPresent = positionOfNext(";", ')') > 0;
}
Space resourcePrefix = resourceVar.getPrefix();
resourceVar = resourceVar.withPrefix(EMPTY); // moved to the containing Try.Resource
if (semicolonPresent && resourceVar instanceof J.VariableDeclarations) {
J.VariableDeclarations resourceVarDecl = (J.VariableDeclarations) resourceVar;
resourceVar = resourceVarDecl.getPadding().withVariables(Space.formatLastSuffix(resourceVarDecl
.getPadding().getVariables(), sourceBefore(";")));
}
J.Try.Resource tryResource = new J.Try.Resource(randomId(), resourcePrefix, Markers.EMPTY,
resourceVar.withPrefix(EMPTY), semicolonPresent);
// Starting in Java 9, you can have an identifier in the try with resource, if an Identifier
// is parsed, the cursor is advance to the trailing semicolon. We do not want to pick this up in the
// prefix of the next resource (or the right padding of identifier (if it is the last resource)
skip(";");
resourceList.add(padRight(tryResource, i == node.getResources().size() - 1 ?
sourceBefore(")") : EMPTY));
}
resources = JContainer.build(before, resourceList, Markers.EMPTY);
}
J.Block block = convert(node.getBlock());
List catches = convertAll(node.getCatches());
JLeftPadded finally_ = node.getFinallyBlock() == null ? null :
padLeft(sourceBefore("finally"), convert(node.getFinallyBlock()));
return new J.Try(randomId(), fmt, Markers.EMPTY, resources, block, catches, finally_);
}
@Override
public J visitTypeCast(TypeCastTree node, Space fmt) {
return new J.TypeCast(randomId(), fmt, Markers.EMPTY,
new J.ControlParentheses<>(randomId(),
sourceBefore("("), Markers.EMPTY,
convert(node.getType(), t -> sourceBefore(")"))),
convert(node.getExpression()));
}
@Override
public J visitAnnotatedType(AnnotatedTypeTree node, Space fmt) {
Map annotationPosTable = mapAnnotations(node.getAnnotations(),
new HashMap<>(node.getAnnotations().size()));
List leadingAnnotations = leadingAnnotations(annotationPosTable);
if (!annotationPosTable.isEmpty()) {
if (node.getUnderlyingType() instanceof JCFieldAccess) {
return new J.AnnotatedType(randomId(), fmt, Markers.EMPTY, leadingAnnotations, annotatedTypeTree(node.getUnderlyingType(), annotationPosTable));
} else if (node.getUnderlyingType() instanceof JCArrayTypeTree) {
return new J.AnnotatedType(randomId(), fmt, Markers.EMPTY, leadingAnnotations, arrayTypeTree(node, annotationPosTable));
}
}
return new J.AnnotatedType(randomId(), fmt, Markers.EMPTY, leadingAnnotations, convert(node.getUnderlyingType()));
}
private Map mapAnnotations(List extends AnnotationTree> annotations, Map annotationPosTable) {
for (AnnotationTree annotationNode : annotations) {
JCAnnotation annotation = (JCAnnotation) annotationNode;
annotationPosTable.put(annotation.pos, annotation);
}
return annotationPosTable;
}
private List leadingAnnotations(Map annotationPosTable) {
List annotations = new ArrayList<>(annotationPosTable.size());
int saveCursor = cursor;
whitespace();
while (annotationPosTable.containsKey(cursor)) {
JCTree.JCAnnotation jcAnnotation = annotationPosTable.get(cursor);
annotationPosTable.remove(cursor);
cursor = saveCursor;
J.Annotation ann = convert(jcAnnotation);
annotations.add(ann);
saveCursor = cursor;
whitespace();
}
cursor = saveCursor;
return annotations.isEmpty() ? emptyList() : annotations;
}
private TypeTree annotatedTypeTree(Tree node, Map annotationPosTable) {
if (node instanceof JCFieldAccess) {
Space prefix = whitespace();
JCFieldAccess fieldAccess = (JCFieldAccess) node;
JavaType type = typeMapping.type(node);
Expression select = (Expression) annotatedTypeTree(fieldAccess.selected, annotationPosTable);
Space dotPrefix = sourceBefore(".");
List annotations = leadingAnnotations(annotationPosTable);
return new J.FieldAccess(randomId(), prefix, Markers.EMPTY,
select,
padLeft(dotPrefix, new J.Identifier(randomId(),
sourceBefore(fieldAccess.name.toString()), Markers.EMPTY,
annotations, fieldAccess.name.toString(), type, typeMapping.variableType(fieldAccess.sym))),
type
);
}
return convert(node);
}
private TypeTree arrayTypeTree(Tree tree, Map annotationPosTable) {
Tree typeIdent = tree;
int count = 0;
JCArrayTypeTree arrayTypeTree = null;
while (typeIdent instanceof JCAnnotatedType || typeIdent instanceof JCArrayTypeTree) {
if (typeIdent instanceof JCAnnotatedType) {
typeIdent = ((JCAnnotatedType) typeIdent).getUnderlyingType();
}
if (typeIdent instanceof JCArrayTypeTree) {
if (count == 0) {
arrayTypeTree = (JCArrayTypeTree) typeIdent;
}
count++;
typeIdent = ((JCArrayTypeTree) typeIdent).getType();
}
}
Space prefix = whitespace();
TypeTree elemType = convert(typeIdent);
List annotations = leadingAnnotations(annotationPosTable);
JLeftPadded dimension = padLeft(sourceBefore("["), sourceBefore("]"));
assert arrayTypeTree != null;
return new J.ArrayType(randomId(), prefix, Markers.EMPTY,
count == 1 ? elemType : mapDimensions(elemType, arrayTypeTree.getType(), annotationPosTable),
annotations,
dimension,
typeMapping.type(tree));
}
private TypeTree mapDimensions(TypeTree baseType, Tree tree, Map annotationPosTable) {
Tree typeIdent = tree;
if (typeIdent instanceof JCAnnotatedType) {
mapAnnotations(((JCAnnotatedType) typeIdent).getAnnotations(), annotationPosTable);
typeIdent = ((JCAnnotatedType) typeIdent).getUnderlyingType();
}
if (typeIdent instanceof JCArrayTypeTree) {
List annotations = leadingAnnotations(annotationPosTable);
int saveCursor = cursor;
whitespace();
if (source.startsWith("[", cursor)) {
cursor = saveCursor;
JLeftPadded dimension = padLeft(sourceBefore("["), sourceBefore("]"));
return new J.ArrayType(
randomId(),
EMPTY,
Markers.EMPTY,
mapDimensions(baseType, ((JCArrayTypeTree) typeIdent).elemtype, annotationPosTable),
annotations,
dimension,
typeMapping.type(tree)
);
}
cursor = saveCursor;
}
return baseType;
}
@Override
public J visitTypeParameter(TypeParameterTree node, Space fmt) {
List annotations = convertAll(node.getAnnotations());
Expression name = buildName(node.getName().toString())
.withPrefix(sourceBefore(node.getName().toString()));
// see https://docs.oracle.com/javase/tutorial/java/generics/bounded.html
JContainer bounds = node.getBounds().isEmpty() ? null :
JContainer.build(sourceBefore("extends"),
convertAll(node.getBounds(), t -> sourceBefore("&"), noDelim), Markers.EMPTY);
return new J.TypeParameter(randomId(), fmt, Markers.EMPTY, annotations, emptyList(), name, bounds);
}
private T buildName(String fullyQualifiedName) {
String[] parts = fullyQualifiedName.split("\\.");
String fullName = "";
Expression expr = null;
for (int i = 0; i < parts.length; i++) {
String part = parts[i];
if (i == 0) {
fullName = part;
expr = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), part, null, null);
} else {
fullName += "." + part;
int endOfPrefix = indexOfNextNonWhitespace(0, part);
Space identFmt = endOfPrefix > 0 ? format(part, 0, endOfPrefix) : EMPTY;
Matcher whitespaceSuffix = whitespaceSuffixPattern.matcher(part);
//noinspection ResultOfMethodCallIgnored
whitespaceSuffix.matches();
Space namePrefix = i == parts.length - 1 ? Space.EMPTY : format(whitespaceSuffix.group(1));
expr = new J.FieldAccess(
randomId(),
EMPTY,
Markers.EMPTY,
expr,
padLeft(namePrefix, new J.Identifier(randomId(), identFmt, Markers.EMPTY, emptyList(), part.trim(), null, null)),
(Character.isUpperCase(part.charAt(0)) || i == parts.length - 1) ?
JavaType.ShallowClass.build(fullName) :
null
);
}
}
//noinspection unchecked,ConstantConditions
return (T) expr;
}
@Override
public J visitUnionType(UnionTypeTree node, Space fmt) {
return new J.MultiCatch(randomId(), fmt, Markers.EMPTY,
convertAll(node.getTypeAlternatives(), t -> sourceBefore("|"), noDelim));
}
@Override
public J visitUnary(UnaryTree node, Space fmt) {
JCUnary unary = (JCUnary) node;
Tag tag = unary.getTag();
JLeftPadded op;
Expression expr;
switch (tag) {
case POS:
skip("+");
op = padLeft(EMPTY, J.Unary.Type.Positive);
expr = convert(unary.arg);
break;
case NEG:
skip("-");
op = padLeft(EMPTY, J.Unary.Type.Negative);
expr = convert(unary.arg);
break;
case PREDEC:
skip("--");
op = padLeft(EMPTY, J.Unary.Type.PreDecrement);
expr = convert(unary.arg);
break;
case PREINC:
skip("++");
op = padLeft(EMPTY, J.Unary.Type.PreIncrement);
expr = convert(unary.arg);
break;
case POSTDEC:
expr = convert(unary.arg);
op = padLeft(sourceBefore("--"), J.Unary.Type.PostDecrement);
break;
case POSTINC:
expr = convert(unary.arg);
op = padLeft(sourceBefore("++"), J.Unary.Type.PostIncrement);
break;
case COMPL:
skip("~");
op = padLeft(EMPTY, J.Unary.Type.Complement);
expr = convert(unary.arg);
break;
case NOT:
skip("!");
op = padLeft(EMPTY, J.Unary.Type.Not);
expr = convert(unary.arg);
break;
default:
throw new IllegalArgumentException("Unexpected unary tag " + tag);
}
return new J.Unary(randomId(), fmt, Markers.EMPTY, op, expr, typeMapping.type(node));
}
@Override
public J visitVariable(VariableTree node, Space fmt) {
return hasFlag(node.getModifiers(), Flags.ENUM) ?
visitEnumVariable(node, fmt) :
visitVariables(singletonList(node), fmt); // method arguments cannot be multi-declarations
}
private J.VariableDeclarations visitVariables(List nodes, Space fmt) {
JCTree.JCVariableDecl node = (JCVariableDecl) nodes.get(0);
JCExpression vartype = node.vartype;
Map annotationPosTable = mapAnnotations(node.getModifiers().getAnnotations(),
new HashMap<>(node.getModifiers().getAnnotations().size()));
ReloadableJava21ModifierResults modifierResults = sortedModifiersAndAnnotations(node.getModifiers(), annotationPosTable);
List typeExprAnnotations = collectAnnotations(annotationPosTable);
TypeTree typeExpr;
if (vartype == null) {
typeExpr = null;
} else if (endPos(vartype) < 0) {
if ((node.sym.flags() & Flags.PARAMETER) > 0) {
// this is a lambda parameter with an inferred type expression
typeExpr = null;
} else {
typeExpr = new J.Identifier(randomId(), sourceBefore("var"), Markers.EMPTY, emptyList(), "var", typeMapping.type(vartype), null);
typeExpr = typeExpr.withMarkers(typeExpr.getMarkers().add(JavaVarKeyword.build()));
}
} else if (vartype instanceof JCArrayTypeTree) {
JCExpression elementType = vartype;
while (elementType instanceof JCArrayTypeTree || elementType instanceof JCAnnotatedType) {
if (elementType instanceof JCAnnotatedType) {
elementType = ((JCAnnotatedType) elementType).underlyingType;
}
if (elementType instanceof JCArrayTypeTree) {
elementType = ((JCArrayTypeTree) elementType).elemtype;
}
}
String check = source.substring(elementType.getEndPosition(endPosTable)).trim();
typeExpr = check.startsWith("@") || check.startsWith("[") ? convert(vartype) :
// we'll capture the array dimensions in a bit, just convert the element type
convert(elementType);
} else {
typeExpr = convert(vartype);
}
if (typeExpr != null && !typeExprAnnotations.isEmpty()) {
Space prefix = typeExprAnnotations.get(0).getPrefix();
typeExpr = new J.AnnotatedType(randomId(), prefix, Markers.EMPTY, ListUtils.mapFirst(typeExprAnnotations, a -> a.withPrefix(EMPTY)), typeExpr);
}
List> beforeDimensions = emptyList();
Space varargs = null;
if (typeExpr != null && typeExpr.getMarkers().findFirst(JavaVarKeyword.class).isEmpty()) {
int varargStart = indexOfNextNonWhitespace(cursor, source);
if (source.startsWith("...", varargStart)) {
varargs = format(source, cursor, varargStart);
cursor = varargStart + 3;
}
}
List> vars = new ArrayList<>(nodes.size());
for (int i = 0; i < nodes.size(); i++) {
JCVariableDecl n = (JCVariableDecl) nodes.get(i);
Space namedVarPrefix = sourceBefore(n.getName().toString());
JavaType type = typeMapping.type(n);
J.Identifier name = new J.Identifier(randomId(), EMPTY, Markers.EMPTY, emptyList(), n.getName().toString(),
type instanceof JavaType.Variable ? ((JavaType.Variable) type).getType() : type,
type instanceof JavaType.Variable ? (JavaType.Variable) type : null);
List> dimensionsAfterName = arrayDimensions();
vars.add(
padRight(
new J.VariableDeclarations.NamedVariable(randomId(), namedVarPrefix, Markers.EMPTY,
name,
dimensionsAfterName,
n.init != null ? padLeft(sourceBefore("="), convertOrNull(n.init)) : null,
(JavaType.Variable) typeMapping.type(n)
),
i == nodes.size() - 1 ? EMPTY : sourceBefore(",")
)
);
}
return new J.VariableDeclarations(randomId(), fmt, Markers.EMPTY, modifierResults.getLeadingAnnotations(), modifierResults.getModifiers(), typeExpr, varargs, beforeDimensions, vars);
}
private List> arrayDimensions() {
List> dims = null;
while (true) {
int beginBracket = indexOfNextNonWhitespace(cursor, source);
if (source.charAt(beginBracket) == '[') {
int endBracket = indexOfNextNonWhitespace(beginBracket + 1, source);
if (dims == null) {
dims = new ArrayList<>(2);
}
dims.add(padLeft(format(source, cursor, beginBracket),
format(source, beginBracket + 1, endBracket)));
cursor = endBracket + 1;
} else {
break;
}
}
return dims == null ? emptyList() : dims;
}
@Override
public J visitWhileLoop(WhileLoopTree node, Space fmt) {
skip("while");
return new J.WhileLoop(randomId(), fmt, Markers.EMPTY,
convert(node.getCondition()),
convert(node.getStatement(), this::statementDelim));
}
@Override
public J visitWildcard(WildcardTree node, Space fmt) {
skip("?");
JCWildcard wildcard = (JCWildcard) node;
JLeftPadded bound;
switch (wildcard.kind.kind) {
case EXTENDS:
bound = padLeft(sourceBefore("extends"), J.Wildcard.Bound.Extends);
break;
case SUPER:
bound = padLeft(sourceBefore("super"), J.Wildcard.Bound.Super);
break;
case UNBOUND:
default:
bound = null;
}
return new J.Wildcard(randomId(), fmt, Markers.EMPTY, bound, convertOrNull(wildcard.inner));
}
/**
* --------------
* Conversion utilities
* --------------
*/
private J2 convert(Tree t) {
try {
String prefix = source.substring(cursor, max(((JCTree) t).getStartPosition(), cursor));
cursor += prefix.length();
@SuppressWarnings("unchecked") J2 j = (J2) scan(t, formatWithCommentTree(prefix, (JCTree) t, docCommentTable.getCommentTree((JCTree) t)));
return j;
} catch (Throwable ex) {
// this SHOULD never happen, but is here simply as a diagnostic measure in the event of unexpected exceptions
StringBuilder message = new StringBuilder("Failed to convert for the following cursor stack:");
message.append("--- BEGIN PATH ---\n");
List paths = stream(getCurrentPath().spliterator(), false).toList();
for (int i = paths.size(); i-- > 0; ) {
JCTree tree = (JCTree) paths.get(i);
if (tree instanceof JCCompilationUnit) {
message.append("JCCompilationUnit(sourceFile = ").append(((JCCompilationUnit) tree).sourcefile.getName()).append(")\n");
} else if (tree instanceof JCClassDecl) {
message.append("JCClassDecl(name = ").append(((JCClassDecl) tree).name).append(", line = ").append(lineNumber(tree)).append(")\n");
} else if (tree instanceof JCVariableDecl) {
message.append("JCVariableDecl(name = ").append(((JCVariableDecl) tree).name).append(", line = ").append(lineNumber(tree)).append(")\n");
} else {
message.append(tree.getClass().getSimpleName()).append("(line = ").append(lineNumber(tree)).append(")\n");
}
}
message.append("--- END PATH ---\n");
ctx.getOnError().accept(new JavaParsingException(message.toString(), ex));
throw ex;
}
}
private JRightPadded convert(Tree t, Function suffix) {
J2 j = convert(t);
@SuppressWarnings("ConstantConditions") JRightPadded rightPadded = j == null ? null :
new JRightPadded<>(j, suffix.apply(t), Markers.EMPTY);
cursor(max(endPos(t), cursor)); // if there is a non-empty suffix, the cursor may have already moved past it
return rightPadded;
}
private long lineNumber(Tree tree) {
return source.substring(0, ((JCTree) tree).getStartPosition()).chars().filter(c -> c == '\n').count() + 1;
}
private @Nullable T convertOrNull(@Nullable Tree t) {
return t == null ? null : convert(t);
}
private @Nullable JRightPadded convertOrNull(@Nullable Tree t, Function suffix) {
return t == null ? null : convert(t, suffix);
}
private List convertAll(List extends Tree> trees) {
List converted = new ArrayList<>(trees.size());
for (Tree tree : trees) {
converted.add(convert(tree));
}
return converted;
}
private List> convertAll(List extends Tree> trees,
Function innerSuffix,
Function suffix) {
int size = trees.size();
if (size == 0) {
return emptyList();
}
List> converted = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
converted.add(convert(trees.get(i), i == size - 1 ? suffix : innerSuffix));
}
return converted;
}
private @Nullable JContainer convertTypeParameters(@Nullable List extends Tree> typeArguments) {
if (typeArguments == null) {
return null;
}
Space typeArgPrefix = sourceBefore("<");
List> params;
if (typeArguments.isEmpty()) {
// raw type, see http://docs.oracle.com/javase/tutorial/java/generics/rawTypes.html
// adding space before > as a suffix to be consistent with space before > for non-empty lists of type args
params = singletonList(padRight(new J.Empty(randomId(), sourceBefore(">"), Markers.EMPTY), EMPTY));
} else {
params = convertAll(typeArguments, commaDelim, t -> sourceBefore(">"));
}
return JContainer.build(typeArgPrefix, params, Markers.EMPTY);
}
private Space statementDelim(@Nullable Tree t) {
switch (t.getKind()) {
case ASSERT:
case ASSIGNMENT:
case BREAK:
case CONTINUE:
case DO_WHILE_LOOP:
case METHOD_INVOCATION:
case NEW_CLASS:
case RETURN:
case THROW:
case EXPRESSION_STATEMENT:
case VARIABLE:
case YIELD:
return sourceBefore(";");
case LABELED_STATEMENT:
return statementDelim(((JCLabeledStatement) t).getStatement());
case METHOD:
JCMethodDecl m = (JCMethodDecl) t;
return sourceBefore(m.body == null || m.defaultValue != null ? ";" : "");
default:
return t instanceof JCAssignOp || t instanceof JCUnary ? sourceBefore(";") : EMPTY;
}
}
private List> convertStatements(@Nullable List extends Tree> trees) {
return convertStatements(trees, this::statementDelim);
}
@SuppressWarnings("unchecked")
private List> convertStatements(@Nullable List extends Tree> trees,
Function suffix) {
if (trees == null || trees.isEmpty()) {
return emptyList();
}
Map> treesGroupedByStartPosition = new LinkedHashMap<>();
for (Tree t : trees) {
treesGroupedByStartPosition.computeIfAbsent(((JCTree) t).getStartPosition(), k -> new ArrayList<>(1)).add(t);
}
List> converted = new ArrayList<>(treesGroupedByStartPosition.size());
for (List extends Tree> treeGroup : treesGroupedByStartPosition.values()) {
if (treeGroup.size() == 1) {
converted.add(convert(treeGroup.get(0), suffix));
} else {
// multi-variable declarations are split into independent overlapping JCVariableDecl's by the OpenJDK AST
String prefix = source.substring(cursor, max(((JCTree) treeGroup.get(0)).getStartPosition(), cursor));
cursor += prefix.length();
Tree last = treeGroup.get(treeGroup.size() - 1);
@SuppressWarnings("unchecked")
J.VariableDeclarations vars = visitVariables((List) treeGroup, format(prefix));
JRightPadded paddedVars = padRight(vars, semiDelim.apply(last));
cursor(max(endPos(last), cursor));
converted.add(paddedVars);
}
}
return converted;
}
/**
* --------------
* Other convenience utilities
* --------------
*/
private int endPos(Tree t) {
return ((JCTree) t).getEndPosition(endPosTable);
}
private Space sourceBefore(String untilDelim) {
return sourceBefore(untilDelim, null);
}
/**
* @return Source from cursor
to next occurrence of untilDelim
,
* and if not found in the remaining source, the empty String. If stop
is reached before
* untilDelim
return the empty String.
*/
private Space sourceBefore(String untilDelim, @Nullable Character stop) {
int delimIndex = positionOfNext(untilDelim, stop);
if (delimIndex < 0) {
return EMPTY; // unable to find this delimiter
}
if (delimIndex == cursor) {
cursor += untilDelim.length();
return EMPTY;
}
Space space = format(source, cursor, delimIndex);
cursor = delimIndex + untilDelim.length(); // advance past the delimiter
return space;
}
private JRightPadded padRight(T tree, Space right) {
return new JRightPadded<>(tree, right, Markers.EMPTY);
}
private JLeftPadded padLeft(Space left, T tree) {
return new JLeftPadded<>(left, tree, Markers.EMPTY);
}
private int positionOfNext(String untilDelim, @Nullable Character stop) {
boolean inMultiLineComment = false;
boolean inSingleLineComment = false;
int delimIndex = cursor;
for (; delimIndex < source.length() - untilDelim.length() + 1; delimIndex++) {
if (inSingleLineComment) {
if (source.charAt(delimIndex) == '\n') {
inSingleLineComment = false;
}
} else {
if (source.length() - untilDelim.length() > delimIndex + 1) {
char c1 = source.charAt(delimIndex);
char c2 = source.charAt(delimIndex + 1);
switch (c1) {
case '/':
switch (c2) {
case '/':
inSingleLineComment = true;
delimIndex++;
break;
case '*':
inMultiLineComment = true;
delimIndex++;
break;
}
break;
case '*':
if (c2 == '/') {
inMultiLineComment = false;
delimIndex += 2;
}
break;
}
}
if (!inMultiLineComment && !inSingleLineComment) {
if (stop != null && source.charAt(delimIndex) == stop)
return -1; // reached stop word before finding the delimiter
if (source.startsWith(untilDelim, delimIndex)) {
break; // found it!
}
}
}
}
return delimIndex > source.length() - untilDelim.length() ? -1 : delimIndex;
}
private final Function semiDelim = ignored -> sourceBefore(";");
private final Function commaDelim = ignored -> sourceBefore(",");
private final Function noDelim = ignored -> EMPTY;
private Space whitespace() {
int nextNonWhitespace = indexOfNextNonWhitespace(cursor, source);
if (nextNonWhitespace == cursor) {
return EMPTY;
}
Space space = format(source, cursor, nextNonWhitespace);
cursor = nextNonWhitespace;
return space;
}
private String skip(@Nullable String token) {
if (token == null) {
//noinspection ConstantConditions
return null;
}
if (source.startsWith(token, cursor)) {
cursor += token.length();
}
return token;
}
// Only exists as a function to make it easier to debug unexpected cursor shifts
private void cursor(int n) {
cursor = n;
}
private boolean hasFlag(ModifiersTree modifiers, long flag) {
return (((JCModifiers) modifiers).flags & flag) != 0L;
}
@SuppressWarnings("unused")
// Used for debugging
private List listFlags(long flags) {
Map allFlags = Arrays.stream(Flags.class.getDeclaredFields())
.filter(field -> {
field.setAccessible(true);
try {
// FIXME instanceof probably not right here...
return field.get(null) instanceof Long &&
field.getName().matches("[A-Z_]+");
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toMap(Field::getName, field -> {
try {
return (Long) field.get(null);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}));
List all = new ArrayList<>(allFlags.size());
for (Map.Entry flagNameAndCode : allFlags.entrySet()) {
if ((flagNameAndCode.getValue() & flags) != 0L) {
all.add(flagNameAndCode.getKey());
}
}
return all;
}
/**
* Leading annotations and modifiers in the order they appear in the source, which is not necessarily the same as the order in
* which they appear in the OpenJDK AST
*/
private ReloadableJava21ModifierResults sortedModifiersAndAnnotations(ModifiersTree modifiers, Map annotationPosTable) {
List leadingAnnotations = new ArrayList<>();
List sortedModifiers = new ArrayList<>();
List currentAnnotations = new ArrayList<>(2);
boolean afterFirstModifier = false;
boolean inComment = false;
boolean inMultilineComment = false;
int afterLastModifierPosition = cursor;
int lastAnnotationPosition = cursor;
int keywordStartIdx = -1;
for (int i = cursor; i < source.length(); i++) {
if (annotationPosTable.containsKey(i)) {
J.Annotation annotation = convert(annotationPosTable.get(i));
if (afterFirstModifier) {
currentAnnotations.add(annotation);
} else {
leadingAnnotations.add(annotation);
}
i = cursor -1;
lastAnnotationPosition = cursor;
continue;
}
char c = source.charAt(i);
if (c == '/' && source.length() > i + 1) {
char next = source.charAt(i + 1);
if (next == '*') {
inMultilineComment = true;
} else if (next == '/') {
inComment = true;
}
}
if (inMultilineComment && c == '/' && source.charAt(i - 1) == '*') {
inMultilineComment = false;
} else if (inComment && c == '\n' || c == '\r') {
inComment = false;
} else if (!inMultilineComment && !inComment) {
if (Character.isWhitespace(c)) {
if (keywordStartIdx != -1) {
Modifier matching = MODIFIER_BY_KEYWORD.get(source.substring(keywordStartIdx, i));
keywordStartIdx = -1;
if (matching == null) {
this.cursor = afterLastModifierPosition;
break;
} else {
sortedModifiers.add(mapModifier(matching, currentAnnotations));
afterFirstModifier = true;
currentAnnotations = new ArrayList<>(2);
afterLastModifierPosition = cursor;
}
}
} else if (keywordStartIdx == -1) {
keywordStartIdx = i;
}
}
}
if (sortedModifiers.isEmpty()) {
cursor = lastAnnotationPosition;
}
return new ReloadableJava21ModifierResults(
leadingAnnotations.isEmpty() ? emptyList() : leadingAnnotations,
sortedModifiers.isEmpty() ? emptyList() : sortedModifiers
);
}
private J.Modifier mapModifier(Modifier mod, List annotations) {
Space modFormat = whitespace();
cursor += mod.name().length();
J.Modifier.Type type;
switch (mod) {
case DEFAULT:
type = J.Modifier.Type.Default;
break;
case PUBLIC:
type = J.Modifier.Type.Public;
break;
case PROTECTED:
type = J.Modifier.Type.Protected;
break;
case PRIVATE:
type = J.Modifier.Type.Private;
break;
case ABSTRACT:
type = J.Modifier.Type.Abstract;
break;
case STATIC:
type = J.Modifier.Type.Static;
break;
case FINAL:
type = J.Modifier.Type.Final;
break;
case NATIVE:
type = J.Modifier.Type.Native;
break;
case STRICTFP:
type = J.Modifier.Type.Strictfp;
break;
case SYNCHRONIZED:
type = J.Modifier.Type.Synchronized;
break;
case TRANSIENT:
type = J.Modifier.Type.Transient;
break;
case VOLATILE:
type = J.Modifier.Type.Volatile;
break;
case SEALED:
type = J.Modifier.Type.Sealed;
break;
case NON_SEALED:
type = J.Modifier.Type.NonSealed;
break;
default:
throw new IllegalArgumentException("Unexpected modifier " + mod);
}
return new J.Modifier(randomId(), modFormat, Markers.EMPTY, null, type, annotations);
}
private List collectAnnotations(Map annotationPosTable) {
int maxAnnotationPosition = 0;
for (Integer pos : annotationPosTable.keySet()) {
if (pos > maxAnnotationPosition) {
maxAnnotationPosition = pos;
}
}
List annotations = new ArrayList<>();
boolean inComment = false;
boolean inMultilineComment = false;
for (int i = cursor; i <= maxAnnotationPosition && i < source.length(); i++) {
if (annotationPosTable.containsKey(i)) {
annotations.add(convert(annotationPosTable.get(i)));
i = cursor;
continue;
}
char c = source.charAt(i);
if (c == '/' && source.length() > i + 1) {
char next = source.charAt(i + 1);
if (next == '*') {
inMultilineComment = true;
} else if (next == '/') {
inComment = true;
}
}
if (inMultilineComment && c == '/' && i > 0 && source.charAt(i - 1) == '*') {
inMultilineComment = false;
} else if (inComment && c == '\n' || c == '\r') {
inComment = false;
} else if (!inMultilineComment && !inComment) {
if (!Character.isWhitespace(c)) {
break;
}
}
}
return annotations;
}
Space formatWithCommentTree(String prefix, JCTree tree, DCTree.@Nullable DCDocComment commentTree) {
Space fmt = format(prefix);
if (commentTree != null) {
List comments = fmt.getComments();
int i;
for (i = comments.size() - 1; i >= 0; i--) {
Comment comment = comments.get(i);
if (comment.isMultiline() && ((TextComment) comment).getText().startsWith("*")) {
break;
}
}
AtomicReference javadoc = new AtomicReference<>();
int commentCursor = cursor - prefix.length() + fmt.getWhitespace().length();
for (int j = 0; j < comments.size(); j++) {
Comment comment = comments.get(j);
if (i == j) {
javadoc.set((Javadoc.DocComment) new ReloadableJava21JavadocVisitor(
context,
getCurrentPath(),
typeMapping,
source.substring(commentCursor, source.indexOf("*/", commentCursor + 1)),
tree
).scan(commentTree, new ArrayList<>(1)));
break;
} else {
commentCursor += comment.printComment(new Cursor(null, "root")).length() + comment.getSuffix().length();
}
}
int javadocIndex = i;
return fmt.withComments(ListUtils.map(fmt.getComments(), (j, c) ->
j == javadocIndex ? javadoc.get().withSuffix(c.getSuffix()) : c));
}
return fmt;
}
}