blob: 65723a89862465b0318e69699189c6cc56eeb857 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2005 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.fix;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.text.edits.DeleteEdit;
import org.eclipse.text.edits.MoveSourceEdit;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.text.edits.TextEditGroup;
import org.eclipse.text.edits.TextEditVisitor;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.CompositeChange;
import org.eclipse.ltk.core.refactoring.NullChange;
import org.eclipse.ltk.core.refactoring.Refactoring;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.RefactoringTickProvider;
import org.eclipse.ltk.core.refactoring.TextChange;
import org.eclipse.ltk.core.refactoring.TextEditBasedChangeGroup;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.WorkingCopyOwner;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTRequestor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.internal.corext.refactoring.changes.TextChangeCompatibility;
import org.eclipse.jdt.internal.corext.refactoring.composite.MultiStateCompilationUnitChange;
import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.internal.ui.fix.ICleanUp;
import org.eclipse.jdt.internal.ui.javaeditor.ASTProvider;
public class CleanUpRefactoring extends Refactoring {
private static final int BATCH_SIZE= 40;
private class FixCalculationException extends RuntimeException {
private static final long serialVersionUID= 3807273310144726165L;
private final CoreException fException;
public FixCalculationException(CoreException exception) {
fException= exception;
}
public CoreException getException() {
return fException;
}
}
private class ParseListElement {
private final ICompilationUnit fUnit;
private List fCleanUpsToGo;
private ICleanUp[] fCleanUpsArray;
public ParseListElement(ICompilationUnit unit) {
fUnit= unit;
fCleanUpsToGo= new ArrayList();
fCleanUpsArray= new ICleanUp[0];
}
public ParseListElement(ICompilationUnit unit, ICleanUp[] cleanUps) {
fUnit= unit;
fCleanUpsArray= cleanUps;
fCleanUpsToGo= null;
}
public void addCleanUp(ICleanUp multiFix) {
if (fCleanUpsToGo == null) {
fCleanUpsToGo= Arrays.asList(fCleanUpsArray);
}
fCleanUpsToGo.add(multiFix);
fCleanUpsArray= null;
}
public ICompilationUnit getCompilationUnit() {
return fUnit;
}
public ICleanUp[] getCleanUps() {
if (fCleanUpsArray == null) {
fCleanUpsArray= (ICleanUp[])fCleanUpsToGo.toArray(new ICleanUp[fCleanUpsToGo.size()]);
}
return fCleanUpsArray;
}
}
private static final RefactoringTickProvider CLEAN_UP_REFACTORING_TICK_PROVIDER= new RefactoringTickProvider(0, 0, 1, 0);
private List/*<ICompilationUnit>*/ fCompilationUnits;
private List/*<ICleanUp>*/ fCleanUps;
public CleanUpRefactoring() {
fCleanUps= new ArrayList();
fCompilationUnits= new ArrayList();
}
public void addCompilationUnit(ICompilationUnit unit) {
fCompilationUnits.add(unit);
}
public void clearCompilationUnits() {
fCompilationUnits.clear();
}
public boolean hasCompilationUnits() {
return !fCompilationUnits.isEmpty();
}
public ICompilationUnit[] getCompilationUnits() {
return (ICompilationUnit[])fCompilationUnits.toArray(new ICompilationUnit[fCompilationUnits.size()]);
}
public void addCleanUp(ICleanUp fix) {
fCleanUps.add(fix);
}
public void clearCleanUps() {
fCleanUps.clear();
}
public boolean hasCleanUps() {
return !fCleanUps.isEmpty();
}
/* (non-Javadoc)
* @see org.eclipse.ltk.core.refactoring.Refactoring#getName()
*/
public String getName() {
return FixMessages.CleanUpRefactoring_Refactoring_name;
}
/* (non-Javadoc)
* @see org.eclipse.ltk.core.refactoring.Refactoring#checkInitialConditions(org.eclipse.core.runtime.IProgressMonitor)
*/
public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException, OperationCanceledException {
if (pm != null) {
pm.beginTask("", 1); //$NON-NLS-1$
pm.worked(1);
pm.done();
}
return new RefactoringStatus();
}
/* (non-Javadoc)
* @see org.eclipse.ltk.core.refactoring.Refactoring#checkFinalConditions(org.eclipse.core.runtime.IProgressMonitor)
*/
public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException, OperationCanceledException {
if (pm != null) {
pm.beginTask("", 1); //$NON-NLS-1$
pm.worked(1);
pm.done();
}
return new RefactoringStatus();
}
/* (non-Javadoc)
* @see org.eclipse.ltk.core.refactoring.Refactoring#createChange(org.eclipse.core.runtime.IProgressMonitor)
*/
public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException {
if (pm == null)
pm= new NullProgressMonitor();
if (fCompilationUnits.size() == 0 || fCleanUps.size() == 0) {
pm.beginTask("", 1); //$NON-NLS-1$
pm.worked(1);
pm.done();
return new NullChange();
}
pm.beginTask("", fCompilationUnits.size()); //$NON-NLS-1$
Map cleanUpOptions= getCleanUpOptions();
Hashtable resultingFixes= new Hashtable();
ICleanUp[] cleanUps= (ICleanUp[])fCleanUps.toArray(new ICleanUp[fCleanUps.size()]);
List toGo= new ArrayList();
for (Iterator iter= fCompilationUnits.iterator(); iter.hasNext();) {
ICompilationUnit cu= (ICompilationUnit)iter.next();
toGo.add(new ParseListElement(cu, cleanUps));
}
int start= 0;
int end= 0;
ICompilationUnit beforeEndUnit= ((ParseListElement)toGo.get(start)).getCompilationUnit();
while (end < toGo.size()) {
while (end - start < BATCH_SIZE && end < toGo.size()) {
end++;
if (end == toGo.size()) {
break;
} else {
ICompilationUnit endUnit = ((ParseListElement)toGo.get(end)).getCompilationUnit();
if (endUnit.getJavaProject() != beforeEndUnit.getJavaProject()) {
beforeEndUnit= endUnit;
break;
} else {
beforeEndUnit= endUnit;
}
}
}
List toParse= toGo.subList(start, end);
IJavaProject javaProject= ((ParseListElement)toParse.get(0)).getCompilationUnit().getJavaProject();
ASTParser parser= createParser(cleanUpOptions, javaProject);
List redoList= parse(resultingFixes, start, toParse, new SubProgressMonitor(pm, toParse.size()), parser, toGo.size());
toGo.addAll(redoList);
start= end;
}
CompositeChange result= new CompositeChange(getName());
for (Iterator iter= resultingFixes.values().iterator(); iter.hasNext();) {
Change element= (Change)iter.next();
result.add(element);
}
pm.done();
return result;
}
/**
* @return Options for all multi-fixes in <code>fMultiFixes</code>
*/
private Map getCleanUpOptions() {
Map cleanUpOptions= new Hashtable();
for (Iterator iter= fCleanUps.iterator(); iter.hasNext();) {
ICleanUp cleanUp= (ICleanUp)iter.next();
Map currentCleanUpOption= cleanUp.getRequiredOptions();
if (currentCleanUpOption != null)
cleanUpOptions.putAll(currentCleanUpOption);
}
return cleanUpOptions;
}
private List/*<ParseListElement>*/ parse(final Hashtable solutions, final int start, List/*<ParseListElement*/ toParse, final SubProgressMonitor sub, ASTParser parser, int totalElementsCount) throws CoreException {
final ICompilationUnit[] compilationUnits= new ICompilationUnit[toParse.size()];
final ICleanUp[][] cleanUps= new ICleanUp[toParse.size()][];
final List workingCopys= new ArrayList();
try {
int i= 0;
for (Iterator iter= toParse.iterator(); iter.hasNext();) {
ParseListElement element= (ParseListElement) iter.next();
ICompilationUnit compilationUnit= element.getCompilationUnit();
if (solutions.containsKey(compilationUnit)) {
ICompilationUnit workingCopy= createWorkingCopy(solutions, compilationUnit);
compilationUnits[i]= workingCopy;
workingCopys.add(workingCopy);
} else {
compilationUnits[i]= compilationUnit;
}
cleanUps[i]= element.getCleanUps();
i++;
}
final List result= new ArrayList();
final int[] index= new int[] {start};
final Integer size= new Integer(totalElementsCount);
sub.subTask(Messages.format(FixMessages.CleanUpRefactoring_ProcessingCompilationUnit_message, new Object[] {getTypeName(compilationUnits[index[0] - start]), new Integer(index[0] + 1), size}));
try {
parser.createASTs(compilationUnits, new String[0], new ASTRequestor() {
public void acceptAST(ICompilationUnit source, CompilationUnit ast) {
ParseListElement tuple= calculateSolution(solutions, ast, cleanUps[index[0] - start]);
if (tuple != null) {
result.add(tuple);
}
index[0]++;
if (index[0] - start < compilationUnits.length) {
sub.subTask(Messages.format(FixMessages.CleanUpRefactoring_ProcessingCompilationUnit_message, new Object[] {getTypeName(compilationUnits[index[0] - start]), new Integer(index[0] + 1), size}));
}
}
}, sub);
} catch (FixCalculationException e) {
throw e.getException();
}
return result;
} finally {
for (Iterator iter= workingCopys.iterator(); iter.hasNext();) {
ICompilationUnit cu= (ICompilationUnit)iter.next();
cu.discardWorkingCopy();
}
}
}
private ICompilationUnit createWorkingCopy(final Hashtable solutions, ICompilationUnit compilationUnit) throws JavaModelException, CoreException {
Change oldChange= (Change)solutions.get(compilationUnit);
MultiStateCompilationUnitChange mscuc;
if (!(oldChange instanceof MultiStateCompilationUnitChange)) {
mscuc= new MultiStateCompilationUnitChange(getChangeName(compilationUnit), compilationUnit);
mscuc.setKeepPreviewEdits(true);
mscuc.addChange((TextChange)oldChange);
solutions.remove(compilationUnit);
solutions.put(compilationUnit, mscuc);
} else {
mscuc= (MultiStateCompilationUnitChange)oldChange;
}
ICompilationUnit workingCopy= compilationUnit.getWorkingCopy(new WorkingCopyOwner() {}, null, null);
IBuffer buffer= workingCopy.getBuffer();
buffer.setContents(mscuc.getPreviewContent(null));
return workingCopy;
}
private String getChangeName(ICompilationUnit compilationUnit) {
// TODO: Use JavaElementLabels for labels
StringBuffer buf= new StringBuffer();
IJavaElement p= compilationUnit.getParent();
if (p != null) {
buf.insert(0, p.getElementName());
p= p.getParent();
while (p != null) {
if (p.getElementName().length() > 0) {
buf.insert(0, p.getElementName() + "/"); //$NON-NLS-1$
}
p= p.getParent();
}
}
buf.insert(0, " - "); //$NON-NLS-1$
buf.insert(0, compilationUnit.getElementName());
return buf.toString();
}
private ASTParser createParser(Map cleanUpOptions, IJavaProject javaProject) {
ASTParser parser= ASTParser.newParser(ASTProvider.AST_LEVEL);
parser.setResolveBindings(true);
parser.setProject(javaProject);
Map options= RefactoringASTParser.getCompilerOptions(javaProject);
options.putAll(cleanUpOptions);
parser.setCompilerOptions(options);
return parser;
}
private String getTypeName(final ICompilationUnit unit) {
return JavaCore.removeJavaLikeExtension(unit.getElementName());
}
private ParseListElement calculateSolution(Hashtable solutions, CompilationUnit ast, ICleanUp[] cleanUps) {
TextChange solution= null;
ParseListElement result= null;
for (int i= 0; i < cleanUps.length; i++) {
ICleanUp cleanUp= cleanUps[i];
try {
IFix fix= cleanUp.createFix(ast);
if (fix != null) {
TextChange current= fix.createChange();
if (solution != null) {
if (intersects(current.getEdit(),solution.getEdit())) {
if (result == null) {
result= new ParseListElement((ICompilationUnit)ast.getJavaElement());
}
result.addCleanUp(cleanUp);
} else {
mergeTextChanges(current, solution);
solution= current;
}
} else {
solution= current;
}
}
} catch (CoreException e) {
throw new FixCalculationException(e);
}
}
if (solution != null) {
if (solutions.containsKey(ast.getJavaElement().getPrimaryElement())) {
MultiStateCompilationUnitChange oldChange= (MultiStateCompilationUnitChange)solutions.get(ast.getJavaElement().getPrimaryElement());
oldChange.addChange(solution);
} else {
solutions.put(ast.getJavaElement(), solution);
}
}
return result;
}
private boolean intersects(TextEdit edit1, TextEdit edit2) {
if (edit1 instanceof MultiTextEdit && edit2 instanceof MultiTextEdit) {
MultiTextEdit multiTextEdit1= (MultiTextEdit)edit1;
TextEdit[] children1= multiTextEdit1.getChildren();
MultiTextEdit multiTextEdit2= (MultiTextEdit)edit2;
TextEdit[] children2= multiTextEdit2.getChildren();
int i1= 0;
int i2= 0;
while (i1 < children1.length && i2 < children2.length) {
while (i1 + 1 < children1.length && children1[i1 + 1].getOffset() < children2[i2].getOffset()) {
i1++;
}
while (i2 + 1 < children2.length && children2[i2 + 1].getOffset() < children1[i1].getOffset()) {
i2++;
}
if (intersects(children1[i1], children2[i2]))
return true;
if (children1[i1].getOffset() < children2[i2].getOffset()) {
i1++;
} else {
i2++;
}
}
return false;
} else if (edit1 instanceof MultiTextEdit) {
MultiTextEdit multiTextEdit1= (MultiTextEdit)edit1;
TextEdit[] children= multiTextEdit1.getChildren();
for (int i= 0; i < children.length; i++) {
TextEdit child= children[i];
if (intersects(child, edit2))
return true;
}
return false;
} else if (edit2 instanceof MultiTextEdit) {
MultiTextEdit multiTextEdit2= (MultiTextEdit)edit2;
TextEdit[] children= multiTextEdit2.getChildren();
for (int i= 0; i < children.length; i++) {
TextEdit child= children[i];
if (intersects(child, edit1))
return true;
}
return false;
} else {
int start1= edit1.getOffset();
int end1= start1 + edit1.getLength();
int start2= edit2.getOffset();
int end2= start2 + edit2.getLength();
if (start1 > end2)
return false;
if (start2 > end1)
return false;
return true;
}
}
private static void mergeTextChanges(TextChange target, TextChange source) {
final List edits= new ArrayList();
source.getEdit().accept(new TextEditVisitor() {
public boolean visitNode(TextEdit edit) {
if (edit instanceof MoveSourceEdit)
return false;
if (edit instanceof MultiTextEdit)
return true;
edits.add(edit);
return super.visitNode(edit);
}
});
if (edits.isEmpty())
return;
final List removedEdits= new ArrayList();
target.getEdit().accept(new TextEditVisitor() {
public boolean visit(DeleteEdit deleteEdit) {
int start= deleteEdit.getOffset();
int end= start + deleteEdit.getLength();
List toRemove= new ArrayList();
for (Iterator iter= edits.iterator(); iter.hasNext();) {
TextEdit edit= (TextEdit)iter.next();
int offset= edit.getOffset();
if (offset >= start && offset <= end) {
toRemove.add(edit);
}
}
if (!toRemove.isEmpty()) {
edits.removeAll(toRemove);
removedEdits.addAll(toRemove);
}
return super.visit(deleteEdit);
}
});
for (Iterator iter= edits.iterator(); iter.hasNext();) {
TextEdit edit= (TextEdit)iter.next();
edit.getParent().removeChild(edit);
TextChangeCompatibility.insert(target.getEdit(), edit);
}
TextEditBasedChangeGroup[] changeGroups= source.getChangeGroups();
for (int i= 0; i < changeGroups.length; i++) {
TextEditGroup textEditGroup= changeGroups[i].getTextEditGroup();
TextEditGroup newGroup= new TextEditGroup(textEditGroup.getName());
TextEdit[] textEdits= textEditGroup.getTextEdits();
for (int j= 0; j < textEdits.length; j++) {
if (!removedEdits.contains(textEdits[j]))
newGroup.addTextEdit(textEdits[j]);
}
target.addTextEditGroup(newGroup);
}
}
/* (non-Javadoc)
* @see org.eclipse.ltk.core.refactoring.Refactoring#getRefactoringTickProvider()
*/
protected RefactoringTickProvider doGetRefactoringTickProvider() {
return CLEAN_UP_REFACTORING_TICK_PROVIDER;
}
}