/*******************************************************************************
 * Copyright (c) 2005 IBM Corporation and others.
 * 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.bpel.ui.commands;

import java.util.Iterator;
import java.util.List;

import org.eclipse.bpel.ui.util.ModelHelper;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;

import org.eclipse.wst.wsdl.ExtensibilityElement;
import org.eclipse.wst.wsdl.ExtensibleElement;

/** 
 * Sets an extension property of a model object.  E.g. BPEL+ uses extensions to
 * add properties to existing BPEL model types.
 * 
 * Similarly to SetCommand, subclasses need to implement get() and set() in terms
 * of the particular extension property they set.  They must also implement
 * createExtension() to create an extension of the proper type, and
 * isTargetExtensionUnused() to determine if the extension contains no meaningful
 * information.
 * 
 * Using these methods, SetExtensionCommand will automatically create the extension
 * when it doesn't exist (removing it again on undo).  It will also remove extensions
 * which become unused (restoring them on undo).
 * 
 * Lastly, SetExtensionCommand touches the target model object's extension list as
 * part of every operation (even if it doesn't add or remove the extension), so that
 * Adapters listening to the target model object are notified.  Avoiding the need to
 * listen to the specific extension is helpful because the lifetimes of the extension
 * objects themselves vary depending on the values stored in them.
 */
public abstract class SetExtensionCommand extends SetCommand {

	protected ExtensibilityElement oldExt, newExt;
	protected ExtensibilityElement targetExt;
	protected EClass extClass;

	public SetExtensionCommand(EObject target, EClass extClass, Object newValue)  {
		super(target, newValue);
		this.extClass = extClass;
	}

	public ExtensibilityElement createExtension() {
		return (ExtensibilityElement)extClass.getEPackage().getEFactoryInstance().create(extClass);
	}
	
	/**
	 * Returns true if the extension object is currently meaningless and should be
	 * removed.  The default behaviour is to return true iff all properties (except
	 * for internal ExtensionImpl properties) have their default values. 
	 */
	public boolean isTargetExtensionUnused() {
		for (Iterator it = targetExt.eClass().getEAllStructuralFeatures().iterator(); it.hasNext(); ) {
			EStructuralFeature feature = (EStructuralFeature)it.next();

			// TODO: this is a hack!  there must be a better way
			if (feature.isTransient() && "elementType".equals(feature.getName())) continue; //$NON-NLS-1$
			
			Object defaultValue = feature.getDefaultValue();
			if (defaultValue == null) {
				if (targetExt.eGet(feature) != null) return false;
			} else {
				if (!defaultValue.equals(targetExt.eGet(feature))) return false;
			}
		}
		return true;
	}

	@Override
	public void doExecute() {
		targetExt = oldExt = ModelHelper.getExtensibilityElement(fTarget, extClass);
		if (targetExt != null)  {
			fOldValue = get();
		}

		targetExt = newExt = (oldExt==null)? createExtension() : oldExt;

		// Cause a touch *before* the set as well as after...
		// necessary for automatic undo/redo to work correctly here.
		List eeList = ((ExtensibleElement)fTarget).getEExtensibilityElements();
		if (newExt == oldExt) {
			// NOTE: the following line deliberately causes a touch in the containing
			// object.  This reduces the need for everybody to put adapters on the
			// extension objects (having them on the extended object usually suffices).
			if (oldExt != null)  eeList.set(eeList.indexOf(oldExt), newExt);
		}
				
		if (targetExt != null)  {
			set(fNewValue);
		}
		
		if (isTargetExtensionUnused()) newExt = null;

		if (newExt == oldExt) {
			// NOTE: the following line deliberately causes a touch in the containing
			// object.  This reduces the need for everybody to put adapters on the
			// extension objects (having them on the extended object usually suffices).
			if (oldExt != null)  eeList.set(eeList.indexOf(oldExt), newExt);
		} else {
			if (oldExt != null)  eeList.remove(oldExt);
			if (newExt != null)  eeList.add(newExt);
		}
	}
}
