/**
* This file is licensed under the University of Illinois/NCSA Open Source License. See LICENSE.TXT for details.
*/
package edu.illinois.codingspectator.refactoringproblems.logger;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.core.commands.operations.IOperationHistoryListener;
import org.eclipse.core.commands.operations.IUndoableOperation;
import org.eclipse.core.commands.operations.OperationHistoryEvent;
import org.eclipse.core.commands.operations.OperationHistoryFactory;
import org.eclipse.core.commands.operations.TriggeredOperations;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.ltk.core.refactoring.ChangeDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringChangeDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringCore;
import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
import org.eclipse.ltk.core.refactoring.codingspectator.Logger;
import org.eclipse.ltk.core.refactoring.history.IRefactoringExecutionListener;
import org.eclipse.ltk.core.refactoring.history.RefactoringExecutionEvent;
import org.eclipse.ltk.internal.core.refactoring.UndoableOperation2ChangeAdapter;
import org.eclipse.ui.IStartup;
/**
*
* This class is based on edu.illinois.codingtracker.listeners.OperationHistoryListener and
* edu.illinois.codingtracker.listeners.RefactoringExecutionListener
*
* @author Balaji Ambresh Rajkumar
* @author Mohsen Vakilian
* @author nchen
* @author Stas Negara
*
*/
@SuppressWarnings("restriction")
public class RefactoringProblemsListener implements IStartup, IOperationHistoryListener, IRefactoringExecutionListener {
private ProblemsFinder problemsFinder;
private ProblemsComparer problemsComparer;
private Set<ICompilationUnit> affectedCompilationUnits;
public RefactoringProblemsListener() {
problemsFinder= new ProblemsFinder();
problemsComparer= new ProblemsComparer();
}
@Override
public void earlyStartup() {
OperationHistoryFactory.getOperationHistory().addOperationHistoryListener(this);
RefactoringCore.getHistoryService().addExecutionListener(this);
}
@Override
public void historyNotification(OperationHistoryEvent event) {
if (isAboutToRefactor(event)) {
affectedCompilationUnits= getAffectedCompilationUnits(event);
try {
storeCurrentProblems();
} catch (JavaModelException e) {
Activator.getDefault().getLog().log(new Status(IStatus.WARNING, Activator.PLUGIN_ID, "CODINGSPECTATOR: Failed to log compilation problems", e));
}
} else if (hasRefactoringFailed(event)) {
storeAndCompareProblems();
}
}
private Collection<ICompilationUnit> makeAffectedCompilationUnitsBecomeWorkingCopies() throws JavaModelException {
Collection<ICompilationUnit> compilationUnitsMadeWorkingCopies= new HashSet<ICompilationUnit>();
for (ICompilationUnit cu : affectedCompilationUnits) {
if (cu.exists() && !cu.isWorkingCopy()) {
cu.becomeWorkingCopy(new NullProgressMonitor());
compilationUnitsMadeWorkingCopies.add(cu);
}
}
return compilationUnitsMadeWorkingCopies;
}
private void revertWorkingCopyChanges(Collection<ICompilationUnit> wereWorkingCopies) throws JavaModelException {
for (ICompilationUnit cu : wereWorkingCopies) {
cu.discardWorkingCopy();
}
}
private void storeCurrentProblems() throws JavaModelException {
Collection<ICompilationUnit> compilationUnitsMadeWorkingCopies= makeAffectedCompilationUnitsBecomeWorkingCopies();
Set<DefaultProblemWrapper> computeProblems= problemsFinder.computeProblems(affectedCompilationUnits);
revertWorkingCopyChanges(compilationUnitsMadeWorkingCopies);
problemsComparer.pushNewProblemsSet(computeProblems);
}
private UndoableOperation2ChangeAdapter getUndoableOperation2ChangeAdapter(IUndoableOperation operation) {
if (operation instanceof TriggeredOperations) {
operation= ((TriggeredOperations)operation).getTriggeringOperation();
}
if (operation instanceof UndoableOperation2ChangeAdapter) {
return (UndoableOperation2ChangeAdapter)operation;
}
return null;
}
/**
*
* @see org.eclipse.ltk.internal.core.refactoring.history.RefactoringHistoryService#getRefactoringDescriptor(IUndoableOperation)
*
* @param operation
* @return
*/
private RefactoringDescriptor getRefactoringDescriptor(IUndoableOperation operation) {
UndoableOperation2ChangeAdapter o= getUndoableOperation2ChangeAdapter(operation);
if (o == null) {
return null;
}
ChangeDescriptor changeDescriptor= o.getChangeDescriptor();
if (changeDescriptor instanceof RefactoringChangeDescriptor) {
return ((RefactoringChangeDescriptor)changeDescriptor).getRefactoringDescriptor();
}
return null;
}
private RefactoringDescriptor getRefactoringDescriptor(OperationHistoryEvent event) {
return getRefactoringDescriptor(event.getOperation());
}
private Set<ICompilationUnit> getAffectedCompilationUnits(OperationHistoryEvent event) {
Set<ICompilationUnit> affectedCompilationUnits= new HashSet<ICompilationUnit>();
UndoableOperation2ChangeAdapter o= getUndoableOperation2ChangeAdapter(event.getOperation());
if (o != null) {
Object[] affectedObjects= o.getAllAffectedObjects();
if (affectedObjects != null) {
for (Object affectedObject : affectedObjects) {
if (affectedObject instanceof ICompilationUnit) {
affectedCompilationUnits.add((ICompilationUnit)affectedObject);
} else if (affectedObject instanceof IPackageFragment) {
affectedCompilationUnits.addAll(getPackageCompilationUnits((IPackageFragment)affectedObject));
}
}
}
}
return affectedCompilationUnits;
}
private Set<ICompilationUnit> getPackageCompilationUnits(IPackageFragment packageFragment) {
Set<ICompilationUnit> packageCompilationUnits= new HashSet<ICompilationUnit>();
try {
for (ICompilationUnit compilationUnit : packageFragment.getCompilationUnits()) {
packageCompilationUnits.add(compilationUnit);
}
} catch (JavaModelException e) {
//do nothing
}
return packageCompilationUnits;
}
private boolean isAboutToRefactor(OperationHistoryEvent event) {
if (!isRefactoringEvent(event)) {
return false;
}
int eventType= event.getEventType();
return eventType == OperationHistoryEvent.ABOUT_TO_EXECUTE || (eventType == OperationHistoryEvent.ABOUT_TO_REDO) ||
eventType == OperationHistoryEvent.ABOUT_TO_UNDO;
}
private boolean isRefactoringEvent(OperationHistoryEvent event) {
RefactoringDescriptor descriptor= getRefactoringDescriptor(event);
return descriptor != null;
}
private long getRefactoringTimestamp(RefactoringExecutionEvent event) {
return event.getDescriptor().getTimeStamp();
}
/**
* A refactroing can fail in many ways, e.g. if a resource that it expects does not exist.
* Create a project, say, P1. Create a class in the project, say, C. Create another project, say
* P2. Perform a move refactoring of Class C from P1 to P2. Delete P2. Go to the project
* explorer view and start undoing. First the project P2 will appear. The next undo will fail.
* That's the case we're looking for.
*
* @param event the history event that is in progress.
*
* @return true if the event is about the failure of a refactoring.
*/
private boolean hasRefactoringFailed(OperationHistoryEvent event) {
return isRefactoringEvent(event) && event.getEventType() == OperationHistoryEvent.OPERATION_NOT_OK;
}
@Override
public void executionNotification(RefactoringExecutionEvent event) {
if (isRefactoringPerformedEvent(event)) {
problemsComparer.setRefactoringTimestamp(getRefactoringTimestamp(event));
storeAndCompareProblems();
}
}
private void storeAndCompareProblems() {
try {
storeCurrentProblems();
ProblemChanges problemChanges= problemsComparer.compareProblems();
Logger.logDebug(problemChanges.toString());
problemChanges.log();
} catch (JavaModelException e) {
Activator.getDefault().getLog().log(new Status(IStatus.WARNING, Activator.PLUGIN_ID, "CODINGSPECTATOR: Failed to log compilation problems", e));
}
}
private boolean isRefactoringPerformedEvent(RefactoringExecutionEvent event) {
int eventType= event.getEventType();
return (eventType == RefactoringExecutionEvent.PERFORMED || eventType == RefactoringExecutionEvent.REDONE || eventType == RefactoringExecutionEvent.UNDONE);
}
}