/*------------------------------------------------------------------------------------
Copyright (c)2000 TogetherSoft Corporation. Patents pending. All rights reserved. 
------------------------------------------------------------------------------------*/ 

package com.togethersoft.modules.xmleditor.dtd.impl;

import java.util.*;
import java.io.*;
import com.ibm.xml.parser.*;
import com.togethersoft.together.util.xml.*;
import com.togethersoft.together.util.message.Informator;
import com.togethersoft.together.util.message.DefaultInformatorImpl;
import com.togethersoft.modules.xmleditor.dtd.*;
import com.togethersoft.modules.ejbframework.utils.ConfigProperties;


public class DTDDocumentTypeImpl extends DTDDocumentType
{   
  /**
   *@link aggregation
   *      @associates <{com.togethersoft.modules.xmleditor.dtd.impl.DTDElementDefImpl}>
   * @supplierCardinality 0..*
   * @directed
   */
  private Hashtable m_elementDefs = new Hashtable();

  private ConfigProperties m_configProps = new ConfigProperties();

  //--------------------------------------------------------------------
  public DTDDocumentTypeImpl()
  {
  }

  //--------------------------------------------------------------------
  static public DTDDocumentType newInstance()
  {
    return new DTDDocumentTypeImpl();
  }

  //--------------------------------------------------------------------
  public Vector elementDefs()
  {
    Vector v = new Vector (m_elementDefs.values());
    Collections.sort (v);
    return v;
  }

  //--------------------------------------------------------------------
  public DTDElementDef createElementDef (String tagName, int type)
  {
    DTDElementDef elementDef = new DTDElementDefImpl (this, tagName, type);
    m_elementDefs.put (tagName, elementDef);

    return elementDef;
  }

  //--------------------------------------------------------------------
  public DTDElementItemDef createElementItemDef (DTDElementDef elementDef, int cardinality)
  {
    return new DTDElementItemDefImpl ((DTDElementDefImpl) elementDef, cardinality);
  }

  //--------------------------------------------------------------------
  public DTDGroupItemDef createGroupItemDef (int groupType, int cardinality)
  {
    return new DTDGroupItemDefImpl (groupType, cardinality);
  }

  //--------------------------------------------------------------------
  public DTDAttributeDef createAttributeDef (String attrName, int dataType)
  {
    return new DTDAttributeDefImpl (attrName, dataType);
  }

  //--------------------------------------------------------------------
  public DTDAttributeDef createAttributeDef (String attrName)
  {
    return new DTDAttributeDefImpl (attrName, DTDAttributeDef.DT_CDATA);
  }

  //--------------------------------------------------------------------
  public DTDElementDef findElementDef (String tagName)
  {
    DTDElementDef elementDef = (DTDElementDef) m_elementDefs.get (tagName);

    if (elementDef == null && tagName.equals("#PCDATA"))
    {
      elementDef = createElementDef (tagName, DTDElementDef.ET_PCDATA);
      elementDef.setFullName ("<TEXT>");
      elementDef.setValueTypeName ("ExtendedStringField");
    }

    return elementDef;
  }

  //--------------------------------------------------------------------
  // LOAD FROM DTD FILE
  //--------------------------------------------------------------------

  public void load (InputStream is, String dtdFileName) throws IOException, IllegalArgumentException
  {
    Informator informator = new DefaultInformatorImpl();
    Parser parser = new Parser (dtdFileName);
    parser.setErrorHandler (new XMLErrorHandler (dtdFileName, informator));

    DTD dtd = parser.readDTDStream (is);

    try
    {
      load (dtd);
    }
    catch (IllegalArgumentException e)
    {
      throw new IllegalArgumentException ("Error in DTD-file '" + dtdFileName + "'.\n" +
                                          e.getMessage());
    }
  }

  //--------------------------------------------------------------------
  public void load (String dtdFileName) throws IOException, IllegalArgumentException
  {
    String configFileName = null;

    int extPos = dtdFileName.indexOf ('.');
    if (extPos > 0)
      configFileName = dtdFileName.substring (0, extPos) + ".config";

    load (dtdFileName, configFileName);
  }

  //--------------------------------------------------------------------
  public void load (String dtdFileName, String dtdConfigFileName) throws IOException, IllegalArgumentException
  {
    FileInputStream is = new FileInputStream (dtdFileName);
    load (is, dtdFileName);

    if (dtdConfigFileName != null)
    {
      try
      {
        loadConfig (dtdConfigFileName);
      }
      catch (FileNotFoundException e) { }
    }
  }

  //--------------------------------------------------------------------
  public void load (DTD dtd) throws IllegalArgumentException
  {
    if (dtd == null) return;

    // create empty definitions of all elements
    for (Enumeration e = dtd.getElementDeclarations(); e.hasMoreElements(); )
    {
      ElementDecl elementDecl = (ElementDecl) e.nextElement();
      createElementDef (dtd, elementDecl);
    }

    // load element definitions
    for (Enumeration e = dtd.getElementDeclarations(); e.hasMoreElements(); )
    {
      ElementDecl elementDecl = (ElementDecl) e.nextElement();
      loadElementDef (dtd, elementDecl);
    }
  }

  //--------------------------------------------------------------------
  private void createElementDef (DTD dtd, ElementDecl elementDecl)
  {
    String tagName = elementDecl.getName();
    int elementType = DTDElementDef.ET_PCDATA;

    switch (elementDecl.getContentType())
    {
      case ElementDecl.ANY:
        elementType = DTDElementDef.ET_ANY;
        break;

      case ElementDecl.EMPTY:
        elementType = DTDElementDef.ET_EMPTY;
        break;

      case ElementDecl.MODEL_GROUP:

        ContentModel contentModel = dtd.getContentModel (tagName);
        CMNode node = contentModel.getContentModelNode();

        if ((node instanceof CMLeaf) && ((CMLeaf) node).getName().equals("#PCDATA"))
          elementType = DTDElementDef.ET_PCDATA;
        else
          elementType = DTDElementDef.ET_COMPOUND;

        break;
    }

    createElementDef (tagName, elementType);
  }

  //--------------------------------------------------------------------
  private void loadElementDef (DTD dtd, ElementDecl elementDecl) throws IllegalArgumentException
  {
    String tagName = elementDecl.getName();

    DTDElementDef elementDef = (DTDElementDef) m_elementDefs.get (tagName);
    if (elementDef == null)
      return;

    switch (elementDef.getElementType())
    {
      case DTDElementDef.ET_COMPOUND:

        ContentModel contentModel = dtd.getContentModel (tagName);
        CMNode node = contentModel.getContentModelNode();

        try
        {
          DTDItemDef childItemDef = loadItemDef (node);
          elementDef.setChildrenItemDef (childItemDef);
        }
        catch (UnknownXMLElementException e)
        {
          throw new IllegalArgumentException ("Compound XML-element '" + tagName +
                          "' contains reference to unknown XML-element '" + e.getElementName() + '\'');
        }

        break;

      case DTDElementDef.ET_ANY:

        // treat it like compound with OR-group:  (element1 | element2 | .. all elements | #PCDATA)*

        DTDGroupItemDef groupItemDef = createGroupItemDef (DTDGroupItemDef.OR_GROUP, DTDItemDef.CARDINALITY_ANY_TIMES);

        findElementDef ("#PCDATA");  // ensure there is a dummy element #PCDATA

        for (Enumeration allElements = elementDefs().elements(); allElements.hasMoreElements();)
        {
          DTDElementDef childElementDef = (DTDElementDef) allElements.nextElement();
          DTDItemDef itemDef = createElementItemDef (childElementDef, DTDItemDef.CARDINALITY_ONCE);
          groupItemDef.addItemDef (itemDef);
        }

        elementDef.setChildrenItemDef (groupItemDef);
        break;
    }


    // load definitions of attributes
    for (Enumeration e = dtd.getAttributeDeclarations (tagName); e.hasMoreElements(); )
    {
      AttDef attrDecl = (AttDef) e.nextElement();
      DTDAttributeDef attrDef = loadAttributeDef (attrDecl);
      elementDef.addAttributeDef (attrDef);
    }
  }

  //--------------------------------------------------------------------
  private DTDItemDef loadItemDef (CMNode node) throws UnknownXMLElementException
  {
    DTDItemDef itemDef = null;

    if (node instanceof CMLeaf)
      itemDef = loadItemDef ((CMLeaf) node, DTDItemDef.CARDINALITY_ONCE);
    else
    if (node instanceof CM1op)
      itemDef = loadItemDef ((CM1op) node, DTDItemDef.CARDINALITY_ONCE);
    else
    if (node instanceof CM2op)
      itemDef = loadItemDef ((CM2op) node, DTDItemDef.CARDINALITY_ONCE);

    return itemDef;
  }

  //--------------------------------------------------------------------
  private DTDItemDef loadItemDef (CMNode node, int cardinality) throws UnknownXMLElementException
  {
    DTDItemDef itemDef = null;

    if (node instanceof CMLeaf)
      itemDef = loadItemDef ((CMLeaf) node, cardinality);
    else
    if (node instanceof CM1op)
      itemDef = loadItemDef ((CM1op) node, cardinality);
    else
    if (node instanceof CM2op)
      itemDef = loadItemDef ((CM2op) node, cardinality);

    return itemDef;
  }

  //--------------------------------------------------------------------
  private DTDItemDef loadItemDef (CMLeaf leafNode, int cardinality) throws UnknownXMLElementException
  {
    String tagName = leafNode.getName();

    DTDElementDef elementDef = findElementDef (tagName);
    if (elementDef == null)
      throw new UnknownXMLElementException (tagName);

    return createElementItemDef (elementDef, cardinality);
  }

  //--------------------------------------------------------------------
  private DTDItemDef loadItemDef (CM1op cm1Group, int cardinality) throws UnknownXMLElementException
  {
    boolean canMany  = false;
    boolean canNever = false;

    switch (cardinality)
    {
      case DTDItemDef.CARDINALITY_ANY_TIMES:      canMany = canNever = true;  break;
      case DTDItemDef.CARDINALITY_ONCE_OR_NEVER:  canNever = true;  break;
      case DTDItemDef.CARDINALITY_ONCE_OR_MORE:   canMany = true;  break;
    }

    switch (cm1Group.getType())
    {
      case '*':  canMany = canNever = true;  break;
      case '?':  canNever = true;  break;
      case '+':  canMany = true;  break;
    }

    int newCardinality =
       (canMany) ? ((canNever) ? DTDItemDef.CARDINALITY_ANY_TIMES : DTDItemDef.CARDINALITY_ONCE_OR_MORE)
                 : ((canNever) ? DTDItemDef.CARDINALITY_ONCE_OR_NEVER : DTDItemDef.CARDINALITY_ONCE);

    CMNode node = cm1Group.getNode();
    return loadItemDef (node, newCardinality);
  }

  //--------------------------------------------------------------------
  private DTDItemDef loadItemDef (CM2op cm2Group, int cardinality) throws UnknownXMLElementException
  {
    int groupType = 0;
    switch (cm2Group.getType())
    {
      case ',':  groupType = DTDGroupItemDef.AND_GROUP; break;
      case '|':  groupType = DTDGroupItemDef.OR_GROUP; break;
    }

    DTDGroupItemDef groupItemDef = createGroupItemDef (groupType, cardinality);

    CMNode leftNode = cm2Group.getLeft();
    CMNode rightNode = cm2Group.getRight();

    groupItemDef.addItemDef (loadItemDef (leftNode));
    groupItemDef.addItemDef (loadItemDef (rightNode));

    return groupItemDef;
  }

  //--------------------------------------------------------------------
  private DTDAttributeDef loadAttributeDef (AttDef attrDecl)
  {
    int dataType = DTDAttributeDef.DT_CDATA;

    switch (attrDecl.getDeclaredType())
    {
      case AttDef.CDATA:
      case AttDef.ENTITIES:
      case AttDef.ENTITY:
      case AttDef.ID:
      case AttDef.IDREF:
      case AttDef.IDREFS:
      case AttDef.NMTOKEN:
      case AttDef.NMTOKENS:

        dataType = DTDAttributeDef.DT_CDATA;
        break;

      case AttDef.NAME_TOKEN_GROUP:
      case AttDef.NOTATION:

        dataType = DTDAttributeDef.DT_ENUMERATED;
        break;
    }

    DTDAttributeDef attrDef = createAttributeDef (attrDecl.getName(), dataType);

    if (dataType == DTDAttributeDef.DT_ENUMERATED)
    {
      for (Enumeration e = attrDecl.elements(); e.hasMoreElements(); )
      {
        String value = (String) e.nextElement();
        attrDef.addEnumValue (value);
      }
    }

    switch (attrDecl.getDefaultType())
    {
      case AttDef.IMPLIED:
        attrDef.setDefaultModifier (DTDAttributeDef.DM_IMPLIED);
        break;

      case AttDef.REQUIRED:
        attrDef.setDefaultModifier (DTDAttributeDef.DM_REQUIRED);
        break;

      case AttDef.FIXED:

        attrDef.setDefaultModifier (DTDAttributeDef.DM_FIXED);
        break;
    }

    String defaultValue = attrDecl.getDefaultStringValue();
    attrDef.setDefaultValue (defaultValue);

    return attrDef;
  }

  //--------------------------------------------------------------------
  // LOAD ADDITIONAL SETTINGS FROM PROPERTIES TABLE
  //--------------------------------------------------------------------

  public ConfigProperties getConfigProperties()
  {
    return m_configProps;
  }

  //--------------------------------------------------------------------
  private void load (ConfigProperties props)
  {
    m_configProps = props;

    for (Enumeration e = m_elementDefs.elements(); e.hasMoreElements(); )
    {
      DTDElementDefImpl elementDefImpl = (DTDElementDefImpl) e.nextElement();
      elementDefImpl.load (props);
    }
  }

  //--------------------------------------------------------------------
  public void loadConfig (InputStream is) throws IOException
  {
    Properties props = new Properties();
    props.load (is);
    is.close();
     
    load (new ConfigProperties (props));
  }

  //--------------------------------------------------------------------
  public void loadConfig (String dtdConfigFileName) throws IOException
  {
    FileInputStream is = new FileInputStream (dtdConfigFileName);
    loadConfig (is);
  }

  //--------------------------------------------------------------------
}
