1 /*******************************************************************************
2  * Copyright (c) 2000, 2019 IBM Corporation 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  *     IBM Corporation - initial API and implementation
13  *     Pierre-Yves B. <pyvesdev@gmail.com> - [inline] Allow inlining of local variable initialized to null. - https://bugs.eclipse.org/93850
14  *******************************************************************************/
15 package org.eclipse.jdt.internal.corext.refactoring.code;
16 
17 import java.util.ArrayList;
18 import java.util.Arrays;
19 import java.util.Collection;
20 import java.util.Collections;
21 import java.util.HashMap;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.StringTokenizer;
26 
27 import org.eclipse.core.runtime.Assert;
28 import org.eclipse.core.runtime.CoreException;
29 import org.eclipse.core.runtime.IProgressMonitor;
30 import org.eclipse.core.runtime.SubProgressMonitor;
31 
32 import org.eclipse.text.edits.TextEditGroup;
33 
34 import org.eclipse.ltk.core.refactoring.Change;
35 import org.eclipse.ltk.core.refactoring.Refactoring;
36 import org.eclipse.ltk.core.refactoring.RefactoringChangeDescriptor;
37 import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
38 import org.eclipse.ltk.core.refactoring.RefactoringStatus;
39 
40 import org.eclipse.jdt.core.ICompilationUnit;
41 import org.eclipse.jdt.core.IJavaElement;
42 import org.eclipse.jdt.core.IJavaProject;
43 import org.eclipse.jdt.core.JavaModelException;
44 import org.eclipse.jdt.core.NamingConventions;
45 import org.eclipse.jdt.core.SourceRange;
46 import org.eclipse.jdt.core.dom.AST;
47 import org.eclipse.jdt.core.dom.ASTNode;
48 import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
49 import org.eclipse.jdt.core.dom.Annotation;
50 import org.eclipse.jdt.core.dom.BodyDeclaration;
51 import org.eclipse.jdt.core.dom.CompilationUnit;
52 import org.eclipse.jdt.core.dom.EnumDeclaration;
53 import org.eclipse.jdt.core.dom.Expression;
54 import org.eclipse.jdt.core.dom.ExpressionStatement;
55 import org.eclipse.jdt.core.dom.FieldAccess;
56 import org.eclipse.jdt.core.dom.FieldDeclaration;
57 import org.eclipse.jdt.core.dom.IExtendedModifier;
58 import org.eclipse.jdt.core.dom.ITypeBinding;
59 import org.eclipse.jdt.core.dom.Initializer;
60 import org.eclipse.jdt.core.dom.Javadoc;
61 import org.eclipse.jdt.core.dom.MethodDeclaration;
62 import org.eclipse.jdt.core.dom.Modifier;
63 import org.eclipse.jdt.core.dom.Name;
64 import org.eclipse.jdt.core.dom.NullLiteral;
65 import org.eclipse.jdt.core.dom.QualifiedName;
66 import org.eclipse.jdt.core.dom.SimpleName;
67 import org.eclipse.jdt.core.dom.SwitchCase;
68 import org.eclipse.jdt.core.dom.Type;
69 import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
70 import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
71 import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
72 import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext;
73 import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.TypeLocation;
74 import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
75 import org.eclipse.jdt.core.manipulation.CodeGeneration;
76 import org.eclipse.jdt.core.refactoring.CompilationUnitChange;
77 import org.eclipse.jdt.core.refactoring.IJavaRefactorings;
78 import org.eclipse.jdt.core.refactoring.descriptors.ExtractConstantDescriptor;
79 import org.eclipse.jdt.core.refactoring.descriptors.JavaRefactoringDescriptor;
80 
81 import org.eclipse.jdt.internal.core.manipulation.StubUtility;
82 import org.eclipse.jdt.internal.core.manipulation.dom.ASTResolving;
83 import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels;
84 import org.eclipse.jdt.internal.core.refactoring.descriptors.RefactoringSignatureDescriptorFactory;
85 import org.eclipse.jdt.internal.corext.Corext;
86 import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext;
87 import org.eclipse.jdt.internal.corext.dom.ASTNodes;
88 import org.eclipse.jdt.internal.corext.dom.Bindings;
89 import org.eclipse.jdt.internal.corext.dom.ScopeAnalyzer;
90 import org.eclipse.jdt.internal.corext.dom.fragments.ASTFragmentFactory;
91 import org.eclipse.jdt.internal.corext.dom.fragments.IASTFragment;
92 import org.eclipse.jdt.internal.corext.dom.fragments.IExpressionFragment;
93 import org.eclipse.jdt.internal.corext.fix.LinkedProposalModel;
94 import org.eclipse.jdt.internal.corext.fix.LinkedProposalPositionGroup;
95 import org.eclipse.jdt.internal.corext.refactoring.Checks;
96 import org.eclipse.jdt.internal.corext.refactoring.JDTRefactoringDescriptorComment;
97 import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringArguments;
98 import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringDescriptorUtil;
99 import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages;
100 import org.eclipse.jdt.internal.corext.refactoring.base.RefactoringStatusCodes;
101 import org.eclipse.jdt.internal.corext.refactoring.rename.RefactoringAnalyzeUtil;
102 import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
103 import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser;
104 import org.eclipse.jdt.internal.corext.util.JdtFlags;
105 import org.eclipse.jdt.internal.corext.util.Messages;
106 
107 import org.eclipse.jdt.internal.ui.preferences.JavaPreferencesSettings;
108 import org.eclipse.jdt.internal.ui.text.correction.ModifierCorrectionSubProcessorCore;
109 
110 public class ExtractConstantRefactoring extends Refactoring {
111 
112 	private static final String ATTRIBUTE_REPLACE= "replace"; //$NON-NLS-1$
113 	private static final String ATTRIBUTE_QUALIFY= "qualify"; //$NON-NLS-1$
114 	private static final String ATTRIBUTE_VISIBILITY= "visibility"; //$NON-NLS-1$
115 
116 	private static final String MODIFIER= "static final"; //$NON-NLS-1$
117 
118 	private static final String KEY_NAME= "name"; //$NON-NLS-1$
119 	private static final String KEY_TYPE= "type"; //$NON-NLS-1$
120 
121 	private CompilationUnitRewrite fCuRewrite;
122 	private int fSelectionStart;
123 	private int fSelectionLength;
124 	private ICompilationUnit fCu;
125 
126 	private IExpressionFragment fSelectedExpression;
127 	private Type fConstantTypeCache;
128 	private boolean fReplaceAllOccurrences= true; //default value
129 	private boolean fQualifyReferencesWithDeclaringClassName= false;	//default value
130 
131 	private String fVisibility= JdtFlags.VISIBILITY_STRING_PRIVATE; //default value
132 	private boolean fTargetIsInterface= false;
133 	private String fConstantName;
134 	private String[] fExcludedVariableNames;
135 
136 	private boolean fSelectionAllStaticFinal;
137 	private boolean fAllStaticFinalCheckPerformed= false;
138 
139 	//Constant Declaration Location
140 	private BodyDeclaration fToInsertAfter;
141 	private boolean fInsertFirst;
142 
143 	private CompilationUnitChange fChange;
144 	private String[] fGuessedConstNames;
145 
146 	private LinkedProposalModel fLinkedProposalModel;
147 	private boolean fCheckResultForCompileProblems;
148 
149 	/**
150 	 * Creates a new extract constant refactoring
151 	 * @param unit the compilation unit, or <code>null</code> if invoked by scripting
152 	 * @param selectionStart start
153 	 * @param selectionLength length
154 	 */
ExtractConstantRefactoring(ICompilationUnit unit, int selectionStart, int selectionLength)155 	public ExtractConstantRefactoring(ICompilationUnit unit, int selectionStart, int selectionLength) {
156 		Assert.isTrue(selectionStart >= 0);
157 		Assert.isTrue(selectionLength >= 0);
158 		fSelectionStart= selectionStart;
159 		fSelectionLength= selectionLength;
160 		fCu= unit;
161 		fCuRewrite= null;
162 		fLinkedProposalModel= null;
163 		fConstantName= ""; //$NON-NLS-1$
164 		fCheckResultForCompileProblems= true;
165 	}
166 
ExtractConstantRefactoring(CompilationUnit astRoot, int selectionStart, int selectionLength)167 	public ExtractConstantRefactoring(CompilationUnit astRoot, int selectionStart, int selectionLength) {
168 		Assert.isTrue(selectionStart >= 0);
169 		Assert.isTrue(selectionLength >= 0);
170 		Assert.isTrue(astRoot.getTypeRoot() instanceof ICompilationUnit);
171 
172 		fSelectionStart= selectionStart;
173 		fSelectionLength= selectionLength;
174 		fCu= (ICompilationUnit) astRoot.getTypeRoot();
175 		fCuRewrite= new CompilationUnitRewrite(fCu, astRoot);
176 		fLinkedProposalModel= null;
177 		fConstantName= ""; //$NON-NLS-1$
178 		fCheckResultForCompileProblems= true;
179 	}
180 
ExtractConstantRefactoring(JavaRefactoringArguments arguments, RefactoringStatus status)181     public ExtractConstantRefactoring(JavaRefactoringArguments arguments, RefactoringStatus status) {
182    		this((ICompilationUnit) null, 0, 0);
183    		RefactoringStatus initializeStatus= initialize(arguments);
184    		status.merge(initializeStatus);
185     }
186 
setCheckResultForCompileProblems(boolean checkResultForCompileProblems)187 	public void setCheckResultForCompileProblems(boolean checkResultForCompileProblems) {
188 		fCheckResultForCompileProblems= checkResultForCompileProblems;
189 	}
190 
setLinkedProposalModel(LinkedProposalModel linkedProposalModel)191 	public void setLinkedProposalModel(LinkedProposalModel linkedProposalModel) {
192 		fLinkedProposalModel= linkedProposalModel;
193 	}
194 
195 	@Override
getName()196 	public String getName() {
197 		return RefactoringCoreMessages.ExtractConstantRefactoring_name;
198 	}
199 
replaceAllOccurrences()200 	public boolean replaceAllOccurrences() {
201 		return fReplaceAllOccurrences;
202 	}
203 
setReplaceAllOccurrences(boolean replaceAllOccurrences)204 	public void setReplaceAllOccurrences(boolean replaceAllOccurrences) {
205 		fReplaceAllOccurrences= replaceAllOccurrences;
206 	}
207 
setVisibility(String am)208 	public void setVisibility(String am) {
209 		Assert.isTrue(
210 			am == JdtFlags.VISIBILITY_STRING_PRIVATE || am == JdtFlags.VISIBILITY_STRING_PROTECTED || am == JdtFlags.VISIBILITY_STRING_PACKAGE || am == JdtFlags.VISIBILITY_STRING_PUBLIC
211 		);
212 		fVisibility= am;
213 	}
214 
getVisibility()215 	public String getVisibility() {
216 		return fVisibility;
217 	}
218 
getTargetIsInterface()219 	public boolean getTargetIsInterface() {
220 		return fTargetIsInterface;
221 	}
222 
qualifyReferencesWithDeclaringClassName()223 	public boolean qualifyReferencesWithDeclaringClassName() {
224 		return fQualifyReferencesWithDeclaringClassName;
225 	}
226 
setQualifyReferencesWithDeclaringClassName(boolean qualify)227 	public void setQualifyReferencesWithDeclaringClassName(boolean qualify) {
228 		fQualifyReferencesWithDeclaringClassName= qualify;
229 	}
230 
guessConstantName()231 	public String guessConstantName() {
232 		String[] proposals= guessConstantNames();
233 		if (proposals.length > 0)
234 			return proposals[0];
235 		else
236 			return fConstantName;
237 	}
238 
239 	/**
240 	 * @return proposed variable names (may be empty, but not null).
241 	 * The first proposal should be used as "best guess" (if it exists).
242 	 */
guessConstantNames()243 	public String[] guessConstantNames() {
244 		if (fGuessedConstNames == null) {
245 			try {
246 				Expression expression= getSelectedExpression().getAssociatedExpression();
247 				if (expression != null) {
248 					ITypeBinding binding= guessBindingForReference(expression);
249 					fGuessedConstNames= StubUtility.getVariableNameSuggestions(NamingConventions.VK_STATIC_FINAL_FIELD, fCu.getJavaProject(), binding, expression, Arrays.asList(getExcludedVariableNames()));
250 				}
251 			} catch (JavaModelException e) {
252 			}
253 			if (fGuessedConstNames == null)
254 				fGuessedConstNames= new String[0];
255 		}
256 		return fGuessedConstNames;
257 	}
258 
259 
getExcludedVariableNames()260 	private String[] getExcludedVariableNames() {
261 		if (fExcludedVariableNames == null) {
262 			try {
263 				IExpressionFragment expr= getSelectedExpression();
264 				Collection<String> takenNames= new ScopeAnalyzer(fCuRewrite.getRoot()).getUsedVariableNames(expr.getStartPosition(), expr.getLength());
265 				fExcludedVariableNames= takenNames.toArray(new String[takenNames.size()]);
266 			} catch (JavaModelException e) {
267 				fExcludedVariableNames= new String[0];
268 			}
269 		}
270 		return fExcludedVariableNames;
271 	}
272 
273 	@Override
checkInitialConditions(IProgressMonitor pm)274 	public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException {
275 		try {
276 			pm.beginTask("", 7); //$NON-NLS-1$
277 
278 			RefactoringStatus result= Checks.validateEdit(fCu, getValidationContext(), pm);
279 			if (result.hasFatalError())
280 				return result;
281 			pm.worked(1);
282 
283 			if (fCuRewrite == null) {
284 				CompilationUnit cuNode= RefactoringASTParser.parseWithASTProvider(fCu, true, new SubProgressMonitor(pm, 3));
285 				fCuRewrite= new CompilationUnitRewrite(fCu, cuNode);
286 			} else {
287 				pm.worked(3);
288 			}
289 			result.merge(checkSelection(new SubProgressMonitor(pm, 3)));
290 
291 			if (result.hasFatalError())
292 				return result;
293 
294 			if (isLiteralNodeSelected())
295 				fReplaceAllOccurrences= false;
296 
297 			if (isInTypeDeclarationAnnotation(getSelectedExpression().getAssociatedNode())) {
298 				fVisibility= JdtFlags.VISIBILITY_STRING_PACKAGE;
299 			}
300 
301 			ITypeBinding targetType= getContainingTypeBinding();
302 			if (targetType.isInterface()) {
303 				fTargetIsInterface= true;
304 				fVisibility= JdtFlags.VISIBILITY_STRING_PUBLIC;
305 			}
306 
307 			return result;
308 		} finally {
309 			pm.done();
310 		}
311 	}
312 
selectionAllStaticFinal()313 	public boolean selectionAllStaticFinal() {
314 		Assert.isTrue(fAllStaticFinalCheckPerformed);
315 		return fSelectionAllStaticFinal;
316 	}
317 
checkAllStaticFinal()318 	private void checkAllStaticFinal() throws JavaModelException {
319 		fSelectionAllStaticFinal= ConstantChecks.isStaticFinalConstant(getSelectedExpression());
320 		fAllStaticFinalCheckPerformed= true;
321 	}
322 
checkSelection(IProgressMonitor pm)323 	private RefactoringStatus checkSelection(IProgressMonitor pm) throws JavaModelException {
324 		try {
325 			pm.beginTask("", 2); //$NON-NLS-1$
326 
327 			IExpressionFragment selectedExpression= getSelectedExpression();
328 
329 			if (selectedExpression == null) {
330 				String message= RefactoringCoreMessages.ExtractConstantRefactoring_select_expression;
331 				return CodeRefactoringUtil.checkMethodSyntaxErrors(fSelectionStart, fSelectionLength, fCuRewrite.getRoot(), message);
332 			}
333 			pm.worked(1);
334 
335 			RefactoringStatus result= new RefactoringStatus();
336 			result.merge(checkExpression());
337 			if (result.hasFatalError())
338 				return result;
339 			pm.worked(1);
340 
341 			return result;
342 		} finally {
343 			pm.done();
344 		}
345 	}
346 
checkExpressionBinding()347 	private RefactoringStatus checkExpressionBinding() throws JavaModelException {
348 		return checkExpressionFragmentIsRValue();
349 	}
350 
checkExpressionFragmentIsRValue()351 	private RefactoringStatus checkExpressionFragmentIsRValue() throws JavaModelException {
352 		/* Moved this functionality to Checks, to allow sharing with
353 		   ExtractTempRefactoring, others */
354 		switch(Checks.checkExpressionIsRValue(getSelectedExpression().getAssociatedExpression())) {
355 			case Checks.NOT_RVALUE_MISC:
356 				return RefactoringStatus.createStatus(RefactoringStatus.FATAL, RefactoringCoreMessages.ExtractConstantRefactoring_select_expression, null, Corext.getPluginId(), RefactoringStatusCodes.EXPRESSION_NOT_RVALUE, null);
357 			case Checks.NOT_RVALUE_VOID:
358 				return RefactoringStatus.createStatus(RefactoringStatus.FATAL, RefactoringCoreMessages.ExtractConstantRefactoring_no_void, null, Corext.getPluginId(), RefactoringStatusCodes.EXPRESSION_NOT_RVALUE_VOID, null);
359 			case Checks.IS_RVALUE_GUESSED:
360 			case Checks.IS_RVALUE:
361 				return new RefactoringStatus();
362 			default:
363 				Assert.isTrue(false); return null;
364 		}
365 	}
366 
guessBindingForReference(Expression expression)367 	private ITypeBinding guessBindingForReference(Expression expression) {
368 		ITypeBinding binding= expression.resolveTypeBinding();
369 		if (binding == null) {
370 			binding= ASTResolving.guessBindingForReference(expression);
371 		}
372 		return binding;
373 	}
374 
375 	//	 !!! -- same as in ExtractTempRefactoring
isLiteralNodeSelected()376 	private boolean isLiteralNodeSelected() throws JavaModelException {
377 		IExpressionFragment fragment= getSelectedExpression();
378 		if (fragment == null)
379 			return false;
380 		Expression expression= fragment.getAssociatedExpression();
381 		if (expression == null)
382 			return false;
383 		switch (expression.getNodeType()) {
384 			case ASTNode.BOOLEAN_LITERAL :
385 			case ASTNode.CHARACTER_LITERAL :
386 			case ASTNode.NULL_LITERAL :
387 			case ASTNode.NUMBER_LITERAL :
388 				return true;
389 
390 			default :
391 				return false;
392 		}
393 	}
394 
checkExpression()395 	private RefactoringStatus checkExpression() throws JavaModelException {
396 		RefactoringStatus result= new RefactoringStatus();
397 		result.merge(checkExpressionBinding());
398 		if(result.hasFatalError())
399 			return result;
400 		checkAllStaticFinal();
401 
402 		IExpressionFragment selectedExpression= getSelectedExpression();
403 		Expression associatedExpression= selectedExpression.getAssociatedExpression();
404 		if (associatedExpression instanceof NullLiteral)
405 			result.merge(RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractConstantRefactoring_null_literals));
406 		else if (!ConstantChecks.isLoadTimeConstant(selectedExpression))
407 			result.merge(RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractConstantRefactoring_not_load_time_constant));
408 		else if (associatedExpression instanceof SimpleName) {
409 			if (associatedExpression.getParent() instanceof QualifiedName && associatedExpression.getLocationInParent() == QualifiedName.NAME_PROPERTY
410 					|| associatedExpression.getParent() instanceof FieldAccess && associatedExpression.getLocationInParent() == FieldAccess.NAME_PROPERTY)
411 				return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractConstantRefactoring_select_expression);
412 		}
413 
414 		return result;
415 	}
416 
setConstantName(String newName)417 	public void setConstantName(String newName) {
418 		Assert.isNotNull(newName);
419 		fConstantName= newName;
420 	}
421 
getConstantName()422 	public String getConstantName() {
423 		return fConstantName;
424 	}
425 
426 	/**
427 	 * This method performs checks on the constant name which are
428 	 * quick enough to be performed every time the ui input component
429 	 * contents are changed.
430 	 *
431 	 * @return return the resulting status
432 	 * @throws JavaModelException thrown when the operation could not be executed
433 	 */
checkConstantNameOnChange()434 	public RefactoringStatus checkConstantNameOnChange() throws JavaModelException {
435 		if (Arrays.asList(getExcludedVariableNames()).contains(fConstantName))
436 			return RefactoringStatus.createErrorStatus(Messages.format(RefactoringCoreMessages.ExtractConstantRefactoring_another_variable, BasicElementLabels.getJavaElementName(getConstantName())));
437 		return Checks.checkConstantName(fConstantName, fCu);
438 	}
439 
440 	// !! similar to ExtractTempRefactoring equivalent
getConstantSignaturePreview()441 	public String getConstantSignaturePreview() throws JavaModelException {
442 		String space= " "; //$NON-NLS-1$
443 		return getVisibility() + space + MODIFIER + space + getConstantTypeName() + space + fConstantName;
444 	}
445 
createTextChange(IProgressMonitor pm)446 	public CompilationUnitChange createTextChange(IProgressMonitor pm) throws CoreException {
447 		createConstantDeclaration();
448 		replaceExpressionsWithConstant();
449 		return fCuRewrite.createChange(RefactoringCoreMessages.ExtractConstantRefactoring_change_name, true, pm);
450 	}
451 
452 
453 	@Override
checkFinalConditions(IProgressMonitor pm)454 	public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException {
455 		pm.beginTask(RefactoringCoreMessages.ExtractConstantRefactoring_checking_preconditions, 2);
456 
457 		/* Note: some checks are performed on change of input widget
458 		 * values. (e.g. see ExtractConstantRefactoring.checkConstantNameOnChange())
459 		 */
460 
461 		//TODO: possibly add more checking for name conflicts that might
462 		//      lead to a change in behavior
463 
464 		try {
465 			createConstantDeclaration();
466 			replaceExpressionsWithConstant();
467 			fChange= fCuRewrite.createChange(RefactoringCoreMessages.ExtractConstantRefactoring_change_name, true, new SubProgressMonitor(pm, 1));
468 
469 			return fCheckResultForCompileProblems ? RefactoringAnalyzeUtil.checkNewSource(fChange, fCu, fCuRewrite.getRoot(), pm) : new RefactoringStatus();
470 		} finally {
471 			fConstantTypeCache= null;
472 			fCuRewrite.clearASTAndImportRewrites();
473 			pm.done();
474 		}
475 	}
476 
createConstantDeclaration()477 	private void createConstantDeclaration() throws CoreException {
478 		Type type= getConstantType();
479 
480 		IExpressionFragment fragment= getSelectedExpression();
481 		Expression initializer= getSelectedExpression().createCopyTarget(fCuRewrite.getASTRewrite(), true);
482 
483 		AST ast= fCuRewrite.getAST();
484 		VariableDeclarationFragment variableDeclarationFragment= ast.newVariableDeclarationFragment();
485 		variableDeclarationFragment.setName(ast.newSimpleName(fConstantName));
486 		variableDeclarationFragment.setInitializer(initializer);
487 
488 		FieldDeclaration fieldDeclaration= ast.newFieldDeclaration(variableDeclarationFragment);
489 		fieldDeclaration.setType(type);
490 		Modifier.ModifierKeyword accessModifier= Modifier.ModifierKeyword.toKeyword(fVisibility);
491 		if (accessModifier != null)
492 			fieldDeclaration.modifiers().add(ast.newModifier(accessModifier));
493 		fieldDeclaration.modifiers().add(ast.newModifier(Modifier.ModifierKeyword.STATIC_KEYWORD));
494 		fieldDeclaration.modifiers().add(ast.newModifier(Modifier.ModifierKeyword.FINAL_KEYWORD));
495 
496 		boolean createComments= JavaPreferencesSettings.getCodeGenerationSettings(fCu.getJavaProject()).createComments;
497 		if (createComments) {
498 			String comment= CodeGeneration.getFieldComment(fCu, getConstantTypeName(), fConstantName, StubUtility.getLineDelimiterUsed(fCu));
499 			if (comment != null && comment.length() > 0) {
500 				Javadoc doc= (Javadoc) fCuRewrite.getASTRewrite().createStringPlaceholder(comment, ASTNode.JAVADOC);
501 				fieldDeclaration.setJavadoc(doc);
502 			}
503 		}
504 
505 		AbstractTypeDeclaration parent= getContainingTypeDeclarationNode();
506 		ListRewrite listRewrite= fCuRewrite.getASTRewrite().getListRewrite(parent, parent.getBodyDeclarationsProperty());
507 		TextEditGroup msg= fCuRewrite.createGroupDescription(RefactoringCoreMessages.ExtractConstantRefactoring_declare_constant);
508 		if (insertFirst()) {
509 			listRewrite.insertFirst(fieldDeclaration, msg);
510 		} else {
511 			listRewrite.insertAfter(fieldDeclaration, getNodeToInsertConstantDeclarationAfter(), msg);
512 		}
513 
514 		if (fLinkedProposalModel != null) {
515 			ASTRewrite rewrite= fCuRewrite.getASTRewrite();
516 			LinkedProposalPositionGroup nameGroup= fLinkedProposalModel.getPositionGroup(KEY_NAME, true);
517 			nameGroup.addPosition(rewrite.track(variableDeclarationFragment.getName()), true);
518 
519 			String[] nameSuggestions= guessConstantNames();
520 			if (nameSuggestions.length > 0 && !nameSuggestions[0].equals(fConstantName)) {
521 				nameGroup.addProposal(fConstantName, null, nameSuggestions.length + 1);
522 			}
523 			for (int i= 0; i < nameSuggestions.length; i++) {
524 				nameGroup.addProposal(nameSuggestions[i], null, nameSuggestions.length - i);
525 			}
526 
527 			LinkedProposalPositionGroup typeGroup= fLinkedProposalModel.getPositionGroup(KEY_TYPE, true);
528 			typeGroup.addPosition(rewrite.track(type), true);
529 
530 			ITypeBinding typeBinding= guessBindingForReference(fragment.getAssociatedExpression());
531 			if (typeBinding != null) {
532 				ITypeBinding[] relaxingTypes= ASTResolving.getNarrowingTypes(ast, typeBinding);
533 				for (int i= 0; i < relaxingTypes.length; i++) {
534 					typeGroup.addProposal(relaxingTypes[i], fCuRewrite.getCu(), relaxingTypes.length - i);
535 				}
536 			}
537 			boolean isInterface= parent.resolveBinding() != null && parent.resolveBinding().isInterface();
538 			ModifierCorrectionSubProcessorCore.installLinkedVisibilityProposals(fLinkedProposalModel, rewrite, fieldDeclaration.modifiers(), isInterface);
539 		}
540 	}
541 
getConstantType()542 	private Type getConstantType() throws JavaModelException {
543 		if (fConstantTypeCache == null) {
544 			IExpressionFragment fragment= getSelectedExpression();
545 			ITypeBinding typeBinding= guessBindingForReference(fragment.getAssociatedExpression());
546 			AST ast= fCuRewrite.getAST();
547 			typeBinding= Bindings.normalizeForDeclarationUse(typeBinding, ast);
548 			ImportRewrite importRewrite= fCuRewrite.getImportRewrite();
549 			ImportRewriteContext context= new ContextSensitiveImportRewriteContext(fCuRewrite.getRoot(), fSelectionStart, importRewrite);
550 			fConstantTypeCache= importRewrite.addImport(typeBinding, ast, context, TypeLocation.FIELD);
551 		}
552 		return fConstantTypeCache;
553 	}
554 
555 	@Override
createChange(IProgressMonitor monitor)556 	public Change createChange(IProgressMonitor monitor) throws CoreException {
557 		ExtractConstantDescriptor descriptor= createRefactoringDescriptor();
558 		fChange.setDescriptor(new RefactoringChangeDescriptor(descriptor));
559 		return fChange;
560 	}
561 
createRefactoringDescriptor()562 	private ExtractConstantDescriptor createRefactoringDescriptor() {
563 		final Map<String, String> arguments= new HashMap<>();
564 		String project= null;
565 		IJavaProject javaProject= fCu.getJavaProject();
566 		if (javaProject != null)
567 			project= javaProject.getElementName();
568 		int flags= JavaRefactoringDescriptor.JAR_REFACTORING | JavaRefactoringDescriptor.JAR_SOURCE_ATTACHMENT;
569 		if (JdtFlags.getVisibilityCode(fVisibility) != Modifier.PRIVATE)
570 			flags|= RefactoringDescriptor.STRUCTURAL_CHANGE;
571 
572 		final String expression= ASTNodes.asString(fSelectedExpression.getAssociatedExpression());
573 		final String description= Messages.format(RefactoringCoreMessages.ExtractConstantRefactoring_descriptor_description_short, BasicElementLabels.getJavaElementName(fConstantName));
574 		final String header= Messages.format(RefactoringCoreMessages.ExtractConstantRefactoring_descriptor_description, new String[] { BasicElementLabels.getJavaElementName(fConstantName), BasicElementLabels.getJavaCodeString(expression)});
575 		final JDTRefactoringDescriptorComment comment= new JDTRefactoringDescriptorComment(project, this, header);
576 		comment.addSetting(Messages.format(RefactoringCoreMessages.ExtractConstantRefactoring_constant_name_pattern, BasicElementLabels.getJavaElementName(fConstantName)));
577 		comment.addSetting(Messages.format(RefactoringCoreMessages.ExtractConstantRefactoring_constant_expression_pattern, BasicElementLabels.getJavaCodeString(expression)));
578 		String visibility= fVisibility;
579 		if ("".equals(visibility)) //$NON-NLS-1$
580 			visibility= RefactoringCoreMessages.ExtractConstantRefactoring_default_visibility;
581 		comment.addSetting(Messages.format(RefactoringCoreMessages.ExtractConstantRefactoring_visibility_pattern, visibility));
582 		if (fReplaceAllOccurrences)
583 			comment.addSetting(RefactoringCoreMessages.ExtractConstantRefactoring_replace_occurrences);
584 		if (fQualifyReferencesWithDeclaringClassName)
585 			comment.addSetting(RefactoringCoreMessages.ExtractConstantRefactoring_qualify_references);
586 		arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT, JavaRefactoringDescriptorUtil.elementToHandle(project, fCu));
587 		arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME, fConstantName);
588 		arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION, Integer.toString(fSelectionStart) + " " + Integer.toString(fSelectionLength)); //$NON-NLS-1$
589 		arguments.put(ATTRIBUTE_REPLACE, Boolean.toString(fReplaceAllOccurrences));
590 		arguments.put(ATTRIBUTE_QUALIFY, Boolean.toString(fQualifyReferencesWithDeclaringClassName));
591 		arguments.put(ATTRIBUTE_VISIBILITY, Integer.valueOf(JdtFlags.getVisibilityCode(fVisibility)).toString());
592 
593 		ExtractConstantDescriptor descriptor= RefactoringSignatureDescriptorFactory.createExtractConstantDescriptor(project, description, comment.asString(), arguments, flags);
594 		return descriptor;
595 	}
596 
replaceExpressionsWithConstant()597 	private void replaceExpressionsWithConstant() throws JavaModelException {
598 		ASTRewrite astRewrite= fCuRewrite.getASTRewrite();
599 		AST ast= astRewrite.getAST();
600 
601 		for (IASTFragment fragment : getFragmentsToReplace()) {
602 			ASTNode node= fragment.getAssociatedNode();
603 			boolean inTypeDeclarationAnnotation= isInTypeDeclarationAnnotation(node);
604 			if (inTypeDeclarationAnnotation && JdtFlags.VISIBILITY_STRING_PRIVATE == getVisibility())
605 				continue;
606 
607 			SimpleName ref= ast.newSimpleName(fConstantName);
608 			Name replacement= ref;
609 			boolean qualifyReference= qualifyReferencesWithDeclaringClassName();
610 			if (!qualifyReference) {
611 				qualifyReference= inTypeDeclarationAnnotation;
612 			}
613 			if (qualifyReference) {
614 				replacement= ast.newQualifiedName(ast.newSimpleName(getContainingTypeBinding().getName()), ref);
615 			}
616 			TextEditGroup description= fCuRewrite.createGroupDescription(RefactoringCoreMessages.ExtractConstantRefactoring_replace);
617 
618 			fragment.replace(astRewrite, replacement, description);
619 			if (fLinkedProposalModel != null)
620 				fLinkedProposalModel.getPositionGroup(KEY_NAME, true).addPosition(astRewrite.track(ref), false);
621 		}
622 	}
623 
isInTypeDeclarationAnnotation(ASTNode node)624 	private boolean isInTypeDeclarationAnnotation(ASTNode node) throws JavaModelException {
625 		Annotation enclosingAnnotation= ASTNodes.getParent(node, Annotation.class);
626 		return enclosingAnnotation != null && enclosingAnnotation.getParent() == getContainingTypeDeclarationNode();
627 	}
628 
computeConstantDeclarationLocation()629 	private void computeConstantDeclarationLocation() throws JavaModelException {
630 		if (isDeclarationLocationComputed())
631 			return;
632 
633 		BodyDeclaration lastStaticDependency= null;
634 		Iterator<BodyDeclaration> decls= getContainingTypeDeclarationNode().bodyDeclarations().iterator();
635 
636 		while (decls.hasNext()) {
637 			BodyDeclaration decl= decls.next();
638 
639 			int modifiers;
640 			if (decl instanceof FieldDeclaration)
641 				modifiers= ((FieldDeclaration) decl).getModifiers();
642 			else if (decl instanceof Initializer)
643 				modifiers= ((Initializer) decl).getModifiers();
644 			else {
645 				continue; /* this declaration is not a field declaration
646 				              or initializer, so the placement of the constant
647 				              declaration relative to it does not matter */
648 			}
649 
650 			if (Modifier.isStatic(modifiers) && depends(getSelectedExpression(), decl))
651 				lastStaticDependency= decl;
652 		}
653 
654 		if(lastStaticDependency == null)
655 			fInsertFirst= true;
656 		else
657 			fToInsertAfter= lastStaticDependency;
658 	}
659 
660 	/* bd is a static field declaration or static initializer */
depends(IExpressionFragment selected, BodyDeclaration bd)661 	private static boolean depends(IExpressionFragment selected, BodyDeclaration bd) {
662 		/* We currently consider selected to depend on bd only if db includes a declaration
663 		 * of a static field on which selected depends.
664 		 *
665 		 * A more accurate strategy might be to also check if bd contains (or is) a
666 		 * static initializer containing code which changes the value of a static field on
667 		 * which selected depends.  However, if a static is written to multiple times within
668 		 * during class initialization, it is difficult to predict which value should be used.
669 		 * This would depend on which value is used by expressions instances for which the new
670 		 * constant will be substituted, and there may be many of these; in each, the
671 		 * static field in question may have taken on a different value (if some of these uses
672 		 * occur within static initializers).
673 		 */
674 
675 		if(bd instanceof FieldDeclaration) {
676 			FieldDeclaration fieldDecl = (FieldDeclaration) bd;
677 			for(Iterator<VariableDeclarationFragment> fragments = fieldDecl.fragments().iterator(); fragments.hasNext();) {
678 				VariableDeclarationFragment fragment = fragments.next();
679 				SimpleName staticFieldName = fragment.getName();
680 				if(selected.getSubFragmentsMatching(ASTFragmentFactory.createFragmentForFullSubtree(staticFieldName)).length != 0)
681 					return true;
682 			}
683 		}
684 		return false;
685 	}
686 
isDeclarationLocationComputed()687 	private boolean isDeclarationLocationComputed() {
688 		return fInsertFirst == true || fToInsertAfter != null;
689 	}
690 
insertFirst()691 	private boolean insertFirst() throws JavaModelException {
692 		if(!isDeclarationLocationComputed())
693 			computeConstantDeclarationLocation();
694 		return fInsertFirst;
695 	}
696 
getNodeToInsertConstantDeclarationAfter()697 	private BodyDeclaration getNodeToInsertConstantDeclarationAfter() throws JavaModelException {
698 		if(!isDeclarationLocationComputed())
699 			computeConstantDeclarationLocation();
700 		return fToInsertAfter;
701 	}
702 
getConstantTypeName()703 	private String getConstantTypeName() throws JavaModelException {
704 		return ASTNodes.asString(getConstantType());
705 	}
706 
isStaticFieldOrStaticInitializer(BodyDeclaration node)707 	private static boolean isStaticFieldOrStaticInitializer(BodyDeclaration node) {
708 		if(node instanceof MethodDeclaration || node instanceof AbstractTypeDeclaration)
709 			return false;
710 
711 		int modifiers;
712 		if(node instanceof FieldDeclaration) {
713 			modifiers = ((FieldDeclaration) node).getModifiers();
714 		} else if(node instanceof Initializer) {
715 			modifiers = ((Initializer) node).getModifiers();
716 		} else {
717 			Assert.isTrue(false);
718 			return false;
719 		}
720 
721 		if(!Modifier.isStatic(modifiers))
722 			return false;
723 
724 		return true;
725 	}
726 
727 	/*
728 	 * Elements returned by next() are BodyDeclaration or Annotation instances.
729 	 */
getReplacementScope()730 	private Iterator<ASTNode> getReplacementScope() throws JavaModelException {
731 		boolean declPredecessorReached= false;
732 
733 		Collection<ASTNode> scope= new ArrayList<>();
734 
735 		AbstractTypeDeclaration containingType= getContainingTypeDeclarationNode();
736 		if (containingType instanceof EnumDeclaration) {
737 			// replace in all enum constants bodies
738 			EnumDeclaration enumDeclaration= (EnumDeclaration) containingType;
739 			scope.addAll(enumDeclaration.enumConstants());
740 		}
741 
742 		for (Iterator<IExtendedModifier> iter= containingType.modifiers().iterator(); iter.hasNext();) {
743 			IExtendedModifier modifier= iter.next();
744 			if (modifier instanceof Annotation) {
745 				scope.add((ASTNode) modifier);
746 			}
747 		}
748 
749 		for (Iterator<BodyDeclaration> bodyDeclarations = containingType.bodyDeclarations().iterator(); bodyDeclarations.hasNext();) {
750 		    BodyDeclaration bodyDeclaration= bodyDeclarations.next();
751 
752 		    if(bodyDeclaration == getNodeToInsertConstantDeclarationAfter())
753 		    	declPredecessorReached= true;
754 
755 		    if(insertFirst() || declPredecessorReached || !isStaticFieldOrStaticInitializer(bodyDeclaration))
756 		    	scope.add(bodyDeclaration);
757 		}
758 		return scope.iterator();
759 	}
760 
getFragmentsToReplace()761 	private IASTFragment[] getFragmentsToReplace() throws JavaModelException {
762 		List<IASTFragment> toReplace = new ArrayList<>();
763 		if (fReplaceAllOccurrences) {
764 			Iterator<ASTNode> replacementScope = getReplacementScope();
765 			while(replacementScope.hasNext()) {
766 				ASTNode scope= replacementScope.next();
767 				IASTFragment[] allMatches= ASTFragmentFactory.createFragmentForFullSubtree(scope).getSubFragmentsMatching(getSelectedExpression());
768 				IASTFragment[] replaceableMatches = retainOnlyReplacableMatches(allMatches);
769 				Collections.addAll(toReplace, replaceableMatches);
770 			}
771 		} else if (canReplace(getSelectedExpression()))
772 			toReplace.add(getSelectedExpression());
773 		return toReplace.toArray(new IASTFragment[toReplace.size()]);
774 	}
775 
776 	// !! - like one in ExtractTempRefactoring
retainOnlyReplacableMatches(IASTFragment[] allMatches)777 	private static IASTFragment[] retainOnlyReplacableMatches(IASTFragment[] allMatches) {
778 		List<IASTFragment> result= new ArrayList<>(allMatches.length);
779 		for (IASTFragment match : allMatches) {
780 			if (canReplace(match)) {
781 				result.add(match);
782 			}
783 		}
784 		return result.toArray(new IASTFragment[result.size()]);
785 	}
786 
787 	// !! - like one in ExtractTempRefactoring
canReplace(IASTFragment fragment)788 	private static boolean canReplace(IASTFragment fragment) {
789 		ASTNode node= fragment.getAssociatedNode();
790 		ASTNode parent= node.getParent();
791 		if (parent instanceof VariableDeclarationFragment) {
792 			VariableDeclarationFragment vdf= (VariableDeclarationFragment) parent;
793 			if (node.equals(vdf.getName()))
794 				return false;
795 		}
796 		if (parent instanceof ExpressionStatement)
797 			return false;
798 		if (parent instanceof SwitchCase) {
799 			if (node instanceof Name) {
800 				Name name= (Name) node;
801 				ITypeBinding typeBinding= name.resolveTypeBinding();
802 				if (typeBinding != null) {
803 					return !typeBinding.isEnum();
804 				}
805 			}
806 		}
807 		return true;
808 	}
809 
getSelectedExpression()810 	private IExpressionFragment getSelectedExpression() throws JavaModelException {
811 		if(fSelectedExpression != null)
812 			return fSelectedExpression;
813 
814 		IASTFragment selectedFragment= ASTFragmentFactory.createFragmentForSourceRange(new SourceRange(fSelectionStart, fSelectionLength), fCuRewrite.getRoot(), fCu);
815 
816 		if (selectedFragment instanceof IExpressionFragment
817 				&& ! Checks.isInsideJavadoc(selectedFragment.getAssociatedNode())) {
818 			fSelectedExpression= (IExpressionFragment) selectedFragment;
819 		}
820 
821 		if (fSelectedExpression != null && Checks.isEnumCase(fSelectedExpression.getAssociatedExpression().getParent())) {
822 			fSelectedExpression= null;
823 		}
824 
825 		return fSelectedExpression;
826 	}
827 
828 	/**
829 	 * Returns the type to which the new constant will be added to. It is the first non-anonymous parent.
830 	 * @return the type to add the new constant to
831 	 *
832 	 * @throws JavaModelException shouldn't happen
833 	 */
getContainingTypeDeclarationNode()834 	private AbstractTypeDeclaration getContainingTypeDeclarationNode() throws JavaModelException {
835 		AbstractTypeDeclaration result= ASTNodes.getParent(getSelectedExpression().getAssociatedNode(), AbstractTypeDeclaration.class);
836 		Assert.isNotNull(result);
837 		return result;
838 	}
839 
getContainingTypeBinding()840 	private ITypeBinding getContainingTypeBinding() throws JavaModelException {
841 		ITypeBinding result= getContainingTypeDeclarationNode().resolveBinding();
842 		Assert.isNotNull(result);
843 		return result;
844 	}
845 
initialize(JavaRefactoringArguments arguments)846 	private RefactoringStatus initialize(JavaRefactoringArguments arguments) {
847 		final String selection= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION);
848 		if (selection != null) {
849 			int offset= -1;
850 			int length= -1;
851 			final StringTokenizer tokenizer= new StringTokenizer(selection);
852 			if (tokenizer.hasMoreTokens())
853 				offset= Integer.parseInt(tokenizer.nextToken());
854 			if (tokenizer.hasMoreTokens())
855 				length= Integer.parseInt(tokenizer.nextToken());
856 			if (offset >= 0 && length >= 0) {
857 				fSelectionStart= offset;
858 				fSelectionLength= length;
859 			} else
860 				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_illegal_argument, new Object[] { selection, JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION}));
861 		} else
862 			return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION));
863 		final String handle= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT);
864 		if (handle != null) {
865 			final IJavaElement element= JavaRefactoringDescriptorUtil.handleToElement(arguments.getProject(), handle, false);
866 			if (element == null || !element.exists() || element.getElementType() != IJavaElement.COMPILATION_UNIT)
867 				return JavaRefactoringDescriptorUtil.createInputFatalStatus(element, getName(), IJavaRefactorings.EXTRACT_CONSTANT);
868 			else
869 				fCu= (ICompilationUnit) element;
870 		} else
871 			return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT));
872 		final String visibility= arguments.getAttribute(ATTRIBUTE_VISIBILITY);
873 		if (visibility != null && !"".equals(visibility)) {//$NON-NLS-1$
874 			int flag= 0;
875 			try {
876 				flag= Integer.parseInt(visibility);
877 			} catch (NumberFormatException exception) {
878 				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_VISIBILITY));
879 			}
880 			fVisibility= JdtFlags.getVisibilityString(flag);
881 		}
882 		final String name= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME);
883 		if (name != null && !"".equals(name)) //$NON-NLS-1$
884 			fConstantName= name;
885 		else
886 			return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME));
887 		final String replace= arguments.getAttribute(ATTRIBUTE_REPLACE);
888 		if (replace != null) {
889 			fReplaceAllOccurrences= Boolean.parseBoolean(replace);
890 		} else
891 			return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_REPLACE));
892 		final String declareFinal= arguments.getAttribute(ATTRIBUTE_QUALIFY);
893 		if (declareFinal != null) {
894 			fQualifyReferencesWithDeclaringClassName= Boolean.parseBoolean(declareFinal);
895 		} else
896 			return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_QUALIFY));
897 		return new RefactoringStatus();
898 	}
899 }
900