/** * This file is licensed under the University of Illinois/NCSA Open Source License. See LICENSE.TXT for details. */ package edu.illinois.codingtracker.operations.refactorings; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import org.eclipse.core.runtime.CoreException; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.ltk.core.refactoring.CheckConditionsOperation; import org.eclipse.ltk.core.refactoring.IUndoManager; import org.eclipse.ltk.core.refactoring.PerformRefactoringOperation; import org.eclipse.ltk.core.refactoring.Refactoring; import org.eclipse.ltk.core.refactoring.RefactoringContribution; import org.eclipse.ltk.core.refactoring.RefactoringCore; import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.ltk.internal.core.refactoring.history.DefaultRefactoringDescriptor; import org.eclipse.ltk.internal.core.refactoring.history.RefactoringContributionManager; import edu.illinois.codingtracker.helpers.Configuration; import edu.illinois.codingtracker.helpers.Debugger; import edu.illinois.codingtracker.jdt.project.manipulation.JavaProjectHelper; import edu.illinois.codingtracker.operations.JavaProjectsUpkeeper; import edu.illinois.codingtracker.operations.OperationLexer; import edu.illinois.codingtracker.operations.OperationSymbols; import edu.illinois.codingtracker.operations.OperationTextChunk; import edu.illinois.codingtracker.operations.UserOperation; /** * * @author Stas Negara * */ @SuppressWarnings({ "rawtypes", "restriction" }) public class NewStartedRefactoringOperation extends UserOperation { public static enum RefactoringMode { PERFORM, UNDO, REDO }; private static final int shouldAlwaysReplayOffset= 50; private static Set<Long> unperformedRefactorings= new HashSet<Long>(); //Shows whether this is an older refactoring, which should always be replayed to reproduce the code changes. private boolean shouldAlwaysReplay= false; //this flag is serialized/deserialized together with refactoringMode private RefactoringMode refactoringMode; private String id; private String project; private int flags; //TreeMap is required for the deterministic behavior that is expected by the tests private final Map<String, String> arguments= new TreeMap<String, String>(); public NewStartedRefactoringOperation() { super(); } public NewStartedRefactoringOperation(RefactoringMode refactoringMode, RefactoringDescriptor refactoringDescriptor) { super(getRefactoringTimeStamp(refactoringDescriptor)); this.refactoringMode= refactoringMode; id= refactoringDescriptor.getID(); project= refactoringDescriptor.getProject(); flags= refactoringDescriptor.getFlags(); initializeArguments(getRefactoringArguments(refactoringDescriptor)); } /** * This constructor should be used only for conversions performed by postprocessors. * * @param refactoringMode * @param id * @param project * @param flags * @param arguments * @param timestamp */ public NewStartedRefactoringOperation(boolean shouldAlwaysReplay, RefactoringMode refactoringMode, String id, String project, int flags, Map<String, String> arguments, long timestamp) { super(timestamp); this.shouldAlwaysReplay= shouldAlwaysReplay; this.refactoringMode= refactoringMode; this.id= id; this.project= project; this.flags= flags; initializeArguments(arguments); } @Override protected char getOperationSymbol() { return OperationSymbols.NEW_REFACTORING_STARTED_SYMBOL; } @Override public String getDescription() { return "[new] Started refactoring"; } public boolean getShouldAlwaysReplay() { return shouldAlwaysReplay; } public RefactoringMode getRefactoringMode() { return refactoringMode; } public String getID() { return id; } public String getProject() { return project; } public int getFlags() { return flags; } /** * Note that returning a reference to a private collection breaks incapsulation. But considering * that this method is used only by postprocessors, it should be OK. * * @return */ public Map<String, String> getArguments() { return arguments; } private static long getRefactoringTimeStamp(RefactoringDescriptor refactoringDescriptor) { long refactoringDescriptorTimeStamp= refactoringDescriptor.getTimeStamp(); return refactoringDescriptorTimeStamp == -1 ? System.currentTimeMillis() : refactoringDescriptorTimeStamp; } private Map getRefactoringArguments(RefactoringDescriptor refactoringDescriptor) { Map arguments= null; RefactoringContribution refactoringContribution= RefactoringContributionManager.getInstance().getRefactoringContribution(refactoringDescriptor.getID()); if (refactoringContribution != null) arguments= refactoringContribution.retrieveArgumentMap(refactoringDescriptor); else if (refactoringDescriptor instanceof DefaultRefactoringDescriptor) arguments= ((DefaultRefactoringDescriptor)refactoringDescriptor).getArguments(); return arguments; } private void initializeArguments(Map refactoringArguments) { if (refactoringArguments != null) { for (Object key : refactoringArguments.keySet()) { Object value= refactoringArguments.get(key); arguments.put(key.toString(), value.toString()); } } } @Override protected void populateTextChunk(OperationTextChunk textChunk) { int modeOrdinal= refactoringMode.ordinal(); textChunk.append(shouldAlwaysReplay ? shouldAlwaysReplayOffset + modeOrdinal : modeOrdinal); textChunk.append(id); textChunk.append(project); textChunk.append(flags); textChunk.append(arguments.size()); for (Entry<String, String> argumentEntry : arguments.entrySet()) { textChunk.append(argumentEntry.getKey()); textChunk.append(argumentEntry.getValue()); } } @Override protected void initializeFrom(OperationLexer operationLexer) { int offsetModeOrdinal= operationLexer.readInt(); if (offsetModeOrdinal >= shouldAlwaysReplayOffset) { offsetModeOrdinal-= shouldAlwaysReplayOffset; shouldAlwaysReplay= true; } refactoringMode= RefactoringMode.values()[offsetModeOrdinal]; id= operationLexer.readString(); project= operationLexer.readString(); flags= operationLexer.readInt(); int argumentsCount= operationLexer.readInt(); for (int i= 0; i < argumentsCount; i++) { arguments.put(operationLexer.readString(), operationLexer.readString()); } } @Override public void replay() throws CoreException { if (shouldAlwaysReplay || Configuration.isInTestMode) { //replay only older refactorings or if in the test mode isReplayedRefactoring= true; switch (refactoringMode) { case PERFORM: RefactoringDescriptor refactoringDescriptor= createRefactoringDescriptor(); if (refactoringDescriptor != null) { replayPerform(refactoringDescriptor); } break; case UNDO: replayUndo(); break; case REDO: replayRedo(); break; } } } private RefactoringDescriptor createRefactoringDescriptor() throws CoreException { RefactoringContribution refactoringContribution= RefactoringCore.getRefactoringContribution(id); if (refactoringContribution == null) { Debugger.debugWarning("Failed to get refactoring contribution for id: " + id); return null; } Map<String, String> refactoringArguments= arguments; //Special preprocessing for rename source folder refactoring if ("org.eclipse.jdt.ui.rename.source.folder".equals(id)) { //Add an argument 'path' that is expected by Eclipse refactoringArguments= new TreeMap<String, String>(); //create a new map to keep the original map intact refactoringArguments.putAll(arguments); refactoringArguments.put("path", "/" + project + refactoringArguments.get("input")); //Add this folder to the build path (i.e. make it to be "source folder") IJavaProject javaProject= JavaProjectsUpkeeper.findOrCreateJavaProject(project); JavaProjectHelper.addSourceContainer(javaProject, refactoringArguments.get("input").substring((1))); } return refactoringContribution.createDescriptor(id, project.isEmpty() ? null : project, "Recorded refactoring", "", refactoringArguments, flags); } private void replayPerform(RefactoringDescriptor refactoringDescriptor) throws CoreException { try { //TODO: This is a temporary hack. It is required to overcome the problem that sometimes Eclipse does not finish updating //program's structure yet, and thus, the refactoring can not be properly initialized (i.e. the refactored element is not found). //Find a better solution, e.g. listen for some Eclipse "refreshing" process to complete. Thread.sleep(2000); } catch (InterruptedException e) { //do nothing } RefactoringStatus initializationStatus= new RefactoringStatus(); Refactoring refactoring= refactoringDescriptor.createRefactoring(initializationStatus); if (initializationStatus.hasError()) { Debugger.debugWarning("Failed to initialize a refactoring from its descriptor: " + refactoringDescriptor); unperformedRefactorings.add(getTime()); return; } //This remove is needed to ensure that multiple replays in the same run do not overlap unperformedRefactorings.remove(getTime()); PerformRefactoringOperation performRefactoringOperation= new PerformRefactoringOperation(refactoring, CheckConditionsOperation.ALL_CONDITIONS); performRefactoringOperation.run(null); checkExecutionStatus(refactoring.getName(), performRefactoringOperation); } private void checkExecutionStatus(String refactoringName, PerformRefactoringOperation performRefactoringOperation) { if (performRefactoringOperation.getConditionStatus().hasFatalError()) { throw new RuntimeException("Failed to check preconditions of refactoring: " + refactoringName); } if (performRefactoringOperation.getValidationStatus().hasFatalError()) { throw new RuntimeException("Failed to validate refactoring: " + refactoringName); } } private void replayUndo() throws CoreException { if (!unperformedRefactorings.contains(getTime())) { getRefactoringUndoManager().performUndo(null, null); } } private void replayRedo() throws CoreException { if (!unperformedRefactorings.contains(getTime())) { getRefactoringUndoManager().performRedo(null, null); } } private IUndoManager getRefactoringUndoManager() { return RefactoringCore.getUndoManager(); } @Override public String toString() { StringBuffer sb= new StringBuffer(); sb.append("Should always replay: " + shouldAlwaysReplay + "\n"); sb.append("Refactoring mode: " + refactoringMode + "\n"); sb.append("ID: " + id + "\n"); sb.append("Project: " + project + "\n"); sb.append("Flags: " + flags + "\n"); sb.append("Arguments count: " + arguments.size() + "\n"); for (Entry<String, String> argumentEntry : arguments.entrySet()) { sb.append("Key: " + argumentEntry.getKey() + "\n"); sb.append("Value: " + argumentEntry.getValue() + "\n"); } sb.append(super.toString()); return sb.toString(); } }