blob: 3f10a70bac5c545265153f097f2f233f34bd094c [file] [log] [blame]
/*******************************************************************************
* 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;
}
}