/******************************************************************************* * Copyright (c) 2018 Red Hat Inc. and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Originally copied from org.eclipse.jdt.internal.corext.refactoring.rename.RenameTypeProcessor * * Contributors: * Red Hat Inc. - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.ls.core.internal.corext.refactoring.rename; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IProduct; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IImportDeclaration; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.ILocalVariable; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.ISourceRange; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeHierarchy; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.Modifier; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.refactoring.IJavaElementMapper; import org.eclipse.jdt.core.refactoring.IJavaRefactorings; import org.eclipse.jdt.core.refactoring.RenameTypeArguments; import org.eclipse.jdt.core.refactoring.descriptors.JavaRefactoringDescriptor; import org.eclipse.jdt.core.refactoring.descriptors.RenameJavaElementDescriptor; import org.eclipse.jdt.core.search.IJavaSearchConstants; import org.eclipse.jdt.core.search.IJavaSearchScope; import org.eclipse.jdt.core.search.SearchMatch; import org.eclipse.jdt.core.search.SearchPattern; import org.eclipse.jdt.core.search.TypeReferenceMatch; import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels; import org.eclipse.jdt.internal.core.refactoring.descriptors.RefactoringSignatureDescriptorFactory; import org.eclipse.jdt.internal.corext.dom.IASTSharedValues; import org.eclipse.jdt.internal.corext.refactoring.Checks; import org.eclipse.jdt.internal.corext.refactoring.JDTRefactoringDescriptorComment; import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringArguments; import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringDescriptorUtil; import org.eclipse.jdt.internal.corext.refactoring.SearchResultGroup; import org.eclipse.jdt.internal.corext.refactoring.changes.TextChangeCompatibility; import org.eclipse.jdt.internal.corext.refactoring.util.JavaStatusContext; import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser; import org.eclipse.jdt.internal.corext.util.JavaModelUtil; import org.eclipse.jdt.internal.corext.util.JdtFlags; import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; import org.eclipse.jdt.ls.core.internal.Messages; import org.eclipse.jdt.ls.core.internal.corext.refactoring.RefactoringAvailabilityTester; import org.eclipse.jdt.ls.core.internal.corext.refactoring.RefactoringCoreMessages; import org.eclipse.jdt.ls.core.internal.corext.refactoring.RefactoringScopeFactory; import org.eclipse.jdt.ls.core.internal.corext.refactoring.RefactoringSearchEngine; import org.eclipse.jdt.ls.core.internal.corext.refactoring.base.ReferencesInBinaryContext; import org.eclipse.jdt.ls.core.internal.corext.refactoring.changes.DynamicValidationRefactoringChange; import org.eclipse.jdt.ls.core.internal.corext.refactoring.changes.RenameCompilationUnitChange; import org.eclipse.jdt.ls.core.internal.corext.refactoring.participants.JavaProcessors; import org.eclipse.jdt.ls.core.internal.corext.refactoring.tagging.IQualifiedNameUpdating; import org.eclipse.jdt.ls.core.internal.corext.refactoring.tagging.IReferenceUpdating; import org.eclipse.jdt.ls.core.internal.corext.refactoring.tagging.ISimilarDeclarationUpdating; import org.eclipse.jdt.ls.core.internal.corext.refactoring.tagging.ITextUpdating; import org.eclipse.jdt.ls.core.internal.corext.refactoring.util.QualifiedNameFinder; import org.eclipse.jdt.ls.core.internal.corext.refactoring.util.ResourceUtil; import org.eclipse.jdt.ls.core.internal.corext.util.Changes; import org.eclipse.jdt.ls.core.internal.corext.util.JavaElementUtil; import org.eclipse.jdt.ls.core.internal.corext.util.QualifiedNameSearchResult; import org.eclipse.jdt.ls.core.internal.corext.util.SearchUtils; import org.eclipse.jdt.ls.core.internal.corext.util.TextChangeManager; import org.eclipse.jdt.ls.core.internal.hover.JavaElementLabels; import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.ltk.core.refactoring.GroupCategory; import org.eclipse.ltk.core.refactoring.GroupCategorySet; import org.eclipse.ltk.core.refactoring.IResourceMapper; import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.ltk.core.refactoring.RefactoringStatusContext; import org.eclipse.ltk.core.refactoring.TextChange; import org.eclipse.ltk.core.refactoring.TextFileChange; import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext; import org.eclipse.ltk.core.refactoring.participants.IParticipantDescriptorFilter; import org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor; import org.eclipse.ltk.core.refactoring.participants.RenameArguments; import org.eclipse.ltk.core.refactoring.resource.RenameResourceChange; import org.eclipse.text.edits.ReplaceEdit; public class RenameTypeProcessor extends JavaRenameProcessor implements ITextUpdating, IReferenceUpdating, IQualifiedNameUpdating, ISimilarDeclarationUpdating, IResourceMapper, IJavaElementMapper { private static final String ATTRIBUTE_QUALIFIED = "qualified"; //$NON-NLS-1$ private static final String ATTRIBUTE_TEXTUAL_MATCHES = "textual"; //$NON-NLS-1$ private static final String ATTRIBUTE_PATTERNS = "patterns"; //$NON-NLS-1$ private static final String ATTRIBUTE_SIMILAR_DECLARATIONS = "similarDeclarations"; //$NON-NLS-1$ private static final String ATTRIBUTE_MATCHING_STRATEGY = "matchStrategy"; //$NON-NLS-1$ private static final GroupCategorySet CATEGORY_TYPE_RENAME = new GroupCategorySet(new GroupCategory("org.eclipse.jdt.internal.corext.refactoring.rename.renameType.type", RefactoringCoreMessages.RenameTypeProcessor_changeCategory_type, //$NON-NLS-1$ RefactoringCoreMessages.RenameTypeProcessor_changeCategory_type_description)); private static final GroupCategorySet CATEGORY_METHOD_RENAME = new GroupCategorySet(new GroupCategory("org.eclipse.jdt.internal.corext.refactoring.rename.renameType.method", //$NON-NLS-1$ RefactoringCoreMessages.RenameTypeProcessor_changeCategory_method, RefactoringCoreMessages.RenameTypeProcessor_changeCategory_method_description)); private static final GroupCategorySet CATEGORY_FIELD_RENAME = new GroupCategorySet(new GroupCategory("org.eclipse.jdt.internal.corext.refactoring.rename.renameType.field", //$NON-NLS-1$ RefactoringCoreMessages.RenameTypeProcessor_changeCategory_fields, RefactoringCoreMessages.RenameTypeProcessor_changeCategory_fields_description)); private static final GroupCategorySet CATEGORY_LOCAL_RENAME = new GroupCategorySet(new GroupCategory("org.eclipse.jdt.internal.corext.refactoring.rename.renameType.local", //$NON-NLS-1$ RefactoringCoreMessages.RenameTypeProcessor_changeCategory_local_variables, RefactoringCoreMessages.RenameTypeProcessor_changeCategory_local_variables_description)); private IType fType; private SearchResultGroup[] fReferences; private TextChangeManager fChangeManager; private QualifiedNameSearchResult fQualifiedNameSearchResult; private boolean fUpdateReferences; private boolean fUpdateTextualMatches; private boolean fUpdateQualifiedNames; private String fFilePatterns; // --- similar elements private boolean fUpdateSimilarElements; private Map<IJavaElement, String> fFinalSimilarElementToName = null; private int fRenamingStrategy; // Preloaded information for the UI. private LinkedHashMap<IJavaElement, String> fPreloadedElementToName = null; private Map<IJavaElement, Boolean> fPreloadedElementToSelection = null; private LinkedHashMap<IJavaElement, String> fPreloadedElementToNameDefault = null; // Cache information to decide whether to // re-update references and preload info private String fCachedNewName = null; private boolean fCachedRenameSimilarElements = false; private int fCachedRenamingStrategy = -1; private RefactoringStatus fCachedRefactoringStatus = null; public static final class ParticipantDescriptorFilter implements IParticipantDescriptorFilter { @Override public boolean select(IConfigurationElement element, RefactoringStatus status) { IConfigurationElement[] params = element.getChildren(PARAM); for (int i = 0; i < params.length; i++) { IConfigurationElement param = params[i]; if ("handlesSimilarDeclarations".equals(param.getAttribute(NAME)) && //$NON-NLS-1$ "false".equals(param.getAttribute(VALUE))) { //$NON-NLS-1$ return false; } } return true; } } private class NoOverrideProgressMonitor extends SubProgressMonitor { public NoOverrideProgressMonitor(IProgressMonitor monitor, int ticks) { super(monitor, ticks, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL); } @Override public void setTaskName(String name) { // do nothing } } /** * Creates a new rename type processor. * * @param type * the type, or <code>null</code> if invoked by scripting */ public RenameTypeProcessor(IType type) { fType = type; if (type != null) { setNewElementName(type.getElementName()); } fUpdateReferences = true; //default is yes fUpdateTextualMatches = false; fUpdateSimilarElements = false; // default is no fRenamingStrategy = RenamingNameSuggestor.STRATEGY_EXACT; } public RenameTypeProcessor(JavaRefactoringArguments arguments, RefactoringStatus status) { this(null); status.merge(initialize(arguments)); } public IType getType() { return fType; } @Override public String getIdentifier() { return "org.eclipse.jdt.ui.renameTypeProcessor"; } @Override public boolean isApplicable() throws CoreException { return RefactoringAvailabilityTester.isRenameAvailable(fType); } @Override public String getProcessorName() { return RefactoringCoreMessages.RenameTypeRefactoring_name; } @Override protected String[] getAffectedProjectNatures() throws CoreException { return JavaProcessors.computeAffectedNatures(fType); } @Override public Object[] getElements() { return new Object[] { fType }; } @Override protected RenameModifications computeRenameModifications() { RenameModifications result = new RenameModifications(); result.rename(fType, new RenameTypeArguments(getNewElementName(), getUpdateReferences(), getUpdateSimilarDeclarations(), getSimilarElements()), createParticipantDescriptorFilter()); if (isPrimaryType()) { ICompilationUnit cu = fType.getCompilationUnit(); String newCUName = getNewCompilationUnit().getElementName(); result.rename(cu, new RenameArguments(newCUName, getUpdateReferences())); } return result; } /* * Note: this is a handle-only method! */ private boolean isPrimaryType() { String cuName = fType.getCompilationUnit().getElementName(); String typeName = fType.getElementName(); return Checks.isTopLevel(fType) && JavaCore.removeJavaLikeExtension(cuName).equals(typeName); } //---- IRenameProcessor ---------------------------------------------- @Override public String getCurrentElementName() { return fType.getElementName(); } @Override public String getCurrentElementQualifier() { return JavaModelUtil.getTypeContainerName(fType); } @Override public RefactoringStatus checkNewElementName(String newName) { Assert.isNotNull(newName, "new name"); //$NON-NLS-1$ RefactoringStatus result = Checks.checkTypeName(newName, fType); if (Checks.isAlreadyNamed(fType, newName)) { result.addFatalError(RefactoringCoreMessages.RenameTypeRefactoring_choose_another_name); } return result; } @Override public Object getNewElement() { if (Checks.isTopLevel(fType)) { return getNewCompilationUnit().getType(getNewElementName()); } else { return fType.getDeclaringType().getType(getNewElementName()); } } private ICompilationUnit getNewCompilationUnit() { ICompilationUnit cu = fType.getCompilationUnit(); if (isPrimaryType()) { IPackageFragment parent = fType.getPackageFragment(); String renamedCUName = JavaModelUtil.getRenamedCUName(cu, getNewElementName()); return parent.getCompilationUnit(renamedCUName); } else { return cu; } } //---- JavaRenameProcessor ------------------------------------------- protected RenameArguments createRenameArguments() { return new RenameTypeArguments(getNewElementName(), getUpdateReferences(), getUpdateSimilarDeclarations(), getSimilarElements()); } protected IParticipantDescriptorFilter createParticipantDescriptorFilter() { if (!getUpdateSimilarDeclarations()) { return null; } return new ParticipantDescriptorFilter(); } @Override protected IFile[] getChangedFiles() throws CoreException { List<IFile> result = new ArrayList<>(); result.addAll(Arrays.asList(ResourceUtil.getFiles(fChangeManager.getAllCompilationUnits()))); if (fQualifiedNameSearchResult != null) { result.addAll(Arrays.asList(fQualifiedNameSearchResult.getAllFiles())); } if (willRenameCU()) { result.add(ResourceUtil.getFile(fType.getCompilationUnit())); } return result.toArray(new IFile[result.size()]); } //---- ITextUpdating ------------------------------------------------- @Override public boolean canEnableTextUpdating() { return true; } @Override public boolean getUpdateTextualMatches() { return fUpdateTextualMatches; } @Override public void setUpdateTextualMatches(boolean update) { fUpdateTextualMatches = update; } //---- IReferenceUpdating -------------------------------------- @Override public void setUpdateReferences(boolean update) { fUpdateReferences = update; } @Override public boolean getUpdateReferences() { return fUpdateReferences; } //---- IQualifiedNameUpdating ---------------------------------- @Override public boolean canEnableQualifiedNameUpdating() { return !fType.getPackageFragment().isDefaultPackage() && !(fType.getParent() instanceof IType); } @Override public boolean getUpdateQualifiedNames() { return fUpdateQualifiedNames; } @Override public void setUpdateQualifiedNames(boolean update) { fUpdateQualifiedNames = update; } @Override public String getFilePatterns() { return fFilePatterns; } @Override public void setFilePatterns(String patterns) { Assert.isNotNull(patterns); fFilePatterns = patterns; } // ---- ISimilarDeclarationUpdating @Override public boolean canEnableSimilarDeclarationUpdating() { IProduct product = Platform.getProduct(); if (product != null) { String property = product.getProperty("org.eclipse.jdt.ui.refactoring.handlesSimilarDeclarations"); //$NON-NLS-1$ if ("false".equalsIgnoreCase(property)) { return false; } } return true; } @Override public void setUpdateSimilarDeclarations(boolean update) { fUpdateSimilarElements = update; } @Override public boolean getUpdateSimilarDeclarations() { return fUpdateSimilarElements; } @Override public int getMatchStrategy() { return fRenamingStrategy; } @Override public void setMatchStrategy(int selectedStrategy) { fRenamingStrategy = selectedStrategy; } /** * @return the similar elements of the type, i.e. IFields, IMethods, and * ILocalVariables. Returns <code>null</code> iff similar declaration * updating is not requested. */ public IJavaElement[] getSimilarElements() { if (fFinalSimilarElementToName == null) { return null; } Set<IJavaElement> keys = fFinalSimilarElementToName.keySet(); return keys.toArray(new IJavaElement[keys.size()]); } @Override public IResource getRefactoredResource(IResource element) { if (element instanceof IFile) { if (Checks.isTopLevel(fType) && element.equals(fType.getResource())) { return getNewCompilationUnit().getResource(); } } return element; } @Override public IJavaElement getRefactoredJavaElement(IJavaElement element) { if (element instanceof ICompilationUnit) { if (Checks.isTopLevel(fType) && element.equals(fType.getCompilationUnit())) { return getNewCompilationUnit(); } } else if (element instanceof IMember) { final IType newType = (IType) getNewElement(); final RefactoringHandleTransplanter transplanter = new RefactoringHandleTransplanter(fType, newType, fFinalSimilarElementToName); return transplanter.transplantHandle((IMember) element); } return element; } @Override public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException { IType primary = (IType) fType.getPrimaryElement(); if (primary == null || !primary.exists()) { String qualifiedTypeName = JavaElementLabels.getElementLabel(fType, JavaElementLabels.F_FULLY_QUALIFIED); String message = Messages.format(RefactoringCoreMessages.RenameTypeRefactoring_does_not_exist, new String[] { BasicElementLabels.getJavaElementName(qualifiedTypeName), BasicElementLabels.getFileName(fType.getCompilationUnit()) }); return RefactoringStatus.createFatalErrorStatus(message); } fType = primary; return Checks.checkIfCuBroken(fType); } @Override protected RefactoringStatus doCheckFinalConditions(IProgressMonitor pm, CheckConditionsContext context) throws CoreException { Assert.isNotNull(fType, "type"); //$NON-NLS-1$ Assert.isNotNull(getNewElementName(), "newName"); //$NON-NLS-1$ RefactoringStatus result = new RefactoringStatus(); int referenceSearchTicks = fUpdateReferences || fUpdateSimilarElements ? 15 : 0; int affectedCusTicks = fUpdateReferences || fUpdateSimilarElements ? 10 : 1; int similarElementTicks = fUpdateSimilarElements ? 85 : 0; int createChangeTicks = 5; int qualifiedNamesTicks = fUpdateQualifiedNames ? 50 : 0; try { pm.beginTask("", 12 + referenceSearchTicks + affectedCusTicks + similarElementTicks + createChangeTicks + qualifiedNamesTicks); //$NON-NLS-1$ pm.setTaskName(RefactoringCoreMessages.RenameTypeRefactoring_checking); fChangeManager = new TextChangeManager(true); result.merge(checkNewElementName(getNewElementName())); if (result.hasFatalError()) { return result; } result.merge(Checks.checkIfCuBroken(fType)); if (result.hasFatalError()) { return result; } pm.worked(1); result.merge(checkTypesInCompilationUnit()); pm.worked(1); result.merge(checkForMethodsWithConstructorNames()); pm.worked(1); result.merge(checkImportedTypes()); pm.worked(1); if (Checks.isTopLevel(fType)) { ICompilationUnit cu = fType.getCompilationUnit(); String newCUName = JavaModelUtil.getRenamedCUName(cu, getNewElementName()); if (!newCUName.equals(cu.getElementName())) { result.merge(Checks.checkCompilationUnitNewName(cu, getNewElementName())); } } pm.worked(1); if (isPrimaryType()) { result.merge(checkNewPathValidity()); } pm.worked(1); result.merge(checkEnclosingTypes()); pm.worked(1); result.merge(checkEnclosedTypes()); pm.worked(1); result.merge(checkTypesInPackage()); pm.worked(1); result.merge(checkTypesImportedInCu()); pm.worked(1); result.merge(Checks.checkForMainAndNativeMethods(fType)); pm.worked(1); // before doing any expensive analysis if (result.hasFatalError()) { return result; } result.merge(analyseEnclosedTypes()); pm.worked(1); // before doing _the really_ expensive analysis if (result.hasFatalError()) { return result; } // Load references, including similarly named elements if (fUpdateReferences || fUpdateSimilarElements) { pm.setTaskName(RefactoringCoreMessages.RenameTypeRefactoring_searching); result.merge(initializeReferences(new SubProgressMonitor(pm, referenceSearchTicks))); } else { fReferences = new SearchResultGroup[0]; } pm.setTaskName(RefactoringCoreMessages.RenameTypeRefactoring_checking); if (pm.isCanceled()) { throw new OperationCanceledException(); } if (fUpdateReferences || fUpdateSimilarElements) { result.merge(analyzeAffectedCompilationUnits(new SubProgressMonitor(pm, affectedCusTicks))); } else { Checks.checkCompileErrorsInAffectedFile(result, fType.getResource()); pm.worked(affectedCusTicks); } if (result.hasFatalError()) { return result; } if (fUpdateSimilarElements) { result.merge(initializeSimilarElementsRenameProcessors(new SubProgressMonitor(pm, similarElementTicks), context)); if (result.hasFatalError()) { return result; } } createChanges(new SubProgressMonitor(pm, createChangeTicks)); if (fUpdateQualifiedNames) { computeQualifiedNameMatches(new SubProgressMonitor(pm, qualifiedNamesTicks)); } return result; } finally { pm.done(); } } /** * Initializes the references to the type and the similarly named elements. This * method creates both the fReferences and the fPreloadedElementToName fields. * * May be called from the UI. * * @param monitor * progress monitor * @return initialization status * @throws JavaModelException * some fundamental error with the underlying model * @throws OperationCanceledException * if user canceled the task * */ public RefactoringStatus initializeReferences(IProgressMonitor monitor) throws JavaModelException, OperationCanceledException { Assert.isNotNull(fType); Assert.isNotNull(getNewElementName()); // Do not search again if the preconditions have not changed. // Search depends on the type, the new name, the similarly named elements, and // the strategy if (fReferences != null && (getNewElementName().equals(fCachedNewName)) && (fCachedRenameSimilarElements == getUpdateSimilarDeclarations()) && (fCachedRenamingStrategy == fRenamingStrategy)) { return fCachedRefactoringStatus; } fCachedNewName = getNewElementName(); fCachedRenameSimilarElements = fUpdateSimilarElements; fCachedRenamingStrategy = fRenamingStrategy; fCachedRefactoringStatus = new RefactoringStatus(); try { SearchPattern pattern = SearchPattern.createPattern(fType, IJavaSearchConstants.REFERENCES, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE); String binaryRefsDescription = Messages.format(RefactoringCoreMessages.ReferencesInBinaryContext_ref_in_binaries_description, BasicElementLabels.getJavaElementName(fType.getElementName())); ReferencesInBinaryContext binaryRefs = new ReferencesInBinaryContext(binaryRefsDescription); fReferences = RefactoringSearchEngine.search(pattern, RefactoringScopeFactory.create(fType, true, false), new TypeOccurrenceCollector(fType, binaryRefs), monitor, fCachedRefactoringStatus); binaryRefs.addErrorIfNecessary(fCachedRefactoringStatus); fReferences = Checks.excludeCompilationUnits(fReferences, fCachedRefactoringStatus); fPreloadedElementToName = new LinkedHashMap<>(); fPreloadedElementToSelection = new HashMap<>(); final String unQualifiedTypeName = fType.getElementName(); monitor.beginTask("", fReferences.length); //$NON-NLS-1$ if (getUpdateSimilarDeclarations()) { RenamingNameSuggestor sugg = new RenamingNameSuggestor(fRenamingStrategy); for (int i = 0; i < fReferences.length; i++) { final ICompilationUnit cu = fReferences[i].getCompilationUnit(); if (cu == null) { continue; } final SearchMatch[] results = fReferences[i].getSearchResults(); for (int j = 0; j < results.length; j++) { if (!(results[j] instanceof TypeReferenceMatch)) { continue; } final TypeReferenceMatch match = (TypeReferenceMatch) results[j]; final List<IJavaElement> matches = new ArrayList<>(); if (match.getLocalElement() != null) { if (match.getLocalElement() instanceof ILocalVariable) { matches.add(match.getLocalElement()); } // else don't update (e.g. match in type parameter, annotation, ...) } else { matches.add((IJavaElement) match.getElement()); } final IJavaElement[] others = match.getOtherElements(); if (others != null) { matches.addAll(Arrays.asList(others)); } for (Iterator<IJavaElement> iter = matches.iterator(); iter.hasNext();) { final IJavaElement element = iter.next(); if (!(element instanceof IMethod) && !(element instanceof IField) && !(element instanceof ILocalVariable)) { continue; } if (!isInDeclaredType(match.getOffset(), element)) { continue; } if (element instanceof IField) { final IField currentField = (IField) element; final String newFieldName = sugg.suggestNewFieldName(currentField.getJavaProject(), currentField.getElementName(), Flags.isStatic(currentField.getFlags()), unQualifiedTypeName, getNewElementName()); if (newFieldName != null) { fPreloadedElementToName.put(currentField, newFieldName); } } else if (element instanceof IMethod) { final IMethod currentMethod = (IMethod) element; addMethodRename(unQualifiedTypeName, sugg, currentMethod); } else if (element instanceof ILocalVariable) { final ILocalVariable currentLocal = (ILocalVariable) element; final boolean isParameter; if (currentLocal.isParameter()) { addMethodRename(unQualifiedTypeName, sugg, (IMethod) currentLocal.getParent()); isParameter = true; } else { isParameter = false; } final String newLocalName = sugg.suggestNewLocalName(currentLocal.getJavaProject(), currentLocal.getElementName(), isParameter, unQualifiedTypeName, getNewElementName()); if (newLocalName != null) { fPreloadedElementToName.put(currentLocal, newLocalName); } } } } if (monitor.isCanceled()) { throw new OperationCanceledException(); } } } for (Iterator<IJavaElement> iter = fPreloadedElementToName.keySet().iterator(); iter.hasNext();) { IJavaElement element = iter.next(); fPreloadedElementToSelection.put(element, Boolean.TRUE); } fPreloadedElementToNameDefault = new LinkedHashMap<>(fPreloadedElementToName); } catch (OperationCanceledException e) { fReferences = null; fPreloadedElementToName = null; throw new OperationCanceledException(); } return fCachedRefactoringStatus; } /** * @param matchOffset * offset of the match * @param parentElement * parent element of the match * @return true iff the given search match offset (must be a match of a type * reference) lies before the element name of its enclosing java * element, false if not. In other words: If this method returns true, * the match is the declared type (or return type) of the enclosing * element. * @throws JavaModelException * should not happen * */ private boolean isInDeclaredType(int matchOffset, IJavaElement parentElement) throws JavaModelException { if (parentElement != null) { int enclosingNameOffset = 0; if (parentElement instanceof IMethod || parentElement instanceof IField) { enclosingNameOffset = ((IMember) parentElement).getNameRange().getOffset(); } else if (parentElement instanceof ILocalVariable) { enclosingNameOffset = ((ILocalVariable) parentElement).getNameRange().getOffset(); } return (matchOffset < enclosingNameOffset); } return false; } private void addMethodRename(final String unQualifiedTypeName, RenamingNameSuggestor sugg, final IMethod currentMethod) throws JavaModelException { if (!currentMethod.isConstructor()) { final String newMethodName = sugg.suggestNewMethodName(currentMethod.getElementName(), unQualifiedTypeName, getNewElementName()); if (newMethodName != null) { fPreloadedElementToName.put(currentMethod, newMethodName); } } } private RefactoringStatus checkNewPathValidity() { IContainer c = fType.getCompilationUnit().getResource().getParent(); String notRename = RefactoringCoreMessages.RenameTypeRefactoring_will_not_rename; IStatus status = c.getWorkspace().validateName(getNewElementName(), IResource.FILE); if (status.getSeverity() == IStatus.ERROR) { return RefactoringStatus.createWarningStatus(status.getMessage() + ". " + notRename); //$NON-NLS-1$ } status = c.getWorkspace().validatePath(createNewPath(getNewElementName()), IResource.FILE); if (status.getSeverity() == IStatus.ERROR) { return RefactoringStatus.createWarningStatus(status.getMessage() + ". " + notRename); //$NON-NLS-1$ } return new RefactoringStatus(); } private String createNewPath(String newName) { return fType.getCompilationUnit().getResource().getFullPath().removeLastSegments(1).append(newName).toString(); } private RefactoringStatus checkTypesImportedInCu() throws CoreException { IImportDeclaration imp = getImportedType(fType.getCompilationUnit(), getNewElementName()); if (imp == null) { return null; } String msg = Messages.format(RefactoringCoreMessages.RenameTypeRefactoring_imported, new Object[] { getNewElementLabel(), BasicElementLabels.getPathLabel(fType.getCompilationUnit().getResource().getFullPath(), false) }); IJavaElement grandParent = imp.getParent().getParent(); if (grandParent instanceof ICompilationUnit) { return RefactoringStatus.createErrorStatus(msg, JavaStatusContext.create(imp)); } return null; } private RefactoringStatus checkTypesInPackage() throws CoreException { IType type = Checks.findTypeInPackage(fType.getPackageFragment(), getNewElementName()); if (type == null || !type.exists()) { return null; } String msg = Messages.format(RefactoringCoreMessages.RenameTypeRefactoring_exists, new String[] { getNewElementLabel(), JavaElementLabels.getElementLabel(fType.getPackageFragment(), JavaElementLabels.ALL_DEFAULT) }); return RefactoringStatus.createErrorStatus(msg, JavaStatusContext.create(type)); } private RefactoringStatus checkEnclosedTypes() throws CoreException { IType enclosedType = findEnclosedType(fType, getNewElementName()); if (enclosedType == null) { return null; } String msg = Messages.format(RefactoringCoreMessages.RenameTypeRefactoring_encloses, new String[] { BasicElementLabels.getJavaElementName(fType.getFullyQualifiedName('.')), getNewElementLabel() }); return RefactoringStatus.createErrorStatus(msg, JavaStatusContext.create(enclosedType)); } private RefactoringStatus checkEnclosingTypes() { IType enclosingType = findEnclosingType(fType, getNewElementName()); if (enclosingType == null) { return null; } String msg = Messages.format(RefactoringCoreMessages.RenameTypeRefactoring_enclosed, new String[] { BasicElementLabels.getJavaElementName(fType.getFullyQualifiedName('.')), getNewElementLabel() }); return RefactoringStatus.createErrorStatus(msg, JavaStatusContext.create(enclosingType)); } private String getNewElementLabel() { return BasicElementLabels.getJavaElementName(getNewElementName()); } private static IType findEnclosedType(IType type, String newName) throws CoreException { IType[] enclosedTypes = type.getTypes(); for (int i = 0; i < enclosedTypes.length; i++) { if (newName.equals(enclosedTypes[i].getElementName()) || findEnclosedType(enclosedTypes[i], newName) != null) { return enclosedTypes[i]; } } return null; } private static IType findEnclosingType(IType type, String newName) { IType enclosing = type.getDeclaringType(); while (enclosing != null) { if (newName.equals(enclosing.getElementName())) { return enclosing; } else { enclosing = enclosing.getDeclaringType(); } } return null; } private static IImportDeclaration getImportedType(ICompilationUnit cu, String typeName) throws CoreException { IImportDeclaration[] imports = cu.getImports(); String dotTypeName = "." + typeName; //$NON-NLS-1$ for (int i = 0; i < imports.length; i++) { if (imports[i].getElementName().endsWith(dotTypeName)) { return imports[i]; } } return null; } private RefactoringStatus checkForMethodsWithConstructorNames() throws CoreException { IMethod[] methods = fType.getMethods(); for (int i = 0; i < methods.length; i++) { if (methods[i].isConstructor()) { continue; } RefactoringStatus check = Checks.checkIfConstructorName(methods[i], methods[i].getElementName(), getNewElementName()); if (check != null) { return check; } } return null; } private RefactoringStatus checkImportedTypes() throws CoreException { RefactoringStatus result = new RefactoringStatus(); IImportDeclaration[] imports = fType.getCompilationUnit().getImports(); for (int i = 0; i < imports.length; i++) { analyzeImportDeclaration(imports[i], result); } return result; } private RefactoringStatus checkTypesInCompilationUnit() { RefactoringStatus result = new RefactoringStatus(); if (!Checks.isTopLevel(fType)) { //the other case checked in checkTypesInPackage IType siblingType = fType.getDeclaringType().getType(getNewElementName()); if (siblingType.exists()) { String msg = Messages.format(RefactoringCoreMessages.RenameTypeRefactoring_member_type_exists, new String[] { getNewElementLabel(), BasicElementLabels.getJavaElementName(fType.getDeclaringType().getFullyQualifiedName('.')) }); result.addError(msg, JavaStatusContext.create(siblingType)); } } return result; } private RefactoringStatus analyseEnclosedTypes() throws CoreException { final ISourceRange typeRange = fType.getSourceRange(); final RefactoringStatus result = new RefactoringStatus(); CompilationUnit cuNode = new RefactoringASTParser(IASTSharedValues.SHARED_AST_LEVEL).parse(fType.getCompilationUnit(), false); cuNode.accept(new ASTVisitor() { @Override public boolean visit(TypeDeclaration node) { // enums and annotations can't be local if (node.getStartPosition() <= typeRange.getOffset()) { return true; } if (node.getStartPosition() > typeRange.getOffset() + typeRange.getLength()) { return true; } if (getNewElementName().equals(node.getName().getIdentifier())) { RefactoringStatusContext context = JavaStatusContext.create(fType.getCompilationUnit(), node); String msg = null; if (node.isLocalTypeDeclaration()) { msg = Messages.format(RefactoringCoreMessages.RenameTypeRefactoring_local_type, new String[] { JavaElementUtil.createSignature(fType), getNewElementLabel() }); } else if (node.isMemberTypeDeclaration()) { msg = Messages.format(RefactoringCoreMessages.RenameTypeRefactoring_member_type, new String[] { JavaElementUtil.createSignature(fType), getNewElementLabel() }); } if (msg != null) { result.addError(msg, context); } } MethodDeclaration[] methods = node.getMethods(); for (int i = 0; i < methods.length; i++) { if (Modifier.isNative(methods[i].getModifiers())) { RefactoringStatusContext context = JavaStatusContext.create(fType.getCompilationUnit(), methods[i]); String msg = Messages.format(RefactoringCoreMessages.RenameTypeRefactoring_enclosed_type_native, BasicElementLabels.getJavaElementName(node.getName().getIdentifier())); result.addWarning(msg, context); } } return true; } }); return result; } private static ICompilationUnit getCompilationUnit(IImportDeclaration imp) { return (ICompilationUnit) imp.getParent().getParent(); } private void analyzeImportedTypes(IType[] types, RefactoringStatus result, IImportDeclaration imp) throws CoreException { for (int i = 0; i < types.length; i++) { //could this be a problem (same package imports)? if (JdtFlags.isPublic(types[i]) && types[i].getElementName().equals(getNewElementName())) { String msg = Messages.format(RefactoringCoreMessages.RenameTypeRefactoring_name_conflict1, new Object[] { JavaElementLabels.getElementLabel(types[i], JavaElementLabels.ALL_FULLY_QUALIFIED), BasicElementLabels.getPathLabel(getCompilationUnit(imp).getPath(), false) }); result.addError(msg, JavaStatusContext.create(imp)); } } } private static IJavaElement convertFromImportDeclaration(IImportDeclaration declaration) throws CoreException { if (declaration.isOnDemand()) { String packageName = declaration.getElementName().substring(0, declaration.getElementName().length() - 2); return JavaModelUtil.findTypeContainer(declaration.getJavaProject(), packageName); } else { return JavaModelUtil.findTypeContainer(declaration.getJavaProject(), declaration.getElementName()); } } private void analyzeImportDeclaration(IImportDeclaration imp, RefactoringStatus result) throws CoreException { if (!imp.isOnDemand()) { return; //analyzed earlier } IJavaElement imported = convertFromImportDeclaration(imp); if (imported == null) { return; } if (imported instanceof IPackageFragment) { ICompilationUnit[] cus = ((IPackageFragment) imported).getCompilationUnits(); for (int i = 0; i < cus.length; i++) { analyzeImportedTypes(cus[i].getTypes(), result, imp); } } else { //cast safe: see JavaModelUtility.convertFromImportDeclaration analyzeImportedTypes(((IType) imported).getTypes(), result, imp); } } /* * Analyzes all compilation units in which type is referenced */ private RefactoringStatus analyzeAffectedCompilationUnits(IProgressMonitor pm) throws CoreException { RefactoringStatus result = new RefactoringStatus(); result.merge(Checks.checkCompileErrorsInAffectedFiles(fReferences, fType.getResource())); pm.beginTask("", fReferences.length); //$NON-NLS-1$ result.merge(checkConflictingTypes(pm)); return result; } private RefactoringStatus checkConflictingTypes(IProgressMonitor pm) throws CoreException { RefactoringStatus result = new RefactoringStatus(); IJavaSearchScope scope = RefactoringScopeFactory.create(fType); SearchPattern pattern = SearchPattern.createPattern(getNewElementName(), IJavaSearchConstants.TYPE, IJavaSearchConstants.ALL_OCCURRENCES, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE); ICompilationUnit[] cusWithReferencesToConflictingTypes = RefactoringSearchEngine.findAffectedCompilationUnits(pattern, scope, pm, result); if (cusWithReferencesToConflictingTypes.length == 0) { return result; } ICompilationUnit[] cusWithReferencesToRenamedType = getCus(fReferences); Set<ICompilationUnit> conflicts = getIntersection(cusWithReferencesToRenamedType, cusWithReferencesToConflictingTypes); if (cusWithReferencesToConflictingTypes.length > 0) { cus: for (ICompilationUnit cu : cusWithReferencesToConflictingTypes) { String packageName = fType.getPackageFragment().getElementName(); if (((IPackageFragment) cu.getParent()).getElementName().equals(packageName)) { boolean hasOnDemandImport = false; IImportDeclaration[] imports = cu.getImports(); for (IImportDeclaration importDecl : imports) { if (importDecl.isOnDemand()) { hasOnDemandImport = true; } else { String importName = importDecl.getElementName(); int packageLength = importName.length() - getNewElementName().length() - 1; if (packageLength > 0 && importName.endsWith(getNewElementName()) && importName.charAt(packageLength) == '.') { continue cus; // explicit import from another package => no problem } } } if (hasOnDemandImport) { // the renamed type in the same package will shadow the *-imported type conflicts.add(cu); } } } } for (ICompilationUnit conflict : conflicts) { RefactoringStatusContext context = JavaStatusContext.create(conflict); String message = Messages.format(RefactoringCoreMessages.RenameTypeRefactoring_another_type, new String[] { getNewElementLabel(), BasicElementLabels.getFileName(conflict) }); result.addError(message, context); } return result; } private static Set<ICompilationUnit> getIntersection(ICompilationUnit[] a1, ICompilationUnit[] a2) { Set<ICompilationUnit> set1 = new HashSet<>(Arrays.asList(a1)); Set<ICompilationUnit> set2 = new HashSet<>(Arrays.asList(a2)); set1.retainAll(set2); return set1; } private static ICompilationUnit[] getCus(SearchResultGroup[] searchResultGroups) { List<ICompilationUnit> cus = new ArrayList<>(searchResultGroups.length); for (int i = 0; i < searchResultGroups.length; i++) { ICompilationUnit cu = searchResultGroups[i].getCompilationUnit(); if (cu != null) { cus.add(cu); } } return cus.toArray(new ICompilationUnit[cus.size()]); } @Override public Change createChange(IProgressMonitor monitor) throws CoreException { try { monitor.beginTask(RefactoringCoreMessages.RenameTypeRefactoring_creating_change, 4); String project = null; IJavaProject javaProject = fType.getJavaProject(); if (javaProject != null) { project = javaProject.getElementName(); } int flags = JavaRefactoringDescriptor.JAR_MIGRATION | JavaRefactoringDescriptor.JAR_REFACTORING | RefactoringDescriptor.STRUCTURAL_CHANGE; try { if (!Flags.isPrivate(fType.getFlags())) { flags |= RefactoringDescriptor.MULTI_CHANGE; } if (fType.isAnonymous() || fType.isLocal()) { flags |= JavaRefactoringDescriptor.JAR_SOURCE_ATTACHMENT; } } catch (JavaModelException exception) { JavaLanguageServerPlugin.log(exception); } final String description = Messages.format(RefactoringCoreMessages.RenameTypeProcessor_descriptor_description_short, BasicElementLabels.getJavaElementName(fType.getElementName())); final String header = Messages.format(RefactoringCoreMessages.RenameTypeProcessor_descriptor_description, new String[] { JavaElementLabels.getElementLabel(fType, JavaElementLabels.ALL_FULLY_QUALIFIED), getNewElementLabel() }); final String comment = new JDTRefactoringDescriptorComment(project, this, header).asString(); final RenameJavaElementDescriptor descriptor = RefactoringSignatureDescriptorFactory.createRenameJavaElementDescriptor(IJavaRefactorings.RENAME_TYPE); descriptor.setProject(project); descriptor.setDescription(description); descriptor.setComment(comment); descriptor.setFlags(flags); descriptor.setJavaElement(fType); descriptor.setNewName(getNewElementName()); descriptor.setUpdateQualifiedNames(fUpdateQualifiedNames); descriptor.setUpdateTextualOccurrences(fUpdateTextualMatches); descriptor.setUpdateReferences(fUpdateReferences); if (fUpdateQualifiedNames && fFilePatterns != null && !"".equals(fFilePatterns)) { descriptor.setFileNamePatterns(fFilePatterns); } descriptor.setUpdateSimilarDeclarations(fUpdateSimilarElements); descriptor.setMatchStrategy(fRenamingStrategy); final DynamicValidationRefactoringChange result = new DynamicValidationRefactoringChange(descriptor, RefactoringCoreMessages.RenameTypeProcessor_change_name); if (fChangeManager.containsChangesIn(fType.getCompilationUnit())) { TextChange textChange = fChangeManager.get(fType.getCompilationUnit()); if (textChange instanceof TextFileChange) { ((TextFileChange) textChange).setSaveMode(TextFileChange.FORCE_SAVE); } } if (willRenameCU()) { IResource resource = fType.getCompilationUnit().getResource(); if (resource != null && resource.isLinked()) { result.addAll(fChangeManager.getAllChanges()); String ext = resource.getFileExtension(); String renamedResourceName; if (ext == null) { renamedResourceName = getNewElementName(); } else { renamedResourceName = getNewElementName() + '.' + ext; } result.add(new RenameResourceChange(fType.getCompilationUnit().getPath(), renamedResourceName)); } else { addTypeDeclarationUpdate(fChangeManager); addConstructorRenames(fChangeManager); result.addAll(fChangeManager.getAllChanges()); String renamedCUName = JavaModelUtil.getRenamedCUName(fType.getCompilationUnit(), getNewElementName()); result.add(new RenameCompilationUnitChange(fType.getCompilationUnit(), renamedCUName)); } } else { result.addAll(fChangeManager.getAllChanges()); } monitor.worked(1); return result; } finally { fChangeManager = null; } } @Override public Change postCreateChange(Change[] participantChanges, IProgressMonitor pm) throws CoreException { if (fQualifiedNameSearchResult != null) { try { return fQualifiedNameSearchResult.getSingleChange(Changes.getModifiedFiles(participantChanges)); } finally { fQualifiedNameSearchResult = null; } } else { return null; } } private boolean willRenameCU() { String name = JavaCore.removeJavaLikeExtension(fType.getCompilationUnit().getElementName()); if (!(Checks.isTopLevel(fType) && name.equals(fType.getElementName()))) { return false; } if (!checkNewPathValidity().isOK()) { return false; } if (!Checks.checkCompilationUnitNewName(fType.getCompilationUnit(), getNewElementName()).isOK()) { return false; } return true; } private void createChanges(IProgressMonitor pm) throws CoreException { try { pm.beginTask("", 12); //$NON-NLS-1$ pm.setTaskName(RefactoringCoreMessages.RenameTypeProcessor_creating_changes); if (fUpdateReferences) { addReferenceUpdates(fChangeManager, new SubProgressMonitor(pm, 3)); } // Similar names updates have already been added. pm.worked(1); IResource resource = fType.getCompilationUnit().getResource(); // if we have a linked resource then we don't use CU renaming // directly. So we have to update the code by ourselves. if ((resource != null && resource.isLinked()) || !willRenameCU()) { addTypeDeclarationUpdate(fChangeManager); pm.worked(1); addConstructorRenames(fChangeManager); pm.worked(1); } else { pm.worked(2); } if (fUpdateTextualMatches) { pm.subTask(RefactoringCoreMessages.RenameTypeRefactoring_searching_text); TextMatchUpdater.perform(new SubProgressMonitor(pm, 1), RefactoringScopeFactory.create(fType), this, fChangeManager, fReferences); if (fUpdateSimilarElements) { addSimilarElementsTextualUpdates(fChangeManager, new SubProgressMonitor(pm, 3)); } } } finally { pm.done(); } } private void addTypeDeclarationUpdate(TextChangeManager manager) throws CoreException { String name = RefactoringCoreMessages.RenameTypeRefactoring_update; int typeNameLength = fType.getElementName().length(); ICompilationUnit cu = fType.getCompilationUnit(); TextChangeCompatibility.addTextEdit(manager.get(cu), name, new ReplaceEdit(fType.getNameRange().getOffset(), typeNameLength, getNewElementName())); } private void addConstructorRenames(TextChangeManager manager) throws CoreException { ICompilationUnit cu = fType.getCompilationUnit(); IMethod[] methods = fType.getMethods(); int typeNameLength = fType.getElementName().length(); for (int i = 0; i < methods.length; i++) { if (methods[i].isConstructor()) { /* * constructor declarations cannot be fully qualified so we can use simple replace here * * if (methods[i].getNameRange() == null), then it's a binary file so it's wrong anyway * (checked as a precondition) */ String name = RefactoringCoreMessages.RenameTypeRefactoring_rename_constructor; TextChangeCompatibility.addTextEdit(manager.get(cu), name, new ReplaceEdit(methods[i].getNameRange().getOffset(), typeNameLength, getNewElementName())); } } } private void addReferenceUpdates(TextChangeManager manager, IProgressMonitor pm) { pm.beginTask("", fReferences.length); //$NON-NLS-1$ for (int i = 0; i < fReferences.length; i++) { ICompilationUnit cu = fReferences[i].getCompilationUnit(); if (cu == null) { continue; } String name = RefactoringCoreMessages.RenameTypeRefactoring_update_reference; SearchMatch[] results = fReferences[i].getSearchResults(); for (int j = 0; j < results.length; j++) { SearchMatch match = results[j]; ReplaceEdit replaceEdit = new ReplaceEdit(match.getOffset(), match.getLength(), getNewElementName()); TextChangeCompatibility.addTextEdit(manager.get(cu), name, replaceEdit, CATEGORY_TYPE_RENAME); } pm.worked(1); } } private void computeQualifiedNameMatches(IProgressMonitor pm) { IPackageFragment fragment = fType.getPackageFragment(); if (fQualifiedNameSearchResult == null) { fQualifiedNameSearchResult = new QualifiedNameSearchResult(); } QualifiedNameFinder.process(fQualifiedNameSearchResult, fType.getFullyQualifiedName(), fragment.getElementName() + "." + getNewElementName(), //$NON-NLS-1$ fFilePatterns, fType.getJavaProject().getProject(), pm); } private RefactoringStatus initialize(JavaRefactoringArguments extended) { final String handle = extended.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT); if (handle != null) { final IJavaElement element = JavaRefactoringDescriptorUtil.handleToElement(extended.getProject(), handle, false); if (element == null || !element.exists() || element.getElementType() != IJavaElement.TYPE) { return JavaRefactoringDescriptorUtil.createInputFatalStatus(element, getProcessorName(), IJavaRefactorings.RENAME_TYPE); } else { fType = (IType) element; } } else { return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT)); } final String name = extended.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME); if (name != null && !"".equals(name)) { setNewElementName(name); } else { return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME)); } final String patterns = extended.getAttribute(ATTRIBUTE_PATTERNS); if (patterns != null && !"".equals(patterns)) { fFilePatterns = patterns; } final String references = extended.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_REFERENCES); if (references != null) { fUpdateReferences = Boolean.valueOf(references).booleanValue(); } else { return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_REFERENCES)); } final String matches = extended.getAttribute(ATTRIBUTE_TEXTUAL_MATCHES); if (matches != null) { fUpdateTextualMatches = Boolean.valueOf(matches).booleanValue(); } else { return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_TEXTUAL_MATCHES)); } final String qualified = extended.getAttribute(ATTRIBUTE_QUALIFIED); if (qualified != null) { fUpdateQualifiedNames = Boolean.valueOf(qualified).booleanValue(); } else { return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_QUALIFIED)); } final String similarDeclarations = extended.getAttribute(ATTRIBUTE_SIMILAR_DECLARATIONS); if (similarDeclarations != null) { fUpdateSimilarElements = Boolean.valueOf(similarDeclarations).booleanValue(); } else { return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_SIMILAR_DECLARATIONS)); } final String similarDeclarationsMatchingStrategy = extended.getAttribute(ATTRIBUTE_MATCHING_STRATEGY); if (similarDeclarationsMatchingStrategy != null) { try { fRenamingStrategy = Integer.valueOf(similarDeclarationsMatchingStrategy).intValue(); } catch (NumberFormatException e) { return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_illegal_argument, new String[] { similarDeclarationsMatchingStrategy, ATTRIBUTE_QUALIFIED })); } } else { return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_MATCHING_STRATEGY)); } return new RefactoringStatus(); } // --------- Similar names /** * Creates and initializes the refactoring processors for similarly named * elements * * @param progressMonitor * progress monitor * @param context * context * @return status * @throws CoreException * should not happen */ private RefactoringStatus initializeSimilarElementsRenameProcessors(IProgressMonitor progressMonitor, CheckConditionsContext context) throws CoreException { Assert.isNotNull(fPreloadedElementToName); Assert.isNotNull(fPreloadedElementToSelection); final RefactoringStatus status = new RefactoringStatus(); final Set<IMethod> handledTopLevelMethods = new HashSet<>(); final Set<Warning> warnings = new HashSet<>(); final List<RefactoringProcessor> processors = new ArrayList<>(); fFinalSimilarElementToName = new HashMap<>(); CompilationUnit currentResolvedCU = null; ICompilationUnit currentCU = null; int current = 0; final int max = fPreloadedElementToName.size(); progressMonitor.beginTask("", max * 3); //$NON-NLS-1$ progressMonitor.setTaskName(RefactoringCoreMessages.RenameTypeProcessor_checking_similarly_named_declarations_refactoring_conditions); for (Iterator<IJavaElement> iter = fPreloadedElementToName.keySet().iterator(); iter.hasNext();) { final IJavaElement element = iter.next(); current++; progressMonitor.worked(3); // not selected? -> skip if (!(fPreloadedElementToSelection.get(element)).booleanValue()) { continue; } // already registered? (may happen with overridden methods) -> skip if (fFinalSimilarElementToName.containsKey(element)) { continue; } // CompilationUnit changed? (note: fPreloadedElementToName is sorted by CompilationUnit) ICompilationUnit newCU = (ICompilationUnit) element.getAncestor(IJavaElement.COMPILATION_UNIT); if (!newCU.equals(currentCU)) { checkCUCompleteConditions(status, currentResolvedCU, currentCU, processors); if (status.hasFatalError()) { return status; } // reset values currentResolvedCU = null; currentCU = newCU; processors.clear(); } final String newName = fPreloadedElementToName.get(element); RefactoringProcessor processor = null; if (element instanceof ILocalVariable) { final ILocalVariable currentLocal = (ILocalVariable) element; if (currentResolvedCU == null) { currentResolvedCU = new RefactoringASTParser(IASTSharedValues.SHARED_AST_LEVEL).parse(currentCU, true); } processor = createLocalRenameProcessor(currentLocal, newName, currentResolvedCU); // don't check for conflicting rename => is done by #checkCUCompleteConditions(). if (status.hasFatalError()) { return status; } fFinalSimilarElementToName.put(currentLocal, newName); } if (element instanceof IField) { final IField currentField = (IField) element; processor = createFieldRenameProcessor(currentField, newName); status.merge(checkForConflictingRename(currentField, newName)); if (status.hasFatalError()) { return status; } fFinalSimilarElementToName.put(currentField, newName); } if (element instanceof IMethod) { IMethod currentMethod = (IMethod) element; if (MethodChecks.isVirtual(currentMethod)) { final IType declaringType = currentMethod.getDeclaringType(); ITypeHierarchy hierarchy = null; if (!declaringType.isInterface()) { hierarchy = declaringType.newTypeHierarchy(new NullProgressMonitor()); } final IMethod topmost = MethodChecks.getTopmostMethod(currentMethod, hierarchy, new NullProgressMonitor()); if (topmost != null) { currentMethod = topmost; } if (handledTopLevelMethods.contains(currentMethod)) { continue; } handledTopLevelMethods.add(currentMethod); final IMethod[] ripples = RippleMethodFinder2.getRelatedMethods(currentMethod, new NullProgressMonitor(), null); if (checkForWarnings(warnings, newName, ripples)) { continue; } status.merge(checkForConflictingRename(ripples, newName)); if (status.hasFatalError()) { return status; } processor = createVirtualMethodRenameProcessor(currentMethod, newName, ripples, hierarchy); fFinalSimilarElementToName.put(currentMethod, newName); for (int i = 0; i < ripples.length; i++) { fFinalSimilarElementToName.put(ripples[i], newName); } } else { status.merge(checkForConflictingRename(new IMethod[] { currentMethod }, newName)); if (status.hasFatalError()) { break; } fFinalSimilarElementToName.put(currentMethod, newName); processor = createNonVirtualMethodRenameProcessor(currentMethod, newName); } } progressMonitor.subTask(Messages.format(RefactoringCoreMessages.RenameTypeProcessor_progress_current_total, new Object[] { String.valueOf(current), String.valueOf(max) })); status.merge(processor.checkInitialConditions(new NoOverrideProgressMonitor(progressMonitor, 1))); if (status.hasFatalError()) { return status; } status.merge(processor.checkFinalConditions(new NoOverrideProgressMonitor(progressMonitor, 1), context)); if (status.hasFatalError()) { return status; } processors.add(processor); progressMonitor.worked(1); if (progressMonitor.isCanceled()) { throw new OperationCanceledException(); } } // check last CU checkCUCompleteConditions(status, currentResolvedCU, currentCU, processors); status.merge(addWarnings(warnings)); progressMonitor.done(); return status; } private void checkCUCompleteConditions(final RefactoringStatus status, CompilationUnit currentResolvedCU, ICompilationUnit currentCU, List<RefactoringProcessor> processors) throws CoreException { // check local variable conditions List<RefactoringProcessor> locals = getProcessorsOfType(processors, RenameLocalVariableProcessor.class); if (!locals.isEmpty()) { RenameAnalyzeUtil.LocalAnalyzePackage[] analyzePackages = new RenameAnalyzeUtil.LocalAnalyzePackage[locals.size()]; TextChangeManager manager = new TextChangeManager(); int current = 0; TextChange textChange = manager.get(currentCU); textChange.setKeepPreviewEdits(true); for (Iterator<RefactoringProcessor> iterator = locals.iterator(); iterator.hasNext();) { RenameLocalVariableProcessor localProcessor = (RenameLocalVariableProcessor) iterator.next(); RenameAnalyzeUtil.LocalAnalyzePackage analyzePackage = localProcessor.getLocalAnalyzePackage(); analyzePackages[current] = analyzePackage; for (int i = 0; i < analyzePackage.fOccurenceEdits.length; i++) { TextChangeCompatibility.addTextEdit(textChange, "", analyzePackage.fOccurenceEdits[i], GroupCategorySet.NONE); //$NON-NLS-1$ } current++; } status.merge(RenameAnalyzeUtil.analyzeLocalRenames(analyzePackages, textChange, currentResolvedCU, false)); } /* * There is room for performance improvement here: One could move * shadowing analyzes out of the field and method processors and perform * it here, thus saving on working copy creation. Drawback is increased * heap consumption. */ } private List<RefactoringProcessor> getProcessorsOfType(List<RefactoringProcessor> processors, Class<RenameLocalVariableProcessor> type) { List<RefactoringProcessor> tmp = new ArrayList<>(); for (Iterator<RefactoringProcessor> iter = processors.iterator(); iter.hasNext();) { RefactoringProcessor element = iter.next(); if (element.getClass().equals(type)) { tmp.add(element); } } return tmp; } // ------------------ Error checking ------------- /** * Checks whether one of the given methods, which will all be renamed to * "newName", shares a type with another already registered method which is * renamed to the same new name and shares the same parameters. * * @param methods * methods to check * @param newName * the new name * @return status * * @see #checkForConflictingRename(IField, String) */ private RefactoringStatus checkForConflictingRename(IMethod[] methods, String newName) { RefactoringStatus status = new RefactoringStatus(); for (Iterator<IJavaElement> iter = fFinalSimilarElementToName.keySet().iterator(); iter.hasNext();) { IJavaElement element = iter.next(); if (element instanceof IMethod) { IMethod alreadyRegisteredMethod = (IMethod) element; String alreadyRegisteredMethodName = fFinalSimilarElementToName.get(element); for (int i = 0; i < methods.length; i++) { IMethod method2 = methods[i]; if ((alreadyRegisteredMethodName.equals(newName)) && (method2.getDeclaringType().equals(alreadyRegisteredMethod.getDeclaringType())) && (sameParams(alreadyRegisteredMethod, method2))) { String message = Messages.format(RefactoringCoreMessages.RenameTypeProcessor_cannot_rename_methods_same_new_name, new String[] { BasicElementLabels.getJavaElementName(alreadyRegisteredMethod.getElementName()), BasicElementLabels.getJavaElementName(method2.getElementName()), BasicElementLabels.getJavaElementName(alreadyRegisteredMethod.getDeclaringType().getFullyQualifiedName('.')), BasicElementLabels.getJavaElementName(newName) }); status.addFatalError(message); return status; } } } } return status; } private static boolean sameParams(IMethod method, IMethod method2) { if (method.getNumberOfParameters() != method2.getNumberOfParameters()) { return false; } String[] params = method.getParameterTypes(); String[] params2 = method2.getParameterTypes(); for (int i = 0; i < params.length; i++) { String t1 = Signature.getSimpleName(Signature.toString(params[i])); String t2 = Signature.getSimpleName(Signature.toString(params2[i])); if (!t1.equals(t2)) { return false; } } return true; } /** * If suffix matching is enabled, the refactoring may suggest two fields to have * the same name which reside in the same type. Same thing may also happen if * the user makes poor choices for the field names. * * Consider: FooBarThing fFooBarThing; FooBarThing fBarThing; * * Rename "FooBarThing" to "DifferentHunk". Suggestion for both fields is * "fDifferentHunk" (and rightly so). * * @param currentField * field * @param newName * new name * @return status */ private RefactoringStatus checkForConflictingRename(IField currentField, String newName) { RefactoringStatus status = new RefactoringStatus(); for (Iterator<IJavaElement> iter = fFinalSimilarElementToName.keySet().iterator(); iter.hasNext();) { IJavaElement element = iter.next(); if (element instanceof IField) { IField alreadyRegisteredField = (IField) element; String alreadyRegisteredFieldName = fFinalSimilarElementToName.get(element); if (alreadyRegisteredFieldName.equals(newName)) { if (alreadyRegisteredField.getDeclaringType().equals(currentField.getDeclaringType())) { String message = Messages.format(RefactoringCoreMessages.RenameTypeProcessor_cannot_rename_fields_same_new_name, new String[] { BasicElementLabels.getJavaElementName(alreadyRegisteredField.getElementName()), BasicElementLabels.getJavaElementName(currentField.getElementName()), BasicElementLabels.getJavaElementName(alreadyRegisteredField.getDeclaringType().getFullyQualifiedName('.')), BasicElementLabels.getJavaElementName(newName) }); status.addFatalError(message); return status; } } } } return status; } private RefactoringStatus addWarnings(final Set<Warning> warnings) { RefactoringStatus status = new RefactoringStatus(); // Remove deleted ripple methods from user selection and add warnings for (Iterator<Warning> iter = warnings.iterator(); iter.hasNext();) { final Warning warning = iter.next(); final IMethod[] elements = warning.getRipple(); if (warning.isSelectionWarning()) { String message = Messages.format(RefactoringCoreMessages.RenameTypeProcessor_deselected_method_is_overridden, new String[] { JavaElementLabels.getElementLabel(elements[0], JavaElementLabels.ALL_DEFAULT), JavaElementLabels.getElementLabel(elements[0].getDeclaringType(), JavaElementLabels.ALL_DEFAULT) }); status.addWarning(message); } if (warning.isNameWarning()) { String message = Messages.format(RefactoringCoreMessages.RenameTypeProcessor_renamed_method_is_overridden, new String[] { JavaElementLabels.getElementLabel(elements[0], JavaElementLabels.ALL_DEFAULT), JavaElementLabels.getElementLabel(elements[0].getDeclaringType(), JavaElementLabels.ALL_DEFAULT) }); status.addWarning(message); } for (int i = 0; i < elements.length; i++) { fPreloadedElementToSelection.put(elements[i], Boolean.FALSE); } } return status; } /* * If one of the methods of this ripple was deselected or renamed by * the user, deselect the whole chain and add warnings. */ private boolean checkForWarnings(final Set<Warning> warnings, final String newName, final IMethod[] ripples) { boolean addSelectionWarning = false; boolean addNameWarning = false; for (int i = 0; i < ripples.length; i++) { String newNameOfRipple = fPreloadedElementToName.get(ripples[i]); Boolean selected = fPreloadedElementToSelection.get(ripples[i]); // selected may be null here due to supermethods like // setSomeClass(Object class) (subsignature match) // Don't add a warning. if (selected == null) { continue; } if (!selected.booleanValue()) { addSelectionWarning = true; } if (!newName.equals(newNameOfRipple)) { addNameWarning = true; } } if (addSelectionWarning || addNameWarning) { warnings.add(new Warning(ripples, addSelectionWarning, addNameWarning)); } return (addSelectionWarning || addNameWarning); } private class Warning { private IMethod[] fRipple; private boolean fSelectionWarning; private boolean fNameWarning; public Warning(IMethod[] ripple, boolean isSelectionWarning, boolean isNameWarning) { fRipple = ripple; fSelectionWarning = isSelectionWarning; fNameWarning = isNameWarning; } public boolean isNameWarning() { return fNameWarning; } public IMethod[] getRipple() { return fRipple; } public boolean isSelectionWarning() { return fSelectionWarning; } } // ----------------- Processor creation -------- private RenameMethodProcessor createVirtualMethodRenameProcessor(IMethod currentMethod, String newMethodName, IMethod[] ripples, ITypeHierarchy hierarchy) { RenameMethodProcessor processor = new RenameVirtualMethodProcessor(currentMethod, ripples, fChangeManager, hierarchy, CATEGORY_METHOD_RENAME); initMethodProcessor(processor, newMethodName); return processor; } private RenameMethodProcessor createNonVirtualMethodRenameProcessor(IMethod currentMethod, String newMethodName) { RenameMethodProcessor processor = new RenameNonVirtualMethodProcessor(currentMethod, fChangeManager, CATEGORY_METHOD_RENAME); initMethodProcessor(processor, newMethodName); return processor; } private void initMethodProcessor(RenameMethodProcessor processor, String newMethodName) { processor.setNewElementName(newMethodName); processor.setUpdateReferences(getUpdateReferences()); } private RenameFieldProcessor createFieldRenameProcessor(final IField field, final String newName) { final RenameFieldProcessor processor = new RenameFieldProcessor(field, fChangeManager, CATEGORY_FIELD_RENAME); processor.setNewElementName(newName); processor.setRenameGetter(false); processor.setRenameSetter(false); processor.setUpdateReferences(getUpdateReferences()); processor.setUpdateTextualMatches(false); return processor; } private RenameLocalVariableProcessor createLocalRenameProcessor(final ILocalVariable local, final String newName, final CompilationUnit compilationUnit) { final RenameLocalVariableProcessor processor = new RenameLocalVariableProcessor(local, fChangeManager, compilationUnit, CATEGORY_LOCAL_RENAME); processor.setNewElementName(newName); processor.setUpdateReferences(getUpdateReferences()); return processor; } // ----------- Edit creation ----------- /** * Updates textual matches for fields. * * Strategy for matching text matches: Match and replace all fully qualified * field names, but non-qualified field names only iff there are no fields which * have the same original, but a different new name. Don't add java references; * duplicate edits may be created but do not matter. * * @param manager * text change manager * @param monitor * progress monitor * @throws CoreException * if updating failed */ private void addSimilarElementsTextualUpdates(TextChangeManager manager, IProgressMonitor monitor) throws CoreException { final Map<String, String> simpleNames = new HashMap<>(); final List<String> forbiddenSimpleNames = new ArrayList<>(); for (Iterator<IJavaElement> iter = fFinalSimilarElementToName.keySet().iterator(); iter.hasNext();) { final IJavaElement element = iter.next(); if (element instanceof IField) { if (forbiddenSimpleNames.contains(element.getElementName())) { continue; } final String registeredNewName = simpleNames.get(element.getElementName()); final String newNameToCheck = fFinalSimilarElementToName.get(element); if (registeredNewName == null) { simpleNames.put(element.getElementName(), newNameToCheck); } else if (!registeredNewName.equals(newNameToCheck)) { forbiddenSimpleNames.add(element.getElementName()); } } } for (Iterator<IJavaElement> iter = fFinalSimilarElementToName.keySet().iterator(); iter.hasNext();) { final IJavaElement element = iter.next(); if (element instanceof IField) { final IField field = (IField) element; final String newName = fFinalSimilarElementToName.get(field); TextMatchUpdater.perform(monitor, RefactoringScopeFactory.create(field), field.getElementName(), field.getDeclaringType().getFullyQualifiedName(), newName, manager, new SearchResultGroup[0], forbiddenSimpleNames.contains(field.getElementName())); } } } // ------ UI interaction /** * @return the map of similarly named elements (IJavaElement -> String with new * name) This map is live. Callers may change the new names of the * elements; they may not change the key set. */ public Map<IJavaElement, String> getSimilarElementsToNewNames() { return fPreloadedElementToName; } /** * @return the map of similarly named elements (IJavaElement -> Boolean if * selected) This map is live. Callers may change the selection status * of the elements; they may not change the key set. */ public Map<IJavaElement, Boolean> getSimilarElementsToSelection() { return fPreloadedElementToSelection; } /** * Resets the element maps back to the original status. This affects the maps * returned in {@link #getSimilarElementsToNewNames() } and * {@link #getSimilarElementsToSelection() }. All new names are reset to the * calculated ones and every element gets selected. */ public void resetSelectedSimilarElements() { Assert.isNotNull(fPreloadedElementToName); for (Iterator<IJavaElement> iter = fPreloadedElementToNameDefault.keySet().iterator(); iter.hasNext();) { final IJavaElement element = iter.next(); fPreloadedElementToName.put(element, fPreloadedElementToNameDefault.get(element)); fPreloadedElementToSelection.put(element, Boolean.TRUE); } } /** * @return true iff the "update similarly named elements" flag is set AND the * search yielded some elements to be renamed. */ public boolean hasSimilarElementsToRename() { if (!fUpdateSimilarElements) { return false; } if (fPreloadedElementToName == null) { return false; } if (fPreloadedElementToName.size() == 0) { return false; } return true; } }