/*
 * 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.
 */

/* $Id: AFPResourceUtil.java 1679676 2015-05-16 02:10:42Z adelmelle $ */

package org.apache.fop.afp.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Collection;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.fop.afp.AFPConstants;
import org.apache.fop.afp.modca.AbstractAFPObject.Category;
import org.apache.fop.afp.modca.ResourceObject;
import org.apache.fop.afp.parser.MODCAParser;
import org.apache.fop.afp.parser.UnparsedStructuredField;

TODO better docs Utility for AFP resource handling A utility class to read structured fields from a MO:DCA document. Each component of a mixed object document is explicitly defined and delimited in the data. This is accomplished through the use of MO:DCA data structures, called structured fields. Structured fields are used to envelop document components and to provide commands and information to applications using the data. Structured fields may contain one or more parameters. Each parameter provides one value from a set of values defined by the architecture.
MO:DCA structured fields consist of two parts: an introducer that identifies the length and type of the structured field, and data that provides the structured field's effect. The data is contained in a set of parameters, which can consist of other data structures and data elements. The maximum length of a structured field is 32767 bytes.
/** * TODO better docs * Utility for AFP resource handling * * * A utility class to read structured fields from a MO:DCA document. Each * component of a mixed object document is explicitly defined and delimited * in the data. This is accomplished through the use of MO:DCA data structures, * called structured fields. Structured fields are used to envelop document * components and to provide commands and information to applications using * the data. Structured fields may contain one or more parameters. Each * parameter provides one value from a set of values defined by the architecture. * <br> * MO:DCA structured fields consist of two parts: an introducer that identifies * the length and type of the structured field, and data that provides the * structured field's effect. The data is contained in a set of parameters, * which can consist of other data structures and data elements. The maximum * length of a structured field is 32767 bytes. */
public final class AFPResourceUtil { private static final byte TYPE_CODE_BEGIN = (byte) (0xA8 & 0xFF); private static final byte TYPE_CODE_END = (byte) (0xA9 & 0xFF); private static final byte END_FIELD_ANY_NAME = (byte) (0xFF & 0xFF); private static final Log LOG = LogFactory.getLog(AFPResourceUtil.class); private AFPResourceUtil() { //nop }
Get the next structured field as identified by the identifier parameter (this must be a valid MO:DCA structured field).
Params:
  • identifier – the three byte identifier
  • inputStream – the inputStream
Throws:
Returns:the next structured field or null when there are no more
/** * Get the next structured field as identified by the identifier * parameter (this must be a valid MO:DCA structured field). * @param identifier the three byte identifier * @param inputStream the inputStream * @throws IOException if an I/O exception occurred * @return the next structured field or null when there are no more */
public static byte[] getNext(byte[] identifier, InputStream inputStream) throws IOException { MODCAParser parser = new MODCAParser(inputStream); while (true) { UnparsedStructuredField field = parser.readNextStructuredField(); if (field == null) { return null; } if (field.getSfClassCode() == identifier[0] && field.getSfTypeCode() == identifier[1] && field.getSfCategoryCode() == identifier[2]) { return field.getCompleteFieldAsBytes(); } } } private static String getResourceName(UnparsedStructuredField field) throws UnsupportedEncodingException { //The first 8 bytes of the field data represent the resource name byte[] nameBytes = new byte[8]; byte[] fieldData = field.getData(); if (fieldData.length < 8) { throw new IllegalArgumentException("Field data does not contain a resource name"); } System.arraycopy(fieldData, 0, nameBytes, 0, 8); return new String(nameBytes, AFPConstants.EBCIDIC_ENCODING); }
Copy a complete resource file to a given OutputStream.
Params:
  • in – external resource input
  • out – output destination
Throws:
/** * Copy a complete resource file to a given {@link OutputStream}. * @param in external resource input * @param out output destination * @throws IOException if an I/O error occurs */
public static void copyResourceFile(final InputStream in, OutputStream out) throws IOException { MODCAParser parser = new MODCAParser(in); while (true) { UnparsedStructuredField field = parser.readNextStructuredField(); if (field == null) { break; } out.write(MODCAParser.CARRIAGE_CONTROL_CHAR); field.writeTo(out); } }
Copy a named resource to a given OutputStream. The MO:DCA fields read from the InputStream are scanned for the resource with the given name.
Params:
  • name – name of structured field
  • in – external resource input
  • out – output destination
Throws:
/** * Copy a named resource to a given {@link OutputStream}. The MO:DCA fields read from the * {@link InputStream} are scanned for the resource with the given name. * @param name name of structured field * @param in external resource input * @param out output destination * @throws IOException if an I/O error occurs */
public static void copyNamedResource(String name, final InputStream in, final OutputStream out) throws IOException { final MODCAParser parser = new MODCAParser(in); Collection<String> resourceNames = new java.util.HashSet<String>(); //Find matching "Begin" field final UnparsedStructuredField fieldBegin; while (true) { final UnparsedStructuredField field = parser.readNextStructuredField(); if (field == null) { throw new IOException("Requested resource '" + name + "' not found. Encountered resource names: " + resourceNames); } if (field.getSfTypeCode() != TYPE_CODE_BEGIN) { //0xA8=Begin continue; //Not a "Begin" field } final String resourceName = getResourceName(field); resourceNames.add(resourceName); if (resourceName.equals(name)) { if (LOG.isDebugEnabled()) { LOG.debug("Start of requested structured field found:\n" + field); } fieldBegin = field; break; //Name doesn't match } } //Decide whether the resource file has to be wrapped in a resource object boolean wrapInResource; if (fieldBegin.getSfCategoryCode() == Category.PAGE_SEGMENT) { //A naked page segment must be wrapped in a resource object wrapInResource = true; } else if (fieldBegin.getSfCategoryCode() == Category.NAME_RESOURCE) { //A resource object can be copied directly wrapInResource = false; } else { throw new IOException("Cannot handle resource: " + fieldBegin); } //Copy structured fields (wrapped or as is) if (wrapInResource) { ResourceObject resourceObject = new ResourceObject(name) { protected void writeContent(OutputStream os) throws IOException { copyNamedStructuredFields(name, fieldBegin, parser, out); } }; resourceObject.setType(ResourceObject.TYPE_PAGE_SEGMENT); resourceObject.writeToStream(out); } else { copyNamedStructuredFields(name, fieldBegin, parser, out); } } private static void copyNamedStructuredFields(final String name, UnparsedStructuredField fieldBegin, MODCAParser parser, OutputStream out) throws IOException { UnparsedStructuredField field = fieldBegin; while (true) { if (field == null) { throw new IOException("Ending structured field not found for resource " + name); } out.write(MODCAParser.CARRIAGE_CONTROL_CHAR); field.writeTo(out); if (isEndOfStructuredField(field, fieldBegin, name)) { break; } field = parser.readNextStructuredField(); } } private static boolean isEndOfStructuredField(UnparsedStructuredField field, UnparsedStructuredField fieldBegin, String name) throws UnsupportedEncodingException { return fieldMatchesEndTagType(field) && fieldMatchesBeginCategoryCode(field, fieldBegin) && fieldHasValidName(field, name); } private static boolean fieldMatchesEndTagType(UnparsedStructuredField field) { return field.getSfTypeCode() == TYPE_CODE_END; } private static boolean fieldMatchesBeginCategoryCode(UnparsedStructuredField field, UnparsedStructuredField fieldBegin) { return fieldBegin.getSfCategoryCode() == field.getSfCategoryCode(); }
The AFP specification states that it is valid for the end structured field to have: - No tag name specified, which will cause it to match any existing tag type match. - The name has FFFF as its first two bytes - The given name matches the previous structured field name
/** * The AFP specification states that it is valid for the end structured field to have: * - No tag name specified, which will cause it to match any existing tag type match. * - The name has FFFF as its first two bytes * - The given name matches the previous structured field name */
private static boolean fieldHasValidName(UnparsedStructuredField field, String name) throws UnsupportedEncodingException { if (field.getData().length > 0) { if (field.getData()[0] == field.getData()[1] && field.getData()[0] == END_FIELD_ANY_NAME) { return true; } else { return name.equals(getResourceName(field)); } } return true; } }