0001: /*******************************************************************************
0002: * Copyright (c) 2000, 2007 IBM Corporation and others.
0003: * All rights reserved. This program and the accompanying materials
0004: * are made available under the terms of the Eclipse Public License v1.0
0005: * which accompanies this distribution, and is available at
0006: * http://www.eclipse.org/legal/epl-v10.html
0007: *
0008: * Contributors:
0009: * IBM Corporation - initial API and implementation
0010: *******************************************************************************/package org.eclipse.jdt.internal.corext.refactoring.code;
0011:
0012: import java.util.ArrayList;
0013: import java.util.Arrays;
0014: import java.util.Collection;
0015: import java.util.HashMap;
0016: import java.util.Iterator;
0017: import java.util.List;
0018: import java.util.Map;
0019: import java.util.StringTokenizer;
0020:
0021: import org.eclipse.text.edits.TextEditGroup;
0022:
0023: import org.eclipse.core.runtime.Assert;
0024: import org.eclipse.core.runtime.CoreException;
0025: import org.eclipse.core.runtime.IProgressMonitor;
0026: import org.eclipse.core.runtime.NullProgressMonitor;
0027: import org.eclipse.core.runtime.SubProgressMonitor;
0028:
0029: import org.eclipse.ltk.core.refactoring.Change;
0030: import org.eclipse.ltk.core.refactoring.RefactoringChangeDescriptor;
0031: import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
0032: import org.eclipse.ltk.core.refactoring.RefactoringStatus;
0033: import org.eclipse.ltk.core.refactoring.RefactoringStatusEntry;
0034: import org.eclipse.ltk.core.refactoring.participants.RefactoringArguments;
0035:
0036: import org.eclipse.jdt.core.ICompilationUnit;
0037: import org.eclipse.jdt.core.IJavaElement;
0038: import org.eclipse.jdt.core.IJavaProject;
0039: import org.eclipse.jdt.core.JavaModelException;
0040: import org.eclipse.jdt.core.compiler.IProblem;
0041: import org.eclipse.jdt.core.dom.AST;
0042: import org.eclipse.jdt.core.dom.ASTNode;
0043: import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
0044: import org.eclipse.jdt.core.dom.BodyDeclaration;
0045: import org.eclipse.jdt.core.dom.CompilationUnit;
0046: import org.eclipse.jdt.core.dom.Expression;
0047: import org.eclipse.jdt.core.dom.ExpressionStatement;
0048: import org.eclipse.jdt.core.dom.FieldAccess;
0049: import org.eclipse.jdt.core.dom.FieldDeclaration;
0050: import org.eclipse.jdt.core.dom.ITypeBinding;
0051: import org.eclipse.jdt.core.dom.Initializer;
0052: import org.eclipse.jdt.core.dom.Javadoc;
0053: import org.eclipse.jdt.core.dom.MethodDeclaration;
0054: import org.eclipse.jdt.core.dom.Modifier;
0055: import org.eclipse.jdt.core.dom.Name;
0056: import org.eclipse.jdt.core.dom.NullLiteral;
0057: import org.eclipse.jdt.core.dom.QualifiedName;
0058: import org.eclipse.jdt.core.dom.SimpleName;
0059: import org.eclipse.jdt.core.dom.SwitchCase;
0060: import org.eclipse.jdt.core.dom.Type;
0061: import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
0062: import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
0063: import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
0064: import org.eclipse.jdt.core.refactoring.IJavaRefactorings;
0065: import org.eclipse.jdt.core.refactoring.descriptors.ExtractConstantDescriptor;
0066: import org.eclipse.jdt.core.refactoring.descriptors.JavaRefactoringDescriptor;
0067:
0068: import org.eclipse.jdt.internal.corext.Corext;
0069: import org.eclipse.jdt.internal.corext.SourceRange;
0070: import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility;
0071: import org.eclipse.jdt.internal.corext.dom.ASTNodes;
0072: import org.eclipse.jdt.internal.corext.dom.Bindings;
0073: import org.eclipse.jdt.internal.corext.dom.ScopeAnalyzer;
0074: import org.eclipse.jdt.internal.corext.dom.fragments.ASTFragmentFactory;
0075: import org.eclipse.jdt.internal.corext.dom.fragments.IASTFragment;
0076: import org.eclipse.jdt.internal.corext.dom.fragments.IExpressionFragment;
0077: import org.eclipse.jdt.internal.corext.fix.LinkedProposalModel;
0078: import org.eclipse.jdt.internal.corext.fix.LinkedProposalPositionGroup;
0079: import org.eclipse.jdt.internal.corext.refactoring.Checks;
0080: import org.eclipse.jdt.internal.corext.refactoring.JDTRefactoringDescriptorComment;
0081: import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringArguments;
0082: import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringDescriptorUtil;
0083: import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages;
0084: import org.eclipse.jdt.internal.corext.refactoring.base.JavaStringStatusContext;
0085: import org.eclipse.jdt.internal.corext.refactoring.base.RefactoringStatusCodes;
0086: import org.eclipse.jdt.internal.corext.refactoring.changes.CompilationUnitChange;
0087: import org.eclipse.jdt.internal.corext.refactoring.rename.RefactoringAnalyzeUtil;
0088: import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
0089: import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser;
0090: import org.eclipse.jdt.internal.corext.util.JdtFlags;
0091: import org.eclipse.jdt.internal.corext.util.Messages;
0092:
0093: import org.eclipse.jdt.ui.CodeGeneration;
0094: import org.eclipse.jdt.ui.JavaElementLabels;
0095:
0096: import org.eclipse.jdt.internal.ui.JavaPlugin;
0097: import org.eclipse.jdt.internal.ui.preferences.JavaPreferencesSettings;
0098: import org.eclipse.jdt.internal.ui.text.correction.ASTResolving;
0099: import org.eclipse.jdt.internal.ui.text.correction.ModifierCorrectionSubProcessor;
0100: import org.eclipse.jdt.internal.ui.viewsupport.BindingLabelProvider;
0101:
0102: public class ExtractConstantRefactoring extends ScriptableRefactoring {
0103:
0104: private static final String ATTRIBUTE_REPLACE = "replace"; //$NON-NLS-1$
0105: private static final String ATTRIBUTE_QUALIFY = "qualify"; //$NON-NLS-1$
0106: private static final String ATTRIBUTE_VISIBILITY = "visibility"; //$NON-NLS-1$
0107:
0108: private static final String MODIFIER = "static final"; //$NON-NLS-1$
0109:
0110: private static final String KEY_NAME = "name"; //$NON-NLS-1$
0111: private static final String KEY_TYPE = "type"; //$NON-NLS-1$
0112:
0113: private CompilationUnitRewrite fCuRewrite;
0114: private int fSelectionStart;
0115: private int fSelectionLength;
0116: private ICompilationUnit fCu;
0117:
0118: private IExpressionFragment fSelectedExpression;
0119: private Type fConstantTypeCache;
0120: private boolean fReplaceAllOccurrences = true; //default value
0121: private boolean fQualifyReferencesWithDeclaringClassName = false; //default value
0122:
0123: private String fVisibility = JdtFlags.VISIBILITY_STRING_PRIVATE; //default value
0124: private boolean fTargetIsInterface = false;
0125: private String fConstantName;
0126: private String[] fExcludedVariableNames;
0127:
0128: private boolean fSelectionAllStaticFinal;
0129: private boolean fAllStaticFinalCheckPerformed = false;
0130:
0131: private List fBodyDeclarations;
0132:
0133: //Constant Declaration Location
0134: private BodyDeclaration fToInsertAfter;
0135: private boolean fInsertFirst;
0136:
0137: private CompilationUnitChange fChange;
0138: private String[] fGuessedConstNames;
0139:
0140: private LinkedProposalModel fLinkedProposalModel;
0141: private boolean fCheckResultForCompileProblems;
0142:
0143: /**
0144: * Creates a new extract constant refactoring
0145: * @param unit the compilation unit, or <code>null</code> if invoked by scripting
0146: * @param selectionStart
0147: * @param selectionLength
0148: */
0149: public ExtractConstantRefactoring(ICompilationUnit unit,
0150: int selectionStart, int selectionLength) {
0151: Assert.isTrue(selectionStart >= 0);
0152: Assert.isTrue(selectionLength >= 0);
0153: fSelectionStart = selectionStart;
0154: fSelectionLength = selectionLength;
0155: fCu = unit;
0156: fCuRewrite = null;
0157: fLinkedProposalModel = null;
0158: fConstantName = ""; //$NON-NLS-1$
0159: fCheckResultForCompileProblems = true;
0160: }
0161:
0162: public ExtractConstantRefactoring(CompilationUnit astRoot,
0163: int selectionStart, int selectionLength) {
0164: Assert.isTrue(selectionStart >= 0);
0165: Assert.isTrue(selectionLength >= 0);
0166: Assert
0167: .isTrue(astRoot.getTypeRoot() instanceof ICompilationUnit);
0168:
0169: fSelectionStart = selectionStart;
0170: fSelectionLength = selectionLength;
0171: fCu = (ICompilationUnit) astRoot.getTypeRoot();
0172: fCuRewrite = new CompilationUnitRewrite(fCu, astRoot);
0173: fLinkedProposalModel = null;
0174: fConstantName = ""; //$NON-NLS-1$
0175: fCheckResultForCompileProblems = true;
0176: }
0177:
0178: public void setCheckResultForCompileProblems(
0179: boolean checkResultForCompileProblems) {
0180: fCheckResultForCompileProblems = checkResultForCompileProblems;
0181: }
0182:
0183: public void setLinkedProposalModel(
0184: LinkedProposalModel linkedProposalModel) {
0185: fLinkedProposalModel = linkedProposalModel;
0186: }
0187:
0188: public String getName() {
0189: return RefactoringCoreMessages.ExtractConstantRefactoring_name;
0190: }
0191:
0192: public boolean replaceAllOccurrences() {
0193: return fReplaceAllOccurrences;
0194: }
0195:
0196: public void setReplaceAllOccurrences(boolean replaceAllOccurrences) {
0197: fReplaceAllOccurrences = replaceAllOccurrences;
0198: }
0199:
0200: public void setVisibility(String am) {
0201: Assert.isTrue(am == JdtFlags.VISIBILITY_STRING_PRIVATE
0202: || am == JdtFlags.VISIBILITY_STRING_PROTECTED
0203: || am == JdtFlags.VISIBILITY_STRING_PACKAGE
0204: || am == JdtFlags.VISIBILITY_STRING_PUBLIC);
0205: fVisibility = am;
0206: }
0207:
0208: public String getVisibility() {
0209: return fVisibility;
0210: }
0211:
0212: public boolean getTargetIsInterface() {
0213: return fTargetIsInterface;
0214: }
0215:
0216: public boolean qualifyReferencesWithDeclaringClassName() {
0217: return fQualifyReferencesWithDeclaringClassName;
0218: }
0219:
0220: public void setQualifyReferencesWithDeclaringClassName(
0221: boolean qualify) {
0222: fQualifyReferencesWithDeclaringClassName = qualify;
0223: }
0224:
0225: public String guessConstantName() throws JavaModelException {
0226: String[] proposals = guessConstantNames();
0227: if (proposals.length > 0)
0228: return proposals[0];
0229: else
0230: return fConstantName;
0231: }
0232:
0233: /**
0234: * @return proposed variable names (may be empty, but not null).
0235: * The first proposal should be used as "best guess" (if it exists).
0236: */
0237: public String[] guessConstantNames() {
0238: if (fGuessedConstNames == null) {
0239: try {
0240: Expression expression = getSelectedExpression()
0241: .getAssociatedExpression();
0242: if (expression != null) {
0243: ITypeBinding binding = expression
0244: .resolveTypeBinding();
0245: fGuessedConstNames = StubUtility
0246: .getVariableNameSuggestions(
0247: StubUtility.CONSTANT_FIELD,
0248: fCu.getJavaProject(),
0249: binding,
0250: expression,
0251: Arrays
0252: .asList(getExcludedVariableNames()));
0253: }
0254: } catch (JavaModelException e) {
0255: }
0256: if (fGuessedConstNames == null)
0257: fGuessedConstNames = new String[0];
0258: }
0259: return fGuessedConstNames;
0260: }
0261:
0262: private String[] getExcludedVariableNames() {
0263: if (fExcludedVariableNames == null) {
0264: try {
0265: IExpressionFragment expr = getSelectedExpression();
0266: Collection takenNames = new ScopeAnalyzer(fCuRewrite
0267: .getRoot()).getUsedVariableNames(expr
0268: .getStartPosition(), expr.getLength());
0269: fExcludedVariableNames = (String[]) takenNames
0270: .toArray(new String[takenNames.size()]);
0271: } catch (JavaModelException e) {
0272: fExcludedVariableNames = new String[0];
0273: }
0274: }
0275: return fExcludedVariableNames;
0276: }
0277:
0278: public RefactoringStatus checkInitialConditions(IProgressMonitor pm)
0279: throws CoreException {
0280: try {
0281: pm.beginTask("", 7); //$NON-NLS-1$
0282:
0283: RefactoringStatus result = Checks.validateEdit(fCu,
0284: getValidationContext());
0285: if (result.hasFatalError())
0286: return result;
0287: pm.worked(1);
0288:
0289: if (fCuRewrite == null) {
0290: CompilationUnit cuNode = RefactoringASTParser
0291: .parseWithASTProvider(fCu, true,
0292: new SubProgressMonitor(pm, 3));
0293: fCuRewrite = new CompilationUnitRewrite(fCu, cuNode);
0294: } else {
0295: pm.worked(3);
0296: }
0297: result.merge(checkSelection(new SubProgressMonitor(pm, 3)));
0298:
0299: if (result.hasFatalError())
0300: return result;
0301:
0302: if (isLiteralNodeSelected())
0303: fReplaceAllOccurrences = false;
0304:
0305: ITypeBinding targetType = getContainingTypeBinding();
0306: if (targetType.isAnnotation() || targetType.isInterface()) {
0307: fTargetIsInterface = true;
0308: fVisibility = JdtFlags.VISIBILITY_STRING_PUBLIC;
0309: }
0310:
0311: return result;
0312: } finally {
0313: pm.done();
0314: }
0315: }
0316:
0317: public boolean selectionAllStaticFinal() {
0318: Assert.isTrue(fAllStaticFinalCheckPerformed);
0319: return fSelectionAllStaticFinal;
0320: }
0321:
0322: private void checkAllStaticFinal() throws JavaModelException {
0323: fSelectionAllStaticFinal = ConstantChecks
0324: .isStaticFinalConstant(getSelectedExpression());
0325: fAllStaticFinalCheckPerformed = true;
0326: }
0327:
0328: private RefactoringStatus checkSelection(IProgressMonitor pm)
0329: throws JavaModelException {
0330: try {
0331: pm.beginTask("", 2); //$NON-NLS-1$
0332:
0333: IExpressionFragment selectedExpression = getSelectedExpression();
0334:
0335: if (selectedExpression == null) {
0336: String message = RefactoringCoreMessages.ExtractConstantRefactoring_select_expression;
0337: return CodeRefactoringUtil.checkMethodSyntaxErrors(
0338: fSelectionStart, fSelectionLength, fCuRewrite
0339: .getRoot(), message);
0340: }
0341: pm.worked(1);
0342:
0343: RefactoringStatus result = new RefactoringStatus();
0344: result.merge(checkExpression());
0345: if (result.hasFatalError())
0346: return result;
0347: pm.worked(1);
0348:
0349: return result;
0350: } finally {
0351: pm.done();
0352: }
0353: }
0354:
0355: private RefactoringStatus checkExpressionBinding()
0356: throws JavaModelException {
0357: return checkExpressionFragmentIsRValue();
0358: }
0359:
0360: private RefactoringStatus checkExpressionFragmentIsRValue()
0361: throws JavaModelException {
0362: /* Moved this functionality to Checks, to allow sharing with
0363: ExtractTempRefactoring, others */
0364: switch (Checks.checkExpressionIsRValue(getSelectedExpression()
0365: .getAssociatedExpression())) {
0366: case Checks.NOT_RVALUE_MISC:
0367: return RefactoringStatus
0368: .createStatus(
0369: RefactoringStatus.FATAL,
0370: RefactoringCoreMessages.ExtractConstantRefactoring_select_expression,
0371: null,
0372: Corext.getPluginId(),
0373: RefactoringStatusCodes.EXPRESSION_NOT_RVALUE,
0374: null);
0375: case Checks.NOT_RVALUE_VOID:
0376: return RefactoringStatus
0377: .createStatus(
0378: RefactoringStatus.FATAL,
0379: RefactoringCoreMessages.ExtractConstantRefactoring_no_void,
0380: null,
0381: Corext.getPluginId(),
0382: RefactoringStatusCodes.EXPRESSION_NOT_RVALUE_VOID,
0383: null);
0384: case Checks.IS_RVALUE:
0385: return new RefactoringStatus();
0386: default:
0387: Assert.isTrue(false);
0388: return null;
0389: }
0390: }
0391:
0392: // !!! -- same as in ExtractTempRefactoring
0393: private boolean isLiteralNodeSelected() throws JavaModelException {
0394: IExpressionFragment fragment = getSelectedExpression();
0395: if (fragment == null)
0396: return false;
0397: Expression expression = fragment.getAssociatedExpression();
0398: if (expression == null)
0399: return false;
0400: switch (expression.getNodeType()) {
0401: case ASTNode.BOOLEAN_LITERAL:
0402: case ASTNode.CHARACTER_LITERAL:
0403: case ASTNode.NULL_LITERAL:
0404: case ASTNode.NUMBER_LITERAL:
0405: return true;
0406:
0407: default:
0408: return false;
0409: }
0410: }
0411:
0412: private RefactoringStatus checkExpression()
0413: throws JavaModelException {
0414: RefactoringStatus result = new RefactoringStatus();
0415: result.merge(checkExpressionBinding());
0416: if (result.hasFatalError())
0417: return result;
0418: checkAllStaticFinal();
0419:
0420: IExpressionFragment selectedExpression = getSelectedExpression();
0421: Expression associatedExpression = selectedExpression
0422: .getAssociatedExpression();
0423: if (associatedExpression instanceof NullLiteral)
0424: result
0425: .merge(RefactoringStatus
0426: .createFatalErrorStatus(RefactoringCoreMessages.ExtractConstantRefactoring_null_literals));
0427: else if (!ConstantChecks.isLoadTimeConstant(selectedExpression))
0428: result
0429: .merge(RefactoringStatus
0430: .createFatalErrorStatus(RefactoringCoreMessages.ExtractConstantRefactoring_not_load_time_constant));
0431: else if (associatedExpression instanceof SimpleName) {
0432: if (associatedExpression.getParent() instanceof QualifiedName
0433: && associatedExpression.getLocationInParent() == QualifiedName.NAME_PROPERTY
0434: || associatedExpression.getParent() instanceof FieldAccess
0435: && associatedExpression.getLocationInParent() == FieldAccess.NAME_PROPERTY)
0436: return RefactoringStatus
0437: .createFatalErrorStatus(RefactoringCoreMessages.ExtractConstantRefactoring_select_expression);
0438: }
0439:
0440: return result;
0441: }
0442:
0443: public void setConstantName(String newName) {
0444: Assert.isNotNull(newName);
0445: fConstantName = newName;
0446: }
0447:
0448: public String getConstantName() {
0449: return fConstantName;
0450: }
0451:
0452: /**
0453: * This method performs checks on the constant name which are
0454: * quick enough to be performed every time the ui input component
0455: * contents are changed.
0456: *
0457: * @return return the resulting status
0458: * @throws JavaModelException thrown when the operation could not be executed
0459: */
0460: public RefactoringStatus checkConstantNameOnChange()
0461: throws JavaModelException {
0462: if (Arrays.asList(getExcludedVariableNames()).contains(
0463: fConstantName))
0464: return RefactoringStatus
0465: .createErrorStatus(Messages
0466: .format(
0467: RefactoringCoreMessages.ExtractConstantRefactoring_another_variable,
0468: getConstantName()));
0469: return Checks.checkConstantName(fConstantName, fCu);
0470: }
0471:
0472: // !! similar to ExtractTempRefactoring equivalent
0473: public String getConstantSignaturePreview()
0474: throws JavaModelException {
0475: String space = " "; //$NON-NLS-1$
0476: return getVisibility() + space + MODIFIER + space
0477: + getConstantTypeName() + space + fConstantName;
0478: }
0479:
0480: public CompilationUnitChange createTextChange(IProgressMonitor pm)
0481: throws CoreException {
0482: createConstantDeclaration();
0483: replaceExpressionsWithConstant();
0484: return fCuRewrite
0485: .createChange(
0486: RefactoringCoreMessages.ExtractConstantRefactoring_change_name,
0487: true, pm);
0488: }
0489:
0490: public RefactoringStatus checkFinalConditions(IProgressMonitor pm)
0491: throws CoreException {
0492: pm
0493: .beginTask(
0494: RefactoringCoreMessages.ExtractConstantRefactoring_checking_preconditions,
0495: 2);
0496:
0497: /* Note: some checks are performed on change of input widget
0498: * values. (e.g. see ExtractConstantRefactoring.checkConstantNameOnChange())
0499: */
0500:
0501: //TODO: possibly add more checking for name conflicts that might
0502: // lead to a change in behavior
0503: try {
0504: RefactoringStatus result = new RefactoringStatus();
0505:
0506: createConstantDeclaration();
0507: replaceExpressionsWithConstant();
0508: fChange = fCuRewrite
0509: .createChange(
0510: RefactoringCoreMessages.ExtractConstantRefactoring_change_name,
0511: true, new SubProgressMonitor(pm, 1));
0512:
0513: if (fCheckResultForCompileProblems) {
0514: checkSource(new SubProgressMonitor(pm, 1), result);
0515: }
0516: return result;
0517: } finally {
0518: fConstantTypeCache = null;
0519: fCuRewrite.clearASTAndImportRewrites();
0520: pm.done();
0521: }
0522: }
0523:
0524: private void checkSource(SubProgressMonitor monitor,
0525: RefactoringStatus result) throws CoreException {
0526: String newCuSource = fChange
0527: .getPreviewContent(new NullProgressMonitor());
0528: CompilationUnit newCUNode = new RefactoringASTParser(AST.JLS3)
0529: .parse(newCuSource, fCu, true, true, monitor);
0530:
0531: IProblem[] newProblems = RefactoringAnalyzeUtil
0532: .getIntroducedCompileProblems(newCUNode, fCuRewrite
0533: .getRoot());
0534: for (int i = 0; i < newProblems.length; i++) {
0535: IProblem problem = newProblems[i];
0536: if (problem.isError())
0537: result.addEntry(new RefactoringStatusEntry((problem
0538: .isError() ? RefactoringStatus.ERROR
0539: : RefactoringStatus.WARNING), problem
0540: .getMessage(), new JavaStringStatusContext(
0541: newCuSource, new SourceRange(problem))));
0542: }
0543: }
0544:
0545: private void createConstantDeclaration() throws CoreException {
0546: Type type = getConstantType();
0547:
0548: IExpressionFragment fragment = getSelectedExpression();
0549: String initializerSource = fCu.getBuffer().getText(
0550: fragment.getStartPosition(), fragment.getLength());
0551:
0552: AST ast = fCuRewrite.getAST();
0553: VariableDeclarationFragment variableDeclarationFragment = ast
0554: .newVariableDeclarationFragment();
0555: variableDeclarationFragment.setName(ast
0556: .newSimpleName(fConstantName));
0557: variableDeclarationFragment
0558: .setInitializer((Expression) fCuRewrite.getASTRewrite()
0559: .createStringPlaceholder(initializerSource,
0560: ASTNode.SIMPLE_NAME));
0561:
0562: FieldDeclaration fieldDeclaration = ast
0563: .newFieldDeclaration(variableDeclarationFragment);
0564: fieldDeclaration.setType(type);
0565: Modifier.ModifierKeyword accessModifier = Modifier.ModifierKeyword
0566: .toKeyword(fVisibility);
0567: if (accessModifier != null)
0568: fieldDeclaration.modifiers().add(
0569: ast.newModifier(accessModifier));
0570: fieldDeclaration
0571: .modifiers()
0572: .add(
0573: ast
0574: .newModifier(Modifier.ModifierKeyword.STATIC_KEYWORD));
0575: fieldDeclaration
0576: .modifiers()
0577: .add(
0578: ast
0579: .newModifier(Modifier.ModifierKeyword.FINAL_KEYWORD));
0580:
0581: boolean createComments = JavaPreferencesSettings
0582: .getCodeGenerationSettings(fCu.getJavaProject()).createComments;
0583: if (createComments) {
0584: String comment = CodeGeneration.getFieldComment(fCu,
0585: getConstantTypeName(), fConstantName, StubUtility
0586: .getLineDelimiterUsed(fCu));
0587: if (comment != null && comment.length() > 0) {
0588: Javadoc doc = (Javadoc) fCuRewrite.getASTRewrite()
0589: .createStringPlaceholder(comment,
0590: ASTNode.JAVADOC);
0591: fieldDeclaration.setJavadoc(doc);
0592: }
0593: }
0594:
0595: AbstractTypeDeclaration parent = getContainingTypeDeclarationNode();
0596: ListRewrite listRewrite = fCuRewrite.getASTRewrite()
0597: .getListRewrite(parent,
0598: parent.getBodyDeclarationsProperty());
0599: TextEditGroup msg = fCuRewrite
0600: .createGroupDescription(RefactoringCoreMessages.ExtractConstantRefactoring_declare_constant);
0601: if (insertFirst()) {
0602: listRewrite.insertFirst(fieldDeclaration, msg);
0603: } else {
0604: listRewrite.insertAfter(fieldDeclaration,
0605: getNodeToInsertConstantDeclarationAfter(), msg);
0606: }
0607:
0608: if (fLinkedProposalModel != null) {
0609: ASTRewrite rewrite = fCuRewrite.getASTRewrite();
0610: LinkedProposalPositionGroup nameGroup = fLinkedProposalModel
0611: .getPositionGroup(KEY_NAME, true);
0612: nameGroup
0613: .addPosition(rewrite
0614: .track(variableDeclarationFragment
0615: .getName()), true);
0616:
0617: String[] nameSuggestions = guessConstantNames();
0618: if (nameSuggestions.length > 0
0619: && !nameSuggestions[0].equals(fConstantName)) {
0620: nameGroup.addProposal(fConstantName, null,
0621: nameSuggestions.length + 1);
0622: }
0623: for (int i = 0; i < nameSuggestions.length; i++) {
0624: nameGroup.addProposal(nameSuggestions[i], null,
0625: nameSuggestions.length - i);
0626: }
0627:
0628: LinkedProposalPositionGroup typeGroup = fLinkedProposalModel
0629: .getPositionGroup(KEY_TYPE, true);
0630: typeGroup.addPosition(rewrite.track(type), true);
0631:
0632: ITypeBinding typeBinding = fragment
0633: .getAssociatedExpression().resolveTypeBinding();
0634: if (typeBinding != null) {
0635: ITypeBinding[] relaxingTypes = ASTResolving
0636: .getNarrowingTypes(ast, typeBinding);
0637: for (int i = 0; i < relaxingTypes.length; i++) {
0638: typeGroup.addProposal(relaxingTypes[i], fCuRewrite
0639: .getCu(), relaxingTypes.length - i);
0640: }
0641: }
0642: boolean isInterface = parent.resolveBinding() != null
0643: && parent.resolveBinding().isInterface();
0644: ModifierCorrectionSubProcessor
0645: .installLinkedVisibilityProposals(
0646: fLinkedProposalModel, rewrite,
0647: fieldDeclaration.modifiers(), isInterface);
0648: }
0649: }
0650:
0651: private Type getConstantType() throws JavaModelException {
0652: if (fConstantTypeCache == null) {
0653: IExpressionFragment fragment = getSelectedExpression();
0654: ITypeBinding typeBinding = fragment
0655: .getAssociatedExpression().resolveTypeBinding();
0656: AST ast = fCuRewrite.getAST();
0657: typeBinding = Bindings.normalizeForDeclarationUse(
0658: typeBinding, ast);
0659: fConstantTypeCache = fCuRewrite.getImportRewrite()
0660: .addImport(typeBinding, ast);
0661: }
0662: return fConstantTypeCache;
0663: }
0664:
0665: public Change createChange(IProgressMonitor monitor)
0666: throws CoreException {
0667: ExtractConstantDescriptor descriptor = createRefactoringDescriptor();
0668: fChange.setDescriptor(new RefactoringChangeDescriptor(
0669: descriptor));
0670: return fChange;
0671: }
0672:
0673: private ExtractConstantDescriptor createRefactoringDescriptor() {
0674: final Map arguments = new HashMap();
0675: String project = null;
0676: IJavaProject javaProject = fCu.getJavaProject();
0677: if (javaProject != null)
0678: project = javaProject.getElementName();
0679: int flags = JavaRefactoringDescriptor.JAR_REFACTORING
0680: | JavaRefactoringDescriptor.JAR_SOURCE_ATTACHMENT;
0681: if (JdtFlags.getVisibilityCode(fVisibility) != Modifier.PRIVATE)
0682: flags |= RefactoringDescriptor.STRUCTURAL_CHANGE;
0683: String pattern = ""; //$NON-NLS-1$
0684: try {
0685: pattern = BindingLabelProvider.getBindingLabel(
0686: getContainingTypeBinding(),
0687: JavaElementLabels.ALL_FULLY_QUALIFIED)
0688: + "."; //$NON-NLS-1$
0689: } catch (JavaModelException exception) {
0690: JavaPlugin.log(exception);
0691: }
0692: final String expression = ASTNodes.asString(fSelectedExpression
0693: .getAssociatedExpression());
0694: final String description = Messages
0695: .format(
0696: RefactoringCoreMessages.ExtractConstantRefactoring_descriptor_description_short,
0697: fConstantName);
0698: final String header = Messages
0699: .format(
0700: RefactoringCoreMessages.ExtractConstantRefactoring_descriptor_description,
0701: new String[] { pattern + fConstantName,
0702: expression });
0703: final JDTRefactoringDescriptorComment comment = new JDTRefactoringDescriptorComment(
0704: project, this , header);
0705: comment
0706: .addSetting(Messages
0707: .format(
0708: RefactoringCoreMessages.ExtractConstantRefactoring_constant_name_pattern,
0709: fConstantName));
0710: comment
0711: .addSetting(Messages
0712: .format(
0713: RefactoringCoreMessages.ExtractConstantRefactoring_constant_expression_pattern,
0714: expression));
0715: String visibility = fVisibility;
0716: if ("".equals(visibility)) //$NON-NLS-1$
0717: visibility = RefactoringCoreMessages.ExtractConstantRefactoring_default_visibility;
0718: comment
0719: .addSetting(Messages
0720: .format(
0721: RefactoringCoreMessages.ExtractConstantRefactoring_visibility_pattern,
0722: visibility));
0723: if (fReplaceAllOccurrences)
0724: comment
0725: .addSetting(RefactoringCoreMessages.ExtractConstantRefactoring_replace_occurrences);
0726: if (fQualifyReferencesWithDeclaringClassName)
0727: comment
0728: .addSetting(RefactoringCoreMessages.ExtractConstantRefactoring_qualify_references);
0729: arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT,
0730: JavaRefactoringDescriptorUtil.elementToHandle(project,
0731: fCu));
0732: arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME,
0733: fConstantName);
0734: arguments
0735: .put(
0736: JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION,
0737: new Integer(fSelectionStart).toString()
0738: + " " + new Integer(fSelectionLength).toString()); //$NON-NLS-1$
0739: arguments.put(ATTRIBUTE_REPLACE, Boolean.valueOf(
0740: fReplaceAllOccurrences).toString());
0741: arguments.put(ATTRIBUTE_QUALIFY, Boolean.valueOf(
0742: fQualifyReferencesWithDeclaringClassName).toString());
0743: arguments.put(ATTRIBUTE_VISIBILITY, new Integer(JdtFlags
0744: .getVisibilityCode(fVisibility)).toString());
0745:
0746: ExtractConstantDescriptor descriptor = new ExtractConstantDescriptor(
0747: project, description, comment.asString(), arguments,
0748: flags);
0749: return descriptor;
0750: }
0751:
0752: private void replaceExpressionsWithConstant()
0753: throws JavaModelException {
0754: ASTRewrite astRewrite = fCuRewrite.getASTRewrite();
0755: AST ast = astRewrite.getAST();
0756:
0757: IASTFragment[] fragmentsToReplace = getFragmentsToReplace();
0758: for (int i = 0; i < fragmentsToReplace.length; i++) {
0759: IASTFragment fragment = fragmentsToReplace[i];
0760:
0761: SimpleName ref = ast.newSimpleName(fConstantName);
0762: Name replacement = ref;
0763: if (qualifyReferencesWithDeclaringClassName()) {
0764: replacement = ast.newQualifiedName(ast
0765: .newSimpleName(getContainingTypeBinding()
0766: .getName()), ref);
0767: }
0768: TextEditGroup description = fCuRewrite
0769: .createGroupDescription(RefactoringCoreMessages.ExtractConstantRefactoring_replace);
0770:
0771: fragment.replace(astRewrite, replacement, description);
0772: if (fLinkedProposalModel != null)
0773: fLinkedProposalModel.getPositionGroup(KEY_NAME, true)
0774: .addPosition(astRewrite.track(ref), false);
0775: }
0776: }
0777:
0778: private void computeConstantDeclarationLocation()
0779: throws JavaModelException {
0780: if (isDeclarationLocationComputed())
0781: return;
0782:
0783: BodyDeclaration lastStaticDependency = null;
0784: Iterator decls = getBodyDeclarations();
0785:
0786: Assert.isTrue(decls.hasNext()); /* Admissible selected expressions must occur
0787: within a body declaration. Thus, the
0788: class/interface in which such an expression occurs
0789: must have at least one body declaration */
0790:
0791: while (decls.hasNext()) {
0792: BodyDeclaration decl = (BodyDeclaration) decls.next();
0793:
0794: int modifiers;
0795: if (decl instanceof FieldDeclaration)
0796: modifiers = ((FieldDeclaration) decl).getModifiers();
0797: else if (decl instanceof Initializer)
0798: modifiers = ((Initializer) decl).getModifiers();
0799: else {
0800: continue; /* this declaration is not a field declaration
0801: or initializer, so the placement of the constant
0802: declaration relative to it does not matter */
0803: }
0804:
0805: if (Modifier.isStatic(modifiers)
0806: && depends(getSelectedExpression(), decl))
0807: lastStaticDependency = decl;
0808: }
0809:
0810: if (lastStaticDependency == null)
0811: fInsertFirst = true;
0812: else
0813: fToInsertAfter = lastStaticDependency;
0814: }
0815:
0816: /* bd is a static field declaration or static initializer */
0817: private static boolean depends(IExpressionFragment selected,
0818: BodyDeclaration bd) {
0819: /* We currently consider selected to depend on bd only if db includes a declaration
0820: * of a static field on which selected depends.
0821: *
0822: * A more accurate strategy might be to also check if bd contains (or is) a
0823: * static initializer containing code which changes the value of a static field on
0824: * which selected depends. However, if a static is written to multiple times within
0825: * during class initialization, it is difficult to predict which value should be used.
0826: * This would depend on which value is used by expressions instances for which the new
0827: * constant will be substituted, and there may be many of these; in each, the
0828: * static field in question may have taken on a different value (if some of these uses
0829: * occur within static initializers).
0830: */
0831:
0832: if (bd instanceof FieldDeclaration) {
0833: FieldDeclaration fieldDecl = (FieldDeclaration) bd;
0834: for (Iterator fragments = fieldDecl.fragments().iterator(); fragments
0835: .hasNext();) {
0836: VariableDeclarationFragment fragment = (VariableDeclarationFragment) fragments
0837: .next();
0838: SimpleName staticFieldName = fragment.getName();
0839: if (selected.getSubFragmentsMatching(ASTFragmentFactory
0840: .createFragmentForFullSubtree(staticFieldName)).length != 0)
0841: return true;
0842: }
0843: }
0844: return false;
0845: }
0846:
0847: private boolean isDeclarationLocationComputed() {
0848: return fInsertFirst == true || fToInsertAfter != null;
0849: }
0850:
0851: private boolean insertFirst() throws JavaModelException {
0852: if (!isDeclarationLocationComputed())
0853: computeConstantDeclarationLocation();
0854: return fInsertFirst;
0855: }
0856:
0857: private BodyDeclaration getNodeToInsertConstantDeclarationAfter()
0858: throws JavaModelException {
0859: if (!isDeclarationLocationComputed())
0860: computeConstantDeclarationLocation();
0861: return fToInsertAfter;
0862: }
0863:
0864: private Iterator getBodyDeclarations() throws JavaModelException {
0865: if (fBodyDeclarations == null)
0866: fBodyDeclarations = getContainingTypeDeclarationNode()
0867: .bodyDeclarations();
0868: return fBodyDeclarations.iterator();
0869: }
0870:
0871: private String getConstantTypeName() throws JavaModelException {
0872: return ASTNodes.asString(getConstantType());
0873: }
0874:
0875: private static boolean isStaticFieldOrStaticInitializer(
0876: BodyDeclaration node) {
0877: if (node instanceof MethodDeclaration
0878: || node instanceof AbstractTypeDeclaration)
0879: return false;
0880:
0881: int modifiers;
0882: if (node instanceof FieldDeclaration) {
0883: modifiers = ((FieldDeclaration) node).getModifiers();
0884: } else if (node instanceof Initializer) {
0885: modifiers = ((Initializer) node).getModifiers();
0886: } else {
0887: Assert.isTrue(false);
0888: return false;
0889: }
0890:
0891: if (!Modifier.isStatic(modifiers))
0892: return false;
0893:
0894: return true;
0895: }
0896:
0897: /*
0898: * Elements returned by next() are BodyDeclaration
0899: * instances.
0900: */
0901: private Iterator getReplacementScope() throws JavaModelException {
0902: boolean declPredecessorReached = false;
0903:
0904: Collection scope = new ArrayList();
0905: for (Iterator bodyDeclarations = getBodyDeclarations(); bodyDeclarations
0906: .hasNext();) {
0907: BodyDeclaration bodyDeclaration = (BodyDeclaration) bodyDeclarations
0908: .next();
0909:
0910: if (bodyDeclaration == getNodeToInsertConstantDeclarationAfter())
0911: declPredecessorReached = true;
0912:
0913: if (insertFirst()
0914: || declPredecessorReached
0915: || !isStaticFieldOrStaticInitializer(bodyDeclaration))
0916: scope.add(bodyDeclaration);
0917: }
0918: return scope.iterator();
0919: }
0920:
0921: private IASTFragment[] getFragmentsToReplace()
0922: throws JavaModelException {
0923: List toReplace = new ArrayList();
0924: if (fReplaceAllOccurrences) {
0925: Iterator replacementScope = getReplacementScope();
0926: while (replacementScope.hasNext()) {
0927: BodyDeclaration bodyDecl = (BodyDeclaration) replacementScope
0928: .next();
0929: IASTFragment[] allMatches = ASTFragmentFactory
0930: .createFragmentForFullSubtree(bodyDecl)
0931: .getSubFragmentsMatching(
0932: getSelectedExpression());
0933: IASTFragment[] replaceableMatches = retainOnlyReplacableMatches(allMatches);
0934: for (int i = 0; i < replaceableMatches.length; i++)
0935: toReplace.add(replaceableMatches[i]);
0936: }
0937: } else if (canReplace(getSelectedExpression()))
0938: toReplace.add(getSelectedExpression());
0939: return (IASTFragment[]) toReplace
0940: .toArray(new IASTFragment[toReplace.size()]);
0941: }
0942:
0943: // !! - like one in ExtractTempRefactoring
0944: private static IASTFragment[] retainOnlyReplacableMatches(
0945: IASTFragment[] allMatches) {
0946: List result = new ArrayList(allMatches.length);
0947: for (int i = 0; i < allMatches.length; i++) {
0948: if (canReplace(allMatches[i]))
0949: result.add(allMatches[i]);
0950: }
0951: return (IASTFragment[]) result.toArray(new IASTFragment[result
0952: .size()]);
0953: }
0954:
0955: // !! - like one in ExtractTempRefactoring
0956: private static boolean canReplace(IASTFragment fragment) {
0957: ASTNode node = fragment.getAssociatedNode();
0958: ASTNode parent = node.getParent();
0959: if (parent instanceof VariableDeclarationFragment) {
0960: VariableDeclarationFragment vdf = (VariableDeclarationFragment) parent;
0961: if (node.equals(vdf.getName()))
0962: return false;
0963: }
0964: if (parent instanceof ExpressionStatement)
0965: return false;
0966: if (parent instanceof SwitchCase)
0967: return false;
0968: return true;
0969: }
0970:
0971: private IExpressionFragment getSelectedExpression()
0972: throws JavaModelException {
0973: if (fSelectedExpression != null)
0974: return fSelectedExpression;
0975:
0976: IASTFragment selectedFragment = ASTFragmentFactory
0977: .createFragmentForSourceRange(new SourceRange(
0978: fSelectionStart, fSelectionLength), fCuRewrite
0979: .getRoot(), fCu);
0980:
0981: if (selectedFragment instanceof IExpressionFragment
0982: && !Checks.isInsideJavadoc(selectedFragment
0983: .getAssociatedNode())) {
0984: fSelectedExpression = (IExpressionFragment) selectedFragment;
0985: }
0986:
0987: if (fSelectedExpression != null
0988: && Checks.isEnumCase(fSelectedExpression
0989: .getAssociatedExpression().getParent())) {
0990: fSelectedExpression = null;
0991: }
0992:
0993: return fSelectedExpression;
0994: }
0995:
0996: private AbstractTypeDeclaration getContainingTypeDeclarationNode()
0997: throws JavaModelException {
0998: AbstractTypeDeclaration result = (AbstractTypeDeclaration) ASTNodes
0999: .getParent(getSelectedExpression().getAssociatedNode(),
1000: AbstractTypeDeclaration.class);
1001: Assert.isNotNull(result);
1002: return result;
1003: }
1004:
1005: private ITypeBinding getContainingTypeBinding()
1006: throws JavaModelException {
1007: ITypeBinding result = getContainingTypeDeclarationNode()
1008: .resolveBinding();
1009: Assert.isNotNull(result);
1010: return result;
1011: }
1012:
1013: public RefactoringStatus initialize(
1014: final RefactoringArguments arguments) {
1015: if (arguments instanceof JavaRefactoringArguments) {
1016: final JavaRefactoringArguments extended = (JavaRefactoringArguments) arguments;
1017: final String selection = extended
1018: .getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION);
1019: if (selection != null) {
1020: int offset = -1;
1021: int length = -1;
1022: final StringTokenizer tokenizer = new StringTokenizer(
1023: selection);
1024: if (tokenizer.hasMoreTokens())
1025: offset = Integer.valueOf(tokenizer.nextToken())
1026: .intValue();
1027: if (tokenizer.hasMoreTokens())
1028: length = Integer.valueOf(tokenizer.nextToken())
1029: .intValue();
1030: if (offset >= 0 && length >= 0) {
1031: fSelectionStart = offset;
1032: fSelectionLength = length;
1033: } else
1034: return RefactoringStatus
1035: .createFatalErrorStatus(Messages
1036: .format(
1037: RefactoringCoreMessages.InitializableRefactoring_illegal_argument,
1038: new Object[] {
1039: selection,
1040: JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION }));
1041: } else
1042: return RefactoringStatus
1043: .createFatalErrorStatus(Messages
1044: .format(
1045: RefactoringCoreMessages.InitializableRefactoring_argument_not_exist,
1046: JavaRefactoringDescriptorUtil.ATTRIBUTE_SELECTION));
1047: final String handle = extended
1048: .getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT);
1049: if (handle != null) {
1050: final IJavaElement element = JavaRefactoringDescriptorUtil
1051: .handleToElement(extended.getProject(), handle,
1052: false);
1053: if (element == null
1054: || !element.exists()
1055: || element.getElementType() != IJavaElement.COMPILATION_UNIT)
1056: return createInputFatalStatus(element,
1057: IJavaRefactorings.EXTRACT_CONSTANT);
1058: else
1059: fCu = (ICompilationUnit) element;
1060: } else
1061: return RefactoringStatus
1062: .createFatalErrorStatus(Messages
1063: .format(
1064: RefactoringCoreMessages.InitializableRefactoring_argument_not_exist,
1065: JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT));
1066: final String visibility = extended
1067: .getAttribute(ATTRIBUTE_VISIBILITY);
1068: if (visibility != null && !"".equals(visibility)) {//$NON-NLS-1$
1069: int flag = 0;
1070: try {
1071: flag = Integer.parseInt(visibility);
1072: } catch (NumberFormatException exception) {
1073: return RefactoringStatus
1074: .createFatalErrorStatus(Messages
1075: .format(
1076: RefactoringCoreMessages.InitializableRefactoring_argument_not_exist,
1077: ATTRIBUTE_VISIBILITY));
1078: }
1079: fVisibility = JdtFlags.getVisibilityString(flag);
1080: }
1081: final String name = extended
1082: .getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME);
1083: if (name != null && !"".equals(name)) //$NON-NLS-1$
1084: fConstantName = name;
1085: else
1086: return RefactoringStatus
1087: .createFatalErrorStatus(Messages
1088: .format(
1089: RefactoringCoreMessages.InitializableRefactoring_argument_not_exist,
1090: JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME));
1091: final String replace = extended
1092: .getAttribute(ATTRIBUTE_REPLACE);
1093: if (replace != null) {
1094: fReplaceAllOccurrences = Boolean.valueOf(replace)
1095: .booleanValue();
1096: } else
1097: return RefactoringStatus
1098: .createFatalErrorStatus(Messages
1099: .format(
1100: RefactoringCoreMessages.InitializableRefactoring_argument_not_exist,
1101: ATTRIBUTE_REPLACE));
1102: final String declareFinal = extended
1103: .getAttribute(ATTRIBUTE_QUALIFY);
1104: if (declareFinal != null) {
1105: fQualifyReferencesWithDeclaringClassName = Boolean
1106: .valueOf(declareFinal).booleanValue();
1107: } else
1108: return RefactoringStatus
1109: .createFatalErrorStatus(Messages
1110: .format(
1111: RefactoringCoreMessages.InitializableRefactoring_argument_not_exist,
1112: ATTRIBUTE_QUALIFY));
1113: } else
1114: return RefactoringStatus
1115: .createFatalErrorStatus(RefactoringCoreMessages.InitializableRefactoring_inacceptable_arguments);
1116: return new RefactoringStatus();
1117: }
1118: }
|