Copyright (c) 2014, 2019 Mateusz Matela and others. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which accompanies this distribution, and is available at https://www.eclipse.org/legal/epl-2.0/ Contributors: Mateusz Matela - [formatter] Formatter does not format Java code correctly, especially when max line width is set - https://bugs.eclipse.org/303519 Mateusz Matela - [formatter] IndexOutOfBoundsException in TokenManager - https://bugs.eclipse.org/462945 Mateusz Matela - [formatter] follow up bug for comments - https://bugs.eclipse.org/458208 IBM Corporation - DOM AST changes for JEP 354
/******************************************************************************* * Copyright (c) 2014, 2019 Mateusz Matela and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * * Contributors: * Mateusz Matela <mateusz.matela@gmail.com> - [formatter] Formatter does not format Java code correctly, especially when max line width is set - https://bugs.eclipse.org/303519 * Mateusz Matela <mateusz.matela@gmail.com> - [formatter] IndexOutOfBoundsException in TokenManager - https://bugs.eclipse.org/462945 * Mateusz Matela <mateusz.matela@gmail.com> - [formatter] follow up bug for comments - https://bugs.eclipse.org/458208 * IBM Corporation - DOM AST changes for JEP 354 *******************************************************************************/
package org.eclipse.jdt.internal.formatter; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.*; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Predicate; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration; import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration; import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; import org.eclipse.jdt.core.dom.ArrayAccess; import org.eclipse.jdt.core.dom.ArrayCreation; import org.eclipse.jdt.core.dom.ArrayInitializer; import org.eclipse.jdt.core.dom.ArrayType; import org.eclipse.jdt.core.dom.AssertStatement; import org.eclipse.jdt.core.dom.Assignment; import org.eclipse.jdt.core.dom.Block; import org.eclipse.jdt.core.dom.CastExpression; import org.eclipse.jdt.core.dom.CatchClause; import org.eclipse.jdt.core.dom.ClassInstanceCreation; import org.eclipse.jdt.core.dom.ConditionalExpression; import org.eclipse.jdt.core.dom.ConstructorInvocation; import org.eclipse.jdt.core.dom.CreationReference; import org.eclipse.jdt.core.dom.Dimension; import org.eclipse.jdt.core.dom.DoStatement; import org.eclipse.jdt.core.dom.EmptyStatement; import org.eclipse.jdt.core.dom.EnhancedForStatement; import org.eclipse.jdt.core.dom.EnumConstantDeclaration; import org.eclipse.jdt.core.dom.EnumDeclaration; import org.eclipse.jdt.core.dom.ExportsDirective; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.ExpressionMethodReference; import org.eclipse.jdt.core.dom.ExpressionStatement; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.ForStatement; import org.eclipse.jdt.core.dom.IfStatement; import org.eclipse.jdt.core.dom.ImportDeclaration; import org.eclipse.jdt.core.dom.InfixExpression; import org.eclipse.jdt.core.dom.InfixExpression.Operator; import org.eclipse.jdt.core.dom.InstanceofExpression; import org.eclipse.jdt.core.dom.IntersectionType; import org.eclipse.jdt.core.dom.LabeledStatement; import org.eclipse.jdt.core.dom.LambdaExpression; import org.eclipse.jdt.core.dom.MarkerAnnotation; import org.eclipse.jdt.core.dom.MemberValuePair; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.MethodInvocation; import org.eclipse.jdt.core.dom.ModuleDeclaration; import org.eclipse.jdt.core.dom.Name; import org.eclipse.jdt.core.dom.NormalAnnotation; import org.eclipse.jdt.core.dom.OpensDirective; import org.eclipse.jdt.core.dom.PackageDeclaration; import org.eclipse.jdt.core.dom.ParameterizedType; import org.eclipse.jdt.core.dom.ParenthesizedExpression; import org.eclipse.jdt.core.dom.PostfixExpression; import org.eclipse.jdt.core.dom.PrefixExpression; import org.eclipse.jdt.core.dom.ProvidesDirective; import org.eclipse.jdt.core.dom.ReturnStatement; import org.eclipse.jdt.core.dom.SingleMemberAnnotation; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.Statement; import org.eclipse.jdt.core.dom.SuperConstructorInvocation; import org.eclipse.jdt.core.dom.SuperMethodInvocation; import org.eclipse.jdt.core.dom.SuperMethodReference; import org.eclipse.jdt.core.dom.SwitchCase; import org.eclipse.jdt.core.dom.SwitchExpression; import org.eclipse.jdt.core.dom.SwitchStatement; import org.eclipse.jdt.core.dom.SynchronizedStatement; import org.eclipse.jdt.core.dom.ThrowStatement; import org.eclipse.jdt.core.dom.TryStatement; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.TypeMethodReference; import org.eclipse.jdt.core.dom.TypeParameter; import org.eclipse.jdt.core.dom.UnionType; import org.eclipse.jdt.core.dom.VariableDeclaration; import org.eclipse.jdt.core.dom.VariableDeclarationExpression; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jdt.core.dom.VariableDeclarationStatement; import org.eclipse.jdt.core.dom.WhileStatement; import org.eclipse.jdt.core.dom.WildcardType; import org.eclipse.jdt.core.dom.YieldStatement; import org.eclipse.jdt.internal.compiler.parser.ScannerHelper; public class SpacePreparator extends ASTVisitor { private static final Map<Operator, Predicate<DefaultCodeFormatterOptions>> SPACE_BEFORE_OPERATOR; private static final Map<Operator, Predicate<DefaultCodeFormatterOptions>> SPACE_AFTER_OPERATOR; static { Map<Operator, Predicate<DefaultCodeFormatterOptions>> spaceBeforeOperator = new HashMap<>(); Map<Operator, Predicate<DefaultCodeFormatterOptions>> spaceAfterOperator = new HashMap<>(); for (Operator op : Arrays.asList(Operator.TIMES, Operator.DIVIDE, Operator.REMAINDER)) { spaceBeforeOperator.put(op, o -> o.insert_space_before_multiplicative_operator); spaceAfterOperator.put(op, o -> o.insert_space_after_multiplicative_operator); } for (Operator op : Arrays.asList(Operator.PLUS, Operator.MINUS)) { spaceBeforeOperator.put(op, o -> o.insert_space_before_additive_operator); spaceAfterOperator.put(op, o -> o.insert_space_after_additive_operator); } for (Operator op : Arrays.asList(Operator.LEFT_SHIFT, Operator.RIGHT_SHIFT_SIGNED, Operator.RIGHT_SHIFT_UNSIGNED)) { spaceBeforeOperator.put(op, o -> o.insert_space_before_shift_operator); spaceAfterOperator.put(op, o -> o.insert_space_after_shift_operator); } for (Operator op : Arrays.asList(Operator.LESS, Operator.GREATER, Operator.LESS_EQUALS, Operator.GREATER_EQUALS, Operator.EQUALS, Operator.NOT_EQUALS)) { spaceBeforeOperator.put(op, o -> o.insert_space_before_relational_operator); spaceAfterOperator.put(op, o -> o.insert_space_after_relational_operator); } for (Operator op : Arrays.asList(Operator.AND, Operator.XOR, Operator.OR)) { spaceBeforeOperator.put(op, o -> o.insert_space_before_bitwise_operator); spaceAfterOperator.put(op, o -> o.insert_space_after_bitwise_operator); } for (Operator op : Arrays.asList(Operator.CONDITIONAL_AND, Operator.CONDITIONAL_OR)) { spaceBeforeOperator.put(op, o -> o.insert_space_before_logical_operator); spaceAfterOperator.put(op, o -> o.insert_space_after_logical_operator); } SPACE_BEFORE_OPERATOR = Collections.unmodifiableMap(spaceBeforeOperator); SPACE_AFTER_OPERATOR = Collections.unmodifiableMap(spaceAfterOperator); } TokenManager tm; private DefaultCodeFormatterOptions options; public SpacePreparator(TokenManager tokenManager, DefaultCodeFormatterOptions options) { this.tm = tokenManager; this.options = options; } @Override public boolean preVisit2(ASTNode node) { boolean isMalformed = (node.getFlags() & ASTNode.MALFORMED) != 0; return !isMalformed; } @Override public boolean visit(PackageDeclaration node) { handleSemicolon(node); return true; } @Override public boolean visit(ImportDeclaration node) { handleSemicolon(node); return true; } @Override public boolean visit(TypeDeclaration node) { if (node.getName().getStartPosition() == -1) return true; // this is a fake type created by parsing in class body mode handleToken(node.getName(), TokenNameIdentifier, true, false); List<TypeParameter> typeParameters = node.typeParameters(); handleTypeParameters(typeParameters); if (!node.isInterface() && !node.superInterfaceTypes().isEmpty()) { // fix for: class A<E> extends ArrayList<String>implements Callable<String> handleToken(node.getName(), TokenNameimplements, true, false); } handleToken(node.getName(), TokenNameLBRACE, this.options.insert_space_before_opening_brace_in_type_declaration, false); handleCommas(node.superInterfaceTypes(), this.options.insert_space_before_comma_in_superinterfaces, this.options.insert_space_after_comma_in_superinterfaces); return true; } @Override public boolean visit(EnumDeclaration node) { handleToken(node.getName(), TokenNameLBRACE, this.options.insert_space_before_opening_brace_in_enum_declaration, false); handleCommas(node.superInterfaceTypes(), this.options.insert_space_before_comma_in_superinterfaces, this.options.insert_space_after_comma_in_superinterfaces); handleCommas(node.enumConstants(), this.options.insert_space_before_comma_in_enum_declarations, this.options.insert_space_after_comma_in_enum_declarations); return true; } @Override public boolean visit(EnumConstantDeclaration node) { List<Expression> arguments = node.arguments(); Token openingParen = null; if (!arguments.isEmpty()) { openingParen = this.tm.firstTokenIn(node, TokenNameLPAREN); if (this.options.insert_space_after_opening_paren_in_enum_constant) openingParen.spaceAfter(); handleTokenAfter(arguments.get(arguments.size() - 1), TokenNameRPAREN, this.options.insert_space_before_closing_paren_in_enum_constant, false); } else { // look for empty parenthesis, may not be there int from = this.tm.firstIndexIn(node.getName(), TokenNameIdentifier) + 1; AnonymousClassDeclaration classDeclaration = node.getAnonymousClassDeclaration(); int to = classDeclaration != null ? this.tm.firstIndexBefore(classDeclaration, -1) : this.tm.lastIndexIn(node, -1); for (int i = from; i <= to; i++) { if (this.tm.get(i).tokenType == TokenNameLPAREN) { openingParen = this.tm.get(i); if (this.options.insert_space_between_empty_parens_in_enum_constant) openingParen.spaceAfter(); break; } } } if (openingParen != null && this.options.insert_space_before_opening_paren_in_enum_constant) openingParen.spaceBefore(); handleCommas(arguments, this.options.insert_space_before_comma_in_enum_constant_arguments, this.options.insert_space_after_comma_in_enum_constant_arguments); return true; } @Override public boolean visit(AnonymousClassDeclaration node) { boolean spaceBeforeOpenBrace = this.options.insert_space_before_opening_brace_in_anonymous_type_declaration; if (node.getParent() instanceof EnumConstantDeclaration) spaceBeforeOpenBrace = this.options.insert_space_before_opening_brace_in_enum_constant; handleToken(node, TokenNameLBRACE, spaceBeforeOpenBrace, false); return true; } @Override public boolean visit(MethodDeclaration node) { handleToken(node.getName(), TokenNameIdentifier, true, false); boolean spaceBeforeOpenParen = node.isConstructor() ? this.options.insert_space_before_opening_paren_in_constructor_declaration : this.options.insert_space_before_opening_paren_in_method_declaration; boolean spaceAfterOpenParen = node.isConstructor() ? this.options.insert_space_after_opening_paren_in_constructor_declaration : this.options.insert_space_after_opening_paren_in_method_declaration; boolean spaceBetweenEmptyParens = node.isConstructor() ? this.options.insert_space_between_empty_parens_in_constructor_declaration : this.options.insert_space_between_empty_parens_in_method_declaration; if (handleEmptyParens(node.getName(), spaceBetweenEmptyParens)) { handleToken(node.getName(), TokenNameLPAREN, spaceBeforeOpenParen, false); } else { handleToken(node.getName(), TokenNameLPAREN, spaceBeforeOpenParen, spaceAfterOpenParen); boolean spaceBeforeCloseParen = node.isConstructor() ? this.options.insert_space_before_closing_paren_in_constructor_declaration : this.options.insert_space_before_closing_paren_in_method_declaration; if (spaceBeforeCloseParen) { List<SingleVariableDeclaration> params = node.parameters(); ASTNode beforeBrace = params.isEmpty() ? node.getName() : params.get(params.size() - 1); handleTokenAfter(beforeBrace, TokenNameRPAREN, true, false); } } if ((node.isConstructor() ? this.options.insert_space_before_opening_brace_in_constructor_declaration : this.options.insert_space_before_opening_brace_in_method_declaration) && node.getBody() != null) this.tm.firstTokenIn(node.getBody(), TokenNameLBRACE).spaceBefore(); if (node.getReceiverType() != null) this.tm.lastTokenIn(node.getReceiverType(), -1).spaceAfter(); boolean beforeComma = node.isConstructor() ? this.options.insert_space_before_comma_in_constructor_declaration_parameters : this.options.insert_space_before_comma_in_method_declaration_parameters; boolean afterComma = node.isConstructor() ? this.options.insert_space_after_comma_in_constructor_declaration_parameters : this.options.insert_space_after_comma_in_method_declaration_parameters; List<SingleVariableDeclaration> params = node.parameters(); if (node.getReceiverType() != null) { params = new ArrayList<>(params); params.add(0, null); // space for explicit receiver, null OK - first value not read in handleCommas } handleCommas(params, beforeComma, afterComma); List<Type> thrownExceptionTypes = node.thrownExceptionTypes(); if (!thrownExceptionTypes.isEmpty()) { this.tm.firstTokenBefore(thrownExceptionTypes.get(0), TokenNamethrows).spaceBefore(); beforeComma = node.isConstructor() ? this.options.insert_space_before_comma_in_constructor_declaration_throws : this.options.insert_space_before_comma_in_method_declaration_throws; afterComma = node.isConstructor() ? this.options.insert_space_after_comma_in_constructor_declaration_throws : this.options.insert_space_after_comma_in_method_declaration_throws; handleCommas(thrownExceptionTypes, beforeComma, afterComma); } List<TypeParameter> typeParameters = node.typeParameters(); if (!typeParameters.isEmpty()) { handleTypeParameters(typeParameters); handleTokenBefore(typeParameters.get(0), TokenNameLESS, true, false); handleTokenAfter(typeParameters.get(typeParameters.size() - 1), TokenNameGREATER, false, true); } handleSemicolon(node); return true; } private void handleTypeParameters(List<TypeParameter> typeParameters) { if (!typeParameters.isEmpty()) { handleTokenBefore(typeParameters.get(0), TokenNameLESS, this.options.insert_space_before_opening_angle_bracket_in_type_parameters, this.options.insert_space_after_opening_angle_bracket_in_type_parameters); handleTokenAfter(typeParameters.get(typeParameters.size() - 1), TokenNameGREATER, this.options.insert_space_before_closing_angle_bracket_in_type_parameters, this.options.insert_space_after_closing_angle_bracket_in_type_parameters); handleCommas(typeParameters, this.options.insert_space_before_comma_in_type_parameters, this.options.insert_space_after_comma_in_type_parameters); } } @Override public boolean visit(FieldDeclaration node) { handleToken((ASTNode) node.fragments().get(0), TokenNameIdentifier, true, false); handleCommas(node.fragments(), this.options.insert_space_before_comma_in_multiple_field_declarations, this.options.insert_space_after_comma_in_multiple_field_declarations); handleSemicolon(node); return true; } @Override public boolean visit(VariableDeclarationStatement node) { handleToken((ASTNode) node.fragments().get(0), TokenNameIdentifier, true, false); handleCommas(node.fragments(), this.options.insert_space_before_comma_in_multiple_local_declarations, this.options.insert_space_after_comma_in_multiple_local_declarations); return true; } @Override public boolean visit(VariableDeclarationFragment node) { if (node.getInitializer() != null) { handleToken(node.getName(), TokenNameEQUAL, this.options.insert_space_before_assignment_operator, this.options.insert_space_after_assignment_operator); } return true; } @Override public void endVisit(SingleVariableDeclaration node) { // this must be endVisit in case a space added by a visit on a child node needs to be cleared if (node.isVarargs()) { handleTokenBefore(node.getName(), TokenNameELLIPSIS, this.options.insert_space_before_ellipsis, this.options.insert_space_after_ellipsis); List<Annotation> varargsAnnotations = node.varargsAnnotations(); if (!varargsAnnotations.isEmpty()) { this.tm.firstTokenIn(varargsAnnotations.get(0), TokenNameAT).spaceBefore(); this.tm.lastTokenIn(varargsAnnotations.get(varargsAnnotations.size() - 1), -1).clearSpaceAfter(); } } else { handleToken(node.getName(), TokenNameIdentifier, true, false); } } @Override public boolean visit(SwitchStatement node) { handleToken(node, TokenNameLPAREN, this.options.insert_space_before_opening_paren_in_switch, this.options.insert_space_after_opening_paren_in_switch); handleTokenAfter(node.getExpression(), TokenNameRPAREN, this.options.insert_space_before_closing_paren_in_switch, false); handleTokenAfter(node.getExpression(), TokenNameLBRACE, this.options.insert_space_before_opening_brace_in_switch, false); handleSemicolon(node.statements()); return true; } @Override public boolean visit(SwitchExpression node) { handleToken(node, TokenNameLPAREN, this.options.insert_space_before_opening_paren_in_switch, this.options.insert_space_after_opening_paren_in_switch); handleTokenAfter(node.getExpression(), TokenNameRPAREN, this.options.insert_space_before_closing_paren_in_switch, false); handleTokenAfter(node.getExpression(), TokenNameLBRACE, this.options.insert_space_before_opening_brace_in_switch, false); handleSemicolon(node.statements()); return true; } @Override public boolean visit(SwitchCase node) { if (node.getAST().apiLevel() == AST.JLS13 && node.isSwitchLabeledRule()) { handleToken(this.tm.lastTokenIn(node, TokenNameARROW), node.isDefault() ? this.options.insert_space_before_arrow_in_switch_default : this.options.insert_space_before_arrow_in_switch_case, node.isDefault() ? this.options.insert_space_after_arrow_in_switch_default : this.options.insert_space_after_arrow_in_switch_case); } else { handleToken(this.tm.lastTokenIn(node, TokenNameCOLON), node.isDefault() ? this.options.insert_space_before_colon_in_default : this.options.insert_space_before_colon_in_case, false); } if (!node.isDefault()) { handleToken(node, TokenNamecase, false, true); if (node.getAST().apiLevel() == AST.JLS13) { handleCommas(node.expressions(), this.options.insert_space_before_comma_in_switch_case_expressions, this.options.insert_space_after_comma_in_switch_case_expressions); } } return true; } @Override public boolean visit(YieldStatement node) { if (node.getExpression() != null && !node.isImplicit()) { this.tm.firstTokenIn(node, -1).spaceAfter(); } return true; } @Override public boolean visit(DoStatement node) { handleTokenBefore(node.getExpression(), TokenNameLPAREN, this.options.insert_space_before_opening_paren_in_while, this.options.insert_space_after_opening_paren_in_while); handleTokenBefore(node.getExpression(), TokenNamewhile, !(node.getBody() instanceof Block) || this.options.insert_space_after_closing_brace_in_block, false); handleTokenAfter(node.getExpression(), TokenNameRPAREN, this.options.insert_space_before_closing_paren_in_while, false); return true; } @Override public boolean visit(WhileStatement node) { handleToken(node, TokenNameLPAREN, this.options.insert_space_before_opening_paren_in_while, this.options.insert_space_after_opening_paren_in_while); handleTokenBefore(node.getBody(), TokenNameRPAREN, this.options.insert_space_before_closing_paren_in_while, false); handleLoopBody(node.getBody()); return true; } @Override public boolean visit(SynchronizedStatement node) { handleToken(node, TokenNameLPAREN, this.options.insert_space_before_opening_paren_in_synchronized, this.options.insert_space_after_opening_paren_in_synchronized); handleTokenBefore(node.getBody(), TokenNameRPAREN, this.options.insert_space_before_closing_paren_in_synchronized, false); return true; } @Override public boolean visit(TryStatement node) { List<Expression> resources = node.resources(); if (!resources.isEmpty()) { handleToken(node, TokenNameLPAREN, this.options.insert_space_before_opening_paren_in_try, this.options.insert_space_after_opening_paren_in_try); handleTokenBefore(node.getBody(), TokenNameRPAREN, this.options.insert_space_before_closing_paren_in_try, false); for (int i = 1; i < resources.size(); i++) { handleTokenBefore(resources.get(i), TokenNameSEMICOLON, this.options.insert_space_before_semicolon_in_try_resources, this.options.insert_space_after_semicolon_in_try_resources); } // there can be a semicolon after the last resource int index = this.tm.firstIndexAfter(resources.get(resources.size() - 1), -1); while (index < this.tm.size()) { Token token = this.tm.get(index++); if (token.tokenType == TokenNameSEMICOLON) { handleToken(token, this.options.insert_space_before_semicolon_in_try_resources, false); } else if (token.tokenType == TokenNameRPAREN) { break; } } } return true; } @Override public boolean visit(CatchClause node) { handleToken(node, TokenNameLPAREN, this.options.insert_space_before_opening_paren_in_catch, this.options.insert_space_after_opening_paren_in_catch); handleTokenBefore(node.getBody(), TokenNameRPAREN, this.options.insert_space_before_closing_paren_in_catch, false); return true; } @Override public boolean visit(AssertStatement node) { this.tm.firstTokenIn(node, TokenNameassert).spaceAfter(); if (node.getMessage() != null) { handleTokenBefore(node.getMessage(), TokenNameCOLON, this.options.insert_space_before_colon_in_assert, this.options.insert_space_after_colon_in_assert); } return true; } @Override public boolean visit(ReturnStatement node) { if (node.getExpression() != null) { int returnTokenIndex = this.tm.firstIndexIn(node, TokenNamereturn); if (!(node.getExpression() instanceof ParenthesizedExpression) || this.options.insert_space_before_parenthesized_expression_in_return) { this.tm.get(returnTokenIndex).spaceAfter(); } } return true; } @Override public boolean visit(ThrowStatement node) { int returnTokenIndex = this.tm.firstIndexIn(node, TokenNamethrow); if (this.tm.get(returnTokenIndex + 1).tokenType != TokenNameLPAREN || this.options.insert_space_before_parenthesized_expression_in_throw) { this.tm.get(returnTokenIndex).spaceAfter(); } return true; } @Override public boolean visit(LabeledStatement node) { handleToken(node, TokenNameCOLON, this.options.insert_space_before_colon_in_labeled_statement, this.options.insert_space_after_colon_in_labeled_statement); return true; } @Override public boolean visit(AnnotationTypeDeclaration node) { handleToken(node, TokenNameAT, this.options.insert_space_before_at_in_annotation_type_declaration, this.options.insert_space_after_at_in_annotation_type_declaration); handleToken(node.getName(), TokenNameLBRACE, this.options.insert_space_before_opening_brace_in_annotation_type_declaration, false); return true; } @Override public boolean visit(AnnotationTypeMemberDeclaration node) { handleToken(node.getName(), TokenNameIdentifier, true, false); handleToken(node.getName(), TokenNameLPAREN, this.options.insert_space_before_opening_paren_in_annotation_type_member_declaration, false); handleEmptyParens(node.getName(), this.options.insert_space_between_empty_parens_in_annotation_type_member_declaration); if (node.getDefault() != null) handleTokenBefore(node.getDefault(), TokenNamedefault, true, true); return true; } @Override public boolean visit(NormalAnnotation node) { handleAnnotation(node, true); handleCommas(node.values(), this.options.insert_space_before_comma_in_annotation, this.options.insert_space_after_comma_in_annotation); return true; } @Override public boolean visit(MemberValuePair node) { handleToken(node, TokenNameEQUAL, this.options.insert_space_before_assignment_operator, this.options.insert_space_after_assignment_operator); return true; } @Override public boolean visit(SingleMemberAnnotation node) { handleAnnotation(node, true); return true; } @Override public boolean visit(MarkerAnnotation node) { handleAnnotation(node, false); return true; } private void handleAnnotation(Annotation node, boolean handleParenthesis) { handleToken(node, TokenNameAT, false, this.options.insert_space_after_at_in_annotation); if (handleParenthesis) { handleToken(node, TokenNameLPAREN, this.options.insert_space_before_opening_paren_in_annotation, this.options.insert_space_after_opening_paren_in_annotation); if (this.options.insert_space_before_closing_paren_in_annotation) this.tm.lastTokenIn(node, TokenNameRPAREN).spaceBefore(); } ASTNode parent = node.getParent(); boolean skipSpaceAfter = parent instanceof Annotation || parent instanceof MemberValuePair || (parent instanceof AnnotationTypeMemberDeclaration && ((AnnotationTypeMemberDeclaration) parent).getDefault() == node) || parent instanceof ArrayInitializer; if (!skipSpaceAfter) this.tm.lastTokenIn(node, -1).spaceAfter(); } @Override public boolean visit(LambdaExpression node) { handleToken(node, TokenNameARROW, this.options.insert_space_before_lambda_arrow, this.options.insert_space_after_lambda_arrow); List<VariableDeclaration> parameters = node.parameters(); if (node.hasParentheses()) { if (handleEmptyParens(node, this.options.insert_space_between_empty_parens_in_method_declaration)) { handleToken(node, TokenNameLPAREN, this.options.insert_space_before_opening_paren_in_method_declaration, false); } else { handleToken(node, TokenNameLPAREN, this.options.insert_space_before_opening_paren_in_method_declaration, this.options.insert_space_after_opening_paren_in_method_declaration); handleTokenBefore(node.getBody(), TokenNameRPAREN, this.options.insert_space_before_closing_paren_in_method_declaration, false); } handleCommas(parameters, this.options.insert_space_before_comma_in_method_declaration_parameters, this.options.insert_space_after_comma_in_method_declaration_parameters); } return true; } @Override public boolean visit(Block node) { handleSemicolon(node.statements()); ASTNode parent = node.getParent(); if (parent.getLength() == 0) return true; // this is a fake block created by parsing in statements mode if (parent instanceof MethodDeclaration) return true; // spaces handled in #visit(MethodDeclaration) handleToken(node, TokenNameLBRACE, this.options.insert_space_before_opening_brace_in_block, false); if (this.options.insert_space_after_closing_brace_in_block && (parent instanceof Statement || parent instanceof CatchClause)) { int closeBraceIndex = this.tm.lastIndexIn(node, TokenNameRBRACE); this.tm.get(closeBraceIndex).spaceAfter(); } return true; } @Override public boolean visit(IfStatement node) { handleToken(node, TokenNameLPAREN, this.options.insert_space_before_opening_paren_in_if, this.options.insert_space_after_opening_paren_in_if); Statement thenStatement = node.getThenStatement(); handleTokenBefore(thenStatement, TokenNameRPAREN, this.options.insert_space_before_closing_paren_in_if, false); handleLoopBody(thenStatement); handleSemicolon(thenStatement); return true; } @Override public boolean visit(ForStatement node) { handleToken(node, TokenNameLPAREN, this.options.insert_space_before_opening_paren_in_for, this.options.insert_space_after_opening_paren_in_for); handleTokenBefore(node.getBody(), TokenNameRPAREN, this.options.insert_space_before_closing_paren_in_for, false); handleCommas(node.initializers(), this.options.insert_space_before_comma_in_for_inits, this.options.insert_space_after_comma_in_for_inits); handleCommas(node.updaters(), this.options.insert_space_before_comma_in_for_increments, this.options.insert_space_after_comma_in_for_increments); boolean part1Empty = node.initializers().isEmpty(); boolean part2Empty = node.getExpression() == null; boolean part3Empty = node.updaters().isEmpty(); handleToken(node, TokenNameSEMICOLON, this.options.insert_space_before_semicolon_in_for && !part1Empty, this.options.insert_space_after_semicolon_in_for && !part2Empty); handleTokenBefore(node.getBody(), TokenNameSEMICOLON, this.options.insert_space_before_semicolon_in_for && !part2Empty, this.options.insert_space_after_semicolon_in_for && !part3Empty); handleLoopBody(node.getBody()); return true; } @Override public boolean visit(VariableDeclarationExpression node) { ASTNode parent = node.getParent(); if (parent instanceof ForStatement) { handleCommas(node.fragments(), this.options.insert_space_before_comma_in_for_inits, this.options.insert_space_after_comma_in_for_inits); } else if (parent instanceof ExpressionStatement) { handleCommas(node.fragments(), this.options.insert_space_before_comma_in_multiple_local_declarations, this.options.insert_space_after_comma_in_multiple_local_declarations); } this.tm.firstTokenAfter(node.getType(), -1).spaceBefore(); return true; } @Override public boolean visit(EnhancedForStatement node) { handleToken(node, TokenNameLPAREN, this.options.insert_space_before_opening_paren_in_for, this.options.insert_space_after_opening_paren_in_for); handleTokenBefore(node.getBody(), TokenNameRPAREN, this.options.insert_space_before_closing_paren_in_for, false); handleTokenAfter(node.getParameter(), TokenNameCOLON, this.options.insert_space_before_colon_in_for, this.options.insert_space_after_colon_in_for); handleLoopBody(node.getBody()); return true; } @Override public boolean visit(MethodInvocation node) { handleTypeArguments(node.typeArguments()); handleInvocation(node, node.getName()); handleCommas(node.arguments(), this.options.insert_space_before_comma_in_method_invocation_arguments, this.options.insert_space_after_comma_in_method_invocation_arguments); return true; } @Override public boolean visit(SuperMethodInvocation node) { handleTypeArguments(node.typeArguments()); handleInvocation(node, node.getName()); handleCommas(node.arguments(), this.options.insert_space_before_comma_in_method_invocation_arguments, this.options.insert_space_after_comma_in_method_invocation_arguments); return true; } @Override public boolean visit(ClassInstanceCreation node) { List<Type> typeArguments = node.typeArguments(); handleTypeArguments(typeArguments); handleInvocation(node, node.getType(), node.getAnonymousClassDeclaration()); if (!typeArguments.isEmpty()) { handleTokenBefore(typeArguments.get(0), TokenNamenew, false, true); // fix for: new<Integer>A<String>() } handleCommas(node.arguments(), this.options.insert_space_before_comma_in_allocation_expression, this.options.insert_space_after_comma_in_allocation_expression); return true; } @Override public boolean visit(ConstructorInvocation node) { handleTypeArguments(node.typeArguments()); handleInvocation(node, node); handleCommas(node.arguments(), this.options.insert_space_before_comma_in_explicit_constructor_call_arguments, this.options.insert_space_after_comma_in_explicit_constructor_call_arguments); return true; } @Override public boolean visit(SuperConstructorInvocation node) { handleTypeArguments(node.typeArguments()); handleInvocation(node, node); handleCommas(node.arguments(), this.options.insert_space_before_comma_in_explicit_constructor_call_arguments, this.options.insert_space_after_comma_in_explicit_constructor_call_arguments); return true; } private void handleInvocation(ASTNode invocationNode, ASTNode nodeBeforeOpeningParen) { handleInvocation(invocationNode, nodeBeforeOpeningParen, null); } private void handleInvocation(ASTNode invocationNode, ASTNode nodeBeforeOpeningParen, ASTNode nodeAfterClosingParen) { if (handleEmptyParens(nodeBeforeOpeningParen, this.options.insert_space_between_empty_parens_in_method_invocation)) { handleToken(nodeBeforeOpeningParen, TokenNameLPAREN, this.options.insert_space_before_opening_paren_in_method_invocation, false); } else { handleToken(nodeBeforeOpeningParen, TokenNameLPAREN, this.options.insert_space_before_opening_paren_in_method_invocation, this.options.insert_space_after_opening_paren_in_method_invocation); if (this.options.insert_space_before_closing_paren_in_method_invocation) { Token closingParen = nodeAfterClosingParen == null ? this.tm.lastTokenIn(invocationNode, TokenNameRPAREN) : this.tm.firstTokenBefore(nodeAfterClosingParen, TokenNameRPAREN); closingParen.spaceBefore(); } } } @Override public boolean visit(Assignment node) { handleOperator(node.getOperator().toString(), node.getRightHandSide(), this.options.insert_space_before_assignment_operator, this.options.insert_space_after_assignment_operator); return true; } @Override public boolean visit(InfixExpression node) { Operator operator = node.getOperator(); boolean spaceBefore = SPACE_BEFORE_OPERATOR.get(operator).test(this.options); boolean spaceAfter = SPACE_AFTER_OPERATOR.get(operator).test(this.options); if (this.tm.isStringConcatenation(node)) { spaceBefore = this.options.insert_space_before_string_concatenation; spaceAfter = this.options.insert_space_after_string_concatenation; } handleOperator(operator.toString(), node.getRightOperand(), spaceBefore, spaceAfter); List<Expression> extendedOperands = node.extendedOperands(); for (Expression operand : extendedOperands) { handleOperator(operator.toString(), operand, spaceBefore, spaceAfter); } return true; } @Override public boolean visit(PrefixExpression node) { PrefixExpression.Operator operator = node.getOperator(); if (operator.equals(PrefixExpression.Operator.INCREMENT) || operator.equals(PrefixExpression.Operator.DECREMENT)) { handleOperator(operator.toString(), node.getOperand(), this.options.insert_space_before_prefix_operator, this.options.insert_space_after_prefix_operator); } else if (operator.equals(PrefixExpression.Operator.NOT)) { handleOperator(operator.toString(), node.getOperand(), this.options.insert_space_before_unary_operator, this.options.insert_space_after_not_operator); } else { handleOperator(operator.toString(), node.getOperand(), this.options.insert_space_before_unary_operator, this.options.insert_space_after_unary_operator); } return true; } @Override public boolean visit(PostfixExpression node) { if (this.options.insert_space_before_postfix_operator || this.options.insert_space_after_postfix_operator) { String operator = node.getOperator().toString(); int i = this.tm.firstIndexAfter(node.getOperand(), -1); while (!operator.equals(this.tm.toString(i))) { i++; } handleToken(this.tm.get(i), this.options.insert_space_before_postfix_operator, this.options.insert_space_after_postfix_operator); } return true; } private void handleOperator(String operator, ASTNode nodeAfter, boolean spaceBefore, boolean spaceAfter) { if (spaceBefore || spaceAfter) { int i = this.tm.firstIndexBefore(nodeAfter, -1); while (!operator.equals(this.tm.toString(i))) { i--; } handleToken(this.tm.get(i), spaceBefore, spaceAfter); } } @Override public boolean visit(ParenthesizedExpression node) { handleToken(node, TokenNameLPAREN, this.options.insert_space_before_opening_paren_in_parenthesized_expression, this.options.insert_space_after_opening_paren_in_parenthesized_expression); handleTokenAfter(node.getExpression(), TokenNameRPAREN, this.options.insert_space_before_closing_paren_in_parenthesized_expression, false); return true; } @Override public boolean visit(CastExpression node) { handleToken(node, TokenNameLPAREN, false, this.options.insert_space_after_opening_paren_in_cast); handleTokenBefore(node.getExpression(), TokenNameRPAREN, this.options.insert_space_before_closing_paren_in_cast, this.options.insert_space_after_closing_paren_in_cast); return true; } @Override public boolean visit(IntersectionType node) { List<Type> types = node.types(); for (int i = 1; i < types.size(); i++) handleTokenBefore(types.get(i), TokenNameAND, this.options.insert_space_before_bitwise_operator, this.options.insert_space_after_bitwise_operator); return true; } @Override public boolean visit(ConditionalExpression node) { handleTokenBefore(node.getThenExpression(), TokenNameQUESTION, this.options.insert_space_before_question_in_conditional, this.options.insert_space_after_question_in_conditional); handleTokenBefore(node.getElseExpression(), TokenNameCOLON, this.options.insert_space_before_colon_in_conditional, this.options.insert_space_after_colon_in_conditional); return true; } @Override public boolean visit(ArrayType node) { ASTNode parent = node.getParent(); boolean spaceBeofreOpening, spaceBetween; if (parent instanceof ArrayCreation) { spaceBeofreOpening = this.options.insert_space_before_opening_bracket_in_array_allocation_expression; spaceBetween = this.options.insert_space_between_empty_brackets_in_array_allocation_expression; } else { spaceBeofreOpening = this.options.insert_space_before_opening_bracket_in_array_type_reference; spaceBetween = this.options.insert_space_between_brackets_in_array_type_reference; } List<Dimension> dimensions = node.dimensions(); for (Dimension dimension : dimensions) { handleToken(dimension, TokenNameLBRACKET, spaceBeofreOpening, false); handleEmptyBrackets(dimension, spaceBetween); } return true; } @Override public boolean visit(ArrayAccess node) { handleTokenBefore(node.getIndex(), TokenNameLBRACKET, this.options.insert_space_before_opening_bracket_in_array_reference, this.options.insert_space_after_opening_bracket_in_array_reference); handleTokenAfter(node.getIndex(), TokenNameRBRACKET, this.options.insert_space_before_closing_bracket_in_array_reference, false); return true; } @Override public boolean visit(ArrayCreation node) { List<Expression> dimensions = node.dimensions(); for (Expression dimension : dimensions) { handleTokenBefore(dimension, TokenNameLBRACKET, false, this.options.insert_space_after_opening_bracket_in_array_allocation_expression); handleTokenAfter(dimension, TokenNameRBRACKET, this.options.insert_space_before_closing_bracket_in_array_allocation_expression, false); } return true; } @Override public boolean visit(ArrayInitializer node) { int openingBraceIndex = this.tm.firstIndexIn(node, TokenNameLBRACE); int closingBraceIndex = this.tm.lastIndexIn(node, TokenNameRBRACE); Token lastToken = this.tm.get(closingBraceIndex - 1); if (lastToken.tokenType == TokenNameLBRACE) { handleToken(this.tm.get(openingBraceIndex), this.options.insert_space_before_opening_brace_in_array_initializer && !(node.getParent() instanceof ArrayInitializer) && !(node.getParent() instanceof SingleMemberAnnotation), this.options.insert_space_between_empty_braces_in_array_initializer); } else { boolean endsWithComma = lastToken.tokenType == TokenNameCOMMA; handleToken(this.tm.get(openingBraceIndex), this.options.insert_space_before_opening_brace_in_array_initializer && !(node.getParent() instanceof ArrayInitializer) && !(node.getParent() instanceof SingleMemberAnnotation), this.options.insert_space_after_opening_brace_in_array_initializer && !(endsWithComma && node.expressions().isEmpty())); handleCommas(node.expressions(), this.options.insert_space_before_comma_in_array_initializer, this.options.insert_space_after_comma_in_array_initializer); if (endsWithComma) { handleToken(lastToken, this.options.insert_space_before_comma_in_array_initializer, false); //this.options.insert_space_after_comma_in_array_initializer); } handleToken(this.tm.get(closingBraceIndex), this.options.insert_space_before_closing_brace_in_array_initializer && !(endsWithComma && node.expressions().isEmpty()), false); } return true; } @Override public boolean visit(ParameterizedType node) { List<Type> typeArguments = node.typeArguments(); boolean hasArguments = !typeArguments.isEmpty(); handleTokenAfter(node.getType(), TokenNameLESS, this.options.insert_space_before_opening_angle_bracket_in_parameterized_type_reference, hasArguments && this.options.insert_space_after_opening_angle_bracket_in_parameterized_type_reference); if (hasArguments) { handleTokenAfter(typeArguments.get(typeArguments.size() - 1), TokenNameGREATER, this.options.insert_space_before_closing_angle_bracket_in_parameterized_type_reference, false); handleCommas(node.typeArguments(), this.options.insert_space_before_comma_in_parameterized_type_reference, this.options.insert_space_after_comma_in_parameterized_type_reference); } return true; } @Override public boolean visit(TypeParameter node) { List<Type> typeBounds = node.typeBounds(); for (int i = 1; i < typeBounds.size(); i++) { handleTokenBefore(typeBounds.get(i), TokenNameAND, this.options.insert_space_before_and_in_type_parameter, this.options.insert_space_after_and_in_type_parameter); } return true; } @Override public boolean visit(WildcardType node) { handleToken(node, TokenNameQUESTION, this.options.insert_space_before_question_in_wilcard, this.options.insert_space_after_question_in_wilcard || node.getBound() != null); return true; } @Override public boolean visit(UnionType node) { List<Type> types = node.types(); for (int i = 1; i < types.size(); i++) handleTokenBefore(types.get(i), TokenNameOR, this.options.insert_space_before_bitwise_operator, this.options.insert_space_after_bitwise_operator); return true; } @Override public boolean visit(Dimension node) { List<Annotation> annotations = node.annotations(); if (!annotations.isEmpty()) handleToken(annotations.get(0), TokenNameAT, true, false); return true; } @Override public boolean visit(TypeMethodReference node) { handleTypeArguments(node.typeArguments()); return true; } @Override public boolean visit(ExpressionMethodReference node) { handleTypeArguments(node.typeArguments()); return true; } @Override public boolean visit(SuperMethodReference node) { handleTypeArguments(node.typeArguments()); return true; } @Override public boolean visit(CreationReference node) { handleTypeArguments(node.typeArguments()); return true; } private void handleTypeArguments(List<Type> typeArguments) { if (typeArguments.isEmpty()) return; handleTokenBefore(typeArguments.get(0), TokenNameLESS, this.options.insert_space_before_opening_angle_bracket_in_type_arguments, this.options.insert_space_after_opening_angle_bracket_in_type_arguments); handleTokenAfter(typeArguments.get(typeArguments.size() - 1), TokenNameGREATER, this.options.insert_space_before_closing_angle_bracket_in_type_arguments, this.options.insert_space_after_closing_angle_bracket_in_type_arguments); handleCommas(typeArguments, this.options.insert_space_before_comma_in_type_arguments, this.options.insert_space_after_comma_in_type_arguments); } @Override public boolean visit(InstanceofExpression node) { handleTokenAfter(node.getLeftOperand(), TokenNameinstanceof, true, true); return true; } @Override public boolean visit(ModuleDeclaration node) { handleToken(node.getName(), TokenNameLBRACE, this.options.insert_space_before_opening_brace_in_type_declaration, false); return true; } @Override public boolean visit(ExportsDirective node) { handleModuleStatementCommas(node.modules()); return true; } @Override public boolean visit(OpensDirective node) { handleModuleStatementCommas(node.modules()); return true; } @Override public boolean visit(ProvidesDirective node) { handleModuleStatementCommas(node.implementations()); return true; } private void handleModuleStatementCommas(List<Name> names) { // using settings for fields for now, add new settings if necessary handleCommas(names, this.options.insert_space_before_comma_in_multiple_field_declarations, this.options.insert_space_after_comma_in_multiple_field_declarations); } private void handleCommas(List<? extends ASTNode> nodes, boolean spaceBefore, boolean spaceAfter) { if (spaceBefore || spaceAfter) { for (int i = 1; i < nodes.size(); i++) { handleTokenBefore(nodes.get(i), TokenNameCOMMA, spaceBefore, spaceAfter); } } } private void handleToken(ASTNode node, int tokenType, boolean spaceBefore, boolean spaceAfter) { if (spaceBefore || spaceAfter) { Token token = this.tm.get(this.tm.findIndex(node.getStartPosition(), tokenType, true)); // ^not the same as "firstTokenIn(node, tokenType)" - do not assert the token is inside the node handleToken(token, spaceBefore, spaceAfter); } } private void handleTokenBefore(ASTNode node, int tokenType, boolean spaceBefore, boolean spaceAfter) { if (spaceBefore || spaceAfter) { Token token = this.tm.firstTokenBefore(node, tokenType); handleToken(token, spaceBefore, spaceAfter); } } private void handleTokenAfter(ASTNode node, int tokenType, boolean spaceBefore, boolean spaceAfter) { if (tokenType == TokenNameGREATER) { // there could be ">>" or ">>>" instead, get rid of them int index = this.tm.lastIndexIn(node, -1); for (int i = index; i < index + 2; i++) { Token token = this.tm.get(i); if (token.tokenType == TokenNameRIGHT_SHIFT || token.tokenType == TokenNameUNSIGNED_RIGHT_SHIFT) { this.tm.remove(i); for (int j = 0; j < (token.tokenType == TokenNameRIGHT_SHIFT ? 2 : 3); j++) { this.tm.insert(i + j, new Token(token.originalStart + j, token.originalStart + j, TokenNameGREATER)); } } } } if (spaceBefore || spaceAfter) { Token token = this.tm.firstTokenAfter(node, tokenType); handleToken(token, spaceBefore, spaceAfter); } } private void handleToken(Token token, boolean spaceBefore, boolean spaceAfter) { if (spaceBefore) token.spaceBefore(); if (spaceAfter) token.spaceAfter(); } private boolean handleEmptyParens(ASTNode nodeBeforeParens, boolean insertSpace) { int openingIndex = this.tm.findIndex(nodeBeforeParens.getStartPosition(), TokenNameLPAREN, true); if (this.tm.get(openingIndex + 1).tokenType == TokenNameRPAREN) { if (insertSpace) this.tm.get(openingIndex).spaceAfter(); return true; } return false; } private boolean handleEmptyBrackets(ASTNode nodeContainingBrackets, boolean insertSpace) { int openingIndex = this.tm.firstIndexIn(nodeContainingBrackets, TokenNameLBRACKET); if (this.tm.get(openingIndex + 1).tokenType == TokenNameRBRACKET) { if (insertSpace) this.tm.get(openingIndex).spaceAfter(); return true; } return false; } private void handleSemicolon(ASTNode node) { if (this.options.insert_space_before_semicolon) { Token lastToken = this.tm.lastTokenIn(node, -1); if (lastToken.tokenType == TokenNameSEMICOLON) lastToken.spaceBefore(); } } private void handleSemicolon(List<ASTNode> nodes) { if (this.options.insert_space_before_semicolon) { for (ASTNode node : nodes) handleSemicolon(node); } } private void handleLoopBody(Statement loopBody) { /* space before body statement may be needed if it will stay on the same line */ int firstTokenIndex = this.tm.firstIndexIn(loopBody, -1); if (!(loopBody instanceof Block) && !(loopBody instanceof EmptyStatement) && !this.tm.get(firstTokenIndex - 1).isComment()) { this.tm.get(firstTokenIndex).spaceBefore(); } } public void finishUp() { this.tm.traverse(0, new TokenTraverser() { boolean isPreviousJIDP = false; @Override protected boolean token(Token token, int index) { // put space between consecutive keywords, numbers or identifiers char c = SpacePreparator.this.tm.charAt(token.originalStart); boolean isJIDP = ScannerHelper.isJavaIdentifierPart(c); if ((isJIDP || c == '@') && this.isPreviousJIDP) getPrevious().spaceAfter(); this.isPreviousJIDP = isJIDP; switch (token.tokenType) { case TokenNamePLUS: if (getNext().tokenType == TokenNamePLUS || getNext().tokenType == TokenNamePLUS_PLUS) token.spaceAfter(); break; case TokenNameMINUS: if (getNext().tokenType == TokenNameMINUS || getNext().tokenType == TokenNameMINUS_MINUS) token.spaceAfter(); break; } return true; } }); } }