//Updated 2/19/00, version 1.0.1
//Updated 3/22/00, version 1.1.1
//Updated 3/28/00, version 1.1.3
//Updated 4/10/00, version 1.1.4
//Updated 6/26/00, version 1.2.1

package com.jxml.quick.engine;
import com.jxml.quick.*;
import com.jxml.quick.access.*;
import com.jxml.quick.tf.*;
import java.util.*;

final public class QElementFactory extends QCMSeqFactory
    implements QMEI
{
    public String tagName="";
    public boolean isIdRef=false;
    public boolean wild=false;
    public ArrayList subclassFactories=new ArrayList();
    public QTargetFactory targetFactory=null;
    public boolean isAttList;
    public boolean conditional=false;
    public ArrayList comments=new ArrayList();
    public boolean isBimodal=false;
    public boolean isCData=false;
    
    public QElementFactory()
    {
    }

    protected QFilter newFilter()
	throws QE
    {
	return new QElement();
    }

    public QTargetFactory getTargetFactory()
    {
	return targetFactory;
    }

    public void addSubclass(QElementFactory ef)
	throws QE
    {
	subclassFactories.add(ef);
    }

    public void setSuperclass(QElementFactory ef)
	throws QE
    {
	ef.addSubclass(this);
    }

    final class QElement extends QCMSeq
    {
	ArrayList subclasses;
	public boolean isMeta=false;
	
	QElement()
	    throws QE
	{
	}
	
	public void init()
	    throws QE
	{
	    super.init();
	    isAttList=tagName.equals(QConvert.ATTRIBUTES);
	    int s=subclassFactories.size();
	    int i;
	    subclasses=new ArrayList(s);
	    for (i=0;i<s;++i)
		{
		    QFilterFactory ff=(QFilterFactory)subclassFactories.get(i);
		    QFilter f=ff.createFilter(newContext);
		    subclasses.add(f);
		}
	}

	public State createState()
	{
	    return new EState();
	}

	public boolean isAttList()
	{
	    return QConvert.ATTRIBUTES.equals(tagName);
	}
	
	public QEI matchTags(String name)
	{
	    if (wild)
		{
		    return (QEI)getState();
		}
	    if (tagName.equals(name))
		return (QEI)getState();
	    int s=subclasses.size();
	    int i;
	    for (i=0;i<s;++i)
		{
		    QElement sc=(QElement)subclasses.get(i);
		    QEI st=sc.matchTags(name);
		    if (st!=null)
			return st;
		}
	    if (tagName.equals(""))
		return (QEI)getState();
	    return null;
	}

	public boolean matchInstance(Object object)
	    throws QPE
	{
		if (!targetFactory.isInstance(object,context))
			return false;
		if (!(object instanceof QName))
			return true;
		if (wild)
			return true;
		if ("".equals(tagName))
			return true;
		if ("_attributes".equals(tagName))
			return true;
		QName qn=(QName)object;
		return qn.getQName().equals(tagName);
	}

	public QElement matchClass(Object object, Object oold)
	    throws QPE
	{
		if (isIdRef)
			object=oold;
		if (!isIdRef && !matchInstance(object))	//1.1.3
	    {								//1.1.1
		QMeta t=context.doc.getMeta(object);
		if (t==null)
		{
		    return null;
		}
		if (!matchInstance(t))
		{
		    return null;
		}
		object=t;
		isMeta=true;
	    }
	    QDocImpl doc=(QDocImpl)context.doc;
	    if (isIdRef)
		{
		    doc.getId(object);
		}
	    if (!conditional)
		return this;
	    boolean known=doc.isKnown(object);
	    boolean marked=false;
	    if (known)
		{
		    marked=doc.isMarked(object);
		}
	    if (isIdRef)
		{
		    if (marked)
			{
			    return this;
			}
		    else
			{
			    return null;
			}
		}
	    else
		{
		    if (marked)
			{
			    return null;
			}
		    else
			{
			    return this;
			}
		}
	}

	public QElement matchClasses(Object object, Object oold)
	    throws QPE
	{
	    int s=subclasses.size();
	    int i;
	    for (i=0;i<s;++i)
		{
		    QElement sc=(QElement)subclasses.get(i);
		    sc=sc.matchClasses(object,oold);
		    if (sc!=null)
			return sc;
		}
	    return matchClass(object,oold);
	}

	public void walk(Object object,
			 Object oldobj,
		         QTargetFactory parentFactory, //Updated 2/19/00, version 1.0.1
			 QContext outputContext,
			 boolean isAttribute,
			 String defaultValue)
	    throws QPE
	{
	    if (isAttribute)
	    {
		    String tfv=targetFactory.getText(object,context);
		    if ("".equals(tfv))
			return;
	    }
	    if (isIdRef)
		    object=oldobj;
	    if (!isIdRef 
		&& isAttribute
		&& !outputContext.expressDefaults
		&& defaultValue.equals(targetFactory.getText(object,context)))
	    {
		return;
	    }
	    QDocImpl doc=(QDocImpl)context.doc;
	    boolean known=doc.isKnown(object);
	    boolean marked=false;
	    boolean isAttList=tagName.equals(QConvert.ATTRIBUTES);
	    if (targetFactory instanceof QMALTF) //Updated 2/19/00, version 1.0.1
		((QMALTF)targetFactory).parentFactory=parentFactory; //Updated 2/19/00, version 1.0.1
	    if (known && !isIdRef)
		{
		    if (isAttribute)
			context.throwPE(tagName+
					" not valid as attribute");
		    marked=doc.isMarked(object);
		    if (marked && !isAttList)
		    {
			context.throwPE("double walking");
		    }
		}
	    String nm=tagName;
	    if (wild)
	    {
		nm=targetFactory.getName(object);
	    }
	    outputContext.startElement(nm);
	    if (isIdRef)
		{
		    if (!isAttribute)
			{
			    outputContext.startElement(QConvert.ATTRIBUTES);
			    outputContext.endElement(QConvert.ATTRIBUTES,"",isCData);
			}
		    outputContext.endElement(nm,
					     doc.getId(object),
					     isCData);
		}
	    else
		{
		        walk(object,
			     targetFactory,
			     null,
			     outputContext,
			     false,
			     isAttList);
		        outputContext.endElement(nm,targetFactory.getText(object,context),isCData);
		}
	}

        public void processFixed(Object parent, 
                                 QTargetFactory parentFactory,
				 String defaultValue,
				 QAccess access)
		throws QPE
        {
	    Object theObject=targetFactory.create(context);
	    if (!isIdRef)
	        targetFactory.add(theObject,
			      tagName,
			      defaultValue,
			      access,
			      parent,
			      parentFactory,
			      context);
	    else							//1.1.5
		((QDocImpl)context.doc).resolve(
						defaultValue.trim(),
						access,
						parent,
						parentFactory,	
						this);
	}

	//	25-dec-00
        public void processDefault(Object parent, 
                                 QTargetFactory parentFactory,
				 String defaultValue,
				 QAccess access)
		throws QPE
        {
	    Object theObject=targetFactory.create(context);
	    if (!isIdRef)
	        targetFactory.add(theObject,
			      tagName,
			      defaultValue,
			      access,
			      parent,
			      parentFactory,
			      context);
	    else
		((QDocImpl)context.doc).resolve(
						defaultValue.trim(),
						access,
						parent,
						parentFactory,	
						this);
	}

	final class EState extends SeqState implements QEI
	{
	    Object theObject=null;
	    QIterator cIterator;
	    QTargetFactory parentFactory;
	    String name;
	    QAccess cAccess;
	    String fixed;

	    public Object getObject()
		throws QPE
	    {
		if (theObject==null)
		    {
			if (cIterator==null)
			    {
				theObject=targetFactory.create(context);
			    }
			else
			    {
				theObject=cIterator.getCurrent();
				if (theObject!=null)
				    cIterator.getNext();
				else
				    context.throwPE("Iterator returns null");
			    }
		    }
		return theObject;
	    }

	    public void processStart(QAccess cAccess, 
				     QTargetFactory parentFactory,
				     String name, 
				     QIterator cIterator,
				     String fixed)
		throws QPE
	    {
		this.cAccess=cAccess;
		this.parentFactory=parentFactory;
		this.cIterator=cIterator;
		reset();
		theObject=null;
		this.name=name;
		this.fixed=fixed;
	        if (targetFactory instanceof QMALTF)	//	25-dec-00
		    ((QMALTF)targetFactory).parentFactory=parentFactory;	//	25-dec-00
                if (isAttList)	//	25-dec-00
                {	//	25-dec-00
                    int fs=filters.size();	//	25-dec-00
                    int i;	//	25-dec-00
                    for (i=0;i<fs;++i)	//	25-dec-00
                    {	//	25-dec-00
                        QCElementFactory.QCElement ee=(QCElementFactory.QCElement)filters.get(i);	//	25-dec-00
                        ee.processDefault(getObject(),targetFactory);	//	25-dec-00
                    }	//	25-dec-00
                }	//	25-dec-00
	    }

	    public void processEnd(String text)
		throws QPE 
	    {
	        if (targetFactory instanceof QMALTF)
		    ((QMALTF)targetFactory).parentFactory=parentFactory; // FIX! 000626 Version 1.2.1

                if (isAttList)
                {
                    int fs=filters.size();
                    int i;
                    for (i=0;i<fs;++i)
                    {
                        QCElementFactory.QCElement ee=(QCElementFactory.QCElement)filters.get(i);
                        ee.processFixed(getObject(),targetFactory);
                    }
                }
		if (!isComplete())
		    if (isAttList)
			context.throwPE
			    (tagName+": Missing required attributes");
		    else
			context.throwPE
			    (tagName+": Missing required elements");
		if (fixed!=null && !fixed.equals(text))
		    context.throwPE("Expecting value of "+
				    fixed+
				    ", not"+
				    text);
		if (fixed!=null)
		    return;
		if (theObject==null && cIterator!=null)
		    {
			theObject=cIterator.getCurrent();
			if (theObject!=null)
			    cIterator.getNext();
			else if (!isAttList)
			    context.throwPE("Iterator returns null");
		    }
		Object parent=null;
		if (((QContextImpl)context).ndx>0)
		    parent=((QContextImpl)context).getParentEntry().getObject();
		if (!isIdRef)
		    {
			targetFactory.add(theObject,
					  name,
					  text,
					  cAccess,
					  parent,
					  parentFactory,
					  context);
			return;
		    }
		if ("".equals(text))
		    return;
		if (cAccess==null || !cAccess.writeable(parentFactory,context))
		    context.throwPE("idref requires write access");
		if (parent==null)
		    context.throwPE("idref may not be root");
		((QDocImpl)context.doc).resolve(
						text.trim(),
						cAccess,
						parent,
						parentFactory,		//1.1.4
						QElement.this);
	    }
	    
	    public void processElement(String name)
		throws QPE
	    {
		boolean isAttList=tagName.equals(QConvert.ATTRIBUTES);
		if (isAttList)
		    {
			((QMALTF)targetFactory).parentFactory=parentFactory;
		    }
		if (!match(name,false,null,getObject(),targetFactory,null))
		{
			System.err.println("within "+tagName);
			System.err.println("of object "+getObject().getClass());
		    	context.throwPE("unrecognized tag: "+name);
		}
	    }
	}
    }
}
