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