/******************************************************************************* | |
* Copyright (c) 2004-2011 Abel Hegedus and Daniel Varro | |
* 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: | |
* Abel Hegedus - initial API and implementation | |
*******************************************************************************/ | |
package org.eclipse.viatra2.core.tracebased; | |
import java.util.ArrayList; | |
import java.util.Collections; | |
import java.util.List; | |
import org.eclipse.viatra2.core.IModelSpace; | |
import org.eclipse.viatra2.core.IUndoManager; | |
import org.eclipse.viatra2.core.notification.ICoreNotificationObject; | |
import org.eclipse.viatra2.core.notification.ICoreNotificationObjectTransactionEnd; | |
import org.eclipse.viatra2.core.simple.notification.NotificationObjectUserMark; | |
import org.eclipse.viatra2.core.tracebased.tracetree.ITraceTreeNode; | |
import org.eclipse.viatra2.core.tracebased.tracetree.TransactionTraceTreeNode; | |
import org.eclipse.viatra2.errors.VPMRuntimeException; | |
import org.eclipse.viatra2.logger.Logger; | |
/** | |
* @author Abel Hegedus | |
* | |
*/ | |
public class TraceUndoManager implements IUndoManager { | |
TraceModelSpace modelSpace; | |
TraceModelManager mManager; | |
Logger logger; | |
@Override | |
public void init(Logger l, IModelSpace m) throws VPMRuntimeException { | |
logger = l; | |
modelSpace = (TraceModelSpace) m; | |
mManager = (TraceModelManager) modelSpace.getModelManager(); | |
} | |
@Override | |
public void nextUndoBlock(String name) { | |
// FIXME only for legacy reasons, transaction ID is the user mark | |
((TraceNotificationManager) modelSpace.getNotificationManager()).sendNotification(new NotificationObjectUserMark(name)); | |
} | |
@Override | |
public boolean canUndo() { | |
return !getUndoInfoFromTraceTree(true, 1).isEmpty(); | |
} | |
@Override | |
public List<String> getUndoList(int n) { | |
return getUndoInfoFromTraceTree(true, n); | |
} | |
@Override | |
public void flushUndoBuffer() { | |
logger.info("Trace based undo manager has no buffer"); | |
} | |
@Override | |
public void undo() { | |
undoLastTransaction(); | |
} | |
@Override | |
public void undo(int n) { | |
for (int i = 0; i < n; ++i) | |
undo(); | |
} | |
@Override | |
public void undo(String markName) { | |
String last = null; | |
while((last = undoLastTransaction()) != null){ | |
if(last.equals(markName)){ | |
break; | |
} | |
} | |
} | |
/** | |
* Transaction IDs are unique, therefore this method is useless, | |
* result is the same as calling undo(markName). | |
* <p/> | |
* Note: if multiple transactions with the same name are allowed, | |
* this method will undo at most n of them. | |
* | |
*/ | |
@Override | |
public void undo(String markName, int n) { | |
for (int i = 0; i < n; ++i) | |
undo(markName); | |
} | |
@Override | |
public List<String> getUndoableList() { | |
return getUndoInfoFromTraceTree(false, -1); | |
} | |
/** | |
* Undo the last undoable transaction and return the user mark (ID) | |
* @return the user mark (ID) of the transaction | |
*/ | |
private String undoLastTransaction() { | |
while(true){ | |
ITraceTreeNode current = modelSpace.getTraceManager().getCurrent(); | |
if(current != modelSpace.getTraceManager().getRoot()){ | |
try { | |
// undo trace tree node | |
current.undo(modelSpace.getFramework()); | |
} catch (TraceException e) { | |
logger.fatal("Error performing undo; message:" + e.getMessage()); | |
return null; | |
} | |
// this method doesn't ensure that only transaction | |
// nodes are processed! This is intentional. | |
if(current instanceof TransactionTraceTreeNode){ | |
// if undoable, return id | |
if(((TransactionTraceTreeNode) current).isUndoable()){ | |
return current.getID(); | |
} | |
} | |
} else { | |
// cannot undo from trace tree root | |
logger.fatal("Error performing undo, trace tree root reached"); | |
return null; | |
} | |
} | |
} | |
/** | |
* Collects string representation of undoable operations and user marks for undoable transactions | |
* | |
* @param onlyUserMarks if true, only the list of user marks are returned | |
* @param numberOfUndos if higher than 0, only the specified amount of undoable transactions are collected | |
* @return if onlyUserMarks is true, the list of user marks, otherwise the list of undoable operations | |
*/ | |
private List<String> getUndoInfoFromTraceTree(boolean onlyUserMarks, int numberOfUndos) { | |
List<String> userMarks = new ArrayList<String>(); | |
List<List<String>> operations = new ArrayList<List<String>>(); | |
// temporal store for partial and subtransactions | |
List<List<String>> potentials = new ArrayList<List<String>>(); | |
ITraceTreeNode current = modelSpace.getTraceManager().getCurrent(); | |
boolean userMark = false, partialTrans = false; | |
// go back through the trace hierarchy | |
while(current != null){ | |
// only transaction nodes are allowed | |
if(current instanceof TransactionTraceTreeNode){ | |
TransactionTraceTreeNode tttn = (TransactionTraceTreeNode) current; | |
List<String> events = new ArrayList<String>(); | |
// partial transactions are acceptable | |
if(tttn.getEvents().size() > 0 && tttn.getEvents().get(0) instanceof ICoreNotificationObjectTransactionEnd){ | |
partialTrans = true; | |
} | |
// undoable transactions have user marks | |
if(tttn.isUndoable()){ | |
userMark = true; | |
if(onlyUserMarks){ | |
// store user mark | |
userMarks.add(tttn.getID()); | |
if(numberOfUndos > 0 && userMarks.size() == numberOfUndos){ | |
break; | |
} | |
} | |
} | |
if(!onlyUserMarks){ | |
for (ICoreNotificationObject event : tttn.getEvents()) { | |
// copy event strings | |
events.add(event.toString()); | |
} | |
if(userMark){ | |
operations.addAll(potentials); | |
potentials.clear(); | |
operations.add(events); | |
} else if(partialTrans || tttn.isSubTransaction()){ | |
// an earlier undoable transaction will decide | |
potentials.add(events); | |
} else { | |
// if a transaction is not undoable, partial or subtransaction | |
break; | |
} | |
} | |
} else break; | |
current = current.getParent(); | |
} | |
if(onlyUserMarks){ | |
return userMarks; | |
} else { | |
List<String> resList = new ArrayList<String>(); | |
// operations are returned in execution order | |
Collections.reverse(operations); | |
for (List<String> list : operations) { | |
resList.addAll(list); | |
} | |
return resList; | |
} | |
} | |
} |