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