blob: 741c8c652454f7b30012f55be30fced20f2490ca [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.corext.refactoring.reorg;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.resources.IResource;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.TextChange;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IImportDeclaration;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.ToolFactory;
import org.eclipse.jdt.core.compiler.IScanner;
import org.eclipse.jdt.core.compiler.ITerminalSymbols;
import org.eclipse.jdt.core.compiler.InvalidInputException;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
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.TypeReferenceMatch;
import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility;
import org.eclipse.jdt.internal.corext.refactoring.CollectingSearchRequestor;
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.changes.TextChangeCompatibility;
import org.eclipse.jdt.internal.corext.refactoring.structure.ReferenceFinderUtil;
import org.eclipse.jdt.internal.corext.refactoring.util.TextChangeManager;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.internal.corext.util.SearchUtils;
import org.eclipse.jdt.internal.ui.JavaPlugin;
public class MoveCuUpdateCreator {
private final String fNewPackage;
private ICompilationUnit[] fCus;
private IPackageFragment fDestination;
private Map fImportRewrites; //ICompilationUnit -> ImportEdit
public MoveCuUpdateCreator(ICompilationUnit cu, IPackageFragment pack){
this(new ICompilationUnit[]{cu}, pack);
}
public MoveCuUpdateCreator(ICompilationUnit[] cus, IPackageFragment pack){
Assert.isNotNull(cus);
Assert.isNotNull(pack);
fCus= cus;
fDestination= pack;
fImportRewrites= new HashMap();
fNewPackage= fDestination.isDefaultPackage() ? "" : fDestination.getElementName() + '.'; //$NON-NLS-1$
}
public TextChangeManager createChangeManager(IProgressMonitor pm, RefactoringStatus status) throws JavaModelException{
pm.beginTask("", 5); //$NON-NLS-1$
try{
TextChangeManager changeManager= new TextChangeManager();
addUpdates(changeManager, new SubProgressMonitor(pm, 4), status);
addImportRewriteUpdates(changeManager);
return changeManager;
} catch (JavaModelException e){
throw e;
} catch (CoreException e){
throw new JavaModelException(e);
} finally{
pm.done();
}
}
private void addImportRewriteUpdates(TextChangeManager changeManager) throws CoreException {
for (Iterator iter= fImportRewrites.keySet().iterator(); iter.hasNext();) {
ICompilationUnit cu= (ICompilationUnit) iter.next();
ImportRewrite importRewrite= (ImportRewrite) fImportRewrites.get(cu);
if (importRewrite != null && importRewrite.hasRecordedChanges()) {
TextChangeCompatibility.addTextEdit(changeManager.get(cu), RefactoringCoreMessages.MoveCuUpdateCreator_update_imports, importRewrite.rewriteImports(null));
}
}
}
private void addUpdates(TextChangeManager changeManager, IProgressMonitor pm, RefactoringStatus status) throws CoreException {
pm.beginTask("", fCus.length); //$NON-NLS-1$
for (int i= 0; i < fCus.length; i++){
if (pm.isCanceled())
throw new OperationCanceledException();
addUpdates(changeManager, fCus[i], new SubProgressMonitor(pm, 1), status);
}
}
private void addUpdates(TextChangeManager changeManager, ICompilationUnit movedUnit, IProgressMonitor pm, RefactoringStatus status) throws CoreException{
try{
pm.beginTask("", 3); //$NON-NLS-1$
pm.subTask(Messages.format(RefactoringCoreMessages.MoveCuUpdateCreator_searching, movedUnit.getElementName()));
if (isInAnotherFragmentOfSamePackage(movedUnit, fDestination)){
pm.worked(3);
return;
}
addImportToSourcePackageTypes(movedUnit, new SubProgressMonitor(pm, 1));
removeImportsToDestinationPackageTypes(movedUnit);
addReferenceUpdates(changeManager, movedUnit, new SubProgressMonitor(pm, 2), status);
} finally{
pm.done();
}
}
private void addReferenceUpdates(TextChangeManager changeManager, ICompilationUnit movedUnit, IProgressMonitor pm, RefactoringStatus status) throws JavaModelException, CoreException {
List cuList= Arrays.asList(fCus);
SearchResultGroup[] references= getReferences(movedUnit, pm, status);
for (int i= 0; i < references.length; i++) {
SearchResultGroup searchResultGroup= references[i];
ICompilationUnit referencingCu= searchResultGroup.getCompilationUnit();
if (referencingCu == null)
continue;
boolean simpleReferencesNeedNewImport= simpleReferencesNeedNewImport(movedUnit, referencingCu, cuList);
SearchMatch[] results= searchResultGroup.getSearchResults();
for (int j= 0; j < results.length; j++) {
// TODO: should update type references with results from addImport
TypeReference reference= (TypeReference) results[j];
if (reference.isImportDeclaration()) {
ImportRewrite rewrite= getImportRewrite(referencingCu);
IImportDeclaration importDecl= (IImportDeclaration) SearchUtils.getEnclosingJavaElement(results[j]);
if (Flags.isStatic(importDecl.getFlags())) {
rewrite.removeStaticImport(importDecl.getElementName());
addStaticImport(movedUnit, importDecl, rewrite);
} else {
rewrite.removeImport(importDecl.getElementName());
rewrite.addImport(createStringForNewImport(movedUnit, importDecl));
}
} else if (reference.isQualified()) {
TextChange textChange= changeManager.get(referencingCu);
String changeName= RefactoringCoreMessages.MoveCuUpdateCreator_update_references;
TextEdit replaceEdit= new ReplaceEdit(reference.getOffset(), reference.getSimpleNameStart() - reference.getOffset(), fNewPackage);
TextChangeCompatibility.addTextEdit(textChange, changeName, replaceEdit);
} else if (simpleReferencesNeedNewImport) {
ImportRewrite importEdit= getImportRewrite(referencingCu);
String typeName= reference.getSimpleName();
importEdit.addImport(getQualifiedType(fDestination.getElementName(), typeName));
}
}
}
}
private void addStaticImport(ICompilationUnit movedUnit, IImportDeclaration importDecl, ImportRewrite rewrite) {
String old= importDecl.getElementName();
int oldPackLength= movedUnit.getParent().getElementName().length();
StringBuffer result= new StringBuffer(fDestination.getElementName());
if (oldPackLength == 0) // move FROM default package
result.append('.').append(old);
else if (result.length() == 0) // move TO default package
result.append(old.substring(oldPackLength + 1)); // cut "."
else
result.append(old.substring(oldPackLength));
int index= result.lastIndexOf("."); //$NON-NLS-1$
if (index > 0 && index < result.length() - 1)
rewrite.addStaticImport(result.substring(0, index), result.substring(index + 1, result.length()), true);
}
private String getQualifiedType(String packageName, String typeName) {
if (packageName.length() == 0)
return typeName;
else
return packageName + '.' + typeName;
}
private String createStringForNewImport(ICompilationUnit movedUnit, IImportDeclaration importDecl) {
String old= importDecl.getElementName();
int oldPackLength= movedUnit.getParent().getElementName().length();
StringBuffer result= new StringBuffer(fDestination.getElementName());
if (oldPackLength == 0) // move FROM default package
result.append('.').append(old);
else if (result.length() == 0) // move TO default package
result.append(old.substring(oldPackLength + 1)); // cut "."
else
result.append(old.substring(oldPackLength));
return result.toString();
}
private void removeImportsToDestinationPackageTypes(ICompilationUnit movedUnit) throws CoreException{
ImportRewrite importEdit= getImportRewrite(movedUnit);
IType[] destinationTypes= getDestinationPackageTypes();
for (int i= 0; i < destinationTypes.length; i++) {
importEdit.removeImport(JavaModelUtil.getFullyQualifiedName(destinationTypes[i]));
}
}
private IType[] getDestinationPackageTypes() throws JavaModelException{
List types= new ArrayList();
ICompilationUnit[] cus= fDestination.getCompilationUnits();
for (int i= 0; i < cus.length; i++) {
types.addAll(Arrays.asList(cus[i].getAllTypes()));
}
return (IType[]) types.toArray(new IType[types.size()]);
}
private void addImportToSourcePackageTypes(ICompilationUnit movedUnit, IProgressMonitor pm) throws CoreException{
List cuList= Arrays.asList(fCus);
IType[] allCuTypes= movedUnit.getAllTypes();
IType[] referencedTypes= ReferenceFinderUtil.getTypesReferencedIn(allCuTypes, pm);
ImportRewrite importEdit= getImportRewrite(movedUnit);
importEdit.setFilterImplicitImports(false);
IPackageFragment srcPack= (IPackageFragment)movedUnit.getParent();
for (int i= 0; i < referencedTypes.length; i++) {
IType iType= referencedTypes[i];
if (! iType.exists())
continue;
if (! iType.getPackageFragment().equals(srcPack))
continue;
if (cuList.contains(iType.getCompilationUnit()))
continue;
importEdit.addImport(JavaModelUtil.getFullyQualifiedName(iType));
}
}
private ImportRewrite getImportRewrite(ICompilationUnit cu) throws CoreException{
if (fImportRewrites.containsKey(cu))
return (ImportRewrite)fImportRewrites.get(cu);
ImportRewrite importEdit= StubUtility.createImportRewrite(cu, true);
fImportRewrites.put(cu, importEdit);
return importEdit;
}
private boolean simpleReferencesNeedNewImport(ICompilationUnit movedUnit, ICompilationUnit referencingCu, List cuList) {
if (referencingCu.equals(movedUnit))
return false;
if (cuList.contains(referencingCu))
return false;
if (isReferenceInAnotherFragmentOfSamePackage(referencingCu, movedUnit)) {
/* Destination package is different from source, since
* isDestinationAnotherFragmentOfSamePackage(movedUnit) was false in addUpdates(.) */
return true;
}
//heuristic
if (referencingCu.getImport(movedUnit.getParent().getElementName() + ".*").exists()) //$NON-NLS-1$
return true; // has old star import
if (referencingCu.getParent().equals(movedUnit.getParent()))
return true; //is moved away from same package
return false;
}
private boolean isReferenceInAnotherFragmentOfSamePackage(ICompilationUnit referencingCu, ICompilationUnit movedUnit) {
if (referencingCu == null)
return false;
if (! (referencingCu.getParent() instanceof IPackageFragment))
return false;
IPackageFragment pack= (IPackageFragment) referencingCu.getParent();
return isInAnotherFragmentOfSamePackage(movedUnit, pack);
}
private static boolean isInAnotherFragmentOfSamePackage(ICompilationUnit cu, IPackageFragment pack) {
if (! (cu.getParent() instanceof IPackageFragment))
return false;
IPackageFragment cuPack= (IPackageFragment) cu.getParent();
return ! cuPack.equals(pack) && JavaModelUtil.isSamePackage(cuPack, pack);
}
private static SearchResultGroup[] getReferences(ICompilationUnit unit, IProgressMonitor pm, RefactoringStatus status) throws CoreException {
final SearchPattern pattern= RefactoringSearchEngine.createOrPattern(unit.getTypes(), IJavaSearchConstants.REFERENCES);
if (pattern != null)
return RefactoringSearchEngine.search(pattern, RefactoringScopeFactory.create(unit), new Collector(((IPackageFragment) unit.getParent())), new SubProgressMonitor(pm, 1), status);
return new SearchResultGroup[] {};
}
private final static class Collector extends CollectingSearchRequestor {
private IPackageFragment fSource;
private IScanner fScanner;
public Collector(IPackageFragment source) {
fSource= source;
fScanner= ToolFactory.createScanner(false, false, false, false);
}
/* (non-Javadoc)
* @see org.eclipse.jdt.internal.corext.refactoring.CollectingSearchRequestor#acceptSearchMatch(SearchMatch)
*/
public void acceptSearchMatch(SearchMatch match) throws CoreException {
/*
* Processing is done in collector to reuse the buffer which was
* already required by the search engine to locate the matches.
*/
// [start, end[ include qualification.
IJavaElement element= SearchUtils.getEnclosingJavaElement(match);
int accuracy= match.getAccuracy();
int start= match.getOffset();
int length= match.getLength();
boolean insideDocComment= match.isInsideDocComment();
IResource res= match.getResource();
if (element.getAncestor(IJavaElement.IMPORT_DECLARATION) != null) {
super.acceptSearchMatch(TypeReference.createImportReference(element, accuracy, start, length, insideDocComment, res));
} else {
ICompilationUnit unit= (ICompilationUnit) element.getAncestor(IJavaElement.COMPILATION_UNIT);
if (unit != null) {
IBuffer buffer= unit.getBuffer();
String matchText= buffer.getText(start, length);
if (fSource.isDefaultPackage()) {
super.acceptSearchMatch(TypeReference.createSimpleReference(element, accuracy, start, length, insideDocComment, res, matchText));
} else {
// assert: matchText doesn't start nor end with comment
int simpleNameStart= getLastSimpleNameStart(matchText);
if (simpleNameStart != 0) {
super.acceptSearchMatch(TypeReference.createQualifiedReference(element, accuracy, start, length, insideDocComment, res, start + simpleNameStart));
} else {
super.acceptSearchMatch(TypeReference.createSimpleReference(element, accuracy, start, length, insideDocComment, res, matchText));
}
}
}
}
}
private int getLastSimpleNameStart(String reference) {
fScanner.setSource(reference.toCharArray());
int lastIdentifierStart= -1;
try {
int tokenType= fScanner.getNextToken();
while (tokenType != ITerminalSymbols.TokenNameEOF) {
if (tokenType == ITerminalSymbols.TokenNameIdentifier)
lastIdentifierStart= fScanner.getCurrentTokenStartPosition();
tokenType= fScanner.getNextToken();
}
} catch (InvalidInputException e) {
JavaPlugin.log(e);
}
return lastIdentifierStart;
}
}
private final static class TypeReference extends TypeReferenceMatch {
private String fSimpleTypeName;
private int fSimpleNameStart;
private TypeReference(IJavaElement enclosingElement, int accuracy, int start, int length,
boolean insideDocComment, IResource resource, int simpleNameStart, String simpleName) {
super(enclosingElement, accuracy, start, length,
insideDocComment, SearchEngine.getDefaultSearchParticipant(), resource);
fSimpleNameStart= simpleNameStart;
fSimpleTypeName= simpleName;
}
public static TypeReference createQualifiedReference(IJavaElement enclosingElement, int accuracy, int start, int length,
boolean insideDocComment, IResource resource, int simpleNameStart) {
Assert.isTrue(start < simpleNameStart && simpleNameStart < start + length);
return new TypeReference(enclosingElement, accuracy, start, length, insideDocComment, resource, simpleNameStart, null);
}
public static TypeReference createImportReference(IJavaElement enclosingElement, int accuracy, int start, int length,
boolean insideDocComment, IResource resource) {
return new TypeReference(enclosingElement, accuracy, start, length, insideDocComment, resource, -1, null);
}
public static TypeReference createSimpleReference(IJavaElement enclosingElement, int accuracy, int start, int length,
boolean insideDocComment, IResource resource, String simpleName) {
return new TypeReference(enclosingElement, accuracy, start, length, insideDocComment, resource, -1, simpleName);
}
public boolean isImportDeclaration() {
return SearchUtils.getEnclosingJavaElement(this).getAncestor(IJavaElement.IMPORT_DECLARATION) != null;
}
public boolean isQualified() {
return fSimpleNameStart != -1;
}
public boolean isSimpleReference() {
return fSimpleTypeName != null;
}
/**
* @return start offset of simple type name, or -1 iff ! isQualified()
*/
public int getSimpleNameStart() {
return fSimpleNameStart;
}
/**
* @return simple type name, or null iff ! isSimpleName()
*/
public String getSimpleName() {
return fSimpleTypeName;
}
}
}