/******************************************************************************* | |
* Copyright (c) 2004-2010 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 org.eclipse.viatra2.core.notification.ICoreNotificationObject; | |
import org.eclipse.viatra2.core.notification.NotificationType; | |
import org.eclipse.viatra2.core.tracebased.tracetree.AbstractTraceTreeNode; | |
import org.eclipse.viatra2.core.tracebased.tracetree.AtomicTraceTreeNode; | |
import org.eclipse.viatra2.core.tracebased.tracetree.CompositeTraceTreeNode; | |
import org.eclipse.viatra2.core.tracebased.tracetree.EmptyTraceTreeNode; | |
import org.eclipse.viatra2.core.tracebased.tracetree.ITraceTreeNode; | |
import org.eclipse.viatra2.core.tracebased.tracetree.TransactionTraceTreeNode; | |
/** | |
* Supplementary class for handling new notifications and inserting them into the | |
* trace tree. An instance is connected to a TraceTreeManager that contains the trace tree | |
* itself for a given VIATRA framework. | |
* | |
* @author Abel Hegedus | |
* | |
*/ | |
public class TraceNotificationRegister { | |
private boolean enabled = false; | |
private long id = 0; | |
private TraceTreeManager manager; | |
/** | |
* Instantiate the register with a given TraceTreeManager instance | |
*/ | |
public TraceNotificationRegister(TraceTreeManager manager) { | |
this.manager = manager; | |
} | |
/** | |
* Register a given notification into the trace tree. The method ensures that the trace tree | |
* remains consistent after inserting the notification. | |
* | |
* @param notification the notification to register | |
*/ | |
public void registerNotification(ICoreNotificationObject notification) { | |
// not actual event, return | |
if(!enabled && !notification.getActionTypeEnum().equals(NotificationType.ACTION_SKIP_NOTIFICATIONS_END) | |
&& !notification.getActionTypeEnum().equals(NotificationType.TA_UNDO_END)){ | |
return; | |
} | |
ITraceTreeNode current = manager.getCurrent(); | |
if(current == null && manager.getRoot() == null){ | |
current = new EmptyTraceTreeNode(null, createID("EmptyTTN")); | |
manager.setRoot(current); | |
manager.setCurrent(current); | |
} | |
try { | |
handleEvent(notification, current); | |
} catch (TraceException e) { | |
// TODO Auto-generated catch block | |
e.printStackTrace(); | |
} | |
} | |
/** | |
* Set to false if the following notifications should not be registered (e.g. undo or redo) | |
* | |
* @param enabled the enabled to set | |
*/ | |
public void setEnabled(boolean enabled) { | |
this.enabled = enabled; | |
} | |
/** | |
* True if notifications are registered in the trace tree at the moment | |
* | |
* @return the enabled | |
*/ | |
public boolean isEnabled() { | |
return enabled; | |
} | |
/** Handles the different notifications by type with the following guidelines:<p> | |
Starts composite always, ends previous one:<p> | |
<li>TA_UNDOABLE_TRANSACTION_BEGIN | |
<li>TA_TRANSACTION_BEGIN | |
<li>TA_SUBTRANSACTION_BEGIN<p> | |
Starts composite (if not in one):<p> | |
<li>ACTION_CREATE_ENTITY | |
<li>ACTION_DELETE_ENTITY | |
<li>ACTION_CREATE_RELATION | |
<li>ACTION_DELETE_RELATION | |
<li>ACTION_SET_RELATION_FROM | |
<li>ACTION_SET_RELATION_TO | |
<li>ACTION_CREATE_INSTANCEOF | |
<li>ACTION_DELETE_INSTANCEOF | |
<li>ACTION_CREATE_SUPERTYPEOF | |
<li>ACTION_DELETE_SUPERTYPEOF | |
<li>ACTION_DELETE_CONTAINMENT | |
<li>ACTION_SET_VALUE | |
<li>ACTION_SET_VIEW_INFO | |
<li>ACTION_SET_NAME | |
<li>ACTION_SET_ISFINALTYPE | |
<li>ACTION_SET_ISANY | |
<li>ACTION_MOVE_ELEMENT_TO | |
<li>ACTION_SET_RELATION_INVERSE | |
<li>ACTION_SET_RELATION_ISAGGREGATION | |
<li>ACTION_SET_RELATION_MULTIPLICITY<p> | |
Ends composite (if not transaction):<p> | |
<li>ACTION_ATOMIC_STEP_READY<p> | |
Ends composite (if transaction):<p> | |
<li>TA_SUBTRANSACTION_END | |
<li>TA_TRANSACTION_END<p> | |
Only in composite (otherwise error):<p> | |
<li>USER_MARK<p> | |
Do not store, handle otherwise:<p> | |
<li>TA_UNDO_BEGIN | |
<li>TA_UNDO_END | |
<li>TA_TRANSACTION_ABORT | |
<li>TA_SUBTRANSACTION_ABORT<p> | |
Should not happen:<p> | |
<li>ACTION_MORE_ATOMICS_TO_ONE_ATOM_START | |
<li>ACTION_MORE_ATOMICS_TO_ONE_ATOM_END | |
<li>ACTION_SKIP_NOTIFICATIONS_START | |
<li>ACTION_SKIP_NOTIFICATIONS_END | |
@param event | |
@param current | |
@return the new trace tree node created for the event, otherwise null | |
@throws TraceException if the received notification is not acceptable | |
or is inconsistent with the trace tree | |
*/ | |
private ITraceTreeNode handleEvent(ICoreNotificationObject event, ITraceTreeNode current) throws TraceException{ | |
boolean subtransaction = false, undoable = false; | |
ITraceTreeNode newNode = null; | |
switch(event.getActionTypeEnum()){ | |
// ============ STARTING TRANSACTION =========================================== | |
case TA_SUBTRANSACTION_BEGIN: // FIXME subtransaction maybe not needed in new transaction manager | |
//subtransaction = true; | |
throw new TraceException("This notification should not be used by the transaction manager"); | |
case TA_UNDOABLE_TRANSACTION_BEGIN: // FIXME all transactions are undoable now | |
//(or make only undoable transactions recorded) | |
undoable = true; | |
// fall-through | |
case TA_TRANSACTION_BEGIN: | |
// start new transaction | |
newNode = startTransaction(event, current, undoable); | |
break; | |
// ============ USER MARK IN TRANSACTION ======================================= | |
case USER_MARK: // FIXME probably won't need it in the new transaction engine | |
// store the user mark inside a transaction | |
storeUserMark(event, current); | |
break; | |
// ============ TERMINATING COMPOSITE =========================================== | |
case ACTION_ATOMIC_STEP_READY: | |
// close atomic step or keep going in transaction | |
endAtomicStep(event, current); | |
break; | |
case TA_SUBTRANSACTION_END: | |
//subtransaction = true; | |
throw new TraceException("This notification should not be used by the transaction manager"); | |
case TA_TRANSACTION_END: | |
// terminate transaction | |
newNode = endTransaction(event, current); | |
break; | |
// ============ ATOMIC ACTIONS ================================================= | |
case ACTION_CREATE_ENTITY: | |
case ACTION_DELETE_ENTITY: | |
case ACTION_CREATE_RELATION: | |
case ACTION_DELETE_RELATION: | |
case ACTION_SET_RELATION_FROM: | |
case ACTION_SET_RELATION_TO: | |
case ACTION_CREATE_INSTANCEOF: | |
case ACTION_DELETE_INSTANCEOF: | |
case ACTION_CREATE_SUPERTYPEOF: | |
case ACTION_DELETE_SUPERTYPEOF: | |
case ACTION_DELETE_CONTAINMENT: | |
case ACTION_SET_VALUE: | |
case ACTION_SET_VIEW_INFO: | |
case ACTION_SET_NAME: | |
case ACTION_SET_ISFINALTYPE: | |
case ACTION_SET_ISANY: | |
case ACTION_MOVE_ELEMENT_TO: | |
case ACTION_SET_RELATION_INVERSE: | |
case ACTION_SET_RELATION_ISAGGREGATION: | |
case ACTION_SET_RELATION_MULTIPLICITY: | |
newNode = handleAction(event, current); | |
break; | |
// ============ MORE ATOMICS =================================================== | |
case ACTION_MORE_ATOMICS_TO_ONE_ATOM_START: | |
throw new TraceException("This notification must be supressed by the notification manager"); | |
case ACTION_MORE_ATOMICS_TO_ONE_ATOM_END: | |
throw new TraceException("This notification must be supressed by the notification manager"); | |
// ============ TRANSACTION UNDO AND ABORT ====================================== | |
case TA_UNDO_BEGIN: | |
// during undo, notifications are dropped | |
enabled = false; | |
undoBegin(current); | |
break; | |
case TA_UNDO_END: | |
// after undo, notifications are handled again | |
enabled = true; | |
break; | |
case TA_SUBTRANSACTION_ABORT: | |
throw new TraceException("This notification should not be used by the transaction manager"); | |
//subtransaction = true; | |
case TA_TRANSACTION_ABORT: | |
// FIXME in the current implementation aborted transactions are left hanging | |
// FIXME new transaction engine should use better abort semantics | |
// if in transaction, set it to aborted and terminated | |
if(current instanceof TransactionTraceTreeNode){ | |
TransactionTraceTreeNode tttn = (TransactionTraceTreeNode) current; | |
if(!tttn.isTerminated()){ | |
tttn.getEvents().add(event); | |
tttn.setAborted(true); | |
tttn.setTerminated(true); | |
if(subtransaction){ | |
if(tttn.isSubTransaction()){ | |
// event is stored twice (but this will help recognizing continuing transactions) | |
newNode = new TransactionTraceTreeNode(tttn, createID("TTTN"), event, false); | |
} else { | |
throw new TraceException("Subtransaction abort called for non-sub transaction"); | |
} | |
} | |
} else { | |
throw new TraceException("Abort called for terminated transaction"); | |
} | |
} else { | |
throw new TraceException("Abort called outside of transaction"); | |
} | |
break; | |
// ============ SKIP NOTIFICATIONS ============================================== | |
// FIXME not used yet | |
// FIXME if used, make sure that there start and end is not in different transactions | |
case ACTION_SKIP_NOTIFICATIONS_START: | |
// add event to composite or create new | |
newNode = skipNotificationStart(event, current); | |
break; | |
case ACTION_SKIP_NOTIFICATIONS_END: | |
skipNotificationEnd(event, current); | |
break; | |
} | |
if(newNode != null){ | |
manager.setCurrent(newNode); | |
} | |
return null; | |
} | |
/** | |
* Handles regular model manipulation action notifications | |
* | |
* @param event | |
* @param current | |
* @return the new atomic trace tree node if the event started a new one | |
*/ | |
private ITraceTreeNode handleAction(ICoreNotificationObject event, | |
ITraceTreeNode current) { | |
if(current instanceof CompositeTraceTreeNode){ | |
CompositeTraceTreeNode cttn = (CompositeTraceTreeNode) current; | |
// if composite and not terminated, add event | |
if(!cttn.isTerminated()){ | |
cttn.getEvents().add(event); | |
return null; | |
} else { | |
// otherwise create atomic composite | |
return new AtomicTraceTreeNode(cttn, createID("ATTN"), event); | |
} | |
} else { | |
return new AtomicTraceTreeNode(current, createID("ATTN"), event); | |
} | |
} | |
/** | |
* Handles the beginning of an undo event | |
* | |
* @param current | |
*/ | |
private void undoBegin(ITraceTreeNode current) { | |
if(current instanceof CompositeTraceTreeNode){ | |
CompositeTraceTreeNode cttn = (CompositeTraceTreeNode) current; | |
// composite nodes are terminated before undo, independently of their state | |
if(!cttn.isTerminated()){ | |
cttn.setTerminated(true); | |
} | |
} | |
} | |
/** | |
* Handles the event for ending a phase for skipping notifications. | |
* | |
* @param event | |
* @param current | |
* @throws TraceException if the notification is received while registering notifications, | |
* or while in a terminated composite trace tree node, or while outside of a composite trace tree node. | |
*/ | |
private void skipNotificationEnd(ICoreNotificationObject event, | |
ITraceTreeNode current) throws TraceException { | |
if(enabled){// throw exception if not disabled | |
throw new TraceException("Skip notification end received while notifications enabled"); | |
} else if(current instanceof CompositeTraceTreeNode){ | |
// add event to composite | |
CompositeTraceTreeNode cttn = (CompositeTraceTreeNode) current; | |
if(!cttn.isTerminated()){ | |
cttn.getEvents().add(event); | |
if(cttn.getEvents().get(0).getActionTypeEnum().equals(NotificationType.ACTION_SKIP_NOTIFICATIONS_START)){ | |
cttn.setTerminated(true); | |
} | |
} else { | |
throw new TraceException("Skip notification end received in terminated composite"); | |
} | |
// change to enabled | |
enabled = true; | |
} else { | |
throw new TraceException("Skip notification end received outside composite"); | |
} | |
} | |
/** | |
* Handles the event for starting a phase for skipping notifications. | |
* | |
* @param event | |
* @param current | |
* @return the composite trace tree node storing the starting notification | |
*/ | |
private ITraceTreeNode skipNotificationStart(ICoreNotificationObject event, | |
ITraceTreeNode current) { | |
// change to disabled | |
enabled = false; | |
if(current instanceof CompositeTraceTreeNode){ | |
CompositeTraceTreeNode cttn = (CompositeTraceTreeNode) current; | |
if(!cttn.isTerminated()){ | |
cttn.getEvents().add(event); | |
return null; | |
} else { | |
return new CompositeTraceTreeNode(cttn, createID("CTTN"), event); | |
} | |
} else { | |
return new CompositeTraceTreeNode(current, createID("CTTN"), event); | |
} | |
} | |
/** | |
* Handles notifications that end a transaction. | |
* | |
* @param event | |
* @param current | |
* @return null, if top-level transaction ended, otherwise the new TransactionTraceTreeNode instance created for an upper level transaction | |
* @throws TraceException if the notification is received in a terminated transaction or outside a transaction. | |
*/ | |
private ITraceTreeNode endTransaction(ICoreNotificationObject event, | |
ITraceTreeNode current) throws TraceException { | |
if(current instanceof TransactionTraceTreeNode){ | |
TransactionTraceTreeNode tttn = (TransactionTraceTreeNode) current; | |
if(!tttn.isTerminated()){ | |
tttn.getEvents().add(event); | |
// terminate current node | |
tttn.setTerminated(true); | |
//if(subtransaction){ // FIXME subtransaction maybe not needed in new transaction manager | |
if(tttn.getLevel() > 0){ | |
// start new transaction trace node | |
// event is stored twice (but this will help recognizing continuing transactions) | |
return new TransactionTraceTreeNode(tttn, createID("TTTN"), event, false); | |
// } else { | |
// throw new TraceException("SubTransaction end received outside subtransaction"); | |
// } | |
} else { | |
// terminated the whole transaction | |
tttn.setCommitted(true); | |
return null; | |
} | |
} else { | |
throw new TraceException("Transaction end received for terminated transaction"); | |
} | |
} else { | |
throw new TraceException("Transaction end received outside transaction"); | |
} | |
} | |
/** | |
* Handles events which end an atomic step. | |
* | |
* @param event | |
* @param current | |
* @throws TraceException if the atomic step readysignal is received outside a composite trace tree node | |
*/ | |
private void endAtomicStep(ICoreNotificationObject event, | |
ITraceTreeNode current) throws TraceException { | |
if(current instanceof CompositeTraceTreeNode){ | |
CompositeTraceTreeNode cttn = (CompositeTraceTreeNode) current; | |
// NOTE: duplicate ATOMIC_STEP_READY notifications are ignored | |
if(!cttn.isTerminated()){ | |
cttn.getEvents().add(event); // FIXME do we need to store this notification? | |
if(current instanceof AtomicTraceTreeNode){ | |
AtomicTraceTreeNode attn = (AtomicTraceTreeNode) current; | |
// if more than one atomic steps are treated together, keep going | |
//if(!attn.isMoreThanOneAtomic()){ | |
// otherwise terminate node | |
attn.setTerminated(true); | |
//} | |
} | |
} | |
} else { | |
throw new TraceException("Atomic step ready received outside composite"); | |
} | |
} | |
/** | |
* Stores a user mark typed notification. | |
* | |
* @param event | |
* @param current | |
* @throws TraceException if a user mark is received in a terminated transaction or outside of a transaction. | |
*/ | |
private void storeUserMark(ICoreNotificationObject event, | |
ITraceTreeNode current) throws TraceException { | |
if(current instanceof TransactionTraceTreeNode){ | |
TransactionTraceTreeNode cttn = (TransactionTraceTreeNode) current; | |
if(!cttn.isTerminated()){ | |
cttn.getEvents().add(event); | |
} else { | |
throw new TraceException("User mark received in terminated transaction"); | |
} | |
} else { | |
throw new TraceException("User mark received outside of transaction"); | |
} | |
} | |
/** | |
* Called with notifications starting a new transaction. | |
* | |
* @param event | |
* @param current | |
* @param undoable | |
* @return the new TransactionTraceTreeNode instance created for the new transaction | |
*/ | |
private ITraceTreeNode startTransaction(ICoreNotificationObject event, | |
ITraceTreeNode current, boolean undoable) { | |
// if(current instanceof TransactionTraceTreeNode | |
// && !((TransactionTraceTreeNode) current).isTerminated()){ | |
// // FIXME start new subtransaction if in transaction | |
// throw new TraceException("Transaction begin received inside transaction"); | |
// } else { | |
boolean subtransaction = false; | |
// will begin transaction | |
// terminate actual composite | |
AbstractTraceTreeNode attn = (AbstractTraceTreeNode) current; | |
if(!attn.isTerminated()){ | |
attn.setTerminated(true); | |
} | |
if(current instanceof TransactionTraceTreeNode){ | |
// required for handling new subtransaction after undo | |
if(!((TransactionTraceTreeNode) current).isCommitted()){ | |
subtransaction = true; | |
} | |
} | |
// start new subtransaction if in transaction (last parameter decides) | |
return new TransactionTraceTreeNode(attn,createID("TTTN"),event,subtransaction, undoable); | |
// } | |
} | |
/** | |
* Encapsulates id creation | |
* | |
* @param type Type string for trace tree node to create | |
* @return the id string for the node | |
*/ | |
private String createID(String type){ | |
String idS = String.format("%s_%d", type, id); | |
id++; | |
return idS; | |
} | |
} |