/* * Copyright 2005-2015 by BerryWorks Software, LLC. All rights reserved. * * This file is part of EDIReader. You may obtain a license for its use directly from * BerryWorks Software, and you may also choose to use this software under the terms of the * GPL version 3. Other products in the EDIReader software suite are available only by licensing * with BerryWorks. Only those files bearing the GPL statement below are available under the GPL. * * EDIReader is free software: you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * EDIReader 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along with EDIReader. If not, * see <http://www.gnu.org/licenses/>. */ package com.berryworks.edireader; import com.berryworks.edireader.error.EDISyntaxExceptionHandler; import com.berryworks.edireader.tokenizer.EDITokenizer; import com.berryworks.edireader.tokenizer.Tokenizer; import com.berryworks.edireader.util.BranchingWriter; import org.xml.sax.*; import java.io.*; import java.util.Locale; /** * An adaptor for XMLReader providing default implementations of several methods * to simplify each of the EDIReader classes that have XMLReader as an ancestor. */ public abstract class EDIAbstractReader implements XMLReader { protected static final String BERRYWORKS_NAMESPACE = "http://www.berryworkssoftware.com/2008/edireader"; /** * The ContentHandler for this XMLReader */ private ContentHandler contentHandler; /** * The tokenizer used by this EDIAbstractReader */ private Tokenizer tokenizer; private EDISyntaxExceptionHandler syntaxExceptionHandler; private ErrorHandler errorHandler; private EntityResolver entityResolver; /** * Character marking the boundary between fields */ private char delimiter; /** * Character marking the boundary between sub-fields */ private char subDelimiter; /** * Character marking the boundary between sub-sub-fields */ private char subSubDelimiter; /** * Character used as a decimal point ("." or ",") */ private char decimalMark; /** * Character marking the boundary between repeating fields */ private char repetitionSeparator; /** * Character marking the boundary between segments */ private char terminator; /** * The byte value used as a release or escape character. */ private int release; /** * Whitespace characters observed to follow the formal segment terminator. */ private String terminatorSuffix; /** * Where the functional acknowledgements are (optionally) written. */ private BranchingWriter ackStream; /** * Where the alternate acknowledgements are (optionally) written. */ private BranchingWriter alternateAckStream; /** * If acknowledgements are being written, should an interchange acknowledgment be included? * For ANSI X12, this would be a TA1 segment after the ISA. */ private boolean interchangeAcknowledgment; /** * If acknowledgements are being written, should they be group level only? * For ANSI X12, this would mean 997s without AK2/AK5 transaction level detail.. */ private boolean groupAcknowledgment; /** * Used when producing a copy of the parsed input is needed. */ private Writer copyWriter; /** * May contain a copy of the initial segment of the interchange. */ private String firstSegment; /** * XML attributes relating to the EDI interchange */ private final EDIAttributes interchangeAttributes = new EDIAttributes(); /** * Empty attributes object for convenience in starting elements having no attributes */ private final EDIAttributes noAttributes = new EDIAttributes(); /** * XML attributes relating to the EDI structure that ANSI X12 calls a * functional group. In EDIFACT, this corresponds to the UNG/UNE structure. */ private final EDIAttributes groupAttributes = new EDIAttributes(); /** * XML attributes relating to the document. In ANSI X12 terminology this * would be the Transaction Set (ST/SE). In EDIFACT, it would be a Message * (UNH/UNT).. */ private final EDIAttributes documentAttributes = new EDIAttributes(); private boolean previewed; private boolean externalXmlDocumentStart; private boolean namespaceEnabled; private SyntaxDescriptor acknowledgmentSyntaxDescriptor; private TransactionCallback transactionCallback; /** * Gets the character marking the boundary between segments * * @return The terminator value */ public char getTerminator() { return terminator; } /** * Gets the short String of 'whitespace' characters that follows the * terminator. * * @return The terminator value */ public String getTerminatorSuffix() { return terminatorSuffix; } public void setDelimiter(char delimiter) { this.delimiter = delimiter; } public void setSubDelimiter(char subDelimiter) { this.subDelimiter = subDelimiter; } public void setSubSubDelimiter(char subSubDelimiter) { this.subSubDelimiter = subSubDelimiter; } public void setDecimalMark(char decimalMark) { this.decimalMark = decimalMark; } public void setRepetitionSeparator(char repetitionSeparator) { this.repetitionSeparator = repetitionSeparator; } public void setTerminator(char terminator) { this.terminator = terminator; } public void setRelease(int release) { this.release = release; } public void setTerminatorSuffix(String terminatorSuffix) { this.terminatorSuffix = terminatorSuffix; } /** * Gets the character marking the boundary between fields * * @return The delimiter value */ public char getDelimiter() { return delimiter; } /** * Gets the character marking the boundary between sub-fields. Subfields may * be called by different names in different EDI standards. * * @return The subDelimiter value */ public char getSubDelimiter() { return subDelimiter; } /** * Gets the character used in release/escape sequences. * Exactly how this character is used may differ between standards. * In ANSI, there is no release mechanism. When no release character * is available, the int value -1 is returned, otherwise a char * value is returned via the int. * * @return The release char value or -1 if none */ public int getRelease() { return release; } public char getReleaseCharacter() { return isReleaseCharacterDefined() ? (char) release : ' '; } public boolean isReleaseCharacterDefined() { return release != -1; } /** * Gets the character used as the decimal point in currency. * This is the period (".") in the USA and many other countries, * but can also be the comma (","). * * @return mark */ public char getDecimalMark() { return decimalMark; } /** * Gets the character marking the boundary between sub-sub-fields. * Sub-sub-fields are not used in ANSI or EDIFACT, but appear in HL7. * * @return The subSubDelimiter value */ public char getSubSubDelimiter() { return subSubDelimiter; } /** * Gets the character marking the boundary between repeating fields. * * @return The repetitionSeparator value */ public char getRepetitionSeparator() { return repetitionSeparator; } public Tokenizer getTokenizer() { return tokenizer; } public void setTokenizer(Tokenizer t) { tokenizer = t; } public void setCopyWriter(Writer writer) { if (tokenizer != null) tokenizer.setWriter(writer); } protected static Reader createReader(InputSource source) throws IOException { Reader inputReader; if (source == null) throw new IOException("createReader called with null InputSource"); // first try to establish inputReader from the InputSource's // CharacterStream inputReader = source.getCharacterStream(); if (inputReader == null) { InputStream inputStream = source.getByteStream(); if (inputStream != null) // establish inputReader from a ByteStream inputReader = new InputStreamReader(inputStream); else { String systemId = source.getSystemId(); if (systemId != null) { // try to establish inputReader using the SystemId if (systemId.startsWith("file:")) // systemId names a file inputReader = new FileReader(systemId.substring(5)); else // some kind of URL not yet supported throw new IOException("InputSource using SystemId (" + systemId + ") not yet supported"); } else // getCharacterStream(), getByteStream(), and // getSystemId() all return null throw new IOException( "Cannot get ByteStream, CharacterStream, or SystemId from EDI InputSource"); } } return inputReader; } /** * Prepare the parser for its parse method to be called. This involves * previewing some of the interchange to discover syntactic details, and * making sure a tokenizer is in place. The preview method, of course, * varies with each EDI standard. * * @param source provides read access to the EDI data * @throws EDISyntaxException if invalid EDI is detected * @throws IOException if problem reading source */ protected void parseSetup(InputSource source) throws EDISyntaxException, IOException { Reader inputReader = createReader(source); if (tokenizer == null) setTokenizer(new EDITokenizer(inputReader)); if (!previewed) { preview(); previewed = true; } if (copyWriter != null) tokenizer.setWriter(copyWriter); } /** * Preview the EDI interchange to discover syntactic details that will be * useful to know before the actual parse method is called. * * @throws IOException for problem reading EDI data * @throws EDISyntaxException if invalid EDI is detected */ public abstract void preview() throws EDISyntaxException, IOException; /** * Indicate that functional acknowledgments are to be generated by * designating a Writer. This method should be called before calling parse() * if acknowledgments are desired. * <p> * For example, in ANSI X12 this provides for the generation of 997s. * * @param writer The new acknowledgment value */ public void setAcknowledgment(Writer writer) { ackStream = (writer == null) ? null : new BranchingWriter(writer); } /** * Indicate that an alternative type of acknowledgments are to be generated by * designating a Writer. This method should be called before calling parse() * if acknowledgments are desired. * <p> * For example, in ANSI X12 this provides for the generation of 999s. * * @param writer The new acknowledgment value */ public void setAlternateAcknowledgment(Writer writer) { alternateAckStream = (writer == null) ? null : new BranchingWriter(writer); } public void setAcknowledgment(Writer writer, SyntaxDescriptor syntaxDescriptor) { setAcknowledgment(writer); setAcknowledgmentSyntaxDescriptor(syntaxDescriptor); } public boolean isInterchangeAcknowledgment() { return interchangeAcknowledgment; } public void setInterchangeAcknowledgment(boolean interchangeAcknowledgment) { this.interchangeAcknowledgment = interchangeAcknowledgment; } public boolean isGroupAcknowledgment() { return groupAcknowledgment; } public void setGroupAcknowledgment(boolean groupAcknowledgment) { this.groupAcknowledgment = groupAcknowledgment; } public SyntaxDescriptor getAcknowledgmentSyntaxDescriptor() { return acknowledgmentSyntaxDescriptor; } public void setAcknowledgmentSyntaxDescriptor(SyntaxDescriptor syntaxDescriptor) { acknowledgmentSyntaxDescriptor = syntaxDescriptor; } public TransactionCallback getTransactionCallback() { return transactionCallback; } public void setTransactionCallback(TransactionCallback transactionCallback) { this.transactionCallback = transactionCallback; } public EDISyntaxExceptionHandler getSyntaxExceptionHandler() { return syntaxExceptionHandler; } public void setSyntaxExceptionHandler(EDISyntaxExceptionHandler syntaxExceptionHandler) { this.syntaxExceptionHandler = syntaxExceptionHandler; } public boolean isNamespaceEnabled() { return namespaceEnabled; } public void setNamespaceEnabled(boolean namespaceEnabled) { this.namespaceEnabled = namespaceEnabled; } public boolean isExternalXmlDocumentStart() { return externalXmlDocumentStart; } public void setExternalXmlDocumentStart(boolean externalXmlDocumentStart) { this.externalXmlDocumentStart = externalXmlDocumentStart; } public void setLocale(Locale locale) throws SAXException { throw new SAXNotSupportedException("setLocale not supported"); } public void setEntityResolver(EntityResolver resolver) { entityResolver = resolver; } public void setDTDHandler(DTDHandler handler) { } public void setErrorHandler(ErrorHandler handler) { errorHandler = handler; } /** * Parse the EDI interchange. Each subclass must override this method. */ public void parse(String systemId) throws SAXException, IOException { throw new SAXException("parse(systemId) not supported"); } public void setContentHandler(ContentHandler handler) { contentHandler = handler; } public ContentHandler getContentHandler() { return contentHandler; } public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException { } public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException { throw new SAXNotSupportedException("Not yet implemented"); } public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException { } public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException { throw new SAXNotSupportedException("Not yet implemented"); } public EDIAttributes getDocumentAttributes() { return documentAttributes; } public EDIAttributes getInterchangeAttributes() { return interchangeAttributes; } public EDIAttributes getGroupAttributes() { return groupAttributes; } public BranchingWriter getAckStream() { return ackStream; } public void setAckStream(BranchingWriter ackStream) { this.ackStream = ackStream; } public BranchingWriter getAlternateAckStream() { return alternateAckStream; } public boolean isPreviewed() { return previewed; } public void setPreviewed(boolean previewed) { this.previewed = previewed; } public ErrorHandler getErrorHandler() { return errorHandler; } public DTDHandler getDTDHandler() { return null; } public EntityResolver getEntityResolver() { return entityResolver; } public int getCharCount() { return tokenizer == null ? 0 : tokenizer.getCharCount(); } public int getSegmentCharCount() { return tokenizer == null ? 0 : tokenizer.getSegmentCharCount(); } public String getFirstSegment() { return firstSegment; } public void setFirstSegment(String firstSegment) { this.firstSegment = firstSegment; } /** * Write a message to a diagnostic trace stream. * * @param msg to appear in trace */ public static void trace(String msg) { System.err.println(msg); } /** * Write a message to a diagnostic trace stream. * * @param e Exception to appear in trace */ protected static void trace(Exception e) { System.err.println(e.toString()); } @Override public String toString() { String lineBreak = System.getProperty("line.separator"); return lineBreak + "EDIReader summary:" + lineBreak + " class: " + getClass().getName() + lineBreak + " delimiter: " + getDelimiter() + lineBreak + " subDelimiter: " + getSubDelimiter() + lineBreak + " subSubDelimiter: " + getSubSubDelimiter() + lineBreak + " repetitionSeparator: " + getRepetitionSeparator() + lineBreak + " terminator: " + getTerminator() + lineBreak + " terminatorSuffix: " + getTerminatorSuffix() + lineBreak + " charCount: " + getCharCount() + lineBreak + " segmentCharCount: " + getSegmentCharCount() + lineBreak; } }