XmlFactory.java

/*
** Module   : XmlFactory
** Abstract : DOM and SAX XML instance creation.
**
** Copyright (c) 2013-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --------------------------------Description-----------------------------------
** 001 EVL 20130204 Created initial version. Major rework to use w3c backend.
** 002 EVL 20130208 Added SAX XML Support.
** 003 CA  20130308 The CREATE APIs need to receive a handle parameter, to properly initiate it,
**                  as returning the new handle instance and changing the handle reference in the
**                  caller is not the right approach.
** 004 CA  20130309 Added missing APIs related to WIDGET-POOL clause.
** 005 CA  20130311 Added more missing APIs related to WIDGET-POOL clause.
** 006 CA  20130918 Added widget-pool support. The pool name must be validated before anything 
**                  else, and a null name means the default pool is used.
** 007 EVK 20130531 Added maps ABL node name to Dom nodeId and Dom nodeId to
**                  ABL node name. Added methods convertNodeToAblNode() and
**                  convertAblNodeToNodeId().
**                  createXNodeReference() methods now uses handle in parameters instead of
**                  Document interface.
**                  Added type support for createXNodeReference() method.
** 008 CA  20130927 Create all resources, even if they are not fully-implemented.
** 009 CA  20230724 Further reduce context-local usage.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
** 
** Additional terms under GNU Affero GPL version 3 section 7:
** 
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
**   terms apply to the works covered under the License.  These additional terms
**   are non-permissive additional terms allowed under Section 7 of the GNU
**   Affero GPL version 3 and may not be removed by you.
** 
**   0. Attribution Requirement.
** 
**     You must preserve all legal notices or author attributions in the covered
**     work or Appropriate Legal Notices displayed by works containing the covered
**     work.  You may not remove from the covered work any author or developer
**     credit already included within the covered work.
** 
**   1. No License To Use Trademarks.
** 
**     This license does not grant any license or rights to use the trademarks
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
**     of Golden Code Development Corporation. You are not authorized to use the
**     name Golden Code, FWD, or the names of any author or contributor, for
**     publicity purposes without written authorization.
** 
**   2. No Misrepresentation of Affiliation.
** 
**     You may not represent yourself as Golden Code Development Corporation or FWD.
** 
**     You may not represent yourself for publicity purposes as associated with
**     Golden Code Development Corporation, FWD, or any author or contributor to
**     the covered work, without written authorization.
** 
**   3. No Misrepresentation of Source or Origin.
** 
**     You may not represent the covered work as solely your work.  All modified
**     versions of the covered work must be marked in a reasonable way to make it
**     clear that the modified work is not originating from Golden Code Development
**     Corporation or FWD.  All modified versions must contain the notices of
**     attribution required in this license.
*/

package com.goldencode.p2j.xml;

import com.goldencode.p2j.util.*;

import org.w3c.dom.*;

import java.util.*;

import static com.goldencode.p2j.xml.XmlTokenTypes.*;

/**
 * The set of methods to work with DOM XML and SAX support implementation in P2J.
 */
public class XmlFactory
{
   /** Map of token names to token types */
   private static final Map<String, Integer> nodeNameToNodeId = new HashMap<String, Integer>();

   /** Map of token types to token names */
   private static final Map<Integer, String> nodeIdToNodeName = new HashMap<Integer, String>();

   // Initialize token maps.
   static
   {
      nodeNameToNodeId.put("ATTRIBUTE", ATTRIBUTE_NODE);
      nodeNameToNodeId.put("CDATA-SECTION", CDATA_SECTION_NODE);
      nodeNameToNodeId.put("COMMENT", COMMENT_NODE);
      nodeNameToNodeId.put("DOCUMENT", DOCUMENT_NODE);
      nodeNameToNodeId.put("DOCUMENT-FRAGMENT", DOCUMENT_FRAGMENT_NODE);
      nodeNameToNodeId.put("ELEMENT", ELEMENT_NODE);
      nodeNameToNodeId.put("ENTITY-REFERENCE", ENTITY_REFERENCE_NODE);
      nodeNameToNodeId.put("PROCESSING-INSTRUCTION", PROCESSING_INSTRUCTION_NODE);
      nodeNameToNodeId.put("TEXT", TEXT_NODE);
      for (Map.Entry<String, Integer> entry : nodeNameToNodeId.entrySet())
      {
         nodeIdToNodeName.put(entry.getValue(), entry.getKey());
      }
   }

   /**
    * Constructs a new instance of the Progress X-Document object and
    * retrieves the handle variable associated with it.
    *
    * @param    h
    *           The handle where the new resource will be set as the referent.
    */
   public static void createXDocument(handle h)
   {
      createXDocument(h, (character) null);
   }

   /**
    * Constructs a new instance of the Progress X-Document object and
    * retrieves the handle variable associated with it.
    *
    * @param    h
    *           The handle where the new resource will be set as the referent.
    * @param    poolName
    *           The name of the widget pool. <code>null</code> if the unnamed pool must be used.
    */
   public static void createXDocument(handle h, String poolName)
   {
      createXDocument(h, (poolName == null ? null : new character(poolName)));
   }

   /**
    * Constructs a new instance of the Progress X-Document object and
    * retrieves the handle variable associated with it.
    *
    * @param    h
    *           The handle where the new resource will be set as the referent.
    * @param    poolName
    *           The name of the widget pool. <code>null</code> if the unnamed pool must be used.
    */
   public static void createXDocument(handle h, character poolName)
   {
      // validate widget pool before anything else!!!
      if (!WidgetPool.validWidgetPool(poolName))
      {
         return;
      }

      XDocumentImpl res = new XDocumentImpl();
      h.assign(res);
      
      res.addToPool(poolName);
   }

   /**
    * Constructs a new instance of the Progress X-Node-Reference object and
    * retrieves the handle variable associated with it.
    * 
    * @param    h
    *           The handle where the new resource will be set as the referent.
    */
   public static void createXNodeReference(handle h)
   {
      createXNodeReference(h, (character) null);
   }

   /**
    * Constructs a new instance of the Progress X-Node-Reference object and
    * retrieves the handle variable associated with it.
    * 
    * @param    h
    *           The handle where the new resource will be set as the referent.
    * @param    poolName
    *           The name of the widget pool. <code>null</code> if the unnamed pool must be used.
    */
   public static void createXNodeReference(handle h, String poolName)
   {
      createXNodeReference(h, (poolName == null ? null : new character(poolName)));
   }

   /**
    * Constructs a new instance of the Progress X-Node-Reference object and
    * retrieves the handle variable associated with it.
    * 
    * @param    h
    *           The handle where the new resource will be set as the referent.
    * @param    poolName
    *           The name of the widget pool. <code>null</code> if the unnamed pool must be used.
    */
   public static void createXNodeReference(handle h, character poolName)
   {
      // validate widget pool before anything else!!!
      if (!WidgetPool.validWidgetPool(poolName))
      {
         return;
      }

      XNodeRefImpl res = new XNodeRefImpl();
      h.assign(res);
      
      res.addToPool(poolName);
   }

   /**
    * Constructs a new instance of the Progress X-Node-Reference object and
    * retrieves the handle variable associated with it. The approach is
    * slightly different. Each node will have additional parameter - Name
    * Space and node will be addressed by namespace and name pair instead
    * of the simple name.
    *
    * @param    h
    *           The handle where the new resource will be set as the referent.
    *           The handle should contains resource with type of XNodeRef.
    * @param    docHandle
    *           handle that should contains the X-Document object to create X-Node under.
    * @param    nsUri
    *           Name space associated with the current set of names.
    * @param    name
    *           The name of the node to create.
    * @param    type
    *           The type of the node to create.
    */
   public static void createXNodeReference(handle    h,
                                           handle    docHandle,
                                           character nsUri,
                                           character name,
                                           character type)
   {
      if (docHandle.isUnknown() || !docHandle._isValid() || !XEntityImpl.isXNodeHandle(h))
      {
         return;
      }

      Document doc = getDocument(docHandle);
      if (doc == null)
      {
         return;
      }
      String nameImpl = null;
      String nsUriImpl = null;
      
      if (!nsUri.isUnknown())
      {
         nsUriImpl = nsUri.toStringMessage();
      }
      if (!name.isUnknown())
      {
         nameImpl = name.toStringMessage();
      }
      Integer nodeType = type.isUnknown() ?
                         ELEMENT_NODE :
                         convertAblNodeToNodeId(type.toStringMessage());

      final Node node;

      if (nodeType == ELEMENT_NODE)
      {
         node = doc.createElementNS(nsUriImpl, nameImpl);
      }
      else if (nodeType == ATTRIBUTE_NODE)
      {
         node = doc.createAttributeNS(nsUriImpl, nameImpl);
      }
      else
      {
         throw new DOMException(DOMException.TYPE_MISMATCH_ERR, "Unknown type");
      }

      ((XEntityImpl)h.getResource()).setNode(node);
   }

   /**
    * Constructs a new instance of the Progress Sax-Attributes object and retrieves the handle
    * variable associated with it.
    * 
    * @param    h
    *           The handle where the new resource will be set as the referent.
    */
   public static void createSaxAttributes(handle h)
   {
      createSaxAttributes(h, (character) null);
   }

   /**
    * Constructs a new instance of the Progress Sax-Attributes object and retrieves the handle
    * variable associated with it.
    *
    * @param    h
    *           The handle where the new resource will be set as the referent.
    * @param    widgetPool
    *           The name of the widget pool. <code>null</code> if the unnamed pool must be used.
    */
   public static void createSaxAttributes(handle h, String widgetPool)
   {
      createSaxAttributes(h, (widgetPool == null ? null : new character(widgetPool)));
   }

   /**
    * Constructs a new instance of the Progress Sax-Attributes object and retrieves the handle
    * variable associated with it.
    *
    * @param    h
    *           The handle where the new resource will be set as the referent.
    * @param    widgetPool
    *           The name of the widget pool. <code>null</code> if the unnamed pool must be used.
    */
   public static void createSaxAttributes(handle h, character widgetPool)
   {
      // validate widget pool before anything else!!!
      if (!WidgetPool.validWidgetPool(widgetPool))
      {
         return;
      }

      SaxAttributesImpl res = new SaxAttributesImpl();
      h.assign(res);

      res.addToPool(widgetPool);
   }
   
   /**
    * Constructs a new instance of the Progress Sax-Reader object and retrieves the handle
    * variable associated with it.
    * 
    * @param    h
    *           The handle where the new resource will be set as the referent.
    */
   public static void createSaxReader(handle h)
   {
      createSaxReader(h, (character) null);
   }

   /**
    * Constructs a new instance of the Progress Sax-Reader object and retrieves the handle
    * variable associated with it.
    *
    * @param    h
    *           The handle where the new resource will be set as the referent.
    * @param    widgetPool
    *           The name of the widget pool. <code>null</code> if the unnamed pool must be used.
    */
   public static void createSaxReader(handle h, String widgetPool)
   {
      createSaxReader(h, (widgetPool == null ? null : new character(widgetPool)));
   }

   /**
    * Constructs a new instance of the Progress Sax-Reader object and retrieves the handle
    * variable associated with it.
    *
    * @param    h
    *           The handle where the new resource will be set as the referent.
    * @param    widgetPool
    *           The name of the widget pool. <code>null</code> if the unnamed pool must be used.
    */
   public static void createSaxReader(handle h, character widgetPool)
   {
      // validate widget pool before anything else!!!
      if (!WidgetPool.validWidgetPool(widgetPool))
      {
         return;
      }

      SaxReaderImpl res = new SaxReaderImpl();
      h.assign(res);

      res.addToPool(widgetPool);
   }
   
   /**
    * Constructs a new instance of the Progress Sax-Writer object and retrieves the handle
    * variable associated with it.
    * 
    * @param    h
    *           The handle where the new resource will be set as the referent.
    */
   public static void createSaxWriter(handle h)
   {
      createSaxWriter(h, (character) null);
   }

   /**
    * Constructs a new instance of the Progress Sax-Writer object and retrieves the handle
    * variable associated with it.
    *
    * @param    h
    *           The handle where the new resource will be set as the referent.
    * @param    widgetPool
    *           The name of the widget pool. <code>null</code> if the unnamed pool must be used.
    */
   public static void createSaxWriter(handle h, String widgetPool)
   {
      createSaxWriter(h, (widgetPool == null ? null : new character(widgetPool)));
   }

   /**
    * Constructs a new instance of the Progress Sax-Writer object and retrieves the handle
    * variable associated with it.
    *
    * @param    h
    *           The handle where the new resource will be set as the referent.
    * @param    widgetPool
    *           The name of the widget pool. <code>null</code> if the unnamed pool must be used.
    */
   public static void createSaxWriter(handle h, character widgetPool)
   {
      // validate widget pool before anything else!!!
      if (!WidgetPool.validWidgetPool(widgetPool))
      {
         return;
      }

      SaxWriterImpl res = new SaxWriterImpl();
      h.assign(res);
      
      res.addToPool(widgetPool);
   }

   /**
    * Resolve the given numeric token type to a character ABL token type.
    *
    * @param   nodeId
    *          integer constant representing node id.
    *
    * @return  character token type or <code>unknow</code> if node id could not be resolved.
    */
   public static character convertNodeToAblNode(int nodeId)
   {
      return new character(nodeIdToNodeName.get(nodeId));
   }

   /**
    * Resolve the given numeric token type to a token name. The lookup is case-insensitive.
    *
    * @param   nodeName
    *          String constant representing token name.
    *
    * @return  int token type or <code>null</code> if name could not
    *          be resolved.
    */
   public static Integer convertAblNodeToNodeId(String nodeName)
   {
      if (nodeName == null)
      {
         return null;
      }
      return nodeNameToNodeId.get(nodeName.toUpperCase());
   }

   /**
    * Constructs a new instance of the Progress X-Node-Reference object and
    * retrieves the handle variable associated with it.
    *
    * @param    h
    *           The handle where the new resource will be set as the referent.
    *           The handle should contains resource with type of XNodeRef.
    * @param    docHandle
    *           handle should be valid and should contains the valid X-Document object to
    *           create X-Node under.
    * @param    name
    *           The name of the node to create.
    * @param    type
    *           The type of the node to create.
    *
    * @throws   DOMException
    *           if type is cannot resolved. Or if name is invalid for XML.
    */
   static void createXNodeReference(handle h,
                                    handle docHandle,
                                    character name,
                                    character type)
   {
      if (docHandle == null || !docHandle._isValid())
      {
         throw new IllegalArgumentException("docHandle must be not null and valid");
      }
      if (!XEntityImpl.isXNodeHandle(h))
      {
         throw new IllegalArgumentException("handle should contains resource with type of " +
                                            "XNodeRef");
      }
   
      Document doc = getDocument(docHandle);
      if (doc == null)
      {
         throw new IllegalArgumentException("docHandle must be of type DOCUMENT_NODE");
      }
   
      final Integer nodeId;
      if (type == null || type.isUnknown())
      {
         nodeId = ELEMENT_NODE;
      }
      else
      {
         nodeId = convertAblNodeToNodeId(type.toStringMessage());
      }
   
      if (nodeId == null)
      {
         throw new DOMException(DOMException.SYNTAX_ERR, "Unknown type");
      }
   
      String nameStr = null;
      if (!name.isUnknown())
      {
         nameStr = name.toStringMessage();
      }
   
      final Node node;
   
      switch (nodeId)
      {
         case ATTRIBUTE_NODE:
            node = doc.createAttribute(nameStr);
            break;
         case CDATA_SECTION_NODE:
            node = doc.createCDATASection("");
            break;
         case COMMENT_NODE:
            node = doc.createComment("");
            break;
         case DOCUMENT_FRAGMENT_NODE:
            node = doc.createDocumentFragment();
            break;
         case ELEMENT_NODE:
            node = doc.createElement(nameStr);
            break;
         case ENTITY_REFERENCE_NODE:
            node = doc.createEntityReference(nameStr);
            break;
         case PROCESSING_INSTRUCTION_NODE:
            node = doc.createProcessingInstruction(nameStr, "");
            break;
         case TEXT_NODE:
            node = doc.createTextNode("");
            break;
         default:
            throw new DOMException(DOMException.TYPE_MISMATCH_ERR, "Unknown type");
      }
   
      ((XEntityImpl)h.getResource()).setNode(node);
   }

   /**
    * Extract {@link Document} from valid handle, which contains {@link XDocumentImpl} class.
    * If handle is not valid or doesn't inherit {@link XDocumentImpl} than return null.
    *
    * @param    docHandle
    *           The handle reference.
    *
    * @return   {@link Document} or <code>null</code> if handle invalid or doesn't contains
    *           Document object.
    */
   private static Document getDocument(handle docHandle) {
      if (!docHandle._isValid())
      {
         return null;
      }
      Object resource = docHandle.get();
   
      XDocumentImpl xDocument;
   
      if (resource instanceof XDocumentImpl)
      {
         xDocument = (XDocumentImpl) resource;
      }
      else
      {
         return null;
      }
   
      return (Document) xDocument.getNode();
   }
}