blob: fbcebe3fc54d1332ab7afb3cf10de3ba80b77379 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2018 IBM Corporation and others.
*
* 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
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.corext.refactoring.rename;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.jface.util.Util;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.CompositeChange;
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.participants.CheckConditionsContext;
import org.eclipse.ltk.core.refactoring.participants.RenameArguments;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IImportDeclaration;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IOrdinaryClassFile;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.refactoring.IJavaElementMapper;
import org.eclipse.jdt.core.refactoring.IJavaRefactorings;
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.SearchEngine;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.jdt.internal.core.refactoring.descriptors.RefactoringSignatureDescriptorFactory;
import org.eclipse.jdt.internal.corext.refactoring.Checks;
import org.eclipse.jdt.internal.corext.refactoring.CollectingSearchRequestor;
import org.eclipse.jdt.internal.corext.refactoring.CuCollectingSearchRequestor;
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.RefactoringAvailabilityTester;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringScopeFactory;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringSearchEngine;
import org.eclipse.jdt.internal.corext.refactoring.SearchResultGroup;
import org.eclipse.jdt.internal.corext.refactoring.base.ReferencesInBinaryContext;
import org.eclipse.jdt.internal.corext.refactoring.changes.DynamicValidationRefactoringChange;
import org.eclipse.jdt.internal.corext.refactoring.changes.RenamePackageChange;
import org.eclipse.jdt.internal.corext.refactoring.changes.TextChangeCompatibility;
import org.eclipse.jdt.internal.corext.refactoring.participants.JavaProcessors;
import org.eclipse.jdt.internal.corext.refactoring.rename.RenamePackageProcessor.ImportsManager.ImportChange;
import org.eclipse.jdt.internal.corext.refactoring.tagging.IQualifiedNameUpdating;
import org.eclipse.jdt.internal.corext.refactoring.tagging.IReferenceUpdating;
import org.eclipse.jdt.internal.corext.refactoring.tagging.ITextUpdating;
import org.eclipse.jdt.internal.corext.refactoring.util.Changes;
import org.eclipse.jdt.internal.corext.refactoring.util.CommentAnalyzer;
import org.eclipse.jdt.internal.corext.refactoring.util.JavaElementUtil;
import org.eclipse.jdt.internal.corext.refactoring.util.JavaStatusContext;
import org.eclipse.jdt.internal.corext.refactoring.util.QualifiedNameFinder;
import org.eclipse.jdt.internal.corext.refactoring.util.QualifiedNameSearchResult;
import org.eclipse.jdt.internal.corext.refactoring.util.ResourceUtil;
import org.eclipse.jdt.internal.corext.refactoring.util.TextChangeManager;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.internal.corext.util.Resources;
import org.eclipse.jdt.internal.corext.util.SearchUtils;
import org.eclipse.jdt.ui.JavaElementLabels;
import org.eclipse.jdt.ui.refactoring.RefactoringSaveHelper;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.core.manipulation.StubUtility;
import org.eclipse.jdt.internal.core.manipulation.util.BasicElementLabels;
public class RenamePackageProcessor extends JavaRenameProcessor implements
IReferenceUpdating, ITextUpdating, IQualifiedNameUpdating, 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_HIERARCHICAL= "hierarchical"; //$NON-NLS-1$
private IPackageFragment fPackage;
private TextChangeManager fChangeManager;
private ImportsManager fImportsManager;
private QualifiedNameSearchResult fQualifiedNameSearchResult;
private boolean fUpdateReferences;
private boolean fUpdateTextualMatches;
private boolean fUpdateQualifiedNames;
private String fFilePatterns;
private boolean fRenameSubpackages;
public static final String IDENTIFIER= "org.eclipse.jdt.ui.renamePackageProcessor"; //$NON-NLS-1$
private RenamePackageChange fRenamePackageChange;
/**
* Creates a new rename package processor.
* @param fragment the package fragment, or <code>null</code> if invoked by scripting
*/
public RenamePackageProcessor(IPackageFragment fragment) {
fPackage= fragment;
if (fPackage != null)
setNewElementName(fPackage.getElementName());
fUpdateReferences= true;
fUpdateTextualMatches= false;
fRenameSubpackages= false;
}
public RenamePackageProcessor(JavaRefactoringArguments arguments, RefactoringStatus status) {
this(null);
status.merge(initialize(arguments));
}
@Override
public String getIdentifier() {
return IDENTIFIER;
}
@Override
public boolean isApplicable() throws CoreException {
return RefactoringAvailabilityTester.isRenameAvailable(fPackage);
}
@Override
public String getProcessorName(){
return RefactoringCoreMessages.RenamePackageRefactoring_name;
}
@Override
protected String[] getAffectedProjectNatures() throws CoreException {
return JavaProcessors.computeAffectedNatures(fPackage);
}
@Override
public Object[] getElements() {
return new Object[] {fPackage};
}
@Override
protected RenameModifications computeRenameModifications() throws CoreException {
RenameModifications result= new RenameModifications();
result.rename(fPackage, new RenameArguments(getNewElementName(), getUpdateReferences()), fRenameSubpackages);
return result;
}
@Override
protected IFile[] getChangedFiles() throws CoreException {
Set<IFile> combined= new HashSet<>();
combined.addAll(Arrays.asList(ResourceUtil.getFiles(fChangeManager.getAllCompilationUnits())));
if (fRenameSubpackages) {
for (IPackageFragment pack : JavaElementUtil.getPackageAndSubpackages(fPackage)) {
combined.addAll(Arrays.asList(ResourceUtil.getFiles(pack.getCompilationUnits())));
}
} else {
combined.addAll(Arrays.asList(ResourceUtil.getFiles(fPackage.getCompilationUnits())));
}
if (fQualifiedNameSearchResult != null)
combined.addAll(Arrays.asList(fQualifiedNameSearchResult.getAllFiles()));
return combined.toArray(new IFile[combined.size()]);
}
@Override
public int getSaveMode() {
return RefactoringSaveHelper.SAVE_ALL;
}
//---- 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 !fPackage.isDefaultPackage();
}
@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;
}
//---- IResourceMapper ----------------------------------
@Override
public IResource getRefactoredResource(IResource element) {
IFolder packageFolder= (IFolder) fPackage.getResource();
if (packageFolder == null)
return element;
IContainer newPackageFolder= (IContainer) getNewPackage().getResource();
if (packageFolder.equals(element))
return newPackageFolder;
IPath packagePath= packageFolder.getProjectRelativePath();
IPath elementPath= element.getProjectRelativePath();
if (packagePath.isPrefixOf(elementPath)) {
if (fRenameSubpackages || (element instanceof IFile && packageFolder.equals(element.getParent()))) {
IPath pathInPackage= elementPath.removeFirstSegments(packagePath.segmentCount());
if (element instanceof IFile)
return newPackageFolder.getFile(pathInPackage);
else
return newPackageFolder.getFolder(pathInPackage);
}
}
return element;
}
//---- IJavaElementMapper ----------------------------------
@Override
public IJavaElement getRefactoredJavaElement(IJavaElement original) {
return new GenericRefactoringHandleTransplanter() {
@Override
protected IPackageFragment transplantHandle(IPackageFragmentRoot parent, IPackageFragment element) {
if (! fRenameSubpackages) {
if (fPackage.equals(element))
return getNewPackage();
} else {
String packName= element.getElementName();
String packageName= fPackage.getElementName();
if (fPackage.getParent().equals(parent)
&& packName.startsWith(packageName + '.')) {
String newPackName= getNewElementName() + packName.substring(packageName.length() - 1);
return getPackageFragmentRoot().getPackageFragment(newPackName);
}
}
return super.transplantHandle(parent, element);
}
@Override
protected IMethod transplantHandle(IType parent, IMethod element) {
String[] parameterTypes= resolveParameterTypes(element);
return parent.getMethod(element.getElementName(), parameterTypes);
}
private String[] resolveParameterTypes(IMethod method) {
final String[] oldParameterTypes= method.getParameterTypes();
final String[] newparams= new String[oldParameterTypes.length];
final String[] possibleOldSigs= new String[2];
//using type signature, since there is no package signature
possibleOldSigs[0]= Signature.createTypeSignature(fPackage.getElementName(), false);
possibleOldSigs[1]= Signature.createTypeSignature(fPackage.getElementName(), true);
final String[] possibleNewSigs= new String[2];
possibleNewSigs[0]= Signature.createTypeSignature(getNewElementName(), false);
possibleNewSigs[1]= Signature.createTypeSignature(getNewElementName(), true);
// Textually replace all occurrences
// This handles stuff like Map<SomeClass, some.package.SomeClass>
for (int i= 0; i < oldParameterTypes.length; i++) {
newparams[i]= oldParameterTypes[i];
for (int j= 0; j < possibleOldSigs.length; j++) {
newparams[i]= Util.replaceAll(newparams[i], possibleOldSigs[j], possibleNewSigs[j]);
}
}
return newparams;
}
}.transplantHandle(original);
}
//----
public boolean canEnableRenameSubpackages() throws JavaModelException {
return fPackage.hasSubpackages();
}
public boolean getRenameSubpackages() {
return fRenameSubpackages;
}
public void setRenameSubpackages(boolean rename) {
fRenameSubpackages= rename;
}
//---- IRenameProcessor ----------------------------------------------
@Override
public final String getCurrentElementName(){
return fPackage.getElementName();
}
@Override
public String getCurrentElementQualifier() {
return ""; //$NON-NLS-1$
}
@Override
public RefactoringStatus checkNewElementName(String newName) throws CoreException {
Assert.isNotNull(newName, "new name"); //$NON-NLS-1$
RefactoringStatus result= Checks.checkPackageName(newName, fPackage);
if (result.hasFatalError())
return result;
if (Checks.isAlreadyNamed(fPackage, newName)) {
result.addFatalError(RefactoringCoreMessages.RenamePackageRefactoring_another_name);
return result;
}
result.merge(checkPackageInCurrentRoot(newName));
return result;
}
@Override
public Object getNewElement(){
return getNewPackage();
}
private IPackageFragment getNewPackage() {
IPackageFragmentRoot root= getPackageFragmentRoot();
return root.getPackageFragment(getNewElementName());
}
@Override
public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException {
return new RefactoringStatus();
}
@Override
protected RefactoringStatus doCheckFinalConditions(IProgressMonitor pm, CheckConditionsContext context) throws CoreException {
try{
pm.beginTask("", 23 + (fUpdateQualifiedNames ? 10 : 0) + (fUpdateTextualMatches ? 10 : 0)); //$NON-NLS-1$
pm.setTaskName(RefactoringCoreMessages.RenamePackageRefactoring_checking);
RefactoringStatus result= new RefactoringStatus();
result.merge(checkNewElementName(getNewElementName()));
pm.worked(1);
result.merge(checkForMainAndNativeMethods());
pm.worked(2);
if (fPackage.isReadOnly()){
String message= Messages.format(RefactoringCoreMessages.RenamePackageRefactoring_Packagered_only, getElementLabel(fPackage));
result.addFatalError(message);
} else if (Resources.isReadOnly(fPackage.getResource())) {
String message= Messages.format(RefactoringCoreMessages.RenamePackageRefactoring_resource_read_only, getElementLabel(fPackage));
result.addError(message);
}
result.merge(checkPackageName(getNewElementName()));
if (result.hasFatalError())
return result;
fChangeManager= new TextChangeManager();
fImportsManager= new ImportsManager();
SubProgressMonitor subPm= new SubProgressMonitor(pm, 16);
if (fRenameSubpackages) {
IPackageFragment[] allSubpackages= JavaElementUtil.getPackageAndSubpackages(fPackage);
subPm.beginTask("", allSubpackages.length); //$NON-NLS-1$
for (IPackageFragment pack : allSubpackages) {
new PackageRenamer(pack, this, fChangeManager, fImportsManager).doRename(new SubProgressMonitor(subPm, 1), result);
}
subPm.done();
} else {
new PackageRenamer(fPackage, this, fChangeManager, fImportsManager).doRename(subPm, result);
}
fImportsManager.rewriteImports(fChangeManager, new SubProgressMonitor(pm, 3));
if (fUpdateTextualMatches) {
pm.subTask(RefactoringCoreMessages.RenamePackageRefactoring_searching_text);
TextMatchUpdater.perform(new SubProgressMonitor(pm, 10), RefactoringScopeFactory.create(fPackage), this, fChangeManager, new SearchResultGroup[0]);
}
if (fUpdateQualifiedNames)
computeQualifiedNameMatches(new SubProgressMonitor(pm, 10));
return result;
} finally{
pm.done();
}
}
public IPackageFragment getPackage() {
return fPackage;
}
private RefactoringStatus checkForMainAndNativeMethods() throws CoreException{
RefactoringStatus result= new RefactoringStatus();
if (fRenameSubpackages) {
for (IPackageFragment pack : JavaElementUtil.getPackageAndSubpackages(fPackage)) {
for (ICompilationUnit cu : pack.getCompilationUnits()) {
result.merge(Checks.checkForMainAndNativeMethods(cu));
}
}
} else {
for (ICompilationUnit cu : fPackage.getCompilationUnits()) {
result.merge(Checks.checkForMainAndNativeMethods(cu));
}
}
return result;
}
/*
* returns true if the new name is ok if the specified root.
* if a package fragment with this name exists and has java resources,
* then the name is not ok.
*/
public static boolean isPackageNameOkInRoot(String newName, IPackageFragmentRoot root) throws CoreException {
IPackageFragment pack= root.getPackageFragment(newName);
if (! pack.exists())
return true;
else if (pack.containsJavaResources())
return false;
else if (pack.getNonJavaResources().length != 0)
return false;
else
return true;
}
private RefactoringStatus checkPackageInCurrentRoot(String newName) throws CoreException {
if (fRenameSubpackages) {
String currentName= getCurrentElementName();
if (isAncestorPackage(currentName, newName)) {
// renaming to subpackage (a -> a.b) is always OK, since all subpackages are also renamed
return null;
}
if (! isAncestorPackage(newName, currentName)) {
// renaming to an unrelated package
if (! isPackageNameOkInRoot(newName, getPackageFragmentRoot())) {
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.RenamePackageRefactoring_package_exists);
}
}
// renaming to superpackage (a.b -> a) or another package is OK iff
// 'a.b' does not contain any subpackage that would collide with another subpackage of 'a'
// (e.g. a.b.c collides if a.c already exists, but a.b.b does not collide with a.b)
for (IPackageFragment pack : JavaElementUtil.getPackageAndSubpackages(fPackage)) {
String newPack= newName + pack.getElementName().substring(currentName.length());
if (! isAncestorPackage(currentName, newPack) && ! isPackageNameOkInRoot(newPack, getPackageFragmentRoot())) {
String msg= Messages.format(RefactoringCoreMessages.RenamePackageProcessor_subpackage_collides, BasicElementLabels.getJavaElementName(newPack));
return RefactoringStatus.createFatalErrorStatus(msg);
}
}
return null;
} else if (! isPackageNameOkInRoot(newName, getPackageFragmentRoot())) {
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.RenamePackageRefactoring_package_exists);
} else {
return null;
}
}
private boolean isAncestorPackage(String ancestor, String descendant) {
int a= ancestor.length();
int d= descendant.length();
if (a == d || (a < d && descendant.charAt(a) == '.'))
return descendant.startsWith(ancestor);
else
return false;
}
private IPackageFragmentRoot getPackageFragmentRoot() {
return ((IPackageFragmentRoot)fPackage.getParent());
}
private RefactoringStatus checkPackageName(String newName) throws CoreException {
RefactoringStatus status= new RefactoringStatus();
Set<String> topLevelTypeNames= getTopLevelTypeNames();
for (IPackageFragmentRoot root : fPackage.getJavaProject().getPackageFragmentRoots()) {
if (! isPackageNameOkInRoot(newName, root)) {
String rootLabel = JavaElementLabels.getElementLabel(root, JavaElementLabels.ALL_DEFAULT);
String newPackageName= BasicElementLabels.getJavaElementName(getNewElementName());
String message= Messages.format(RefactoringCoreMessages.RenamePackageRefactoring_aleady_exists, new Object[]{ newPackageName, rootLabel});
status.merge(RefactoringStatus.createWarningStatus(message));
status.merge(checkTypeNameConflicts(root, newName, topLevelTypeNames));
}
}
return status;
}
private Set<String> getTopLevelTypeNames() throws CoreException {
ICompilationUnit[] cus= fPackage.getCompilationUnits();
Set<String> result= new HashSet<>(2 * cus.length);
for (ICompilationUnit cu : cus) {
result.addAll(getTopLevelTypeNames(cu));
}
return result;
}
private static Collection<String> getTopLevelTypeNames(ICompilationUnit iCompilationUnit) throws CoreException {
IType[] types= iCompilationUnit.getTypes();
List<String> result= new ArrayList<>(types.length);
for (IType type : types) {
result.add(type.getElementName());
}
return result;
}
private RefactoringStatus checkTypeNameConflicts(IPackageFragmentRoot root, String newName, Set<String> topLevelTypeNames) throws CoreException {
IPackageFragment otherPack= root.getPackageFragment(newName);
if (fPackage.equals(otherPack))
return null;
RefactoringStatus result= new RefactoringStatus();
for (ICompilationUnit cu : otherPack.getCompilationUnits()) {
result.merge(checkTypeNameConflicts(cu, topLevelTypeNames));
}
return result;
}
private RefactoringStatus checkTypeNameConflicts(ICompilationUnit iCompilationUnit, Set<String> topLevelTypeNames) throws CoreException {
RefactoringStatus result= new RefactoringStatus();
for (IType type : iCompilationUnit.getTypes()) {
String name= type.getElementName();
if (topLevelTypeNames.contains(name)) {
String[] keys= {getElementLabel(iCompilationUnit.getParent()), getElementLabel(type)};
String msg= Messages.format(RefactoringCoreMessages.RenamePackageRefactoring_contains_type, keys);
RefactoringStatusContext context= JavaStatusContext.create(type);
result.addError(msg, context);
}
}
return result;
}
@Override
public Change createChange(IProgressMonitor monitor) throws CoreException {
try {
monitor.beginTask(RefactoringCoreMessages.RenamePackageRefactoring_creating_change, 1);
final RenameJavaElementDescriptor descriptor= createRefactoringDescriptor();
final DynamicValidationRefactoringChange result= new DynamicValidationRefactoringChange(descriptor, RefactoringCoreMessages.RenamePackageRefactoring_change_name);
result.addAll(fChangeManager.getAllChanges());
fRenamePackageChange= new RenamePackageChange( fPackage, getNewElementName(), fRenameSubpackages);
result.add(fRenamePackageChange);
monitor.worked(1);
return result;
} finally {
fChangeManager= null;
fImportsManager= null;
monitor.done();
}
}
private RenameJavaElementDescriptor createRefactoringDescriptor() {
String project= null;
IJavaProject javaProject= fPackage.getJavaProject();
if (javaProject != null)
project= javaProject.getElementName();
final int flags= JavaRefactoringDescriptor.JAR_MIGRATION | JavaRefactoringDescriptor.JAR_REFACTORING | RefactoringDescriptor.STRUCTURAL_CHANGE | RefactoringDescriptor.MULTI_CHANGE;
final String description= Messages.format(RefactoringCoreMessages.RenamePackageProcessor_descriptor_description_short, getElementLabel(fPackage));
final String header= Messages.format(RefactoringCoreMessages.RenamePackageProcessor_descriptor_description, new String[] { getElementLabel(fPackage), getNewElementName()});
final JDTRefactoringDescriptorComment comment= new JDTRefactoringDescriptorComment(project, this, header);
if (fRenameSubpackages)
comment.addSetting(RefactoringCoreMessages.RenamePackageProcessor_rename_subpackages);
final RenameJavaElementDescriptor descriptor= RefactoringSignatureDescriptorFactory.createRenameJavaElementDescriptor(IJavaRefactorings.RENAME_PACKAGE);
descriptor.setProject(project);
descriptor.setDescription(description);
descriptor.setComment(comment.asString());
descriptor.setFlags(flags);
descriptor.setJavaElement(fPackage);
descriptor.setNewName(getNewElementName());
descriptor.setUpdateReferences(fUpdateReferences);
descriptor.setUpdateTextualOccurrences(fUpdateTextualMatches);
descriptor.setUpdateQualifiedNames(fUpdateQualifiedNames);
if (fUpdateQualifiedNames && fFilePatterns != null && !"".equals(fFilePatterns)) //$NON-NLS-1$
descriptor.setFileNamePatterns(fFilePatterns);
descriptor.setUpdateHierarchy(fRenameSubpackages);
return descriptor;
}
private static String getElementLabel(IJavaElement javaElement) {
return JavaElementLabels.getElementLabel(javaElement, JavaElementLabels.ALL_DEFAULT);
}
@Override
public Change postCreateChange(Change[] participantChanges, IProgressMonitor pm) throws CoreException {
if (fQualifiedNameSearchResult != null) {
CompositeChange parent= (CompositeChange) fRenamePackageChange.getParent();
try {
/*
* Sneak text changes in before the package rename to ensure
* modified files are still at original location (see
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=154238)
*/
parent.remove(fRenamePackageChange);
parent.add(fQualifiedNameSearchResult.getSingleChange(Changes.getModifiedFiles(participantChanges)));
} finally {
fQualifiedNameSearchResult= null;
parent.add(fRenamePackageChange);
fRenamePackageChange= null;
}
}
return null;
}
private void computeQualifiedNameMatches(IProgressMonitor pm) {
if (fQualifiedNameSearchResult == null)
fQualifiedNameSearchResult= new QualifiedNameSearchResult();
QualifiedNameFinder.process(fQualifiedNameSearchResult, fPackage.getElementName(), getNewElementName(),
fFilePatterns, fPackage.getJavaProject().getProject(), pm);
}
public String getNewPackageName(String oldSubPackageName) {
String oldPackageName= getPackage().getElementName();
return getNewElementName() + oldSubPackageName.substring(oldPackageName.length());
}
private static class PackageRenamer {
private final IPackageFragment fPackage;
private final RenamePackageProcessor fProcessor;
private final TextChangeManager fTextChangeManager;
private final ImportsManager fImportsManager;
/** references to fPackage (can include star imports which also import namesake package fragments) */
private SearchResultGroup[] fOccurrences;
/** References in CUs from fOccurrences and fPackage to types in namesake packages.
* <p>These need an import with the old package name.
* <p>- from fOccurrences (without namesakes): may have shared star import
* (star-import not updated here, but for fOccurrences)
* <p>- from fPackage: may have unimported references to types of namesake packages
* <p>- both: may have unused imports of namesake packages.
* <p>Mutable List of SearchResultGroup. */
private List<SearchResultGroup> fReferencesToTypesInNamesakes;
/** References in CUs from namesake packages to types in fPackage.
* <p>These need an import with the new package name.
* <p>Mutable List of SearchResultGroup. */
private List<SearchResultGroup> fReferencesToTypesInPackage;
public PackageRenamer(IPackageFragment pack, RenamePackageProcessor processor, TextChangeManager textChangeManager, ImportsManager importsManager) {
fPackage= pack;
fProcessor= processor;
fTextChangeManager= textChangeManager;
fImportsManager= importsManager;
}
void doRename(IProgressMonitor pm, RefactoringStatus result) throws CoreException {
pm.beginTask("", 16); //$NON-NLS-1$
if (fProcessor.getUpdateReferences()){
pm.setTaskName(RefactoringCoreMessages.RenamePackageRefactoring_searching);
String binaryRefsDescription= Messages.format(RefactoringCoreMessages.ReferencesInBinaryContext_ref_in_binaries_description , getElementLabel(fPackage));
ReferencesInBinaryContext binaryRefs= new ReferencesInBinaryContext(binaryRefsDescription);
fOccurrences= getReferences(new SubProgressMonitor(pm, 4), binaryRefs, result);
fReferencesToTypesInNamesakes= getReferencesToTypesInNamesakes(new SubProgressMonitor(pm, 4), result);
fReferencesToTypesInPackage= getReferencesToTypesInPackage(new SubProgressMonitor(pm, 4), binaryRefs, result);
binaryRefs.addErrorIfNecessary(result);
pm.setTaskName(RefactoringCoreMessages.RenamePackageRefactoring_checking);
result.merge(analyzeAffectedCompilationUnits());
pm.worked(1);
} else {
fOccurrences= new SearchResultGroup[0];
pm.worked(13);
}
if (result.hasFatalError())
return;
if (fProcessor.getUpdateReferences())
addReferenceUpdates(new SubProgressMonitor(pm, 3));
else
pm.worked(3);
pm.done();
}
private SearchResultGroup[] getReferences(IProgressMonitor pm, ReferencesInBinaryContext binaryRefs, RefactoringStatus status) throws CoreException {
IJavaSearchScope scope= RefactoringScopeFactory.create(fPackage, true, false);
SearchPattern pattern= SearchPattern.createPattern(fPackage, IJavaSearchConstants.REFERENCES);
CollectingSearchRequestor requestor= new CuCollectingSearchRequestor(binaryRefs);
return RefactoringSearchEngine.search(pattern, scope, requestor, pm, status);
}
private void addReferenceUpdates(IProgressMonitor pm) throws CoreException {
pm.beginTask("", fOccurrences.length + fReferencesToTypesInPackage.size() + fReferencesToTypesInNamesakes.size()); //$NON-NLS-1$
for (SearchResultGroup occurrence : fOccurrences) {
ICompilationUnit cu= occurrence.getCompilationUnit();
if (cu == null)
continue;
for (SearchMatch result : occurrence.getSearchResults()) {
IJavaElement enclosingElement= SearchUtils.getEnclosingJavaElement(result);
if (enclosingElement instanceof IImportDeclaration) {
IImportDeclaration importDeclaration= (IImportDeclaration) enclosingElement;
String updatedImport= getUpdatedImport(importDeclaration);
updateImport(cu, importDeclaration, updatedImport);
} else { // is reference
TextChangeCompatibility.addTextEdit(fTextChangeManager.get(cu), RefactoringCoreMessages.RenamePackageRefactoring_update_reference, createTextChange(result));
}
}
if (fReferencesToTypesInNamesakes.size() != 0) {
SearchResultGroup typeRefsRequiringOldNameImport= extractGroupFor(cu, fReferencesToTypesInNamesakes);
if (typeRefsRequiringOldNameImport != null)
addTypeImports(typeRefsRequiringOldNameImport);
}
if (fReferencesToTypesInPackage.size() != 0) {
SearchResultGroup typeRefsRequiringNewNameImport= extractGroupFor(cu, fReferencesToTypesInPackage);
if (typeRefsRequiringNewNameImport != null)
updateTypeImports(typeRefsRequiringNewNameImport);
}
pm.worked(1);
}
if (fReferencesToTypesInNamesakes.size() != 0) {
for (SearchResultGroup referencesToTypesInNamesakes : fReferencesToTypesInNamesakes) {
addTypeImports(referencesToTypesInNamesakes);
pm.worked(1);
}
}
if (fReferencesToTypesInPackage.size() != 0) {
for (SearchResultGroup namesakeReferencesToPackage : fReferencesToTypesInPackage) {
updateTypeImports(namesakeReferencesToPackage);
pm.worked(1);
}
}
pm.done();
}
/** Removes the found SearchResultGroup from the list iff found.
* @param cu the cu
* @param searchResultGroups List of SearchResultGroup
* @return the SearchResultGroup for cu, or null iff not found */
private static SearchResultGroup extractGroupFor(ICompilationUnit cu, List<SearchResultGroup> searchResultGroups) {
for (Iterator<SearchResultGroup> iter= searchResultGroups.iterator(); iter.hasNext();) {
SearchResultGroup group= iter.next();
if (cu.equals(group.getCompilationUnit())) {
iter.remove();
return group;
}
}
return null;
}
private TextEdit createTextChange(SearchMatch searchResult) {
return new ReplaceEdit(searchResult.getOffset(), searchResult.getLength(), getNewPackageName());
}
private RefactoringStatus analyzeAffectedCompilationUnits() throws CoreException {
//TODO: also for both fReferencesTo...; only check each CU once!
RefactoringStatus result= new RefactoringStatus();
fOccurrences= Checks.excludeCompilationUnits(fOccurrences, result);
if (result.hasFatalError())
return result;
result.merge(Checks.checkCompileErrorsInAffectedFiles(fOccurrences));
return result;
}
/**
* @return search scope with
* <p>- fPackage and
* <p>- all CUs from fOccurrences which are not in a namesake package
*/
private IJavaSearchScope getPackageAndOccurrencesWithoutNamesakesScope() {
List<IJavaElement> scopeList= new ArrayList<>();
scopeList.add(fPackage);
for (SearchResultGroup occurrence : fOccurrences) {
ICompilationUnit cu= occurrence.getCompilationUnit();
if (cu == null)
continue;
IPackageFragment pack= (IPackageFragment) cu.getParent();
if (! pack.getElementName().equals(fPackage.getElementName()))
scopeList.add(cu);
}
return SearchEngine.createJavaSearchScope(scopeList.toArray(new IJavaElement[scopeList.size()]));
}
private List<SearchResultGroup> getReferencesToTypesInNamesakes(IProgressMonitor pm, RefactoringStatus status) throws CoreException {
pm.beginTask("", 2); //$NON-NLS-1$
// e.g. renaming B-p.p; project C requires B, X and has ref to B-p.p and X-p.p;
// goal: find refs to X-p.p in CUs from fOccurrences
// (1) find namesake packages (scope: all packages referenced by CUs in fOccurrences and fPackage)
IJavaElement[] elements= new IJavaElement[fOccurrences.length + 1];
for (int i= 0; i < fOccurrences.length; i++) {
elements[i]= fOccurrences[i].getCompilationUnit();
}
elements[fOccurrences.length]= fPackage;
IJavaSearchScope namesakePackagesScope= RefactoringScopeFactory.createReferencedScope(elements);
IPackageFragment[] namesakePackages= getNamesakePackages(namesakePackagesScope, new SubProgressMonitor(pm, 1));
if (namesakePackages.length == 0) {
pm.done();
return new ArrayList<>(0);
}
// (2) find refs in fOccurrences and fPackage to namesake packages
// (from fOccurrences (without namesakes): may have shared star import)
// (from fPackage: may have unimported references to types of namesake packages)
IType[] typesToSearch= getTypesInPackages(namesakePackages);
if (typesToSearch.length == 0) {
pm.done();
return new ArrayList<>(0);
}
SearchPattern pattern= RefactoringSearchEngine.createOrPattern(typesToSearch, IJavaSearchConstants.REFERENCES);
IJavaSearchScope scope= getPackageAndOccurrencesWithoutNamesakesScope();
SearchResultGroup[] results= RefactoringSearchEngine.search(pattern, scope, new SubProgressMonitor(pm, 1), status);
pm.done();
return new ArrayList<>(Arrays.asList(results));
}
private List<SearchResultGroup> getReferencesToTypesInPackage(IProgressMonitor pm, ReferencesInBinaryContext binaryRefs, RefactoringStatus status) throws CoreException {
pm.beginTask("", 2); //$NON-NLS-1$
IJavaSearchScope referencedFromNamesakesScope= RefactoringScopeFactory.create(fPackage, true, false);
IPackageFragment[] namesakePackages= getNamesakePackages(referencedFromNamesakesScope, new SubProgressMonitor(pm, 1));
if (namesakePackages.length == 0) {
pm.done();
return new ArrayList<>(0);
}
IJavaSearchScope scope= SearchEngine.createJavaSearchScope(namesakePackages);
IType[] typesToSearch= getTypesInPackage(fPackage);
if (typesToSearch.length == 0) {
pm.done();
return new ArrayList<>(0);
}
SearchPattern pattern= RefactoringSearchEngine.createOrPattern(typesToSearch, IJavaSearchConstants.REFERENCES);
CollectingSearchRequestor requestor= new CuCollectingSearchRequestor(binaryRefs);
SearchResultGroup[] results= RefactoringSearchEngine.search(pattern, scope, requestor, new SubProgressMonitor(pm, 1), status);
pm.done();
return new ArrayList<>(Arrays.asList(results));
}
private IType[] getTypesInPackage(IPackageFragment packageFragment) throws JavaModelException {
List<IType> types= new ArrayList<>();
addContainedTypes(packageFragment, types);
return types.toArray(new IType[types.size()]);
}
/**
* @param scope search scope
* @param pm mrogress monitor
* @return all package fragments in <code>scope</code> with same name as <code>fPackage</code>, excluding fPackage
* @throws CoreException if search failed
*/
private IPackageFragment[] getNamesakePackages(IJavaSearchScope scope, IProgressMonitor pm) throws CoreException {
SearchPattern pattern= SearchPattern.createPattern(fPackage.getElementName(), IJavaSearchConstants.PACKAGE, IJavaSearchConstants.DECLARATIONS, SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE);
final HashSet<IPackageFragment> packageFragments= new HashSet<>();
SearchRequestor requestor= new SearchRequestor() {
@Override
public void acceptSearchMatch(SearchMatch match) throws CoreException {
IJavaElement enclosingElement= SearchUtils.getEnclosingJavaElement(match);
if (enclosingElement instanceof IPackageFragment) {
IPackageFragment pack= (IPackageFragment) enclosingElement;
if (! fPackage.equals(pack))
packageFragments.add(pack);
}
}
};
new SearchEngine().search(pattern, SearchUtils.getDefaultSearchParticipants(), scope, requestor, pm);
return packageFragments.toArray(new IPackageFragment[packageFragments.size()]);
}
private IType[] getTypesInPackages(IPackageFragment[] packageFragments) throws JavaModelException {
List<IType> types= new ArrayList<>();
for (IPackageFragment pack : packageFragments) {
addContainedTypes(pack, types);
}
return types.toArray(new IType[types.size()]);
}
private void addContainedTypes(IPackageFragment pack, List<IType> typesCollector) throws JavaModelException {
for (IJavaElement child : pack.getChildren()) {
if (child instanceof ICompilationUnit) {
typesCollector.addAll(Arrays.asList(((ICompilationUnit) child).getTypes()));
} else if (child instanceof IOrdinaryClassFile) {
typesCollector.add(((IOrdinaryClassFile) child).getType());
}
}
}
private void updateImport(ICompilationUnit cu, IImportDeclaration importDeclaration, String updatedImport) throws JavaModelException {
ImportChange importChange= fImportsManager.getImportChange(cu);
if (Flags.isStatic(importDeclaration.getFlags())) {
importChange.removeStaticImport(importDeclaration.getElementName());
importChange.addStaticImport(Signature.getQualifier(updatedImport), Signature.getSimpleName(updatedImport));
} else {
importChange.removeImport(importDeclaration.getElementName());
importChange.addImport(updatedImport);
}
}
/**
* Add new imports to types in <code>typeReferences</code> with package <code>fPackage</code>.
* @param typeReferences type references
* @throws CoreException should not happen
*/
private void addTypeImports(SearchResultGroup typeReferences) throws CoreException {
for (SearchMatch result : typeReferences.getSearchResults()) {
IJavaElement enclosingElement= SearchUtils.getEnclosingJavaElement(result);
if (! (enclosingElement instanceof IImportDeclaration)) {
String reference= getNormalizedTypeReference(result);
if (! reference.startsWith(fPackage.getElementName())) {
// is unqualified
reference= cutOffInnerTypes(reference);
ImportChange importChange= fImportsManager.getImportChange(typeReferences.getCompilationUnit());
importChange.addImport(fPackage.getElementName() + '.' + reference);
}
}
}
}
/**
* Add new imports to types in <code>typeReferences</code> with package <code>fNewElementName</code>
* and remove old import with <code>fPackage</code>.
* @param typeReferences type references
* @throws CoreException should not happen
*/
private void updateTypeImports(SearchResultGroup typeReferences) throws CoreException {
for (SearchMatch result : typeReferences.getSearchResults()) {
IJavaElement enclosingElement= SearchUtils.getEnclosingJavaElement(result);
if (enclosingElement instanceof IImportDeclaration) {
IImportDeclaration importDeclaration= (IImportDeclaration) enclosingElement;
updateImport(typeReferences.getCompilationUnit(), importDeclaration, getUpdatedImport(importDeclaration));
} else {
String reference= getNormalizedTypeReference(result);
if (! reference.startsWith(fPackage.getElementName())) {
reference= cutOffInnerTypes(reference);
ImportChange importChange= fImportsManager.getImportChange(typeReferences.getCompilationUnit());
importChange.removeImport(fPackage.getElementName() + '.' + reference);
importChange.addImport(getNewPackageName() + '.' + reference);
} // else: already found & updated with package reference search
}
}
}
private static String getNormalizedTypeReference(SearchMatch searchResult) throws JavaModelException {
ICompilationUnit cu= SearchUtils.getCompilationUnit(searchResult);
String reference= cu.getBuffer().getText(searchResult.getOffset(), searchResult.getLength());
//reference may be package-qualified -> normalize (remove comments, etc.):
return CommentAnalyzer.normalizeReference(reference);
}
private static String cutOffInnerTypes(String reference) {
int dotPos= reference.indexOf('.'); // cut off inner types
if (dotPos != -1)
reference= reference.substring(0, dotPos);
return reference;
}
private String getUpdatedImport(IImportDeclaration importDeclaration) {
String fullyQualifiedImportType= importDeclaration.getElementName();
int offsetOfDotBeforeTypeName= fPackage.getElementName().length();
String result= getNewPackageName() + fullyQualifiedImportType.substring(offsetOfDotBeforeTypeName);
return result;
}
private String getNewPackageName() {
return fProcessor.getNewPackageName(fPackage.getElementName());
}
}
/**
* Collector for import additions/removals.
* Saves all changes for a one-pass rewrite.
*/
static class ImportsManager {
public static class ImportChange {
private ArrayList<String> fStaticToRemove= new ArrayList<>();
private ArrayList<String[]> fStaticToAdd= new ArrayList<>();
private ArrayList<String> fToRemove= new ArrayList<>();
private ArrayList<String> fToAdd= new ArrayList<>();
public void removeStaticImport(String elementName) {
fStaticToRemove.add(elementName);
}
public void addStaticImport(String declaringType, String memberName) {
fStaticToAdd.add(new String[] {declaringType, memberName});
}
public void removeImport(String elementName) {
fToRemove.add(elementName);
}
public void addImport(String elementName) {
fToAdd.add(elementName);
}
}
private HashMap<ICompilationUnit, ImportChange> fImportChanges= new HashMap<>();
public ImportChange getImportChange(ICompilationUnit cu) {
ImportChange importChange= fImportChanges.get(cu);
if (importChange == null) {
importChange= new ImportChange();
fImportChanges.put(cu, importChange);
}
return importChange;
}
public void rewriteImports(TextChangeManager changeManager, IProgressMonitor pm) throws CoreException {
for (Entry<ICompilationUnit, ImportChange> entry : fImportChanges.entrySet()) {
ICompilationUnit cu= entry.getKey();
ImportChange importChange= entry.getValue();
ImportRewrite importRewrite= StubUtility.createImportRewrite(cu, true);
importRewrite.setFilterImplicitImports(false);
for (String toRemove : importChange.fStaticToRemove) {
importRewrite.removeStaticImport(toRemove);
}
for (String toRemove : importChange.fToRemove) {
importRewrite.removeImport(toRemove);
}
for (String[] toAdd : importChange.fStaticToAdd) {
importRewrite.addStaticImport(toAdd[0], toAdd[1], true);
}
for (String toAdd : importChange.fToAdd) {
importRewrite.addImport(toAdd);
}
if (importRewrite.hasRecordedChanges()) {
TextEdit importEdit= importRewrite.rewriteImports(pm);
String name= RefactoringCoreMessages.RenamePackageRefactoring_update_imports;
try {
TextChangeCompatibility.addTextEdit(changeManager.get(cu), name, importEdit);
} catch (MalformedTreeException e) {
JavaPlugin.logErrorMessage("MalformedTreeException while processing cu " + cu); //$NON-NLS-1$
throw e;
}
}
}
}
}
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.PACKAGE_FRAGMENT)
return JavaRefactoringDescriptorUtil.createInputFatalStatus(element, getProcessorName(), IJavaRefactorings.RENAME_PACKAGE);
else
fPackage= (IPackageFragment) 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)) //$NON-NLS-1$
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)) //$NON-NLS-1$
fFilePatterns= patterns;
else
fFilePatterns= ""; //$NON-NLS-1$
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 hierarchical= extended.getAttribute(ATTRIBUTE_HIERARCHICAL);
if (hierarchical != null) {
fRenameSubpackages= Boolean.valueOf(hierarchical).booleanValue();
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_HIERARCHICAL));
return new RefactoringStatus();
}
}