This is an automated email from the ASF dual-hosted git repository.
mbeckerle pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/daffodil.git
The following commit(s) were added to refs/heads/main by this push:
new 72cee7cc8 Updated javadoc for layering.
72cee7cc8 is described below
commit 72cee7cc8c503ed67c17ac9baddb2039c0a52156
Author: Michael Beckerle <[email protected]>
AuthorDate: Tue May 28 15:26:18 2024 -0400
Updated javadoc for layering.
DAFFODIL-2569
---
.../apache/daffodil/layers/runtime1/GZipLayer.java | 5 +-
.../runtime1/layers/api/ChecksumLayer.java | 84 +++---
.../apache/daffodil/runtime1/layers/api/Layer.java | 314 ++++++++++++++++++---
.../daffodil/runtime1/layers/api/package-info.java | 70 +++++
daffodil-sapi/root-doc.txt | 1 +
5 files changed, 399 insertions(+), 75 deletions(-)
diff --git
a/daffodil-runtime1-layers/src/main/scala/org/apache/daffodil/layers/runtime1/GZipLayer.java
b/daffodil-runtime1-layers/src/main/scala/org/apache/daffodil/layers/runtime1/GZipLayer.java
index 09b3b5096..0b8d03c45 100644
---
a/daffodil-runtime1-layers/src/main/scala/org/apache/daffodil/layers/runtime1/GZipLayer.java
+++
b/daffodil-runtime1-layers/src/main/scala/org/apache/daffodil/layers/runtime1/GZipLayer.java
@@ -70,12 +70,13 @@ public final class GZipLayer extends Layer {
/**
* Prior to Java 16, the java.util.zip.GZIPOutputStream wrote a value of zero
for
- * the OS field in the header (byte index 9). In Java 16, this was changed to a
+ * the OS field in the header (byte index 9).
+ * In Java 16, this was changed to a
* value of 255 to better abide by the GZIP specification. Unfortunately, this
* means unparsed data using a GZIP layer might have a single byte difference,
* depending on the Java version used. This can lead to inconsistent behavior
of
* test failures that expect a certain byte value.
- *
+ * <p>
* To resolve this issue, we create this GZIPFixedOutputStream. This should
wrap
* the underlying OutputStream and be passed as the OutputStream to the
* GZIPOutputStream. When the GZIPOutputStream writes the 9th byte to this
diff --git
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/layers/api/ChecksumLayer.java
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/layers/api/ChecksumLayer.java
index ed8a0feb9..c89c739cc 100644
---
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/layers/api/ChecksumLayer.java
+++
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/layers/api/ChecksumLayer.java
@@ -21,49 +21,63 @@ import
org.apache.daffodil.runtime1.layers.ChecksumLayerBase;
import java.nio.ByteBuffer;
/**
- * A checksum layer computes some sort of integer value from a region of the
data stream. The term checksum is
- * used generically here to subsume all sorts of CRCs, check digits, data
hash, and digest calculations.
+ * A checksum layer computes a numeric value from a region of the data stream.
* <p>
- * This base is suitable only for checksums computed over small sections of
data, not large data streams or whole large
- * files. The entire region of data the checksum is being computed over, will
be pulled into a byte buffer in memory.
+ * The term checksum is used generically here to subsume all sorts of CRCs,
check digits, data hash, and
+ * digest calculations.
* <p>
- * The resulting checksum is the return value of the compute method.
+ * This abstract base is suitable only for checksums computed over small
sections of data.
+ * It is not for large data streams or whole large files.
+ * The entire region of data the checksum is being computed over will be
pulled into a byte buffer in memory.
* <p>
- * This is delivered into a DFDL variable for use by the DFDL schema. This
variable can have any name
- * such as 'crc', 'digest', or 'dataHash'.
+ * The resulting checksum is the return value of the {@link #compute} method.
* <p>
- * The derived implementation class must also define a getter method based on
the name of the DFDL variable which
- * will be assigned with the checksum value. For example if the checksum is
actually a specific digest/hash calculation
- * and the DFDL variable is named 'digest', then this getter must be defined:
- * <p>
- * int getLayerVariableResult_digest() { return getChecksum() }
+ * This result is delivered into a DFDL variable for use by the DFDL schema.
+ * This DFDL variable can have any name such as 'crc', 'digest', or 'dataHash'.
* <p>
- * This will be called automatically to retrieve the integer value that was
returned from the `compute` method, and
- * the DFDL variable 'digest' will be assigned that value.
+ * The derived implementation class must also define a getter method based on
the name of the DFDL variable which
+ * will be assigned with the checksum value.
+ * For example if the checksum is actually a specific digest/hash calculation
and the DFDL variable is named
+ * {@code digest}, then this getter must be defined:
+ * <pre>{@code
+ * int getLayerVariableResult_digest() {
+ * return this.digest; // usually returns a data member
+ * }
+ * }
+ * </pre>
+ * This will be called automatically to retrieve the integer value that was
returned from the {@code compute} method,
+ * and the DFDL variable named {@code digest} will be assigned that value.
* <p>
* The derived class implementing a checksum layer must call
- * <p>
- * setLength(len) // sets the length in bytes
- * <p>
+ * <pre>{@code
+ * setLength(len); // sets the length in bytes
+ * }
+ * </pre>
* to specify the length of the data region in bytes. Normally this would be
called from the layer's implementation of
- * the
- * <p>
- * void setLayerVariableParameters(...) { }
- * <p>
- * method, which, if defined, is called with arguments populated from DFDL
variables with the same name (and compatible type)
- * defined in a DFDL schema with the Layer's target namespace. So, for example
if a checksum layer needs to
- * receive a parameter from a DFDL variable named "layerEncoding", the setter
would be:
- * <p>
- * void setLayerVariableParameters(String layerEncoding) {
- * this.layerEncoding = layerEncoding;
+ * the {@code setLayerVariableParameters} method:
+ * <pre>{@code
+ * void setLayerVariableParameters(...) {
+ * ...
+ * setLength(len); // len is a constant,
+ * // or is computed from a parameter variable
+ * ...
* }
+ * }</pre>
+ * See the documentation of the {@link Layer} class for a description of how
DFDL variables are passed to the arguments
+ * of the {@code setLayerVariableParameters} method.
* <p>
- * Beside initializing local members, this setter is also an initializer for
the layer class instance. Any exception
- * thrown becomes a Schema Definition Error. If there are no parameter
variables, then this setter, with no arguments,
- * can be used purely for initialization.
+ * See {@link Layer} for more details about layers generally as most of its
documentation is
+ * relevant to this derived abstract base class as well.
+ * </p>
*/
public abstract class ChecksumLayer extends ChecksumLayerBase {
+ /**
+ * Base class constructor
+ * @param layerName the name of the layer
+ * @param layerTargetNamespace the URI that is the target namespace of the
layer
+ * @throws IllegalArgumentException if arguments are null or do not obey
required syntax.
+ */
public ChecksumLayer(String layerName, String layerTargetNamespace) {
super(layerName, layerTargetNamespace);
}
@@ -73,11 +87,11 @@ public abstract class ChecksumLayer extends
ChecksumLayerBase {
*
* @param isUnparse true if the direction is unparsing. Used because in some
cases the computed checksum must
* be written into the byte buffer in a specific location.
- * @param byteBuffer the bytes over which the checksum is to be computed.
This can be modified, (for example so as
- * to embed the computed checksum in the middle of the
data somewhere) and the resulting
- * bytes become the data that is written when unparsing.
- * If the bytes in this buffer are modified by the compute
method, those modified bytes are what
- * the parsing will parse from, and the unparsing will
output.
+ * @param byteBuffer the bytes over which the checksum is to be computed.
+ * This byte buffer can be modified, (for example so as to
embed the computed checksum in the
+ * middle of the data somewhere).
+ * The resulting modified bytes become the data that is
read by the DFDL parsing and written
+ * when unparsing.
* @return the checksum value as an Int (32-bit signed integer)
*/
public abstract int compute(
diff --git
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/layers/api/Layer.java
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/layers/api/Layer.java
index 2561bdde1..395d20def 100644
---
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/layers/api/Layer.java
+++
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/layers/api/Layer.java
@@ -27,51 +27,235 @@ import java.util.List;
/**
* This is the primary API class for writing layers.
* <p>
- * All layers are derived from this class, and must have no-args default
constructors.
+ * All layers are derived from this class.
* <p>
- * Derived classes will be dynamically loaded by Java's SPI system.
- * The names of concrete classes derived from Layer are listed in a
resources/META-INF/services file
- * so that they can be found and dynamically loaded.
+ * This class is used directly as a base class to define <i>transforming</i>
layers.
+ * To simplify the
+ * definition of <i>checksum</i> layers, a specialized sub-class
+ * {@link org.apache.daffodil.runtime1.layers.api.ChecksumLayer}
+ * is also available.
+ * Many of the requirements for layers, such as the naming conventions for
layer variables,
+ * are described here, but they apply equally to checksum layers.
+ * <p>
+ * Derived classes will be dynamically loaded by Java's Service Provider
Interface (SPI) system.
+ * The names of concrete classes derived from Layer are listed in
+ * a metadata resource file
+ * named for this class (that is, the file name is the fully-qualified class
name of this class:
+ * {@code
resources/META-INF/services/org.apache.daffodil.runtime1.layers.api.Layer}).
+ * This file contains lines where each line contains one fully qualified class
name of a derived
+ * layer class.
+ * More than one line in the file denotes that the Jar file contains the
definitions of more than one derived layer
+ * class.
+ * This file is incorporated in the compiled jar file for the derived {@code
Layer} class
+ * so that the class path can be searched and Jars containing layer classes
can be dynamically loaded.
* <p>
* The SPI creates an instance the class by calling a default (no-arg)
constructor, which should be
* the only constructor.
* <p>
- * Instances of derived layer classes can be stateful. They are private to
threads, and each time a layer
+ * Instances of derived layer classes can be stateful.
+ * They are private to threads, and each time a layer
* is encountered during parse/unparse, an instance is created for that
situation.
* <p>
- * Layer instances should not share mutable state (such as via singleton
objects)
+ * Layer instances should not share mutable state (such as via singleton
objects).
* <p>
- * The rest of the Layer class implements the
- * layer decode/encode logic, which is done as part of deriving one's Layer
class from the
- * Layer base class.
+ * <h2>About Layer Variables</h2>
* <p>
- * About variables: Layer logic may read and write DFDL variables.
+ * Layer logic may read and write DFDL variables.
+ * These variables are associated with the layer implementation class by using
+ * Java/Scala reflection to find matches (case-sensitive) between the names of
DFDL variables and method
+ * names and method arguments of the layer's Java/Scala code.
+ * Hence, the layer's DFDL variables must have names suitable for use as
Java/Scala
+ * identifiers.
* <p>
- * Every DFDL Variable in the layer's targetNamespace is used either at the
start of the
- * layer algorithm as a parameter to the layer or at the end of the layer
algorithm it is
- * assigned as a return value (such as a checksum) from the layer.
+ * The layer namespace is used only for its layer.
+ * All DFDL variables defined in that namespace are either used to pass
parameters to the
+ * layer code, or receive results (such as a checksum) back from the layer
code.
+ * This is enforced.
+ * If a layer namespace contains a DFDL variable and there is no corresponding
+ * usage of that variable name in the layer code (following the conventions
below), then
+ * it is a Schema Definition Error when the layer code is loaded.
* <p>
- * Variables being written must be undefined, since variables in DFDL are
single-assignment.
+ * A layer that does not define any DFDL variables does
+ * not have to define a DFDL schema that defines the layer's target
+ * namespace, but any layer that uses DFDL variables *must* define a
+ * schema with the layer's namespace as its target namespace, and with the
+ * variables declared in it (using {@code dfdl:defineVariable}).
+ * </p>
+ * Every DFDL Variable in the layer's target namespace is used either at the
start of the
+ * layer algorithm as a parameter to the layer or at the end of the layer
algorithm where it is
+ * assigned a return value (such as a checksum or flag) from the layer.
* <p>
- * Variables being read must be defined before being read by the layer, and
this is true for both
- * parsing and unparsing. When unparsing, variables being read cannot be
forward-referencing to parts
+ * Variables being read must have values before being read by the layer, and
this is true for both
+ * parsing and unparsing.
+ * This happens when the Daffodil processor begins parsing/unparsing the
layered sequence.
+ * When unparsing, variables being read cannot be forward-referencing to parts
* of the DFDL infoset that have not yet been unparsed.
* <p>
- * A layer that wants to read parameters declares special setter named
'setLayerVariableParameters'
- * which has args such that each has a name and type that match a corresponding
- * dfdl:defineVariable in the layer's namespace.
+ * A layer that wants to read parameters declares a special setter named
+ * {@code setLayerVariableParameters}
+ * which has arguments where each has a name and type that match a
corresponding
+ * {@code dfdl:defineVariable} in the layer's target namespace.
+ * <p>
+ * For example, if the layer logic has a DFDL variable for a parameter named
{@code direction}
+ * of type {@code xs:string} and another DFDL variable for a parameter named
{@code wordCount}
+ * of type {@code xs:unsignedShort}
+ * then the derived Layer class must have a {@code setLayerVariableParameters}
with
+ * arguments corresponding in name and type to these two variables.
+ * This setter will be called passing the value of the variables immediately
after the layer
+ * instance is constructed.
+ * The arguments to {@code setLayerVariableParameters} can be in any order:
+ * <pre>
+ * void setLayerVariableParameters(String direction, int wordCount) {
+ * // usually this setter will assign the values to data members
+ * this.direction = direction;
+ * this.wordCount = wordCount;
+ * }
+ * </pre>
+ * Beside initializing local members, this setter is also an initializer for
the layer class instance.
+ * Any exception thrown becomes a Schema Definition Error.
+ * <p>
+ * If there are no parameter variables, then this setter, with no arguments,
can be used purely for initialization.
* <p>
- * A layer that wants to return a value after the layer algorithm completes
defines a special recognizable
- * getter method. The name of the getter is formed from prefixing the DFDL
variable name with the string
- * 'getLayerVariableResult_'. The return type of the getter must match the
type of the variable.
+ * A DFDL variable used to return a result from a layer must be undefined,
since variables in DFDL are single-assignment.
+ * Usually this means the use of the layer must be surrounded by a {@code
dfdl:newVariableInstance}
+ * annotation which creates a new instance of the layer result variable, over
a limited scope
+ * of use.
+ * The variable is assigned by the layer, and it is then available for reading
by the DFDL schema until
+ * the end of the {@code dfdl:newVariableInstance} scope.
* <p>
- * For example, a result value getter for a DFDL variable named 'checksum' of
type xs:unsignedShort would be:
+ * To return a value into a DFDL variable, the layer implementation defines a
special recognizable
+ * getter method.
+ * The name of the getter is formed from prefixing the DFDL variable name with
the string
+ * "{@code getLayerVariableResult_}".
+ * The return type of the getter must correspond to the type of the variable.
+ * <p>
+ * For example, a result value getter for a DFDL variable named {@code total}
of type {@code xs:unsignedShort} would be:
* <pre>
- * int getLayerVariableResult_checksum() {
- * // returns the value created by the checksum algorithm.
+ * {@code
+ * int getLayerVariableResult_total() {
+ * // returns the value created by the layer's algorithm.
+ * // commonly this returns the value of a data member.
+ * return this.total;
* }
+ * }
* </pre>
+ * Layers could have multiple result variables, but a single variable is most
common, generally for returning checksums.
+ * See the {@link ChecksumLayer} class, which is designed to facilitate
creation of checksum/CRC/hash/digest layers.
+ * <p>
+ * The Java types to use for the setter arguments and getter return types
correspond to the DFDL variable types
+ * according to this table:
+ * <table border="1">
+ * <tr>
+ * <th>DFDL Schema Type</th>
+ * <th>Java Type</th>
+ * </tr>
+ * <tr>
+ * <td>xs:byte</td>
+ * <td>byte</td>
+ * </tr>
+ * <tr>
+ * <td>xs:short</td>
+ * <td>short</td>
+ * </tr>
+ * <tr>
+ * <td>xs:int</td>
+ * <td>int</td>
+ * </tr>
+ * <tr>
+ * <td>xs:long</td>
+ * <td>long</td>
+ * </tr>
+ * <tr>
+ * <td>xs:integer</td>
+ * <td>java.math.BigInteger</td>
+ * </tr>
+ * <tr>
+ * <td>xs:decimal</td>
+ * <td>java.math.BigDecimal</td>
+ * </tr>
+ * <tr>
+ * <td>xs:unsignedInt</td>
+ * <td>long</td>
+ * </tr>
+ * <tr>
+ * <td>xs:unsignedByte</td>
+ * <td>short</td>
+ * </tr>
+ * <tr>
+ * <td>xs:unsignedShort</td>
+ * <td>int</td>
+ * </tr>
+ * <tr>
+ * <td>xs:unsignedLong</td>
+ * <td>java.math.BigInteger</td>
+ * </tr>
+ * <tr>
+ * <td>xs:nonNegativeInteger</td>
+ * <td>java.math.BigInteger</td>
+ * </tr>
+ * <tr>
+ * <td>xs:double</td>
+ * <td>double</td>
+ * </tr>
+ * <tr>
+ * <td>xs:float</td>
+ * <td>float</td>
+ * </tr>
+ * <tr>
+ * <td>xs:hexBinary</td>
+ * <td>byte[]</td>
+ * </tr>
+ * <tr>
+ * <td>xs:anyURI</td>
+ * <td>java.net.URI</td>
+ * </tr>
+ * <tr>
+ * <td>xs:boolean</td>
+ * <td>boolean</td>
+ * </tr>
+ * <tr>
+ * <td>xs:dateTime</td>
+ * <td>com.ibm.icu.util.ICUCalendar</td>
+ * </tr>
+ * <tr>
+ * <td>xs:date</td>
+ * <td>com.ibm.icu.util.ICUCalendar</td>
+ * </tr>
+ * <tr>
+ * <td>xs:time</td>
+ * <td>com.ibm.icu.util.ICUCalendar</td>
+ * </tr>
+ * <caption style="caption-side:bottom"></caption>
+ * </table>
+ * <p>
+ * <h2>Layer Algorithm</h2>
+ * <p>
+ * The rest of the Layer class implements the layer decode/encode logic.
+ * <p>
+ * The actual algorithm of the layer is not implemented in methods of the
derived layer class, but rather is
+ * implemented in the layer's <i>input decoder</i> and <i>output encoder</i>.
+ * These extend the {@code java.io.InputStream} and {@code
java.io.OutputStream} base classes to actually handle the
+ * data.
* <p>
+ * Every layer must implement the {@link #wrapLayerInput} and {@link
#wrapLayerOutput}
+ * methods, which provide the input decoder and output encoder instances to
the Daffodil layer framework.
+ * When parsing/unparsing, the derived Layer class itself is concerned with
setup and tear-down of the
+ * layer's input decoder and output encoder, with providing access to/from
DFDL variables,
+ * and with reporting errors effectively.
+ * <p>
+ * <h3> Layer Exception Handling </h3>
+ * The method {@code setProcessingErrorException } allows the layer to specify
that if the layer throws specific
+ * exceptions or runtime exceptions that they are converted into processing
errors.
+ * This eliminates most need for layer code to contain try-catches.
+ * For example:
+ * <pre>
+ * {@code
+ * setProcessingErrorException(IOException.class);
+ * }
+ * </pre>
+ * informs the DFDL processor that an IOException thrown from the layer is to
be treated as a processing error.
+ * <p>
+ * Unhandled exceptions thrown by the layer code are treated as fatal errors.
*/
public abstract class Layer {
@@ -80,11 +264,10 @@ public abstract class Layer {
private LayerRuntime layerRuntime;
- /**
- * Constructs a new Layer object with the given layer name and namespace.
+ /** Constructs a new Layer object with the given layer name and namespace
URI.
*
* @param localName the local NCName of the layer. Must be usable as a
Java identifier.
- * @param targetNamespace the namespace of the layer. Must obey URI syntax.
+ * @param targetNamespace the namespace URI of the layer. Must obey URI
syntax.
* @throws IllegalArgumentException if arguments are null or do not obey
required syntax.
*/
public Layer(String localName, String targetNamespace) {
@@ -96,23 +279,34 @@ public abstract class Layer {
this.targetNamespace = targetNamespace;
}
- /** The spiName of the Layer class.
+ /** For framework use.
* <p>
* This method and the string it returns are required by the SPI loader.
* @return A unique indentifier for the kind of layer. Contains both local
and namespace components of the layer's complete name.
*/
public final String name() { return LayerUtils.spiName(localName,
targetNamespace); }
+ /** For framework use. */
public final String localName() { return this.localName; }
+
+ /** For framework use. */
public final String namespace() { return this.targetNamespace; }
/**
+ * For framework use.
+ * <p>
* Called by the execution framework to give the context for reporting
errors.
* @param lr runtime data structure used by the framework
*/
public final void setLayerRuntime(LayerRuntime lr) {
this.layerRuntime = lr;
}
+
+ /**
+ * For framework use.
+ * <p>
+ * Called by the execution framework to obtain the context for reporting
errors.
+ */
public final LayerRuntime getLayerRuntime() { return layerRuntime; }
/**
@@ -140,11 +334,16 @@ public abstract class Layer {
* Use to report a runtime schema definition error.
* <p>
* This indicates that the layer is unable to
- * work meaningfully because of the way it is configured. The schema itself
is not well defined due to
+ * work meaningfully because of the way it is configured.
+ * That is, the schema itself is not well defined due to
* the way the layer is configured.
* <p>
- * This error type is always fatal whether parsing or unparsing.
+ * For example suppose a layer had a DFDL variable for a parameter that is
supposed
+ * to be an integer between 1 and 10, and this parameter is generally
provided as a constant value.
+ * If the provided parameter variable value is 0, which is not meaningful,
then a
+ * Runtime Schema Definition Error is the right error to invoke.
* <p>
+ * This error type is always fatal whether parsing or unparsing.
* @param msg describes the error
*/
public void runtimeSchemaDefinitionError(String msg) {
layerRuntime.runtimeSchemaDefinitionError(msg); }
@@ -157,22 +356,54 @@ public abstract class Layer {
* the way the layer is configured.
* <p>
* This error type is always fatal whether parsing or unparsing.
- * <p>
+ * See {@link #runtimeSchemaDefinitionError(String) } for discussion of
situations where
+ * a Runtime Schema Definition Error is appropriate.
* @param cause a throwable object that describes the error
*/
public void runtimeSchemaDefinitionError(Throwable cause) {
layerRuntime.runtimeSchemaDefinitionError(cause); }
/**
- * Wraps a layer input interpreter around an input stream, using the
provided LayerRuntimeFoo for runtime information and stateful services.
- *
+ * Wraps a layer <i>input decoder</i> around an input stream.
+ * <p>
+ * This input decoder does the real parser-time work of the layer.
+ * It implements {@code java.io.InputStream}, by
+ * reading data from the given argument input stream, and decoding it.
+ * <p>
+ * Any exception thrown from this wrap method becomes a Schema Definition
Error (fatal).
+ * <p>
+ * The input decoder generally uses a reference to this layer instance to
report errors.
+ * <p>
+ * To have the Daffodil layer framework convert uncaught exceptions
+ * thrown by the input decoder into processing errors automatically,
+ * call the {@link #setProcessingErrorException(Class)}.
+ * Other kinds of input decoder processing errors can be signaled by calling
the {@link #processingError(String)} or
+ * {@link #processingError(Throwable)} methods.
+ * Input decoder schema definition errors are more rare, but if needed the
{@link #runtimeSchemaDefinitionError(String)} or
+ * {@link #runtimeSchemaDefinitionError(Throwable)} methods may be called.
* @param jis The input stream to be wrapped.
- * @return An input stream with the layer wrapped around it.
+ * @return An input stream with the layer's input decoder wrapped around it.
*/
public abstract InputStream wrapLayerInput(InputStream jis) throws Exception;
/**
- * Wraps a layer output interpreter around an output stream, using the
provided LayerRuntimeFoo for runtime information and stateful services.
- *
+ * Wraps a layer <i>output encoder</i> around an output stream.
+ * <p>
+ * The output encoder does the real unparse-time work of the layer.
+ * It implements {@code java.io.OutputStream} by
+ * accepting data via the usual output stream write calls, encoding this
+ * data, and writing the encoded data to the argument output stream.
+ * <p>
+ * Any exception thrown from this wrap method becomes a Schema Definition
Error (fatal).
+ * <p>
+ * The output encoder generally uses a reference to this layer to report
errors.
+ * <p>
+ * To have the Daffodil layer framework convert uncaught exceptions
+ * thrown by the output encoder into processing errors automatically,
+ * call the {@link #setProcessingErrorException(Class)}.
+ * Other kinds of output encoder processing errors can be signaled by
calling the {@link #processingError(String)} or
+ * {@link #processingError(Throwable)} methods.
+ * Output encoder schema definition errors are more rare, but if needed the
{@link #runtimeSchemaDefinitionError(String)} or
+ * {@link #runtimeSchemaDefinitionError(Throwable)} methods may be called.
* @param jos The output stream to be wrapped.
* @return An output stream with the layer wrapped around it.
*/
@@ -181,13 +412,17 @@ public abstract class Layer {
private ArrayList<Class<? extends Exception>> peExceptions = new
ArrayList<>();
/**
- * Adds an exception class to the list of exceptions that will be
automatically converted
+ * Use to add an exception class to the list of exceptions that will be
automatically converted
* into processing errors.
* <p>
* The purpose of this is to allow one to use java/scala libraries that may
throw
* exceptions when encountering bad data. Such exceptions should be
translated into
* processing errors, which will allow the parser to backtrack and try other
alternatives
* which may work for that data.
+ * By calling this method the layer framework implements the try-catch logic
to capture
+ * these exception types and convert them into processing errors.
+ * This avoids a great deal of try-catch logic that would otherwise be
required in
+ * layer methods.
* <p>
* When considering whether a thrown Exception is to be converted to a
processing error
* RuntimeException classes are handled separately from Exception classes.
@@ -205,6 +440,9 @@ public abstract class Layer {
peExceptions.add(e);
}
+ /**
+ * For framework use.
+ */
public final List<Class<? extends Exception>> getProcessingErrorExceptions()
{
return peExceptions;
}
diff --git
a/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/layers/api/package-info.java
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/layers/api/package-info.java
new file mode 100644
index 000000000..8fe397633
--- /dev/null
+++
b/daffodil-runtime1/src/main/scala/org/apache/daffodil/runtime1/layers/api/package-info.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * <h2>Daffodil Runtime1 Layers API Package</h2>
+ *
+ * This package provides base classes for creating Layers, which provide a
means of
+ * algorithmic operations on the data stream that cannot be expressed using
+ * regular DFDL properties.
+ * <p>
+ * There are two kinds of layers:
+ * <ul><li>transforming layers - such as a base64 decoder/encoder for parts of
a textual format</li>
+ * <li>checksum layers - such as computing CRC32 over a message header</li>
+ * </ul>
+ * Both run an algorithm over a part of the data stream.
+ * <p>
+ * Layers are implemented in Scala or Java as small (usually) Jar files that
are dynamically loaded from the CLASSPATH in
+ * response to a DFDL schema that uses that layer via the {@code dfdlx:layer}
property.
+ * <p>
+ * See the <a href="http://daffodil.apache.org/layers">Layer Usage
Documentation</a> for how a layer is
+ * used from a Daffodil DFDL schema.
+ * <p>
+ * This API documentation is focused on the programming required to create a
new layer implementation
+ * as a custom plug-in for Daffodil.
+ * <p>
+ * {@link org.apache.daffodil.runtime1.layers.api.Layer} is the general
abstract base class
+ * used to define any layer, but most commonly it is used for transforming
layers.
+ * <p>
+ * {@link org.apache.daffodil.runtime1.layers.api.ChecksumLayer} is an
abstract base class
+ * derived from {@link org.apache.daffodil.runtime1.layers.api.Layer}, and
further specialized for defining
+ * checksum layers.
+ *
+ * <h3> About Testing </h3>
+ *
+ * The Daffodil test code base includes tests for many ways that
+ * an API user can goof up the definition of a layer class.
+ * For example there are tests for calling {@code processingError}, {@code
runtimeSchemaDefinitionError}, and throwing
+ * an {@code Exception} from every place a custom-defined Layer could cause
these.
+ * Processing errors cause the parser to backtrack in all sensible cases.
+ * That is to say that if a layer is parsing data, and the data it encounters
is not a match for that layer, then
+ * a properly written layer will issue a processing error, and the parser will
backtrack, allowing the format to try
+ * other alternatives for parsing that data.
+ *
+ * <h3>Compatibility with Daffodil 3.7.0 and prior versions of Apache
Daffodil</h3>
+ *
+ * This new Layer API is entirely incompatible with schemas or layer code
+ * from Daffodil 3.7.0 and all prior versions of Daffodil.
+ * <p>
+ * The layer feature was just an experimental feature in earlier versions of
Daffodil, so we reserved
+ * the right to change it, and for Daffodil 3.8.0 it has changed radically
based on what we learned from the
+ * earlier experimentation.
+ * <p>
+ * It is our intention that this Layer API (introduced in Daffodil 3.8.0) will
prove to be stable
+ * and supportable long term.
+ */
+package org.apache.daffodil.runtime1.layers.api;
diff --git a/daffodil-sapi/root-doc.txt b/daffodil-sapi/root-doc.txt
index 29613045c..c7a39e189 100644
--- a/daffodil-sapi/root-doc.txt
+++ b/daffodil-sapi/root-doc.txt
@@ -6,3 +6,4 @@ This is the documentation for the Apache Daffodil Scala API.
[[org.apache.daffodil.udf]] - Provides the classes necessary to create User
Defined Functions to extend the DFDL expression language
+[[org.apache.daffodil.runtime1.layers.api]] - Provides the classes necessary
to create custom Layer extensions to DFDL.