All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openrewrite.java.isolated.ReloadableJava21ParserVisitor Maven / Gradle / Ivy

There is a newer version: 8.35.4
Show newest version
/*
 * 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.openrewrite.Cursor; import org.openrewrite.ExecutionContext; import org.openrewrite.FileAttributes; import org.openrewrite.internal.EncodingDetectingInputStream; import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.lang.Nullable; 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 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 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 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 trees) { List converted = new ArrayList<>(trees.size()); for (Tree tree : trees) { converted.add(convert(tree)); } return converted; } private List> convertAll(List 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 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 trees) { return convertStatements(trees, this::statementDelim); } @SuppressWarnings("unchecked") private List> convertStatements(@Nullable List 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 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, @Nullable DCTree.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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy