
package org.biomoby.shared.data;

import java.io.StringWriter;
import java.util.Collection;
import java.util.Vector;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import org.biomoby.registry.meta.Registry;
import org.biomoby.shared.*;
import org.biomoby.shared.parser.MobyTags;

/**
 * A class representing a <b>base</b> MOBY object (i.e. just a namespace and id) that has been instantiated.  
 * If you are looking to represent an object with members (e.g. DNASequence), look at  MobyDataComposite.
 * If you are looking to represent a MOBY primitive object, please see the appropriate 
 * subclass of this class (e.g. MobyDataInteger). 
 *
 * This class provides methods to instantiate the object value either simply using an ID (in which case an
 * Object tag is created automatically), or via XML input either from a DOM Element
 * (preferred) or a string containing the XML.  The toXML method will return either service call
 * or search template formatted XML depending on the mode set.
 * Having both modes is useful because the data instance created from a previous call can be 
 * passed to a service search template by switching the XML mode.  By default the object is in 
 * MobyDataInstance.SERVICE_XML_MODE.
 *
 * @author Paul Gordon (gordonp@ucalgary.ca)
 */

public class MobyDataObject extends MobyPrimaryDataSimple implements MobyDataInstance, Cloneable, Comparable{
    protected int xmlMode = MobyDataInstance.CENTRAL_XML_MODE;
    private Object object = new Object();
    private Vector<MobyDataObject> CRIBVector = null;  // CrossReference Info Block from the MOBY-S API
    private MobyProvisionInfo provisionInfo = null; // Provision Info Block from MOBY-S API

    /**
     * Construct the object using a DOM fragment.
     *
     * @throws MobyException if the element is not an Object tag, or is missing a required attribute
     */
    public MobyDataObject(Element element) throws MobyException{
	this(element, null);
    }

    public MobyDataObject(Element element, Registry registry) throws MobyException{
	this(getNamespace(element, registry).getName(), getId(element), registry);
	setName(getName(element));
    }

    /**
     * Constructor with no instance information, just a name (which can be blank).  
     * Instance information can be filled out later with setId() and setNamespace().
     */
    public MobyDataObject(String name){
	this(name, (Registry) null);
    }

    public MobyDataObject(String name, Registry registry){
        super(name);
	setDataType(MobyDataType.getDataType(MobyTags.MOBYOBJECT, registry));
    }

    /**
     * Constructor convenient for a base object with a namespace and ID.
     */
    public MobyDataObject(String namespace, String id){
	this(namespace, id, null);
    }

    public MobyDataObject(String namespace, String id, Registry registry){
	super("");
	setDataType(MobyDataType.getDataType(MobyTags.MOBYOBJECT, registry));
	setId(id);	

	MobyNamespace nsObj = MobyNamespace.getNamespace(namespace, registry);
	if(nsObj == null){
	    // Not in the ontology, so create a token namespace
	    nsObj = new MobyNamespace(namespace == null ? "" : namespace);
	}
        addNamespace(nsObj);
    }

    /**
     * Creates a base object with the same name, namespace and id
     * as the input instance, as opposed to clone, which creates an exact 
     * copy of all the fields and the datatype.
     */
    public MobyDataObject(MobyDataObject mobj){
	this(mobj.getName(), mobj.getDataType().getRegistry());
	setId(mobj.getId());
	setNamespaces(mobj.getNamespaces());
    }

    /**
     * Construct a primitive data type instance from a string value.
     * In the case of data type "Object", the value is used as the ID.
     *
     * @throws MobyException if the requested datatype is not a primtive, or the value could not be used for that data type (e.g an integer out of the string "Bar")
     */
    public static MobyDataObject createInstanceFromString(String typeName, String value) throws MobyException{
	return createInstanceFromString(typeName, value, null);
    }

    public static MobyDataObject createInstanceFromString(String typeName, String value, Registry registry) throws MobyException{
	if(typeName == null){
	    return null;
	}

	if(MobyTags.MOBYOBJECT.equals(typeName)){
	    return new MobyDataObject("", value, registry);
	}
	else if(MobyTags.MOBYINTEGER.equals(typeName)){
	    return new MobyDataInt("", value, registry);
	}
	else if(MobyTags.MOBYFLOAT.equals(typeName)){
	    return new MobyDataFloat("", value, registry);
	}
	else if(MobyTags.MOBYSTRING.equals(typeName)){
	    return new MobyDataString("", value, registry);
	}
	else if(MobyTags.MOBYBOOLEAN.equals(typeName)){
	    return new MobyDataBoolean("", value, registry);
	}
	else if(MobyTags.MOBYDATETIME.equals(typeName)){
	    return new MobyDataDateTime("", value, registry);
	}
	else{
	    MobyDataType type = MobyDataType.getDataType(typeName, registry);
	    if(type != null && type.inheritsFrom(MobyDataBytes.BASE64_DATATYPE)){
		return new MobyDataBytes("", value, type);
	    }
	    throw new MobyException("Attempted to construct a data instance from a string " +
				    "value for a non-primitive data type (" + typeName + ")");
	}
    }

    /**
     * This method creates a MobyDataObject of the appropriate subclass for
     * a given input XML tree (e.g. base objects like MobyDataObject and MobyDataInt, 
     * or complex objects like MobyDataDateTime or MobyDataComposite, or even a 
     * MobyDataSecondaryInstance).  This method
     * will also parse a MOBY XML Object wrapped in a Simple tag when the Simple
     * is passed in as the main element.
     *
     * @param objectTag the W3C DOM Element node corresponding to the object's enclosing tag, or a Simple tag
     */
    public static MobyDataInstance createInstanceFromDOM(Element objectTag) throws MobyException{
	return createInstanceFromDOM(objectTag, null);
    }

    public static MobyDataInstance createInstanceFromDOM(Element objectTag, Registry registry) throws MobyException{
	MobyDataObject object = null;

	if(objectTag == null){
	    return null;
	}

	String objectClass = objectTag.getLocalName();
	if(MobyTags.SIMPLE.equals(objectClass)){
	    Element elementChild = MobyPrefixResolver.getChildElement(objectTag, "*");
	    // A Simple tag is allowed to have exactly one child element
	    if(elementChild == null){
		throw new IllegalArgumentException("Simple element has no MOBY " +
						   "child element, there is no " +
						   "instance to create");
	    }

	    return createInstanceFromDOM(elementChild, registry);
	}

	// There are six types of objects we can populate with data directly
	// plus Collections and Secondary Parameters.  Otherwise it is a composite.
	else if(MobyTags.MOBYOBJECT.equals(objectClass)){
	    return new MobyDataObject(objectTag, registry);	    
	}
	else if(MobyTags.MOBYINTEGER.equals(objectClass)){
	    return new MobyDataInt(objectTag, registry);
	}
	else if(MobyTags.MOBYFLOAT.equals(objectClass)){
	    return new MobyDataFloat(objectTag, registry);
	}
	else if(MobyTags.MOBYSTRING.equals(objectClass)){
	    return new MobyDataString(objectTag, registry);
	}
	else if(MobyTags.MOBYBOOLEAN.equals(objectClass)){
	    return new MobyDataBoolean(objectTag, registry);
	}
	else if(MobyTags.MOBYDATETIME.equals(objectClass)){
	    return new MobyDataDateTime(objectTag, registry);
	}
	else if(MobyTags.COLLECTION.equals(objectClass)){
	    return new MobyDataObjectSet(objectTag, registry);
	}
	else if(MobyDataSecondaryInstance.ELEMENT_NAME.equals(objectClass)){
	    return new MobyDataSecondaryInstance(objectTag);
	}
	// Must otherwise be a composite
	else{	   
	    MobyDataType type = MobyDataType.getDataType(objectClass, registry);
	    // Treat base64-encoded binary data as a special case, as we have a convenience class for it
	    if(type != null && type.inheritsFrom(MobyDataType.getDataType(MobyDataBytes.BASE64_DATATYPE, registry))){
		return new MobyDataBytes(objectTag, registry);
	    }
	    else{
		return new MobyDataComposite(objectTag, registry);
	    }
	}
    }

    /**
     * Please note that you almost never want to call this method in a client.  
     * The articleName of a MOBY Ontology object is normally fully determined
     * by either the parameter name in the service being called (MobyContentInstance 
     * handles this), or the member field name when included as part of a composite object
     * (MobyDataComposite handles this).
     */
    public void setName(String name){
	super.setName(name);
    }

    /**
     * Similar to DOM level 3 getTextContent, but only elements are excepted as input, and text
     * inside children elements is not picked up.
     * Actually calls MobyObjectDecompositionImpl.getTextContent()
     */
    public static String getTextContents(Element element){
	return MobyObjectDecompositionImpl.getTextContent(element, false);
    }

    protected static MobyNamespace getNamespace(Element e){
	return getNamespace(e, null);
    }

    protected static MobyNamespace getNamespace(Element e, Registry registry){
	String namespace = MobyPrefixResolver.getAttr(e, "namespace");
	MobyNamespace nsObj = MobyNamespace.getNamespace(namespace, registry);
	if(nsObj == null){
	    // Not in the ontology, so create a token namespace
	    nsObj = new MobyNamespace(namespace == null ? "" : namespace);
            nsObj.setRegistry(registry);
	}
	return nsObj;
    }

    /**
     * Determine the name of the element if an enclosing tag (e.g. Simple or composite class) exists
     */
    public static String getName(Element e){
	Node p = e.getParentNode();
	String name = null;
	// A top level object whose name is in the enclosing simple tag
	if(p != null && (p instanceof Element) && ((Element) p).getTagName().equals(MobyTags.SIMPLE)){
	    name = MobyPrefixResolver.getAttr((Element) p, MobyTags.ARTICLENAME);
	    if(name != null && name.length() > 0){
		return name;
	    }
	    // If the simple doesn't have a name, grab the name from the child tag
	    name = MobyPrefixResolver.getAttr(e, MobyTags.ARTICLENAME);
	    return name == null ? "" : name;
	}
	// Part of a composite, subobject's name is in its own tag
	else{
	    name = MobyPrefixResolver.getAttr(e, MobyTags.ARTICLENAME);
	    return name == null ? "" : name;
	}
    }

    public static String getId(Element e){
	String id = MobyPrefixResolver.getAttr(e, "id");
	return id == null ? "" : id;
    }

    /**
     * Similar to createObjectFromDOM(), but parses the XML for you using the JAXP 
     * parser discovery mechanism.  NOT YET IMPLEMENTED.
     */
    public static MobyDataInstance createInstanceFromXML(String mobyXML) throws IllegalArgumentException{
	MobyDataObject object = null;
	return object;
    }

    /**
     * Used to see if two objects are equivalent.
     * If the passed in object is a MobyDataObject, its getObject() value 
     * will be used for the comparison.  The namespace and ID are ignored,
     * only the value is compared (such as a MobyDataInteger or Integer or 
     * BigInteger vs. a MobyDataInteger). 
     *
     * @return true if the values are equals according to the underlying Java object's equals() method
     * @throws ClassCastException if and only if a child class or underlying Java object decides the comparison classes are invalid 
     */
    public boolean equals(Object passedInObject) throws ClassCastException{
      Object ourObject = getObject();
      if(passedInObject != null && passedInObject instanceof MobyDataObject){
	  if(((MobyDataObject) passedInObject).getDataType().equals(getDataType())){
	      // An object of the same class.  See if their XMLs are equivalent.
	      int ourOldXMLMode = getXmlMode();
	      int itsOldXMLMode = ((MobyDataObject) passedInObject).getXmlMode();
	      setXmlMode(MobyDataInstance.SERVICE_XML_MODE);
	      ((MobyDataObject) passedInObject).setXmlMode(MobyDataInstance.SERVICE_XML_MODE);

	      boolean equality = ((MobyDataObject) passedInObject).toXML().equals(toXML());

	      setXmlMode(ourOldXMLMode);
	      ((MobyDataObject) passedInObject).setXmlMode(itsOldXMLMode);

	      return equality;
	  }
	  passedInObject = ((MobyDataObject) passedInObject).getObject();
      }
      return ourObject.equals(passedInObject);
    }

    /**
     * This method lexically compares in the order of value, ID, name if passed in object is a MobyDataObject.
     * Otherwise it returns -1 (null fields get pushed to the end of the list).
     *
     * Child classes are encouraged to override this method for numerical comparison, etc. where
     * appropriate, falling back to this method if the result is 0.
     */
    public int compareTo(Object o){
      if(o == null || !(o instanceof MobyDataObject)){
        return -1;
      }
      MobyDataObject mdsi = (MobyDataObject) o;
      if(getValue() == null && mdsi.getValue() != null){
          return 1;
      }
      else if(getValue() != null && getValue().compareTo(mdsi.getValue()) != 0){
          return getValue().compareTo(mdsi.getValue());
      }
      else if(getId() == null && mdsi.getId() != null){
          return 1;
      }
      else if(getId() != null && getId().compareTo(mdsi.getId()) != 0){
          return getId().compareTo(mdsi.getId());
      }
      else if(getName() == null && mdsi.getName() != null){
          return 1;
      }
      else if(getName() != null){
          return getName().compareTo(mdsi.getName());
      }
      // Equivalent in all three fields if we got to this point
      else{
          return 0;
      }
    }

    /**
     * Determined whether toXML will return a Central template value or a service call instance value.
     *
     * @param mode one of MobyDataInstance.CENTRAL_XML_MODE or MobyDataInstance.SERVICE_XML_MODE
     * @throws IllegalArgumentException if the mode is not one of the specified values
     */
    public void setXmlMode(int mode) throws IllegalArgumentException{
        if(mode != MobyDataInstance.CENTRAL_XML_MODE && mode != MobyDataInstance.SERVICE_XML_MODE){
	    throw new IllegalArgumentException("Value passed to setXmlMode was neither " +
					       "MobyDataInstance.CENTRAL_XML_MODE nor MobyDataInstance.SERVICE_XML_MODE");
	}
	xmlMode = mode;
    }

    /**
     * Report whether toXML will produce Central template or service call instance XML.
     *
     * @return one of MobyDataInstance.CENTRAL_XML_MODE or MobyDataInstance.SERVICE_XML_MODE
     */
    public int getXmlMode(){
	return xmlMode;
    }

    private String domToXMLString(Node domnode) throws TransformerException{
	// Use JAXP to transform the DOM into a XML string for storage
	// Serialisation through Tranform.
	DOMSource domSource = new DOMSource(domnode);
	StringWriter stringout = new StringWriter();
	StreamResult streamResult = new StreamResult(stringout);
	TransformerFactory tf = TransformerFactory.newInstance();
	Transformer serializer = tf.newTransformer();
	// We just have a fragment, not a full document
	serializer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,"yes");
	// Make it pretty
	serializer.setOutputProperty(OutputKeys.INDENT,"yes");
	serializer.transform(domSource, streamResult);
	return stringout.toString();
    }

    /**
     * Gives access to the Java object instance underlying the MobyData instance.  
     * For example, to change a MOBY String object, call 
     * ((StringBuffer) (data.getObject()).append("extra text to add to the end")
     *
     * In the case of a base Moby Object, there really is no underlying Java
     * equivalent to return, so a java.lang.Object is returned for completeness's sake.
     * possibly useful for notification events and pointer equivalency testing
     * in derived classes such as MobyDataServiceAssocInstance?
     */
    public Object getObject(){
	return object;
    }

    /**
     * A lexical (prefereably human readable) representation of the underlying object value 
     * (not including the namespace and ID). This value is used in sorting, and potentially for 
     * user interface elements such as tool tips and comment fields.
     *
     * For base objects, there is no underlying value (just a namespace and id), so the result 
     * is always a constant string "[Object]".
     */
    public String getValue(){
	return "[Object]";
    }

    public void addCrossReferences(Element e) throws MobyException{
    	addCrossReferences(e, null);
    }

    public void addCrossReferences(Element e, Registry reg) throws MobyException{
	if(e == null){
	    throw new NullPointerException("Cannot add a null cross-reference element to an object");
	}
	if(e.getLocalName() == null){
	    throw new NullPointerException("Element name is null when trying to add cross-reference element");
	}
	if(!e.getLocalName().equals(MobyTags.CROSSREFERENCE)){
	    throw new NullPointerException("Cannot add an element named '"+e.getLocalName()+
					   "' as a CrossReference element");
	}
	NodeList substructures = MobyPrefixResolver.getChildElements(e, "*"); //wildcard
	int numSubstructures = substructures.getLength();
	for(int i = 0; i < numSubstructures; i++){
	    Element child = (Element) substructures.item(i);
	    String objectClass = child.getLocalName();
	    if(MobyTags.MOBYOBJECT.equals(objectClass)){
		addCrossReference(new MobyDataObject(child, reg));
	    }
	    else if(MobyTags.XREF.equals(objectClass)){
		addCrossReference(new MobyDataXref(child, reg));
	    }
	    else{
		throw new MobyException("Cross-reference block contained an illegal child element ("+objectClass+
					"), only '"+MobyTags.MOBYOBJECT+"' and '"+MobyTags.XREF+"' are allowed");
	    }
	}
    }

    public void addCrossReference(MobyDataObject mdsi) throws NullPointerException{
	if(mdsi == null){
	    throw new NullPointerException("Cannot add a null cross-reference to an object");
	}

	// Hasn't been initialized yet?
	if(CRIBVector == null){
	    CRIBVector = new Vector<MobyDataObject>();
	}

	// Already a class that's acceptable
	if(mdsi instanceof MobyDataXref || 
	   mdsi.getClass().getName().equals("MobyDataObject")){
	    CRIBVector.add((MobyDataObject) mdsi);
	}
	// Needs to be reduced to a base Object
	else{
	    CRIBVector.add(new MobyDataObject(mdsi));
	}
    }

    /**
     * Call this method if you would like to modify the cross references,
     * such as removing items, clearing the collection, etc.  Be sure only to add
     * MobyDataXref and MobyDataObject to the collecton or you'll have
     * a type casting problem later!  When we start requiring Java 1.5, this Collection
     * will have a Generic template applied to it.
     */
    public Collection getCrossReferences(){
	// Hasn't been initialized yet?
	if(CRIBVector == null){
	    CRIBVector = new Vector<MobyDataObject>();
	}
	return CRIBVector;
    }

    public boolean hasCrossReferences(){
	return CRIBVector != null && !CRIBVector.isEmpty();
    }

    public void addProvisionInfo(Element e) throws MobyException{
	if(e == null){
	    throw new NullPointerException("Cannot add a null provision information element to an object");
	}
	if(e.getLocalName() == null){
	    throw new NullPointerException("Element name is null when trying to add provision information element");
	}
	if(!e.getLocalName().equals(MobyTags.PROVISIONINFORMATION)){
	    throw new NullPointerException("Cannot add an element named '"+e.getLocalName()+
					   "' as a ProvisionInformation element");
	}
	setProvisionInfo(new MobyProvisionInfo(e));	
    }

    public void setProvisionInfo(MobyProvisionInfo info){
	provisionInfo = info;
    }

    public MobyProvisionInfo getProvisionInfo(){
	return provisionInfo;
    }

    /**
     * Used to set the data instance value based on an ID string. i.e. just an Object.
     */
    public void setId(String value){
	super.setId(value);
    }

    /**
     * Convenience method to get the basic XML representation
     * @return the raw XML representation of the object
     */
    public String toString(){
	return toXML();
    }

    /**
     * Simply calls new constructor with object's existing data type, name and value.
     * Subclasses should override this method if more datafields need to be copied for 
     * an accurate clone of the Moby Data Instance (i.e. anything but a base object).   The
     * subclasses should ensure that they also return a MobyDataObject.
     * 
     * @return an object of class MobyDataObject
     */
    public MobyDataObject clone(){
	MobyDataObject copy = null;
	copy = new MobyDataObject(getName(), getDataType().getRegistry());
	copy.setNamespaces(getNamespaces());
	copy.setId(getId());
	return copy;
    }

    /**
     * This sets the namespace that will be used in toXML(), since in the XML
     * representation for service execution, you can only have one namespace.
     * If this namespace is not already part of the namespaces for this object,
     * it will be added.  By default, if this method isn't called, the first
     * namespace in the namespace array will be used, and in fact this method
     * rearranges the array to have the desired effect.
     */
    public void setPrimaryNamespace(MobyNamespace ns){
	MobyNamespace[] nss = getNamespaces();
	// special case, if no namespace already, simply add this one
	if(nss == null || nss.length == 0 || nss[0].getName().length() == 0){
	    addNamespace(ns);
	    return;
	}
	
	// See if its already there
	for(int i = 0; i < nss.length; i++){
	    if(nss[i] == ns){
		// Not already the primary
		if(i != 0){
		    // Swap'em
		    nss[i] = nss[0];
		    nss[0] = ns;
		    setNamespaces(null);
		    setNamespaces(nss);
		}
		//It's in slot 0 as it should be
		return;
	    }
	}

	MobyNamespace[] newnss = new MobyNamespace[nss.length+1];
	// Prepend the namespace to the array
	newnss[0] = ns;
	System.arraycopy(nss, 0, newnss, 1, nss.length);
	setNamespaces(null);
	setNamespaces(newnss);
    }

    public MobyNamespace getPrimaryNamespace(){
	MobyNamespace[] ns = getNamespaces();
	if(ns == null || ns.length == 0){
	    return null;
	}
	return ns[0];
    }

    // Should disallow setDataType that casts incorrectly

    /**
     * Converts all of the cross references into an XML Block suitable for 
     * an Object's cross-reference block.
     */
    protected String getCRIBXML(){
	if(!hasCrossReferences()){
	    return "";
	}

	StringBuffer crib = new StringBuffer();
	crib.append("<"+MobyTags.CROSSREFERENCE+">\n");

	for(MobyDataObject obj : CRIBVector){
	    if(obj == null){
		// ignore nulls
	    }
	    else if(obj instanceof MobyDataXref){
		((MobyDataXref) obj).setXmlMode(MobyDataXref.CRIB_XML_MODE_ON);
		crib.append(obj.toXML()+"\n");
		((MobyDataXref) obj).setXmlMode(MobyDataXref.CRIB_XML_MODE_OFF);
	    }
	    else if(obj instanceof MobyDataObject){
		int oldXMLMode = ((MobyDataObject) obj).getXmlMode();
		((MobyDataObject) obj).setXmlMode(xmlMode);
		crib.append(obj.toXML()+"\n");
		((MobyDataObject) obj).setXmlMode(oldXMLMode);
	    }
	}
	crib.append("</"+MobyTags.CROSSREFERENCE+">\n");
	return crib.toString();
    }

    /**
     * Produces a full-blown XML fragment that depending on the value of getXmlMode() is either
     * a template for use in MOBY Central services, or a Simple element for use in calling 
     * a service instance.
     *
     * @return the MOBY XML representation of the data instance
     */
    public String toXML(){
	if(xmlMode == MobyDataInstance.SERVICE_XML_MODE){

	    String cribXML = getCRIBXML();
	    String pibXML = provisionInfo == null ? "" : provisionInfo.toXML();

	    // Object needs to encapsulate info?
	    if(cribXML.length() != 0 || pibXML.length() != 0){
		return "<" + getDataType().getName() + " " + getAttrXML() + ">\n" +
		    cribXML + pibXML +
		    "\"</" + getDataType().getName() + ">";
	    }

	    // One-tag representation, since it doesn't have any CRIB or PIB to enclose
	    if(getDataType() == null){
                // If the data type is null, there must be something wrong
                // with fetching the ontology RDF from MOBY Central.  At the very
                // least, we know the thing must be an Object.
                return "<"+MobyTags.MOBYOBJECT+" " + getAttrXML() + "/>";
            }
            else{
	        return "<" + getDataType().getName() +" " + getAttrXML() + "/>";
            }
	}
	else{
	    // Override super because article name is not actually important to us
	    // but will cause cache misses for MOBY Central calls since the xml 
	    // is used as a key in the call cache.
	    StringBuffer buf = new StringBuffer();
	    buf.append ("<"+MobyTags.SIMPLE+" articleName=\"not_important\">\n");
	    buf.append ("<objectType>");
	    if (getDataType() != null) buf.append (getDataType().getName());
	    buf.append ("</objectType>\n");
	    if (namespaces.size() > 0) {
		for (java.util.Enumeration en = namespaces.elements(); en.hasMoreElements(); ) {
		    MobyNamespace ns = (MobyNamespace)en.nextElement();
		    if(ns != null){
			buf.append ("<Namespace>");
			buf.append ( ns.getName() );
			buf.append ("</Namespace>");
		    }
		}
	    }
	    buf.append ("</"+MobyTags.SIMPLE+">");
	    buf.append ("\n");
	    return buf.toString();
	    //return super.toXML();
	}
    }

    /**
     * Utility method to formulate the object tag attributes articleName, namespace and ID. 
     */
    protected String getAttrXML(){
	MobyNamespace[] ns = getNamespaces();
	String namespace = "";
	if(ns != null && ns.length != 0){
	    namespace = ns[0].getName();
	}

	return (getName() == null ? "" : "articleName=\"" + getName() + "\"") +
	    " namespace=\"" + namespace +
	    "\" id=\"" + (getId() == null ? "" : getId()) + "\"";

    }
}
