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();
}
}
}
}