1 /******************************************************************************* 2 * Copyright (c) 2020 Fabrice TIERCELIN and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * Fabrice TIERCELIN - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.jdt.internal.ui.fix; 15 16 import java.util.ArrayList; 17 import java.util.Collections; 18 import java.util.List; 19 import java.util.Map; 20 import java.util.concurrent.atomic.AtomicInteger; 21 22 import org.eclipse.core.runtime.CoreException; 23 24 import org.eclipse.jdt.core.ICompilationUnit; 25 import org.eclipse.jdt.core.dom.AST; 26 import org.eclipse.jdt.core.dom.ASTMatcher; 27 import org.eclipse.jdt.core.dom.ASTVisitor; 28 import org.eclipse.jdt.core.dom.CompilationUnit; 29 import org.eclipse.jdt.core.dom.Expression; 30 import org.eclipse.jdt.core.dom.IfStatement; 31 import org.eclipse.jdt.core.dom.InfixExpression; 32 import org.eclipse.jdt.core.dom.Statement; 33 import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; 34 35 import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory; 36 import org.eclipse.jdt.internal.corext.dom.ASTNodes; 37 import org.eclipse.jdt.internal.corext.fix.CleanUpConstants; 38 import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix; 39 import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix.CompilationUnitRewriteOperation; 40 import org.eclipse.jdt.internal.corext.fix.LinkedProposalModel; 41 import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite; 42 43 import org.eclipse.jdt.ui.cleanup.CleanUpRequirements; 44 import org.eclipse.jdt.ui.cleanup.ICleanUpFix; 45 import org.eclipse.jdt.ui.text.java.IProblemLocation; 46 47 /** 48 * A fix that merge conditions of if/else if/else that have the same blocks. 49 */ 50 public class MergeConditionalBlocksCleanUp extends AbstractMultiFix { MergeConditionalBlocksCleanUp()51 public MergeConditionalBlocksCleanUp() { 52 this(Collections.emptyMap()); 53 } 54 MergeConditionalBlocksCleanUp(Map<String, String> options)55 public MergeConditionalBlocksCleanUp(Map<String, String> options) { 56 super(options); 57 } 58 59 @Override getRequirements()60 public CleanUpRequirements getRequirements() { 61 boolean requireAST= isEnabled(CleanUpConstants.MERGE_CONDITIONAL_BLOCKS); 62 return new CleanUpRequirements(requireAST, false, false, null); 63 } 64 65 @Override getStepDescriptions()66 public String[] getStepDescriptions() { 67 if (isEnabled(CleanUpConstants.MERGE_CONDITIONAL_BLOCKS)) { 68 return new String[] { MultiFixMessages.MergeConditionalBlocksCleanup_description }; 69 } 70 71 return new String[0]; 72 } 73 74 @Override getPreview()75 public String getPreview() { 76 StringBuilder bld= new StringBuilder(); 77 78 if (isEnabled(CleanUpConstants.MERGE_CONDITIONAL_BLOCKS)) { 79 bld.append("if ((i == 0) || (i == 1)) {\n"); //$NON-NLS-1$ 80 } else { 81 bld.append("if (i == 0) {\n"); //$NON-NLS-1$ 82 bld.append(" System.out.println(\"Duplicate\");\n"); //$NON-NLS-1$ 83 bld.append("} else if (i == 1) {\n"); //$NON-NLS-1$ 84 } 85 86 bld.append(" System.out.println(\"Duplicate\");\n"); //$NON-NLS-1$ 87 bld.append("} else {\n"); //$NON-NLS-1$ 88 bld.append(" System.out.println(\"Different\");\n"); //$NON-NLS-1$ 89 bld.append("}\n"); //$NON-NLS-1$ 90 91 return bld.toString(); 92 } 93 94 @Override createFix(CompilationUnit unit)95 protected ICleanUpFix createFix(CompilationUnit unit) throws CoreException { 96 if (!isEnabled(CleanUpConstants.MERGE_CONDITIONAL_BLOCKS)) { 97 return null; 98 } 99 100 final List<CompilationUnitRewriteOperation> rewriteOperations= new ArrayList<>(); 101 102 unit.accept(new ASTVisitor() { 103 @Override 104 public boolean visit(final IfStatement node) { 105 if (node.getElseStatement() != null) { 106 List<IfStatement> duplicateIfBlocks= new ArrayList<>(4); 107 List<Boolean> isThenStatement= new ArrayList<>(4); 108 AtomicInteger operandCount= new AtomicInteger(ASTNodes.getNbOperands(node.getExpression())); 109 duplicateIfBlocks.add(node); 110 isThenStatement.add(Boolean.TRUE); 111 112 while (addOneMoreIf(duplicateIfBlocks, isThenStatement, operandCount)) { 113 // OK continue 114 } 115 116 if (duplicateIfBlocks.size() > 1) { 117 rewriteOperations.add(new MergeConditionalBlocksOperation(duplicateIfBlocks, isThenStatement)); 118 return false; 119 } 120 } 121 122 return true; 123 } 124 125 private boolean addOneMoreIf(final List<IfStatement> duplicateIfBlocks, final List<Boolean> isThenStatement, final AtomicInteger operandCount) { 126 IfStatement lastBlock= getLast(duplicateIfBlocks); 127 Statement previousStatement= getLast(isThenStatement) ? lastBlock.getThenStatement() : lastBlock.getElseStatement(); 128 Statement nextStatement= getLast(isThenStatement) ? lastBlock.getElseStatement() : lastBlock.getThenStatement(); 129 130 if (nextStatement != null) { 131 IfStatement nextElse= ASTNodes.as(nextStatement, IfStatement.class); 132 133 if (nextElse != null 134 && operandCount.get() + ASTNodes.getNbOperands(nextElse.getExpression()) < ASTNodes.EXCESSIVE_OPERAND_NUMBER) { 135 if (match(previousStatement, nextElse.getThenStatement())) { 136 operandCount.addAndGet(ASTNodes.getNbOperands(nextElse.getExpression())); 137 duplicateIfBlocks.add(nextElse); 138 isThenStatement.add(Boolean.TRUE); 139 return true; 140 } 141 142 if (nextElse.getElseStatement() != null 143 && match(previousStatement, nextElse.getElseStatement())) { 144 operandCount.addAndGet(ASTNodes.getNbOperands(nextElse.getExpression())); 145 duplicateIfBlocks.add(nextElse); 146 isThenStatement.add(Boolean.FALSE); 147 return true; 148 } 149 } 150 } 151 152 return false; 153 } 154 155 private boolean match(final Statement expectedStatement, final Statement actualStatement) { 156 ASTMatcher matcher= new ASTMatcher(); 157 List<Statement> expectedStatements= ASTNodes.asList(expectedStatement); 158 List<Statement> actualStatements= ASTNodes.asList(actualStatement); 159 160 if (expectedStatements.size() != actualStatements.size()) { 161 return false; 162 } 163 164 for (int codeLine= 0; codeLine < expectedStatements.size(); codeLine++) { 165 if (!matcher.safeSubtreeMatch(expectedStatements.get(codeLine), actualStatements.get(codeLine))) { 166 return false; 167 } 168 } 169 170 return true; 171 } 172 }); 173 174 if (rewriteOperations.isEmpty()) { 175 return null; 176 } 177 178 return new CompilationUnitRewriteOperationsFix(MultiFixMessages.MergeConditionalBlocksCleanup_description, unit, 179 rewriteOperations.toArray(new CompilationUnitRewriteOperation[0])); 180 } 181 182 @Override canFix(final ICompilationUnit compilationUnit, final IProblemLocation problem)183 public boolean canFix(final ICompilationUnit compilationUnit, final IProblemLocation problem) { 184 return false; 185 } 186 187 @Override createFix(final CompilationUnit unit, final IProblemLocation[] problems)188 protected ICleanUpFix createFix(final CompilationUnit unit, final IProblemLocation[] problems) throws CoreException { 189 return null; 190 } 191 192 private static class MergeConditionalBlocksOperation extends CompilationUnitRewriteOperation { 193 private final List<IfStatement> duplicateIfBlocks; 194 private final List<Boolean> isThenStatement; 195 MergeConditionalBlocksOperation(final List<IfStatement> duplicateIfBlocks, final List<Boolean> isThenStatement)196 public MergeConditionalBlocksOperation(final List<IfStatement> duplicateIfBlocks, final List<Boolean> isThenStatement) { 197 this.duplicateIfBlocks= duplicateIfBlocks; 198 this.isThenStatement= isThenStatement; 199 } 200 201 @Override rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModel linkedModel)202 public void rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModel linkedModel) throws CoreException { 203 ASTRewrite rewrite= cuRewrite.getASTRewrite(); 204 AST ast= cuRewrite.getRoot().getAST(); 205 206 List<Expression> newConditions= new ArrayList<>(duplicateIfBlocks.size()); 207 208 for (int i= 0; i < duplicateIfBlocks.size(); i++) { 209 if (isThenStatement.get(i)) { 210 newConditions.add(ASTNodeFactory.parenthesizeIfNeeded(ast, ASTNodes.createMoveTarget(rewrite, duplicateIfBlocks.get(i).getExpression()))); 211 } else { 212 newConditions.add(ASTNodeFactory.parenthesizeIfNeeded(ast, ASTNodeFactory.negate(ast, rewrite, duplicateIfBlocks.get(i).getExpression(), true))); 213 } 214 } 215 216 IfStatement lastBlock= getLast(duplicateIfBlocks); 217 Statement remainingStatement= getLast(isThenStatement) ? lastBlock.getElseStatement() : lastBlock.getThenStatement(); 218 InfixExpression newCondition= ast.newInfixExpression(); 219 newCondition.setOperator(InfixExpression.Operator.CONDITIONAL_OR); 220 newCondition.setLeftOperand(newConditions.remove(0)); 221 newCondition.setRightOperand(newConditions.remove(0)); 222 newCondition.extendedOperands().addAll(newConditions); 223 224 rewrite.replace(duplicateIfBlocks.get(0).getExpression(), newCondition, null); 225 226 if (remainingStatement != null) { 227 rewrite.replace(duplicateIfBlocks.get(0).getElseStatement(), ASTNodes.createMoveTarget(rewrite, remainingStatement), null); 228 } else if (duplicateIfBlocks.get(0).getElseStatement() != null) { 229 rewrite.remove(duplicateIfBlocks.get(0).getElseStatement(), null); 230 } 231 } 232 } 233 getLast(final List<E> list)234 private static <E> E getLast(final List<E> list) { 235 return list.get(list.size() - 1); 236 } 237 } 238