code: /**
   * <copyright>
   * Copyright (c) 2010-2014 Henshin developers. 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
   * </copyright>
   */
  package org.eclipse.emf.henshin.interpreter.impl;
  
  import java.util.ArrayList;
  import java.util.List;
  
  import org.eclipse.emf.common.util.EList;
  import org.eclipse.emf.ecore.EAttribute;
  import org.eclipse.emf.ecore.EObject;
  import org.eclipse.emf.ecore.EReference;
  import org.eclipse.emf.ecore.EStructuralFeature;
  import org.eclipse.emf.henshin.interpreter.Change;
  import org.eclipse.emf.henshin.interpreter.EGraph;
  import org.eclipse.emf.henshin.interpreter.util.InterpreterUtil;
  
  /**
   * Default implementation of {@link Change} and its sub-interfaces.
   * @author Christian Krause
   */
  public abstract class ChangeImpl implements Change {
  
  	/**
  	 * Flag indicating whether warnings should be printed.
  	 */
  	public static boolean PRINT_WARNINGS = true;
  	
  	/** 
  	 * {@link EGraph} to be changed.
  	 */
  	protected final EGraph graph;
  
  	/**
  	 * Default constructor.
  	 * @param graph {@link EGraph} to be changed.
  	 */
  	public ChangeImpl(EGraph graph) {
  		this.graph = graph;
  	}
  	
  	/*
  	 * (non-Javadoc)
  	 * @see org.eclipse.emf.henshin.interpreter.Change#getEGraph()
  	 */
  	@Override
  	public EGraph getEGraph() {
  		return graph;
  	}
  	
  	/**
  	 * Default implementation of {@link ObjectChange}.
  	 * @author Christian Krause
  	 */
  	public static final class ObjectChangeImpl extends ChangeImpl implements ObjectChange {
  
  		private final EObject object;
  		private boolean create;
  		
  		public ObjectChangeImpl(EGraph graph, EObject object, boolean create) {
  			super(graph);
  			this.object = object;
  			this.create = create;
  		}
  
  		/*
  		 * (non-Javadoc)
  		 * @see org.eclipse.emf.henshin.interpreter.Change#applyAndReverse()
  		 */
  		@Override
  		public void applyAndReverse() {
  			if (create) {
  				graph.add(object);
  			} else {
  				graph.remove(object);
  			}
  			create = !create;
  		}
  
  		/*
  		 * (non-Javadoc)
  		 * @see org.eclipse.emf.henshin.interpreter.Change.ObjectChange#getObject()
  		 */
  		@Override
  		public EObject getObject() {
  			return object;
  		}
  
  		/*
  		 * (non-Javadoc)
  		 * @see org.eclipse.emf.henshin.interpreter.Change.ObjectChange#isCreate()
  		 */
  		@Override
  		public boolean isCreate() {
  			return create;
  		}
  		
  	}
  	
  	/**
  	 * Default implementation of {@link AttributeChange}.
  	 * @author Christian Krause
  	 */
  	public static final class AttributeChangeImpl extends ChangeImpl implements AttributeChange {
  
  		private final EObject object;
  		private EAttribute attribute;
  		private Object oldValue;
  		private Object newValue;
  		/**
  		 * The semantics of deleting an attribute is to unset it.
  		 */
  		private boolean delete;
  		private boolean initialized;
  		private boolean revert = false;
  
  		public AttributeChangeImpl(EGraph graph, EObject object, EAttribute attribute, Object newValue) {
  			super(graph);
  			this.object = object;
  			this.attribute = attribute;
  			this.newValue = newValue;
  			this.delete = false;
  			this.initialized = false;
  		}
  		
  		public AttributeChangeImpl(EGraph graph, EObject object, EAttribute attribute) {
  			this(graph, object, attribute, null);
  			this.delete = true;
  		}
  
  		/*
  		 * (non-Javadoc)
  		 * @see org.eclipse.emf.henshin.interpreter.Change#applyAndReverse()
  		 */
  		@SuppressWarnings({ "rawtypes", "unchecked" })
  		@Override
  		public void applyAndReverse() {
  			// Need to initialize?
  			 if (!initialized && attribute != null) { 
  				oldValue = object.eGet(attribute);
  				if (!delete 
  						&& ((oldValue==null && newValue==null) 
  								|| (oldValue!=null && oldValue.equals(newValue)))) {
  					attribute = null;
  				}
  			}
  			// Nothing to do?
  			if (attribute==null) {
  				return;
  			}
  			if (delete && !revert) {
  				object.eUnset(attribute);				
  			} else if (attribute.isMany()) {
  				List values = (List) object.eGet(attribute);
  				values.clear();
  				if (newValue instanceof List) {
  					values.addAll((List) newValue);
  				} else {
  					values.add(newValue);
  				}
  			} else {
  				object.eSet(attribute, newValue);
  			}
  			java.lang.Object dummy = oldValue;
  			oldValue = newValue;
  			newValue = dummy;
  			revert = !revert;
  		}
  
  		/*
  		 * (non-Javadoc)
  		 * @see org.eclipse.emf.henshin.interpreter.Change.AttributeChange#getObject()
  		 */
  		@Override
  		public EObject getObject() {
  			return object;
  		}
  	
  		/*
  		 * (non-Javadoc)
  		 * @see org.eclipse.emf.henshin.interpreter.Change.AttributeChange#getAttribute()
  		 */
  		@Override
  		public EAttribute getAttribute() {
  			return attribute;
  		}
  
  		/*
  		 * (non-Javadoc)
  		 * @see org.eclipse.emf.henshin.interpreter.Change.AttributeChange#getOldValue()
  		 */
  		@Override
  		public Object getOldValue() {
  			return oldValue;
  		}
  
  		/*
  		 * (non-Javadoc)
  		 * @see org.eclipse.emf.henshin.interpreter.Change.AttributeChange#getNewValue()
  		 */
  		@Override
  		public Object getNewValue() {
  			return newValue;
  		}		
  	}
  	
  	/**
  	 * Default implementation of {@link ReferenceChange}.
  	 * @author Christian Krause
  	 */
  	public static final class ReferenceChangeImpl extends ChangeImpl implements ReferenceChange {
  		
  		private final EObject source;
  		private EReference reference;
  		private int index;
  		private EObject target, oldTarget; 
  		private boolean create;
  		private boolean initialized;
  		
  		public ReferenceChangeImpl(EGraph graph, EObject source, EObject target, EReference reference, boolean create) {
  			super(graph);
  			this.source = source;
  			this.target = target;
  			this.create = create;
  			this.reference = reference;
  			this.initialized = false;
  		}
  
  		/*
  		 * (non-Javadoc)
  		 * @see org.eclipse.emf.henshin.interpreter.Change#applyAndReverse()
  		 */
  		@SuppressWarnings({ "rawtypes", "unchecked" })
  		@Override
  		public void applyAndReverse() {
  			// Need to initialize ?
  			if (!initialized) {
  				if (reference.isMany()) {
  					List values = (List) source.eGet(reference);
  					index = values.indexOf(target);
  					if ((create && index>=0) || (!create && index<0)) {
  						reference = null; // nothing to do
  					}
  					if (create && index<0) {
  						index = values.size(); // append the new element at the end
  					}
  					// TODO: add a warning for containment side effects
  				} else {
  					oldTarget = (EObject) source.eGet(reference);
  					// reference is redirected to the same target
  					if (((create && target==oldTarget) || (!create && target!=oldTarget))) {
  						if (PRINT_WARNINGS){
  							System.out.println("WARNING (Hidden step): recreating '" + reference.getName() + "'-edge from " +
  									InterpreterUtil.objectToString(source) + " to " +
  									InterpreterUtil.objectToString(oldTarget));
  						}
  						reference = null; // nothing to do
  					}
  					// reference is redirected to a new target
  					if (create && oldTarget!=null && reference!=null && PRINT_WARNINGS) {
  						System.out.println("WARNING (Side effect):  deleting '" + reference.getName() + "'-edge from " +
  											InterpreterUtil.objectToString(source) + " to " +
  											InterpreterUtil.objectToString(oldTarget));
  					}
  					// reference is containment and new child (target) already has a parent
  					if (create && reference!=null && reference.isContainment() && target.eContainer()!=null && PRINT_WARNINGS) {
  						System.out.println("WARNING (Side effect):  deleting '" + reference.getName() + "'-edge from " +
  											InterpreterUtil.objectToString(target.eContainer()) + " to " +
  											InterpreterUtil.objectToString(target));
  					}
  					if (!create) {
  						target = null; // we want to remove it
  					}
  				}
  				initialized = true;
  			}
  			// Nothing to do?
  			if (reference==null) {
  				return;
  			}
  			// Otherwise do the change:
  			if (reference.isMany()) {
  				List values = (List) source.eGet(reference);
  				if (create) {
  					values.add(index, target);
  				} else {
  					values.remove(index);
  				}
  				create = !create;
  			} else {
  				source.eSet(reference, target); // set the new target
  				EObject dummy = target; // switch target and old target
  				target = oldTarget;
  				oldTarget = dummy;
  			}
  		}
  
  		/*
  		 * (non-Javadoc)
  		 * @see org.eclipse.emf.henshin.interpreter.Change.ReferenceChange#getSource()
  		 */
  		@Override
  		public EObject getSource() {
  			return source;
  		}
  
  		/*
  		 * (non-Javadoc)
  		 * @see org.eclipse.emf.henshin.interpreter.Change.ReferenceChange#getTarget()
  		 */
  		@Override
  		public EObject getTarget() {
  			return target;
  		}
  
  		/*
  		 * (non-Javadoc)
  		 * @see org.eclipse.emf.henshin.interpreter.Change.ReferenceChange#getReference()
  		 */
  		@Override
  		public EReference getReference() {
  			return reference;
  		}
  
  		/*
  		 * (non-Javadoc)
  		 * @see org.eclipse.emf.henshin.interpreter.Change.ReferenceChange#isCreate()
  		 */
  		@Override
  		public boolean isCreate() {
  			return create;
  		}
  		
  	}
  
  	/**
  	 * Default implementation of {@link IndexChange}.
  	 * @author Christian Krause
  	 */
  	public static final class IndexChangeImpl extends ChangeImpl implements IndexChange {
  		
  		private final EObject source;
  		private final EObject target; 
  		private final EReference reference;
  		private int oldIndex;
  		private int newIndex;
  		private boolean initialized;
  		
  		public IndexChangeImpl(EGraph graph, EObject source, EObject target, EReference reference, int newIndex) {
  			super(graph);
  			this.source = source;
  			this.target = target;
  			this.reference = reference;
  			this.newIndex = newIndex;
  			this.initialized = false;
  		}
  
  		/*
  		 * (non-Javadoc)
  		 * @see org.eclipse.emf.henshin.interpreter.Change#applyAndReverse()
  		 */
  		@SuppressWarnings({ "rawtypes" })
  		@Override
  		public void applyAndReverse() {
  			// Need to initialize ?
  			if (!initialized) {
  				if (reference.isMany()) {
  					List values = (List) source.eGet(reference);
  					oldIndex = values.indexOf(target);
  				} else {
  					if (target==source.eGet(reference)) {
  						oldIndex = 0;
  					} else {
  						oldIndex = -1;
  					}
  				}
  				if (oldIndex<0) {
  					throw new RuntimeException("Error changing edge index for reference "
  							+ reference + " in object " + source + ": target " + target + " not found");
  				}
  				initialized = true;
  			}
  			// Try to move the element to the new position:
  			if (reference.isMany()) {
  				EList values = (EList) source.eGet(reference);
  				values.move(newIndex, oldIndex);
  			} else if (newIndex!=0) {
  				throw new RuntimeException("Cannot move element to position " + newIndex
  						+ " in the non-multi reference" + reference);
  			}
  			// Revert the change:
  			int dummy = oldIndex;
  			oldIndex = newIndex;
  			newIndex = dummy;
  		}
  
  		/*
  		 * (non-Javadoc)
  		 * @see org.eclipse.emf.henshin.interpreter.Change.IndexChange#getSource()
  		 */
  		@Override
  		public EObject getSource() {
  			return source;
  		}
  
  		/*
  		 * (non-Javadoc)
  		 * @see org.eclipse.emf.henshin.interpreter.Change.IndexChange#getTarget()
  		 */
  		@Override
  		public EObject getTarget() {
  			return target;
  		}
  
  		/*
  		 * (non-Javadoc)
  		 * @see org.eclipse.emf.henshin.interpreter.Change.IndexChange#getReference()
  		 */
  		@Override
  		public EReference getReference() {
  			return reference;
  		}
  
  		/*
  		 * (non-Javadoc)
  		 * @see org.eclipse.emf.henshin.interpreter.Change.IndexChange#getOldIndex()
  		 */
  		@Override
  		public int getOldIndex() {
  			return oldIndex;
  		}
  
  		/*
  		 * (non-Javadoc)
  		 * @see org.eclipse.emf.henshin.interpreter.Change.IndexChange#getNewIndex()
  		 */
  		@Override
  		public int getNewIndex() {
  			return newIndex;
  		}
  		
  	}
  	
  	/**
  	 * Default implementation of {@link CompoundChange}.
  	 * @author Christian Krause
  	 */
  	public static final class CompoundChangeImpl extends ChangeImpl implements CompoundChange {
  
  		// The list of changes:
  		private final List<Change> changes;
  		
  		// Whether to apply them in reverse order:
  		private boolean reverse;
  		
  		/**
  		 * Default constructor.
  		 * @param graph EGraph to be changed.
  		 */
  		public CompoundChangeImpl(EGraph graph) {
  			super(graph);
  			changes = new ArrayList<Change>();
  			reverse = false;
  		}
  
  		/*
  		 * (non-Javadoc)
  		 * @see org.eclipse.emf.henshin.interpreter.Change#applyAndReverse()
  		 */
  		@Override
  		public void applyAndReverse() {
  			int size = changes.size();
  			if (reverse) {
  				for (int i=size-1; i>=0; i--) {
  					changes.get(i).applyAndReverse();
  				}
  			} else {
  				for (int i=0; i<size; i++) {
  					changes.get(i).applyAndReverse();
  				}
  			}
  			reverse = !reverse;
  		}
  
  		/*
  		 * (non-Javadoc)
  		 * @see org.eclipse.emf.henshin.interpreter.Change.ComplexChange#getChanges()
  		 */
  		@Override
  		public List<Change> getChanges() {
  			return changes;
  		}
  		
  	}
  
  }
  