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.Objects; 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.ASTVisitor; 27 import org.eclipse.jdt.core.dom.CastExpression; 28 import org.eclipse.jdt.core.dom.ClassInstanceCreation; 29 import org.eclipse.jdt.core.dom.CompilationUnit; 30 import org.eclipse.jdt.core.dom.Expression; 31 import org.eclipse.jdt.core.dom.ITypeBinding; 32 import org.eclipse.jdt.core.dom.LambdaExpression; 33 import org.eclipse.jdt.core.dom.MethodInvocation; 34 import org.eclipse.jdt.core.dom.NumberLiteral; 35 import org.eclipse.jdt.core.dom.ParameterizedType; 36 import org.eclipse.jdt.core.dom.SingleVariableDeclaration; 37 import org.eclipse.jdt.core.dom.Type; 38 import org.eclipse.jdt.core.dom.VariableDeclarationExpression; 39 import org.eclipse.jdt.core.dom.VariableDeclarationFragment; 40 import org.eclipse.jdt.core.dom.VariableDeclarationStatement; 41 import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; 42 43 import org.eclipse.jdt.internal.corext.dom.ASTNodes; 44 import org.eclipse.jdt.internal.corext.fix.CleanUpConstants; 45 import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix; 46 import org.eclipse.jdt.internal.corext.fix.CompilationUnitRewriteOperationsFix.CompilationUnitRewriteOperation; 47 import org.eclipse.jdt.internal.corext.fix.LinkedProposalModel; 48 import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite; 49 import org.eclipse.jdt.internal.corext.util.JavaModelUtil; 50 51 import org.eclipse.jdt.ui.cleanup.CleanUpRequirements; 52 import org.eclipse.jdt.ui.cleanup.ICleanUpFix; 53 import org.eclipse.jdt.ui.text.java.IProblemLocation; 54 55 /** 56 * A fix that uses the Local variable type inference: 57 * <ul> 58 * <li>As of Java 10, if a variable is initialized by an explicit type value, it can be declared 59 * using the <code>var</code> keyword.</li> 60 * </ul> 61 */ 62 public class VarCleanUp extends AbstractMultiFix { VarCleanUp()63 public VarCleanUp() { 64 this(Collections.emptyMap()); 65 } 66 VarCleanUp(final Map<String, String> options)67 public VarCleanUp(final Map<String, String> options) { 68 super(options); 69 } 70 71 @Override getRequirements()72 public CleanUpRequirements getRequirements() { 73 boolean requireAST= isEnabled(CleanUpConstants.USE_VAR); 74 Map<String, String> requiredOptions= null; 75 return new CleanUpRequirements(requireAST, false, false, requiredOptions); 76 } 77 78 @Override getStepDescriptions()79 public String[] getStepDescriptions() { 80 if (isEnabled(CleanUpConstants.USE_VAR)) { 81 return new String[] { MultiFixMessages.VarCleanUp_description }; 82 } 83 return new String[0]; 84 } 85 86 @Override getPreview()87 public String getPreview() { 88 StringBuilder bld= new StringBuilder(); 89 if (isEnabled(CleanUpConstants.USE_VAR)) { 90 bld.append("var number = 0;\n"); //$NON-NLS-1$ 91 bld.append("var list = new ArrayList<String>();\n"); //$NON-NLS-1$ 92 bld.append("var map = new HashMap<Integer, String>();\n"); //$NON-NLS-1$ 93 } else { 94 bld.append("int number = 0;\n"); //$NON-NLS-1$ 95 bld.append("ArrayList<String> list = new ArrayList<String>();\n"); //$NON-NLS-1$ 96 bld.append("HashMap<Integer, String> map = new HashMap<>();\n"); //$NON-NLS-1$ 97 } 98 99 return bld.toString(); 100 } 101 102 @Override createFix(final CompilationUnit unit)103 protected ICleanUpFix createFix(final CompilationUnit unit) throws CoreException { 104 if (!isEnabled(CleanUpConstants.USE_VAR) || !JavaModelUtil.is10OrHigher(unit.getJavaElement().getJavaProject())) { 105 return null; 106 } 107 108 final List<CompilationUnitRewriteOperation> rewriteOperations= new ArrayList<>(); 109 110 unit.accept(new ASTVisitor() { 111 @Override 112 public boolean visit(final VariableDeclarationStatement node) { 113 if (node.fragments().size() != 1) { 114 return true; 115 } 116 117 VariableDeclarationFragment fragment= (VariableDeclarationFragment) node.fragments().get(0); 118 119 return maybeUseVar(node.getType(), fragment.getInitializer(), fragment.getExtraDimensions()); 120 } 121 122 @Override 123 public boolean visit(final VariableDeclarationExpression node) { 124 if (node.fragments().size() != 1) { 125 return true; 126 } 127 128 VariableDeclarationFragment fragment= (VariableDeclarationFragment) node.fragments().get(0); 129 130 return maybeUseVar(node.getType(), fragment.getInitializer(), fragment.getExtraDimensions()); 131 } 132 133 @Override 134 public boolean visit(final SingleVariableDeclaration node) { 135 return maybeUseVar(node.getType(), node.getInitializer(), node.getExtraDimensions()); 136 } 137 138 private boolean maybeUseVar(final Type type, final Expression initializer, final int extraDimensions) { 139 if (type.isVar() || initializer == null || initializer.resolveTypeBinding() == null || type.resolveBinding() == null 140 || extraDimensions > 0) { 141 return true; 142 } 143 144 ITypeBinding variableType= type.resolveBinding(); 145 ITypeBinding initializerType= initializer.resolveTypeBinding(); 146 147 if (variableType != null 148 && variableType.isParameterizedType() == initializerType.isParameterizedType()) { 149 if (Objects.equals(variableType, initializerType)) { 150 ClassInstanceCreation classInstanceCreation= ASTNodes.as(initializer, ClassInstanceCreation.class); 151 CastExpression castExpression= ASTNodes.as(initializer, CastExpression.class); 152 MethodInvocation methodInvocation= ASTNodes.as(initializer, MethodInvocation.class); 153 LambdaExpression lambdaExpression= ASTNodes.as(initializer, LambdaExpression.class); 154 Expression expression= ASTNodes.as(initializer, Expression.class); 155 156 if (!variableType.isParameterizedType() 157 || (classInstanceCreation != null 158 && classInstanceCreation.getType().isParameterizedType() 159 && classInstanceCreation.getType().resolveBinding() != null 160 && Objects.equals(variableType.getTypeArguments(), classInstanceCreation.getType().resolveBinding().getTypeArguments()) 161 && !((ParameterizedType) classInstanceCreation.getType()).typeArguments().isEmpty()) 162 || (castExpression != null 163 && castExpression.getType().isParameterizedType() 164 && castExpression.getType().resolveBinding() != null 165 && variableType.getTypeArguments().length == ((ParameterizedType) castExpression.getType()).typeArguments().size() 166 && Objects.equals(variableType.getTypeArguments(), castExpression.getType().resolveBinding().getTypeArguments())) 167 || (methodInvocation != null 168 && methodInvocation.resolveMethodBinding() != null 169 && methodInvocation.resolveMethodBinding().getReturnType().isParameterizedType() 170 && Objects.equals(variableType.getTypeArguments(), methodInvocation.resolveMethodBinding().getReturnType().getTypeArguments())) 171 || (classInstanceCreation == null 172 && castExpression == null 173 && methodInvocation == null 174 && lambdaExpression == null 175 && expression != null 176 && expression.resolveTypeBinding() != null 177 && expression.resolveTypeBinding().isParameterizedType() 178 && Objects.equals(variableType.getTypeArguments(), expression.resolveTypeBinding().getTypeArguments()))) { 179 rewriteOperations.add(new VarOperation(type)); 180 return false; 181 } else if (variableType.isParameterizedType() 182 && classInstanceCreation != null 183 && classInstanceCreation.getType().isParameterizedType() 184 && ((ParameterizedType) classInstanceCreation.getType()).typeArguments().isEmpty()) { 185 rewriteOperations.add(new VarOperation(type, classInstanceCreation)); 186 return false; 187 } 188 } else { 189 NumberLiteral literal= ASTNodes.as(initializer, NumberLiteral.class); 190 191 if (literal != null && (literal.getToken().matches(".*[^lLdDfF]") || literal.getToken().matches("0x.*[^lL]"))) { //$NON-NLS-1$ //$NON-NLS-2$ 192 if (ASTNodes.hasType(variableType, long.class.getSimpleName())) { 193 rewriteOperations.add(new VarOperation(type, literal, Character.valueOf('L'))); 194 return false; 195 } 196 197 if (ASTNodes.hasType(variableType, float.class.getSimpleName())) { 198 rewriteOperations.add(new VarOperation(type, literal, Character.valueOf('F'))); 199 return false; 200 } 201 202 if (ASTNodes.hasType(variableType, double.class.getSimpleName())) { 203 rewriteOperations.add(new VarOperation(type, literal, Character.valueOf('D'))); 204 return false; 205 } 206 } 207 } 208 } 209 210 return true; 211 } 212 }); 213 214 if (rewriteOperations.isEmpty()) { 215 return null; 216 } 217 218 return new CompilationUnitRewriteOperationsFix(MultiFixMessages.VarCleanUp_description, unit, 219 rewriteOperations.toArray(new CompilationUnitRewriteOperation[rewriteOperations.size()])); 220 } 221 222 @Override canFix(final ICompilationUnit compilationUnit, final IProblemLocation problem)223 public boolean canFix(final ICompilationUnit compilationUnit, final IProblemLocation problem) { 224 return false; 225 } 226 227 @Override createFix(final CompilationUnit unit, final IProblemLocation[] problems)228 protected ICleanUpFix createFix(final CompilationUnit unit, final IProblemLocation[] problems) throws CoreException { 229 return null; 230 } 231 232 private class VarOperation extends CompilationUnitRewriteOperation { 233 private final Type node; 234 private final ClassInstanceCreation classInstanceCreation; 235 private final NumberLiteral literal; 236 private final Character postfix; 237 VarOperation(final Type node)238 public VarOperation(final Type node) { 239 this(node, null, null, null); 240 } 241 VarOperation(final Type node, final ClassInstanceCreation classInstanceCreation)242 public VarOperation(final Type node, final ClassInstanceCreation classInstanceCreation) { 243 this(node, classInstanceCreation, null, null); 244 } 245 VarOperation(final Type node, final NumberLiteral literal, final Character postfix)246 public VarOperation(final Type node, final NumberLiteral literal, final Character postfix) { 247 this(node, null, literal, postfix); 248 } 249 VarOperation(final Type node, final ClassInstanceCreation classInstanceCreation, final NumberLiteral literal, final Character postfix)250 public VarOperation(final Type node, final ClassInstanceCreation classInstanceCreation, final NumberLiteral literal, final Character postfix) { 251 this.node= node; 252 this.classInstanceCreation= classInstanceCreation; 253 this.literal= literal; 254 this.postfix= postfix; 255 } 256 257 @Override rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModel linkedModel)258 public void rewriteAST(final CompilationUnitRewrite cuRewrite, final LinkedProposalModel linkedModel) throws CoreException { 259 ASTRewrite rewrite= cuRewrite.getASTRewrite(); 260 AST ast= cuRewrite.getRoot().getAST(); 261 262 if (classInstanceCreation != null) { 263 rewrite.replace(classInstanceCreation.getType(), rewrite.createCopyTarget(node), null); 264 } else if (literal != null) { 265 rewrite.replace(literal, ast.newNumberLiteral(literal.getToken() + postfix), null); 266 } 267 268 rewrite.replace(node, ast.newSimpleType(ast.newSimpleName("var")), null); //$NON-NLS-1$ 269 } 270 } 271 } 272