/*******************************************************************************
 * 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.model.util;

import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.xerces.dom.AttrImpl;
import org.apache.xerces.dom.DocumentImpl;
import org.apache.xerces.jaxp.DocumentBuilderFactoryImpl;
import org.eclipse.bpel.model.Correlation;
import org.eclipse.bpel.model.CorrelationSet;
import org.eclipse.bpel.model.CorrelationSets;
import org.eclipse.bpel.model.Expression;
import org.eclipse.bpel.model.FaultHandler;
import org.eclipse.bpel.model.From;
import org.eclipse.bpel.model.Import;
import org.eclipse.bpel.model.Invoke;
import org.eclipse.bpel.model.MessageExchange;
import org.eclipse.bpel.model.MessageExchanges;
import org.eclipse.bpel.model.OnEvent;
import org.eclipse.bpel.model.PartnerLink;
import org.eclipse.bpel.model.PartnerLinks;
import org.eclipse.bpel.model.Process;
import org.eclipse.bpel.model.Scope;
import org.eclipse.bpel.model.To;
import org.eclipse.bpel.model.adapters.AdapterRegistry;
import org.eclipse.bpel.model.adapters.INamespaceMap;
import org.eclipse.bpel.model.proxy.MessageProxy;
import org.eclipse.bpel.model.proxy.OperationProxy;
import org.eclipse.bpel.model.proxy.PortTypeProxy;
import org.eclipse.bpel.model.reordering.IExtensibilityElementListHandler;
import org.eclipse.bpel.model.resource.BPELResource;
import org.eclipse.bpel.model.resource.BPELResourceSetImpl;
import org.eclipse.bpel.names.NCNameWordDetector;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.wst.wsdl.ExtensibleElement;
import org.eclipse.wst.wsdl.Message;
import org.eclipse.wst.wsdl.Operation;
import org.eclipse.wst.wsdl.PortType;
import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

import com.ibm.wsdl.util.xml.DOM2Writer;
import com.ibm.wsdl.util.xml.DOMUtils;

/**
 * 
 * @author (IBM)
 * @author Michal Chmielewski (michal.chmielewski@oracle.com)
 * 
 * @date Feb 27, 2007
 * 
 */

@SuppressWarnings("nls")
public class BPELUtils {

	/** Namespace attribute */
	public static final String ATTR_XMLNS = "xmlns"; //$NON-NLS-1$

	/**
	 * Lookup an externally defined object by its QName by using a ref object
	 * (any object from the BPEL EMF model) using a refType and name.
	 * 
	 * This is a general query mechanism used by the import resolvers to resolve
	 * anything from the perspective of the BPEL process.
	 * 
	 * @param ref
	 *            the reference object
	 * @param qname
	 *            the QName to resolve.
	 * @param name
	 *            the name within the QName to resolve (can be null)
	 * @param refType
	 *            the thing to look up (look in WSDLUtil and XSDUtil for the
	 *            possible values)
	 * @return the looked up object (or null).
	 */

	static public EObject lookup(EObject ref, QName qname, String name,
			String refType) {

		Process process = getProcess(ref);
		if (process == null) {
			return null;
		}

		Iterator<?> it = process.getImports().iterator();
		EObject result = null;

		while (it.hasNext()) {
			Import imp = (Import) it.next();

			// The null and "" problem ...
			String ns = imp.getNamespace();
			if (ns == null) {
				ns = javax.xml.XMLConstants.DEFAULT_NS_PREFIX;
			}

			if (ns.equals(qname.getNamespaceURI()) == false
					|| imp.getLocation() == null) {
				continue;
			}

			ImportResolver[] resolvers = ImportResolverRegistry.INSTANCE
					.getResolvers(imp.getImportType());
			for (int i = 0; i < resolvers.length; i++) {
				result = resolvers[i].resolve(imp, qname, name, refType);
				if (result != null) {
					return result;
				}
			}
		}

		return result;
	}

	/**
	 * Get name namespace associated to the prefix.
	 * 
	 * @param eObject
	 *            the reference object
	 * @param prefix
	 *            the prefix to lookup
	 * @return the namespace associated with the prefix.
	 */

	public static String getNamespace(EObject eObject, String prefix) {

		Map<String, String> prefixNSMap = null;
		EObject context = eObject;

		while (context != null) {
			prefixNSMap = getNamespaceMap(context);
			String value = prefixNSMap.get(prefix);
			if (value != null) {
				return value;
			}
			context = context.eContainer();
		}
		return null;
	}

	/**
	 * Set the namespace prefix on the EObject passed. The namespace -> prefix
	 * mapping is defined on the object passed. Note that this is different then
	 * getPrefix(), where the prefix mapping is searched via the hierarchy of
	 * the EMF model.
	 * 
	 * @param eObject
	 *            an object at which level the prefix mapping ought to be set.
	 * @param namespaceURI
	 *            namespace URI
	 * @param prefix
	 *            the prefix.
	 * @throws RuntimeException
	 *             if the prefix is already set.
	 */

	@SuppressWarnings("unchecked")
	public static void setPrefix(EObject eObject, String namespaceURI,
			String prefix) {

		INamespaceMap<String, String> prefixNSMap = getNamespaceMap(eObject);
		if (prefixNSMap.containsKey(prefix)) {
			throw new RuntimeException("Prefix is already mapped!");
		}
		prefixNSMap.put(prefix, namespaceURI);
	}

	/**
	 * Given a starting object from within our EMF model return the prefix
	 * associated with the namespace passed. The object tree is traversed up to
	 * the root to find the associated mapping and first such association is
	 * returned.
	 * 
	 * @param eObject
	 *            the EMF object to start at.
	 * @param namespace
	 *            the XML namespace to query for prefix mapping
	 * @return the prefix name or null of no mapping exists
	 */

	public static String getNamespacePrefix(EObject eObject, String namespace) {

		for (EObject context = eObject; context != null; context = context
				.eContainer()) {
			List<String> pfxList = getNamespaceMap(context).getReverse(
					namespace);
			if (pfxList.size() > 0) {
				return pfxList.get(0);
			}
		}
		return null;
	}

	/**
	 * Given a starting object from within our EMF model set the prefix
	 * associated with the namespace passed. The object tree is traversed up to
	 * the root to find the associated mapping and first such association is
	 * returned.
	 * 
	 * @param eObject
	 *            the EMF object to start at.
	 * @param namespace
	 *            the XML namespace to query for prefix mapping
	 * @param prefix
	 *            the prefix to set
	 * 
	 */

	public static void setNamespacePrefix(EObject eObject, String namespace,
			String prefix) {
		setPrefix(getNearestScopeOrProcess(eObject), namespace, prefix);
	}

	/**
	 * @param eObject
	 * @return the namespace map for the given object.
	 */

	static public INamespaceMap<String, String> getNamespaceMap(EObject eObject) {

		if (eObject == null) {
			throw new NullPointerException(
					"eObject cannot be null in getNamespaceMap()");
		}

		INamespaceMap<String, String> nsMap = AdapterRegistry.INSTANCE.adapt(
				eObject, INamespaceMap.class);
		if (nsMap == null) {
			throw new IllegalStateException(
					"INamespaceMap cannot be attached to an eObject");
		}

		return nsMap;
	}

	/**
	 * Return the process object, the root of the EMF BPEL model, from the
	 * reference object passed.
	 * 
	 * @param eObj
	 *            the reference object.
	 * @return the process object, or null
	 */

	static public Process getProcess(EObject eObj) {
		EObject context = eObj;

		while (context != null) {
			if (context instanceof Process) {
				return (Process) context;
			}
			context = context.eContainer();
		}
		return null;
	}

	/**
	 * Return the closest Scope or Process objects from the EMF object
	 * hierarchy. We use this code to assign namespace prefix mappings when they
	 * don't exist.
	 * 
	 * @param eObject
	 * @return closest scope or process object.
	 */

	public static EObject getNearestScopeOrProcess(EObject eObject) {

		EObject context = eObject;
		while (context != null) {
			if (context instanceof Scope) {
				return context;
			}
			if (context instanceof Process) {
				return context;
			}
			context = context.eContainer();
		}
		return null;
	}

	/**
	 * Return all global and local namespaces of this context
	 * 
	 * @param eObject
	 *            reference object
	 * @return the prefix map.
	 */

	public static Map<String, String> getAllNamespacesForContext(EObject eObject) {
		Map<String, String> nsMap = new HashMap<String, String>();
		EObject context = eObject;

		while (context != null) {
			Map<String, String> localNSMap = getNamespaceMap(context);
			for (Entry<String, String> entry : localNSMap.entrySet()) {
				if (!nsMap.containsKey(entry.getKey())) {
					nsMap.put(entry.getKey(), entry.getValue());
				}
			}
			context = context.eContainer();
		}
		return nsMap;
	}

	/**
	 * Reorder extensibility list.
	 * 
	 * @param extensibilityElementListHandlers
	 * @param parent
	 * @return the reordered list.
	 */

	public static List<ExtensibleElement> reorderExtensibilityList(
			List<IExtensibilityElementListHandler> extensibilityElementListHandlers,
			ExtensibleElement parent) {

		List<ExtensibleElement> tempExtensibilityElementList = new ArrayList<ExtensibleElement>();
		tempExtensibilityElementList.addAll(parent.getExtensibilityElements());

		if (extensibilityElementListHandlers.isEmpty()
				|| parent.getExtensibilityElements() == null
				|| parent.getExtensibilityElements().size() <= 1)
			return tempExtensibilityElementList;

		for (IExtensibilityElementListHandler element : extensibilityElementListHandlers) {
			element.orderList(parent, tempExtensibilityElementList);
		}

		return tempExtensibilityElementList;
	}

	/**
	 * Convert string to a BPEL DOM node.
	 * 
	 * @param s
	 *            the string
	 * @param bpelResource
	 *            the BPEL resource
	 * @return the node
	 */

	public static Node convertStringToNode(EObject parent, String s, BPELResource bpelResource) {
		// Create DOM document
		DocumentBuilderFactory factory = new DocumentBuilderFactoryImpl();
		factory.setNamespaceAware(true);
		factory.setValidating(false);

		StringBuilder namespaces = new StringBuilder();
		Map<String, String> nsMap = getAllNamespacesForContext(parent);		
		for (Entry<String, String> e : nsMap.entrySet()) {
			String prefix = e.getKey();
			String value = e.getValue();
			if (BPELConstants.isBPELNamespace(value)) {
				continue;
			}
			if (prefix != "") {
				namespaces.append("xmlns:");
				namespaces.append(prefix);
			} else {
				namespaces.append("xmlns");
			}
			namespaces.append("=\"");
			namespaces.append(value);
			namespaces.append("\" ");
		}		
		
		String namespaceURI = bpelResource.getNamespaceURI();
		if (bpelResource.getOptionUseNSPrefix()) {
			String prefix = "bpws";
			s = "<" + prefix + ":from xmlns:" + prefix + "=\"" + namespaceURI + "\" "
					+ namespaces.toString() + ">" + s + "</" + prefix + ":from>";
		} else {
			s = "<from xmlns=\"" + namespaceURI + "\" " + namespaces.toString() + ">" + s + "</from>";
		}

		try {
			StringReader sr = new StringReader(s);
			DocumentBuilder builder = factory.newDocumentBuilder();
			InputSource source = new InputSource(sr);
			source.setEncoding("UTF8");
			Document document = builder.parse(source);
			return document.getDocumentElement();
		} catch (Exception e) {
			return null;
		}

	}

	/**
	 * Convert an element to string.
	 * 
	 * @param element
	 *            the element
	 * @return string version of the element XML source
	 */

	public static String elementToString(Element element) {
		StringWriter writer = new StringWriter();
		DOM2Writer.serializeAsXML(element, writer);
		return writer.getBuffer().toString();
	}

	/**
	 * Create attribute value.
	 * 
	 * @param element
	 * @param attribute
	 * @return QName for the attribute value.
	 */

	public static QName createAttributeValue(Element element, String attribute) {
		String prefixedValue = element.getAttribute(attribute);
		return createQName(element, prefixedValue);
	}

	/**
	 * Create QName
	 * 
	 * @param element
	 *            the element used as a reference for namespace reference.
	 * @param prefixedValue
	 *            the prefixed value (NCName as a QName, that is "foo:bar")
	 * @return the QName created
	 */

	public static QName createQName(Element element, String prefixedValue) {
		int index = prefixedValue.indexOf(':');
		String prefix = (index != -1) ? prefixedValue.substring(0, index)
				: null;
		String localPart = prefixedValue.substring(index + 1);
		String namespaceURI = DOMUtils.getNamespaceURIFromPrefix(element,
				prefix);

		// namespaceURI may be null. That's okay.
		return new QName(namespaceURI, localPart);
	}

	/**
	 * Return a partner link whose name is partnerLinkName.
	 * 
	 * @param eObject
	 *            the reference object
	 * @param partnerLinkName
	 *            the partner link name.
	 * @return the PartnerLink or null if one does not exist.
	 */

	public static PartnerLink getPartnerLink(EObject eObject,
			String partnerLinkName) {

		EObject container = eObject;

		while (container != null) {

			PartnerLinks partnerLinks = null;

			if (container instanceof Process) {
				partnerLinks = ((Process) container).getPartnerLinks();
			} else if (container instanceof Scope) {
				partnerLinks = ((Scope) container).getPartnerLinks();
			}

			if (partnerLinks != null) {
				for (PartnerLink pl : partnerLinks.getChildren()) {
					if (pl.getName().equals(partnerLinkName)) {
						return pl;
					}
				}
			}

			container = container.eContainer();
		}
		return null;
	}

	/**
	 * Return a message exchange whose name is messageExchangeName.
	 * 
	 * @param eObject
	 *            the reference object
	 * @param messageExchangeName
	 *            the message exchange name.
	 * @return the MessageExchange or null if one does not exist.
	 */

	public static MessageExchange getMessageExchange(EObject eObject,
			String messageExchangeName) {

		EObject container = eObject;

		while (container != null) {

			MessageExchanges messageExchanges = null;

			if (container instanceof Process) {
				messageExchanges = ((Process) container).getMessageExchanges();
			} else if (container instanceof Scope) {
				messageExchanges = ((Scope) container).getMessageExchanges();
			}

			if (messageExchanges != null) {
				for (MessageExchange me : messageExchanges.getChildren()) {
					if (me.getName().equals(messageExchangeName)) {
						return me;
					}
				}
			}
			container = container.eContainer();
		}
		return null;
	}

	/**
	 * Get the correlation set for an activity.
	 * 
	 * @param correlation
	 *            the correlation object
	 * @param correlationSetName
	 *            the name of the correlation set
	 * @return the correlation set for that activity.
	 */

	public static CorrelationSet getCorrelationSetForActivity(
			Correlation correlation, String correlationSetName) {
		EObject container = correlation.eContainer();

		while (container != null) {
			CorrelationSets correlationSets = null;
			if (container instanceof Process) {
				correlationSets = ((Process) container).getCorrelationSets();
			} else if (container instanceof Scope) {
				correlationSets = ((Scope) container).getCorrelationSets();
			} else if (container instanceof OnEvent) {
				correlationSets = ((OnEvent) container).getCorrelationSets();
			}

			if (correlationSets != null) {
				for (CorrelationSet correlationSet : correlationSets
						.getChildren()) {
					if (correlationSet.getName().equals(correlationSetName)) {
						return correlationSet;
					}
				}
			}
			container = container.eContainer();
		}
		return null;
	}

	/**
	 * Map default namespace attribute "xmlns" to the empty key "" in the
	 * prefix-to-namespace map.
	 * 
	 * @param attrName
	 *            attribute name
	 * @return the prefix map key
	 */
	public static final String getNSPrefixMapKey(final String attrName) {
		return ATTR_XMLNS.equals(attrName) ? "" : attrName;
	}

	/**
	 * Return a port type proxy for the given URI.
	 * 
	 * @param uri
	 *            the URI
	 * @param activityElement
	 *            the reference element for namespace lookups
	 * @param qnameAttribute
	 *            the QName (as string) representing the portType.
	 * @return the port type proxy.
	 */

	public static PortType getPortType(URI uri, Element activityElement,
			String qnameAttribute) {
		QName qName = createAttributeValue(activityElement, qnameAttribute);
		PortTypeProxy portType = new PortTypeProxy(uri, qName);
		return portType;
	}

	/**
	 * Return the message proxy for the given URI object.
	 * 
	 * @param uri
	 *            the URI
	 * @param activityElement
	 *            the reference element for namespace lookups
	 * @param qnameAttribute
	 *            the QName (as string) representing the message.
	 * @return the message proxy.
	 */

	public static Message getMessage(URI uri, Element activityElement,
			String qnameAttribute) {
		QName qName = createAttributeValue(activityElement, qnameAttribute);
		MessageProxy message = new MessageProxy(uri, qName);
		return message;
	}

	/**
	 * Return the operation proxy for the given URI object.
	 * 
	 * @param uri
	 *            the URI
	 * @param portType
	 *            the port type that has this operation.
	 * @param activityElement
	 *            the reference element for namespace lookups
	 * @param operationAttribute
	 *            the QName (as string) representing the operation.
	 * @return the message proxy.
	 */

	public static Operation getOperation(URI uri, PortType portType,
			Element activityElement, String operationAttribute) {
		if (!activityElement.hasAttribute(operationAttribute))
			return null;
		String operationSignature = activityElement
				.getAttribute(operationAttribute);
		Operation operation = new OperationProxy(uri, portType,
				operationSignature);
		return operation;
	}

	/**
	 * Tests if <code>node</code> is a DOM {@link Element} with a BPEL
	 * namespace.
	 * 
	 * @param node
	 *            the node to test
	 * @return true if BPEL element, false otherwise.
	 */

	public static final boolean isBPELElement(Node node) {
		return node != null && node.getNodeType() == Node.ELEMENT_NODE
				&& BPELConstants.isBPELNamespace(node.getNamespaceURI());
	}

	/**
	 * Boolean as XML version string.
	 * 
	 * @param b
	 *            boolean (true/false)
	 * @return "yes" or "no"
	 */

	public static String boolean2XML(Boolean b) {
		if (b == null || b.equals(Boolean.FALSE)) {
			return "no";
		}
		return "yes";
	}

	/**
	 * Boolean as XML version string.
	 * 
	 * @param b
	 *            boolean (true/false)
	 * @return "yes" or "no"
	 */

	public static String boolean2XML(boolean b) {
		if (!b) {
			return "no";
		}
		return "yes";
	}

	public static boolean xml2boolean(String xml) {
		return "yes".equals(xml);
	}

	/**
	 * Create CData section with the string value indicated.
	 * 
	 * @param document
	 * @param value
	 * @return the CData section.
	 */

	public static CDATASection createCDATASection(Document document,
			String value) {
		return document.createCDATASection(stripExtraCR(value));
	}

	/**
	 * Strip extra carriage returns.
	 * 
	 * @param value
	 * @return the "pretty" string
	 */

	public static String stripExtraCR(String value) {
		if (value == null) {
			return null;
		}
		StringBuffer result = new StringBuffer();
		char[] chars = value.toCharArray();
		for (int i = 0; i < chars.length; i++) {
			result.append(chars[i]);
			if (i < chars.length - 1) {
				if (chars[i] == '\r' && chars[i + 1] == '\n')
					i++;
			}
		}

		return result.toString();
	}

	/**
	 * This is a slightly hacked resource set that we will be using for to solve
	 * the problem of loading the right resources from URLs that betray no
	 * information on the type of the resource.
	 * 
	 * @param resourceSet
	 * 
	 * @return the BPELResourceSetImpl that walks around the problem indicated.
	 * 
	 */

	public static BPELResourceSetImpl slightlyHackedResourceSet(
			ResourceSet resourceSet) {

		if (resourceSet instanceof BPELResourceSetImpl) {
			return (BPELResourceSetImpl) resourceSet;
		}

		Map map = resourceSet.getLoadOptions();
		BPELResourceSetImpl result = (BPELResourceSetImpl) map
				.get(BPELResourceSetImpl.SLIGHTLY_HACKED_KEY);
		if (result == null) {
			result = new BPELResourceSetImpl();
			map.put(BPELResourceSetImpl.SLIGHTLY_HACKED_KEY, result);
		}
		return result;
	}

	/**
	 * Return the resource set that we should be using to load "specific" type
	 * of resources. The "slightlyHacked" resource set is kept in the load
	 * options map.
	 * 
	 * @param eObj
	 * @return the slightly hacked resource set.
	 * 
	 */
	public static BPELResourceSetImpl slightlyHackedResourceSet(EObject eObj) {
		return slightlyHackedResourceSet(eObj.eResource().getResourceSet());
	}

	static Map<String, Locale> string2Locale = new HashMap<String, Locale>();
	static Map<Locale, String> locale2String = new HashMap<Locale, String>();

	static {
		StringBuilder sb = new StringBuilder();
		for (Locale l : Locale.getAvailableLocales()) {
			sb.setLength(0);
			sb.append(l.getLanguage());
			if (isEmptyOrWhitespace(l.getCountry()) == false) {
				sb.append("-").append(l.getCountry());
			}

			String key = sb.toString().toLowerCase();

			string2Locale.put(key, l);
			locale2String.put(l, key);
		}
	}

	/**
	 * Lookup the locale for the key.
	 * 
	 * @param key
	 *            the key (language + country code)
	 * @return the locale or the default locale.
	 */

	public static Locale lookupLocaleFor(String key) {
		if (key == null) {
			return Locale.getDefault();
		}
		key = key.replace('_', '-').toLowerCase();
		Locale locale = string2Locale.get(key);
		if (locale == null) {
			locale = Locale.getDefault();
		}
		return locale;
	}

	/**
	 * Lookup the key for the locale.
	 * 
	 * @param locale
	 *            the locale
	 * @return the key associated with it.
	 */

	public static String lookupLocaleKeyFor(Locale locale) {
		if (locale == null) {
			return locale2String.get(Locale.getDefault());
		}
		String key = locale2String.get(locale);
		if (key == null) {
			key = locale2String.get(Locale.getDefault());
		}
		return key;
	}

	/**
	 * Returns true if the string is either null or contains just whitespace.
	 * 
	 * @param value
	 * @return true if empty or whitespace, false otherwise.
	 */

	static public boolean isEmptyOrWhitespace(String value) {
		if (value == null || value.length() == 0) {
			return true;
		}

		for (int i = 0, j = value.length(); i < j; i++) {
			if (!Character.isWhitespace(value.charAt(i))) {
				return false;
			}
		}
		return true;
	}

	static NCNameWordDetector NCNAME_DETECTOR = new NCNameWordDetector();

	/**
	 * Checks for a valid prefix name.
	 * 
	 * @param pfx
	 * @return true if valid, false if not.
	 */

	public static boolean isValidPrefixName(String pfx) {

		if (pfx == null) {
			return false;
		}

		if (NCNAME_DETECTOR.isValid(pfx) == false) {
			return false;
		}

		/** Must not start with (x|X)(m|M)(l|L) */

		return true;
	}

	public static boolean isTransparent(Object parent, Object child) {
		return (child instanceof FaultHandler && parent instanceof Invoke)
				|| (child instanceof Expression && parent instanceof From)
				|| (child instanceof Expression && parent instanceof To);
	}
	
	public static boolean isActivityNode(Node node) {
		String name = node.getLocalName();
		return isBPELElement(node) && 
			   (BPELConstants.ND_INVOKE.equals(name) ||
			   BPELConstants.ND_ASSIGN.equals(name) ||
			   BPELConstants.ND_WHILE.equals(name) ||
			   BPELConstants.ND_REPEAT_UNTIL.equals(name) ||
			   BPELConstants.ND_RECEIVE.equals(name) ||
			   BPELConstants.ND_REPLY.equals(name) ||
			   BPELConstants.ND_THROW.equals(name) ||
			   BPELConstants.ND_WAIT.equals(name) ||
			   BPELConstants.ND_SEQUENCE.equals(name) ||
			   BPELConstants.ND_PICK.equals(name) ||
			   BPELConstants.ND_FLOW.equals(name) ||
			   BPELConstants.ND_SCOPE.equals(name) ||
			   BPELConstants.ND_COMPENSATE.equals(name) ||
			   BPELConstants.ND_RETHROW.equals(name) ||
			   BPELConstants.ND_EXIT.equals(name) ||
			   BPELConstants.ND_EXTENSION_ACTIVITY.equals(name) ||
			   BPELConstants.ND_INVOKE.equals(name) ||
			   BPELConstants.ND_FOR_EACH.equals(name) ||
			   BPELConstants.ND_IF.equals(name) ||
			   BPELConstants.ND_VALIDATE.equals(name) ||
			   BPELConstants.ND_COMPENSATE_SCOPE.equals(name) ||
			   BPELConstants.ND_EMPTY.equals(name) ||
			   BPELConstants.ND_OPAQUEACTIVITY.equals(name));
			
	}

	// TODO: (DU) This is here due to
	// https://issues.apache.org/jira/browse/XERCESJ-1289
	public static void copyInto(Node src, Node dest) throws DOMException {

		// get node factory
		Document factory = dest.getOwnerDocument();
		boolean domimpl = factory instanceof DocumentImpl;

		// placement variables
		Node start = src;
		Node parent = src;
		Node place = src;

		// traverse source tree
		while (place != null) {

			// copy this node
			Node node = null;
			int type = place.getNodeType();
			switch (type) {
			case Node.CDATA_SECTION_NODE: {
				node = factory.createCDATASection(place.getNodeValue());
				break;
			}
			case Node.COMMENT_NODE: {
				node = factory.createComment(place.getNodeValue());
				break;
			}
			case Node.ELEMENT_NODE: {
				Element element = factory.createElement(place.getNodeName());
				node = element;
				NamedNodeMap attrs = place.getAttributes();
				int attrCount = attrs.getLength();
				for (int i = 0; i < attrCount; i++) {
					Attr attr = (Attr) attrs.item(i);
					String attrName = attr.getNodeName();
					String attrValue = attr.getNodeValue();
					element.setAttribute(attrName, attrValue);
					if (domimpl && !attr.getSpecified()) {
						((AttrImpl) element.getAttributeNode(attrName))
								.setSpecified(false);
					}
				}
				break;
			}
			case Node.ENTITY_REFERENCE_NODE: {
				node = factory.createEntityReference(place.getNodeName());
				break;
			}
			case Node.PROCESSING_INSTRUCTION_NODE: {
				node = factory.createProcessingInstruction(place.getNodeName(),
						place.getNodeValue());
				break;
			}
			case Node.TEXT_NODE: {
				node = factory.createTextNode(place.getNodeValue());
				break;
			}
			default: {
				throw new IllegalArgumentException("can't copy node type, "
						+ type + " (" + place.getNodeName() + ')');
			}
			}
			dest.appendChild(node);

			// iterate over children
			if (place.hasChildNodes()) {
				parent = place;
				place = place.getFirstChild();
				dest = node;
			}

			// advance
			else if (place != start) {
				place = place.getNextSibling();
				while (place == null && parent != start) {
					place = parent.getNextSibling();
					parent = parent.getParentNode();
					dest = dest.getParentNode();
				}
			} else {
				place = null;
			}

		}

	} // copyInto(Node,Node)

}
