# SaxReaderImpl.java

    /*
    ** Module   : SaxReaderImpl
    ** Abstract : SAX reader specific features implementation.
    **
    ** Copyright (c) 2013-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --------------------------------------------Description-----------------------------------
    ** 001 EVL 20130208 Created initial version. Implementation is limited to the stubs to get the
    **                  converted java code compiled.
    ** 002 CA  20130221 Added ADM-DATA and UNIQUE-ID support.
    ** 003 EVL 20130320 Spelling fixes for comments.
    ** 004 EVK 20131001 Implement methods and class logic.
    ** 005 EVK 20140113 Added method resourceDelete to properly remove the object.
    ** 009 CA  20181029 Fixed a typo in SCHEMA-PATH setter.
    ** 010 CA  20190314 Ignore validation errors related to missing DTD.
    ** 011 HC  20200102 Implemented ADD-SCHEMA-LOCATION on SAX-reader object.
    ** 012 CA  20210629 Fixed startDocument and endPrefixMapping calls.
    **                  For prolog errors, report only the first one via the handler callback procedure, and do
    **                  not raise an ERROR.
    **     CA  20210709 XML parse must use a pristine byte stream and use the reader to decode it (using the
    **                  encoding as specified at the XML prolog).  Also, the XML version is forced to 1.1, to
    **                  allow ISO control characters; FWD will automatically escape them.
    **     EVL 20220528 Ensure the memptr based SAX source has finished with new line separator.  Otherwise the
    **                  parser will fail at the end of the file even if the content is properly formatted.
    **     EVL 20220529 Improved fix for re-make memptr data source to include missing new line separator.
    **     CA  20220707 Added 'MemptrInputStream.read(byte[] array, int off, int len)', to improve performance.
    **     CA  20220930 Refactored the callback invocation to be performed via a call-site and InvokeConfig, to
    **                  allow caching of the resolved target.
    ** 013 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
    ** 014 SP  20250319 Fixed bytesRead calculation in MemptrInputStream.read(byte[], off, len).
    ** 015 SP  20250327 Removed the addition of new line separator in MemptrInputStream source.
    */

    /*
    ** 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 javax.xml.stream.*;
    import javax.xml.stream.events.*;
    import javax.xml.stream.util.XMLEventAllocator;
    import java.io.*;
    import java.net.*;
    import java.util.concurrent.atomic.AtomicLong;
    import java.util.logging.*;

    import com.ctc.wstx.api.WstxInputProperties;
    import com.ctc.wstx.cfg.ParsingErrorMsgs;
    import com.ctc.wstx.evt.DefaultEventAllocator;
    import com.goldencode.p2j.util.*;
    import com.goldencode.p2j.util.ErrorManager;
    import com.goldencode.p2j.util.logging.*;
    import com.goldencode.util.XmlHelper;

    import org.codehaus.stax2.*;
    import org.codehaus.stax2.validation.*;

    /**
     * Implementation SAX-Reader specific features of the SAX objects.
     */
    public class SaxReaderImpl
    extends SaxEntityImpl
    implements SaxReader
    {
       /** Anonymous log instance. */
       private static final CentralLogger LOG = CentralLogger.get(SaxReaderImpl.class.getName());

       /** Current status of SAX-Writer */
       private ReadStatus readStatus = ReadStatus.SAX_UNINITIALIZED;

       /** Current input source of SAX-Reader */
       private InputStream inputStream = null;

       /** Sax-parser object */
       private XMLStreamReader2 saxParser;

       /** helps XMLStreamReader to allocate xml events*/
       private XMLEventAllocator allocator;

       /** Special wrapper to call callback procedures*/
       private XMLCallbackWrapper callbackWrapper;

       /** Specify the location of the SAX callback procedures */
       private handle hCallback;

       /** Indicates if the parser validates the XML document against DTD or not. */
       private boolean validate = true;

       /** Indicates if the namespace processing is suppressed or not. */
       private boolean suppress = false;

       /** Schema path location variable */
       private String schemaPaths = null;

       /** Schema locations variable */
       private String schemaLocation = "";

       /** No name Schema location variable */
       private String noNameSchemaLocation = "";

       /** Flag indicating if the parser had a prolog error. */
       private boolean prologError = false;

       /**
        * Default constructor.
        */
       public SaxReaderImpl()
       {
          // no-op
       }

       /**
        * Find DTD in path.
        *
        * @param    workPath
        *           File path or directory. May be relative path. This path is specify in 4gl
        *           SCHEMA-PATH attribute.
        *
        * @param    systemID
        *           DTD schema filename.
        * @param    baseURI
        *           Working directory. May used if workPath is not absolute path.
        *
        * @return   If DTD schema is found, then return InputStream for this schema.
        */
       private static InputStream findSchemaInPath(String workPath, String systemID, String baseURI)
       {
          File path = new File(workPath);

          if (path.isAbsolute())
          {
             return getInputStream(systemID, path);
          }
          else //path may be relative
          {
             URI baseUri = null;
             try
             {
                baseUri = new URI(baseURI);
             }
             catch (URISyntaxException ignored)
             {
                return null;
             }
             String basePath = baseUri.getPath();
             File relativePath = new File(basePath, workPath);

             if (relativePath.exists())
             {
                return getInputStream(systemID, relativePath);
             }
          }
          return null;
       }

       /**
        * Find fileName. 1) If path is file and name is equals to fileName, then return InputStream.
        * 2) if path is directory and inside directory exists file with name fileName, then return
        * InputStream. In other cases return null.
        *
        * @param    fileName
        *           File to find
        * @param    path
        *           May be file which method is try to find or directory in which need to find file.
        *
        * @return   see above.
        */
       private static InputStream getInputStream(String fileName, File path)
       {
          if (path.isFile())
          {
             if (fileName.equals(path.getName()))
             {
                return new FileInputStreamWrapper(path.getAbsolutePath());
             }
             else
             {
                return null;
             }
          }
          else  //directory
          {
             File newPath = new File(path, fileName);
             if (newPath.exists())
             {
                return new FileInputStreamWrapper(newPath.getAbsolutePath());
             }
             else
             {
                return null;
             }
          }
       }

       /**
        * Gets the value of the handler atribute for the given SAX Reader object.
        *
        * @return   The handle variable representing the handler attribute.
        */
       @Override
       public handle getHandler()
       {
          return new handle(hCallback);
       }

       /**
        * Sets the value of the handler atribute for the given SAX Reader object.
        *
        * @param    hCallback
        *           The handle of the new callback handler to be set for the handler attribute.
        */
       @Override
       public void setHandler(handle hCallback)
       {
          if (hCallback.isUnknown())
          {
             invalidArgumentAssignError("HANDLE");
             return;
          }
          this.hCallback = new handle(hCallback);
       }

       /**
        * Gets the value of the current 1-based column in XML source.
        *
        * @return   The integer value of the current column.
        */
       @Override
       public integer getLocatorColumnNumber()
       {
          if (readStatus == ReadStatus.SAX_UNINITIALIZED)
          {
             return new integer();
          }
          return new integer(saxParser.getLocation().getColumnNumber());
       }

       /**
        * Gets the value of the current 1-based line number in XML source.
        *
        * @return   The integer value of the current line.
        */
       @Override
       public integer getLocatorLineNumber()
       {
          if (readStatus == ReadStatus.SAX_UNINITIALIZED)
          {
             return new integer();
          }
          return new integer(saxParser.getLocation().getLineNumber());
       }

       /**
        * Gets the current value of the public identifier of the current XML source.
        *
        * @return   The character value representing the current locator-public-id attribute.
        */
       @Override
       public character getLocatorPublicId()
       {
          if (readStatus == ReadStatus.SAX_UNINITIALIZED)
          {
             return new character();
          }
          return new character(saxParser.getLocation().getPublicId());
       }

       /**
        * Gets the current value of the system identifier of the current XML source.
        *
        * @return   The character value representing the current locator-system-id attribute.
        */
       @Override
       public character getLocatorSystemId()
       {
          if (readStatus == ReadStatus.SAX_UNINITIALIZED)
          {
             return new character();
          }
          return new character(saxParser.getLocation().getSystemId());
       }

       /**
        * Gets the current value of the parse status. The default value is
        * {@link SaxReader.ReadStatus#SAX_UNINITIALIZED}. The other returned values are
        * {@link SaxReader.ReadStatus#SAX_COMPLETE},
        * {@link SaxReader.ReadStatus#SAX_PARSER_ERROR} or {@link SaxReader.ReadStatus#SAX_RUNNING}.
        *
        * @return   The integer value representing the current status of the parse.
        */
       @Override
       public integer getParseStatus()
       {
          return new integer(readStatus.getStatus());
       }

       /**
        * Gets the current value of the namespace location pair mapping.
        *
        * @return   The character value representing the current schema-location attribute.
        */
       @Override
       public character getSchemaLocation()
       {
          return new character(schemaLocation);
       }

       /**
        * Sets the current value of the namespace location pair mapping.
        *
        * @param    location
        *           The new value the current schema-location attribute.
        */
       @Override
       public void setSchemaLocation(String location)
       {
          setSchemaLocation(new character(location));
       }

       /**
        * Gets the delimiter separated list of directory paths used to locate XML DTD associated with
        * a particular XML document.
        *
        * @return   The character value representing the current schema-path attribute.
        */
       @Override
       public character getSchemaPath()
       {
          return new character(schemaPaths);
       }

       /**
        * Sets the delimiter separated list of directory paths used to locate XML DTD associated with
        * a particular XML document.
        *
        * @param    path
        *           The new value the current schema-path attribute.
        */
       @Override
       public void setSchemaPath(String path)
       {
          setSchemaPath(new character(path));
       }

       /**
        * Indicates if the namespace processing is suppressed or not.
        *
        * @return   The logical value representing the current suppress-namespace-processing
        *           attribute.
        */
       @Override
       public logical getSuppressNamespaceProcessing()
       {
          return new logical(suppress);
       }

       /**
        * Redefines whether the namespace processing is suppressed or not.
        *
        * @param    suppress
        *           The new value the current suppress-namespace-processing attribute.
        */
       @Override
       public void setSuppressNamespaceProcessing(boolean suppress)
       {
          setSuppressNamespaceProcessing(new logical(suppress));
       }

       /**
        * Indicates if the strict entity resolution is enabled.
        *
        * @return   The logical value representing the current value.
        */
       @Override
       public logical getStrictEntityResolution()
       {
          // TODO
          return new logical();
       }

       /**
        * Enable or disable strict entity resolution.
        *
        * @param    strict
        *           The new value for the attribute.
        */
       @Override
       public void setStrictEntityResolution(logical strict)
       {
          // TODO
       }

       /**
        * Enable or disable strict entity resolution.
        *
        * @param    strict
        *           The new value for the attribute.
        */
       @Override
       public void setStrictEntityResolution(boolean strict)
       {
          setStrictEntityResolution(new logical(strict));
       }

       /**
        * Get the entity expansion limit.
        *
        * @return   The integer value of the attribute.
        */
       @Override
       public integer getEntityExpansionLimit()
       {
          // TODO
          return new integer();
       }

       /**
        * Set the entity expansion limit.
        *
        * @param    limit
        *           The new value for the attribute.
        */
       @Override
       public void setEntityExpansionLimit(int64 limit)
       {
          // TODO
       }

       /**
        * Set the entity expansion limit.
        *
        * @param    limit
        *           The new value for the attribute.
        */
       @Override
       public void setEntityExpansionLimit(long limit)
       {
          setEntityExpansionLimit(new int64(limit));
       }

       /**
        * Indicates if the parser validates the XML document against DTD or not.
        *
        * @return   The logical value representing the current validation-enabled attribute.
        */
       @Override
       public logical getValidationEnabled()
       {
          return new logical(validate);
       }

       /**
        * Redefines whether the parser will validate the XML document against DTD or not.
        *
        * @param    enable
        *           The new value the current validation-enabled attribute.
        */
       @Override
       public void setValidationEnabled(boolean enable)
       {
          setValidationEnabled(new logical(enable));
       }

       /**
        * Executing single call parse of an XML document associated with a SAX-Reader object.
        * This method is equivalent of call saxParseFirst() and then saxParseNext()
        */
       @Override
       public void saxParse()
       {
          saxParseFirst();
          while(readStatus == ReadStatus.SAX_RUNNING)
          {
             saxParseNext();
          }
       }

       /**
        * Initializes and begins progressive-scan parse of an XML document associated with a
        * SAX-Reader object.
        */
       @Override
       public void saxParseFirst()
       {
          if (inputStream == null)
          {
             saxParseError("SAX-PARSE-FIRST", "No input file specified");
             return;
          }

          try
          {
             inputStream.reset();
          }
          catch (IOException ignored)
          {
             saxParseError("SAX-PARSE-FIRST", "No input file specified");
             return;
          }

          try
          {
             XMLInputFactory xmlif = XMLInputFactory2.newInstance();
             xmlif.setProperty(WstxInputProperties.P_ALLOW_XML11_ESCAPED_CHARS_IN_XML10, true);
             xmlif.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
             xmlif.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, !suppress);
             xmlif.setProperty(XMLInputFactory.SUPPORT_DTD, validate);
             xmlif.setProperty(XMLInputFactory.IS_VALIDATING, validate);
             callbackWrapper = hCallback != null && hCallback._isValid()
                                  ? new XMLCallbackWrapper(hCallback, new handle(this))
                                  : null;
             final boolean isValidate = validate;
             xmlif.setProperty(XMLInputFactory.RESOLVER, new XMLResolver()
             {
                @Override
                public Object resolveEntity(String publicID,
                                            String systemID,
                                            String baseURI,
                                            String namespace)
                throws XMLStreamException
                {
                   if (!isValidate || callbackWrapper == null)
                   {
                      return null;
                   }
                   Object result = callbackWrapper.resolveEntity(publicID, systemID);
                   if (result != null)
                   {
                      return result;
                   }

                   //find dtd schema in file path
                   if (schemaPaths == null || schemaPaths.isEmpty())
                   {
                      return null;
                   }

                   try
                   {
                      URL url = new URL(systemID);
                      String protocol = url.getProtocol();
                      if ("http".equalsIgnoreCase(protocol) || "https".equalsIgnoreCase(protocol) ||
                          "ftp".equalsIgnoreCase(protocol) || "ftps".equalsIgnoreCase(protocol))
                      {
                         return url.openStream();
                      }
                   }
                   catch (MalformedURLException ignored)
                   {
                      // no-op
                   }
                   catch (IOException e)
                   {
                      throw new XMLStreamException(e);
                   }

                   String[] paths = schemaPaths.split(",");

                   for (String path : paths)
                   {
                      InputStream inPath = findSchemaInPath(path.trim(), systemID, baseURI);
                      if (inPath != null)
                      {
                         return inPath;
                      }
                   }
                   throw new XMLStreamException("DTD schema is not found.");
                }
             });

             xmlif.setProperty(XMLInputFactory.REPORTER, new XMLReporter()
             {
                @Override
                public void report(String   message,
                                   String   errorType,
                                   Object   relatedInformation,
                                   Location location)
                throws XMLStreamException
                {
                   if (validate && "doctype declaration".equals(errorType))
                   {
                      // ignore validation errors related to missing DTD
                      return;
                   }

                   if (callbackWrapper != null)
                   {
                      callbackWrapper.error(message);
                   }
                }
             });
             xmlif.setEventAllocator(DefaultEventAllocator.getDefaultInstance());

             String encoding = XmlHelper.readXMLEncoding(inputStream);
             if (encoding == null)
             {
                // always default to UTF-8
                encoding = "UTF-8";
             }
             Reader filtered = new EscapeControlCharsReader(new InputStreamReader(inputStream, encoding));
             saxParser = (XMLStreamReader2) xmlif.createXMLStreamReader(filtered);
             allocator = xmlif.getEventAllocator();

             if (validate)
             {
                addValidation();
             }

             int eventType = saxParser.getEventType();
             if (eventType != XMLStreamConstants.START_DOCUMENT)
             {
                saxParseError("SAX-PARSE-FIRST", "Unexpected error");
                return;
             }
             if (callbackWrapper != null)
             {
                callbackWrapper.startDocument();
             }
             readStatus = ReadStatus.SAX_RUNNING;
          }
          catch (IllegalArgumentException | XMLStreamException | IOException ignored)
          {
             readStatus = ReadStatus.SAX_UNINITIALIZED;
             saxParseError("SAX-PARSE-FIRST", "Unexpected error");
          }
       }

       /**
        * Continues progressive-scan parse of an XML document associated with a SAX-Reader object.
        */
       @Override
       public void saxParseNext()
       {
          if (readStatus == ReadStatus.SAX_COMPLETE)
          {
             return;
          }

          if (readStatus == ReadStatus.SAX_UNINITIALIZED)
          {
             saxParseError("SAX-PARSE-NEXT", "No input file specified");
             return;
          }

          try
          {
             if (!saxParser.hasNext() && readStatus == ReadStatus.SAX_RUNNING)
             {
                readStatus = ReadStatus.SAX_COMPLETE;
                return;
             }
             int eventType = saxParser.next();
             if (callbackWrapper == null)
             {
                return;
             }

             XMLEvent event = getXMLEvent(saxParser);
             switch (eventType)
             {
                case XMLStreamConstants.START_ELEMENT:
                   //emulate startPrefixMapping
                   for (int i = 0; i < saxParser.getNamespaceCount(); i++)
                   {
                      callbackWrapper.startPrefixMapping(saxParser.getNamespacePrefix(i),
                                                         saxParser.getNamespaceURI(i));
                   }
                   callbackWrapper.startElement(event.asStartElement());
                   break;
                case XMLStreamConstants.END_ELEMENT:
                   callbackWrapper.endElement(event.asEndElement());
                   //emulate startPrefixMapping
                   for (int i = saxParser.getNamespaceCount() - 1; i >= 0; i--)
                   {
                      callbackWrapper.endPrefixMapping(saxParser.getNamespacePrefix(i));
                   }
                   break;
                case XMLStreamConstants.PROCESSING_INSTRUCTION:
                   callbackWrapper.processingInstruction((ProcessingInstruction)event);
                   break;
                case XMLStreamConstants.CHARACTERS:
                   callbackWrapper.characters(event.asCharacters());
                   break;
                case XMLStreamConstants.COMMENT:
                   break;
                case XMLStreamConstants.SPACE:
                   callbackWrapper.ignorableWhitespace(event.asCharacters());
                   break;
                case XMLStreamConstants.START_DOCUMENT:
                   callbackWrapper.startDocument();
                   break;
                case XMLStreamConstants.END_DOCUMENT:
                   callbackWrapper.endDocument();
                   break;
                case XMLStreamConstants.NOTATION_DECLARATION:
                   callbackWrapper.notationDecl((NotationDeclaration)event);
                   break;
                case XMLStreamConstants.DTD:
                case XMLStreamConstants.ENTITY_DECLARATION:
                case XMLStreamConstants.ENTITY_REFERENCE:
                case XMLStreamConstants.ATTRIBUTE:
                case XMLStreamConstants.CDATA:
                case XMLStreamConstants.NAMESPACE:
                   break;
                default:
                   saxParseError("SAX-PARSE-NEXT", "Unexpected error");
                   break;
             }
          }
          catch (MismatchedNumberParametersException e)
          {
             mismatchCallbackParameters("SAX-PARSE-NEXT", e.callbackName);
             readStatus = ReadStatus.SAX_PARSER_ERROR;
          }
          catch (MismatchedParametersException e)
          {
             mismatchCallbackNumberParameters(e.callbackName);
             readStatus = ReadStatus.SAX_PARSER_ERROR;
          }
          catch (XMLStreamException e)
          {
             boolean inProlog = e.getMessage().indexOf(ParsingErrorMsgs.SUFFIX_IN_PROLOG) >= 0;;
             if (inProlog)
             {
                if (prologError)
                {
                   return;
                }

                prologError = true;
             }

             LOG.log(Level.SEVERE, "SaxReaderImpl", e);
             try
             {
                if (callbackWrapper == null)
                {
                   readStatus = ReadStatus.SAX_PARSER_ERROR;
                   return;
                }

                Boolean isReturn = callbackWrapper.fatalError(e);
                if (isReturn != null && !isReturn)
                {
                   readStatus = ReadStatus.SAX_PARSER_ERROR;
                   return;
                }
             }
             catch (XMLStreamException ignored) //impossible error
             {
                // no-op
             }

             if (!prologError)
             {
                saxParseError("SAX-PARSE-NEXT", "Unexpected error");
                readStatus = ReadStatus.SAX_PARSER_ERROR;
             }
          }
       }

       /**
        * Specifies the input source of the XML document to be parsed by a SAX-Reader object.
        *
        * @param    mode
        *           The type of the object to process. Can be 'FILE', 'HANDLE', 'MEMPTR' or
        *           'LONGCHAR'.
        * @param    sourceObject
        *           The appropriate object to process.
        *
        * @return   The <code>true</code> in case of success <code>false</code> otherwise.
        */
       @Override
       public logical setInputSource(String mode, Object sourceObject)
       {
          return setInputSource(new character(mode), sourceObject);
       }

       /**
        * Specifies the input source of the XML document to be parsed by a SAX-Reader object.
        *
        * @param    mode
        *           The type of the object to process. Can be 'FILE', 'HANDLE', 'MEMPTR' or
        *           'LONGCHAR'.
        * @param    source
        *           The appropriate object to process.
        *
        * @return   The <code>true</code> in case of success <code>false</code> otherwise.
        */
       @Override
       public logical setInputSource(character mode, Object source)
       {
          final String readMode;
          if (mode.isUnknown())
          {
             readMode = "file";
          }
          else
          {
             readMode = mode.getValue();
          }

          if ("file".equalsIgnoreCase(readMode))
          {
             String fileName = null;
             if (source instanceof String)
             {
                fileName = (String) source;
             }
             if (source instanceof character &&  !((character) source).isUnknown())
             {
                fileName = ((character) source).toStringMessage();
             }
             if (fileName == null)
             {
                invalidArgumentError("SET-INPUT-SOURCE");
                return new logical(false);
             }
             inputStream = new FileInputStreamWrapper(fileName);
          }
          else if ("stream".equalsIgnoreCase(readMode))
          {
             LOG.warning("Stream is not supported");
             inputStream = new InputStream()
             {
                @Override
                public int read()
                throws IOException
                {
                   return -1;
                }
             };
             return new logical(true);
          }
          else if ("memptr".equalsIgnoreCase(readMode))
          {
             if (!(source instanceof memptr))
             {
                invalidArgumentError("SET-INPUT-SOURCE");
                return new logical(false);
             }
             inputStream = new MemptrInputStream((memptr) source);
          }
          else if ("stream-handle".equalsIgnoreCase(readMode))
          {
             LOG.warning("Stream-handle is not supported");
             inputStream = new InputStream()
             {
                @Override
                public int read()
                throws IOException
                {
                   return -1;
                }
             };
             return new logical(true);
          }
          else if ("longchar".equalsIgnoreCase(readMode))
          {
             if (!(source instanceof longchar))
             {
                invalidArgumentError("SET-INPUT-SOURCE");
                return new logical(false);
             }
             inputStream = new LongcharInputStream((longchar) source);
          }
          else
          {
             String errMsg = "Argument for SET-INPUT-SOURCE must be a 'file', 'longchar', 'memptr', " +
                             "or 'handle'";
             ErrorManager.recordOrShowError(10516, errMsg, false, false, false);
             return new logical(false);
          }

          return new logical(true);
       }

       /**
        * Sets the current value of the namespace location pair mapping.
        *
        * @param    location
        *           The new value the current schema-location attribute.
        */
       @Override
       public void setSchemaLocation(character location)
       {
          if (location.isUnknown())
          {
             invalidArgumentError("SCHEMA-LOCATION");
          }
          schemaLocation = location.toStringMessage();
       }

       /**
        * Adding new match pair for namespace schema and physical file location.
        *
        * @param   namespace
        *          Target namespace of the schema or am empty string or Unknown value.
        * @param   location
        *          Location of the XML schema file.
        *
        * @return  The <code>true</code> in case of success <code>false</code> otherwise.
        */
       @Override
       public logical addSchemaLocation(character namespace, character location)
       {
          if (!BaseDataType.isAllKnown(namespace, location))
          {
             return new logical(false);
          }

          String ns = namespace.toStringMessage().trim();
          String loc = location.toStringMessage().trim();

          if (ns.isEmpty() || loc.isEmpty())
          {
             return new logical(false);
          }

          try
          {
             // both namespace and location must be valid uris
             URI.create(ns);
             URI.create(loc);
          }
          catch (IllegalArgumentException ex)
          {
             return new logical(false);
          }

          schemaLocation = (schemaLocation.isEmpty() ? "" : schemaLocation + " ") + ns + " " + loc;
          return new logical(true);
       }

       /**
        * Sets the delimiter separated list of directory paths used to locate XML DTD associated with
        * a particular XML document.
        *
        * @param    path
        *           The new value the current schema-path attribute.
        */
       @Override
       public void setSchemaPath(character path)
       {
          if (path.isUnknown())
          {
             invalidArgumentAssignError("SCHEMA-PATH");
             return;
          }

          schemaPaths = path.toStringMessage();
       }

       /**
        * Gets the value of the XML schema file for elements with no namespaces.
        *
        * @return   The value of the nonamespace-schema-location attribute.
        */
       @Override
       public character getNonamespaceSchemaLocation()
       {
          return new character(noNameSchemaLocation);
       }

       /**
        * Setting the new value for the nonamespace-schema-location attribute.
        *
        * @param    location
        *           New value of the nonamespace-schema-location attribute to set.
        */
       @Override
       public void setNonamespaceSchemaLocation(character location)
       {
          if (location.isUnknown())
          {
             invalidArgumentAssignError("NONAMESPACE-SCHEMA-LOCATION");
             return;
          }

          noNameSchemaLocation = location.toStringMessage();
       }

       /**
        * Redefines whether the namespace processing is suppressed or not.
        *
        * @param    suppress
        *           The new value the current suppress-namespace-processing attribute.
        */
       @Override
       public void setSuppressNamespaceProcessing(logical suppress)
       {
          this.suppress = suppress.booleanValue();
       }

       /**
        * Redefines whether the parser will validate the XML document against DTD or not.
        *
        * @param    enable
        *           The new value the current validation-enabled attribute.
        */
       @Override
       public void setValidationEnabled(logical enable)
       {
          validate = enable.booleanValue();
       }

       /**
        * Causes parser to stop parsing the XML document.
        *
        * @return   The <code>true</code> in case of success <code>false</code> otherwise.
        */
       @Override
       public logical stopParsing()
       {
          readStatus = ReadStatus.SAX_COMPLETE;
          return new logical(true);
       }

       /**
        * Delete the resource.
        *
        * @return   <code>true</code> if the resource was deleted.
        */
       @Override
       protected boolean resourceDelete()
       {
          if (saxParser != null)
          {
             try
             {
                saxParser.closeCompletely();
             }
             catch (XMLStreamException e)
             {
                LOG.log(Level.SEVERE, "SaxReaderImpl", e);
             }
          }
          isValid = false;
          saxParser = null;
          callbackWrapper = null;

          // remove any cache for this resource
          ControlFlowOps.invalidateCallSiteCache(this);

          return true;
       }

       /**
        * Process and display mismatched parameters in callback.
        *
        * @param    method
        *           The read-only attribute.
        * @param    message
        *           Additional error message.
        */
       private static void saxParseError(String method, String message)
       {
          if (method == null)
          {
             throw new IllegalArgumentException("The method is null !");
          }
          String msg = "SAX parser error: %s, %s";
          String err = String.format(msg, method.toUpperCase(), message);
          ErrorManager.recordOrThrowError(14586, err, false);
       }

       /**
        * Add schema validation if it's specified.
        *
        * @throws   XMLStreamException
        *           if the schema file is incorrect or contend of file is invalid.
        */
       private void addValidation()
       throws XMLStreamException
       {
          if (noNameSchemaLocation != null && !noNameSchemaLocation.isEmpty())
          {
             XMLValidationSchemaFactory sf = XMLValidationSchemaFactory.newInstance(
                     XMLValidationSchema.SCHEMA_ID_W3C_SCHEMA);
             boolean isURL = false;
             URL url = null;
             try
             {
                url = new URL(noNameSchemaLocation);
                String protocol = url.getProtocol();
                if ("http".equalsIgnoreCase(protocol) || "https".equalsIgnoreCase(protocol))
                {
                   isURL = true;
                }
             }
             catch (MalformedURLException e)
             {
                // no-op
             }
             final XMLValidationSchema vs;
             if (isURL)
             {
                vs = sf.createSchema(url);
             }
             else
             {
                vs = sf.createSchema(new FileInputStreamWrapper(noNameSchemaLocation));
             }

             saxParser.validateAgainst(vs);
          }
       }

       /**
        * This method allocates an event given the current state of the XMLStreamReader
        *
        * @param    reader
        *           The XMLStreamReader to allocate from
        *
        * @return   The event corresponding to the current reader state.
        *
        * @throws   XMLStreamException
        *           In case of I/O errors.
        */
       private XMLEvent getXMLEvent(XMLStreamReader reader)
       throws XMLStreamException
       {
          return allocator.allocate(reader);
       }

       /**
        * Process and display mismatched parameters in callback.
        *
        * @param    method
        *           The read-only attribute.
        * @param    callbackName
        *           The name of callback.
        */
       private void mismatchCallbackParameters(String method, String callbackName)
       {
          if (method == null)
          {
             throw new IllegalArgumentException("The method is null !");
          }
          String msg = "Callback error: %s sent procedure %s mismatched parameters";
          String err = String.format(msg, method.toUpperCase(), callbackName);
          ErrorManager.recordOrThrowError(10512, err, false);
       }

       /**
        * Process and display mismatched parameters in callback.
        *
        * @param    callbackName
        *           name of callback.
        */
       private void mismatchCallbackNumberParameters(String callbackName)
       {
          if (callbackName == null)
          {
             throw new IllegalArgumentException("The callbackName is null !");
          }
          String msg = "Callback error: mismatched number of parameters passed to procedure %s.";
          String err = String.format(msg, callbackName);
          ErrorManager.recordOrThrowError(10511, err, false);
       }

       /**
        * Special wrapper to wrap read from longchar in lazy mode. It's means that first try to read
        * will check if longchar is available.
        */
       private static class LongcharInputStream
       extends InputStream
       {
          /** A valid longchar object to read*/
          private longchar lchar;

          /** Lazy initialization flag.*/
          private volatile boolean isStarted;

          /** Buffered input Stream.*/
          private ByteArrayInputStream bais;

          /**
           * Constructor accepting a longchar, which will read after first call.
           *
           * @param    source
           *           A valid longchar object.
           */
          private LongcharInputStream(longchar source)
          {
             lchar = source;
             isStarted = false;
          }

          /**
           * Reads the next byte of data from the input stream. The value byte is
           * returned as an <code>int</code> in the range <code>0</code> to
           * <code>255</code>. If no byte is available because the end of the stream
           * has been reached, the value <code>-1</code> is returned.
           */
          @Override
          public int read()
          throws IOException
          {
             if (!isStarted)
             {
                isStarted = true;
                bais = new ByteArrayInputStream(lchar.toStringMessage().getBytes());
             }
             return bais.read();
          }

          /**
           * Resets the input stream to begin reading from the first character
           * of this input stream's underlying buffer.
           */
          @Override
          public synchronized void reset()
          throws IOException
          {
             isStarted = false;
          }

          /**
           * Close input stream.
           */
          @Override
          public void close()
          throws IOException
          {
             if (bais != null)
             {
                bais.close();
             }
          }
       }

       /**
        * Special wrapper to wrap read from memptr in lazy mode. It's means that first try to read
        * will check if memptr is available.
        */
       static class MemptrInputStream
       extends InputStream
       {
          /** Lazy initialization flag.*/
          private volatile boolean isStarted;

          /** A valid memptr */
          private final memptr mptr;

          /** Current read cursor position, started from index 1.*/
          private final AtomicLong position = new AtomicLong();

          /** total size of memptr. */
          private long totalSize = 0;

          /**
           * Constructor accepting a memptr, which will read after first call.
           *
           * @param    source
           *           A valid memptr object.
           */
          MemptrInputStream(memptr source)
          {
             mptr = source;
             position.set(1);
             isStarted = false;
          }

          /**
           * Reads the next byte of data from the input stream. The value byte is
           * returned as an <code>int</code> in the range <code>0</code> to
           * <code>255</code>. If no byte is available because the end of the stream
           * has been reached, the value <code>-1</code> is returned.
           */
          @Override
          public int read()
          throws IOException
          {
             try
             {
                if (!isStarted)
                {
                   isStarted = true;
                   totalSize = mptr.length().longValue();
                }
                integer mptrByte = mptr.getByte(position.getAndIncrement());
                if (mptrByte.isUnknown() || totalSize < position.get())
                {
                   return -1;
                }
                return mptrByte.intValue();
             }
             catch (ErrorConditionException e)
             {
                throw new IOException(e);
             }
          }

          /**
           * Read a chunk of bytes from the current read position in the stream to the
           * offset calculated by given length or to the <code>EOF</code>, whichever
           * comes first. The <code>EOF</code> character is not returned.
           *
           * @param    array
           *           The array in which to save bytes.
           * @param    off
           *           The offset into the array at which to start saving bytes.
           * @param    len
           *           The maximum number of bytes to read.
           *
           * @return   The number of bytes read or -1 if <code>EOF</code> has been
           *           reached). The number of bytes read will be the smaller
           *           of the <code>len</code> parameter or the actual bytes left
           *           before the <code>EOF</code>.
           *
           * @throws   IOException
           *           If an I/O error occurs.
           */
          @Override
          public int read(byte[] array, int off, int len) throws IOException
          {
             if (!isStarted)
             {
                isStarted = true;
                totalSize = mptr.length().longValue();
             }

             int bytesRead = (int) Math.min(totalSize - position.get() + 1, len);
             if (bytesRead <= 0)
             {
                return -1;
             }
             BinaryData bytes = mptr.getBytes(position.get(), bytesRead);
             position.set(position.get() + bytesRead);

             byte[] read = bytes.getByteArray();
             System.arraycopy(read, 0, array, off, bytesRead);

             return bytesRead;
          }

          /**
           * Resets the input stream to begin reading from the first character
           * of this input stream's underlying buffer.
           */
          @Override
          public synchronized void reset()
          throws IOException
          {
             position.set(1);
             isStarted = false;
          }
       }

       /**
        * Special wrapper to wrap read from file in lazy mode. It's means that first try to read
        * will check if file is available.
        */
       static class FileInputStreamWrapper
       extends InputStream
       {
          /** File name.*/
          private final String fileName;

          /** Lazy initialization flag.*/
          private volatile boolean isStarted;

          /** Buffered input Stream.*/
          private Stream stream;

          /** The marked position in the stream. */
          private long mark = -1;

          /**
           * Constructor accepting a file name, which will read after first call.
           *
           * @param    source
           *           A file name.
           */
          FileInputStreamWrapper(String source)
          {
             isStarted = false;
             fileName = source;
          }

          /**
           * Reads the next byte of data from the input stream. The value byte is
           * returned as an <code>int</code> in the range <code>0</code> to
           * <code>255</code>. If no byte is available because the end of the stream
           * has been reached, the value <code>-1</code> is returned.
           */
          @Override
          public int read()
          throws IOException
          {
             if (!isStarted)
             {
                init(false);
             }
             int b = stream.readByte();
             if (b == -2)
             {
                return -1;
             }
             if (b == -1)
             {
                throw new IOException("An exception occurred!");
             }
             return b;
          }

          /**
           * Read a chunk of bytes from the current read position in the stream to the
           * offset calculated by given length or to the <code>EOF</code>, whichever
           * comes first. The <code>EOF</code> character is not returned.
           *
           * @param    array
           *           The array in which to save bytes.
           * @param    off
           *           The offset into the array at which to start saving bytes.
           * @param    len
           *           The maximum number of bytes to read.
           *
           * @return   The number of bytes read or -1 if <code>EOF</code> has been
           *           reached). The number of bytes read will be the smaller
           *           of the <code>len</code> parameter or the actual bytes left
           *           before the <code>EOF</code>.
           *
           * @throws   IOException
           *           If an I/O error occurs.
           */
          @Override
          public int read(byte[] array, int off, int len)
          throws IOException
          {
             if (!isStarted)
             {
                init(false);
             }

             byte[] bytes = stream.readBytes(len);
             if (bytes == null)
             {
                return -1;
             }

             System.arraycopy(bytes, 0, array, off, bytes.length);

             return bytes.length;
          }

          /**
           * Obtains an estimate of the number of bytes that can be read from this stream without blocking.
           * This number is not reliable in an absolute sense, but it does specify a minimum that can be used.
           *
           * @return   The minimum number of bytes immediately available.
           */
          @Override
          public int available()
          throws IOException
          {
             return (int) stream.available();
          }

          /**
           * Resets the input stream to begin reading from the first character
           * of this input stream's underlying buffer.
           */
          @Override
          public synchronized void reset()
          throws IOException
          {
             if (isStarted && mark != -1)
             {
                stream.setPos(mark);
             }

             mark = -1;
             isStarted = false;
          }

          /**
           * Mark the current position in the stream.
           *
           * @param    readlimit
           *           This argument is not used.
           */
          @Override
          public synchronized void mark(int readlimit)
          {
             if (!isStarted)
             {
                try
                {
                   init(false);
                }
                catch (IOException e)
                {
                   mark = -1;
                }
             }

             try
             {
                mark = stream.getPos();
             }
             catch (UnsupportedOperationException |
                    IOException e)
             {
                mark = -1;
             }
          }

          /**
           * Tests if this stream supports the mark mode.
           *
           * @return  Always<code>true</code>.
           */
          @Override
          public boolean markSupported()
          {
             return true;
          }

          /**
           * Initialization method which creates stream wrapper.
           *
           * @param    canWrite
           *           if {@code true} than file will open in read/write mode, otherwise only in
           *           read mode.
           *
           * @throws   IOException
           *           In case of I/O errors.
           */
          private void init(boolean canWrite)
          throws IOException
          {
             try
             {
                stream = StreamFactory.openFileStream(fileName, canWrite, false);
                if (stream == null)
                {
                   throw new IOException("An exception occurred!");
                }
             }
             catch (ErrorConditionException e)
             {
                throw new IOException("An exception occurred!", e);
             }
             isStarted = true;
          }

          /**
           * Close input stream.
           */
          @Override
          public void close()
          throws IOException
          {
             if (stream != null)
             {
                stream.close();
             }
          }
       }
    }