]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Fixed a bug in AFP where the object area axes of an Include Object was incorrectly...
authorPeter Hancock <phancock@apache.org>
Wed, 27 Jul 2011 12:50:12 +0000 (12:50 +0000)
committerPeter Hancock <phancock@apache.org>
Wed, 27 Jul 2011 12:50:12 +0000 (12:50 +0000)
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1151452 13f79535-47bb-0310-9956-ffa450edef68

12 files changed:
src/java/org/apache/fop/afp/modca/AbstractAFPObject.java
src/java/org/apache/fop/afp/modca/AbstractNamedAFPObject.java
src/java/org/apache/fop/afp/modca/AbstractTripletStructuredObject.java
src/java/org/apache/fop/afp/modca/IncludeObject.java
src/java/org/apache/fop/afp/modca/TagLogicalElement.java
test/java/org/apache/fop/StandardTestSuite.java
test/java/org/apache/fop/afp/AFPTestSuite.java [new file with mode: 0644]
test/java/org/apache/fop/afp/modca/AbstractAFPObjectTestCase.java [new file with mode: 0644]
test/java/org/apache/fop/afp/modca/AbstractNamedAFPObjectTestCase.java [new file with mode: 0644]
test/java/org/apache/fop/afp/modca/AbstractStructuredObjectTestCase.java [new file with mode: 0644]
test/java/org/apache/fop/afp/modca/AbstractTripletStructuredObjectTestCase.java [new file with mode: 0644]
test/java/org/apache/fop/afp/modca/IncludeObjectTestCase.java [new file with mode: 0644]

index af679fa25cfb861f3edadc2f0e77b9a27c2306bd..a88c0e4c99671152e6e131f88e9753f7cde6789d 100644 (file)
@@ -39,13 +39,12 @@ import org.apache.fop.afp.util.BinaryUtils;
 public abstract class AbstractAFPObject implements Streamable {
 
     /** Static logging instance */
-    protected static final Log LOG = LogFactory.getLog("org.apache.xmlgraphics.afp.modca");
+    protected static final Log LOG = LogFactory.getLog(AbstractAFPObject.class);
 
     /** the structured field class id */
     protected static final byte SF_CLASS = (byte)0xD3;
 
-    /** the structure field header */
-    static final byte[] SF_HEADER = new byte[] {
+    private static final byte[] SF_HEADER = new byte[] {
         0x5A, // Structured field identifier
         0x00, // Length byte 1
         0x10, // Length byte 2
@@ -57,6 +56,8 @@ public abstract class AbstractAFPObject implements Streamable {
         0x00, // Reserved
     };
 
+    protected static final int SF_HEADER_LENGTH = SF_HEADER.length;
+
     /**
      * Copies the template structured field data array to the given byte array
      *
@@ -90,50 +91,18 @@ public abstract class AbstractAFPObject implements Streamable {
      * @param os The stream to write to
      * @throws java.io.IOException an I/O exception of some sort has occurred.
      */
-    protected void writeObjects(Collection/*<Streamable>*/ objects, OutputStream os)
-        throws IOException {
-        if (objects != null && objects.size() > 0) {
-            Iterator it = objects.iterator();
+    protected <S extends Streamable> void writeObjects(Collection<S> objects, OutputStream os)
+            throws IOException {
+        if (objects != null) {
+            Iterator<S> it = objects.iterator();
             while (it.hasNext()) {
-                Object object = it.next();
-                if (object instanceof Streamable) {
-                    ((Streamable)object).writeToStream(os);
-                    it.remove(); // once written, immediately remove the object
-                }
+                Streamable s = it.next();
+                s.writeToStream(os);
+                it.remove(); // once written, immediately remove the object
             }
         }
     }
 
-    /**
-     * Reads data chunks from an InputStream
-     * and then formats them with a structured header to a given OutputStream
-     *
-     * @param dataHeader the header data
-     * @param lengthOffset offset of length field in data chunk
-     * @param maxChunkLength the maximum chunk length
-     * @param inputStream the InputStream to read from
-     * @param outputStream the OutputStream to write to
-     * @throws IOException thrown if an I/O exception of some sort has occurred.
-     */
-    protected static void copyChunks(byte[] dataHeader, int lengthOffset,
-            int maxChunkLength, InputStream inputStream, OutputStream outputStream)
-    throws IOException {
-        int headerLen = dataHeader.length - lengthOffset;
-        // length field is just before data so do not include in data length
-        if (headerLen == 2) {
-            headerLen = 0;
-        }
-        byte[] data = new byte[maxChunkLength];
-        int numBytesRead = 0;
-        while ((numBytesRead = inputStream.read(data, 0, maxChunkLength)) > 0) {
-            byte[] len = BinaryUtils.convert(headerLen + numBytesRead, 2);
-            dataHeader[lengthOffset] = len[0]; // Length byte 1
-            dataHeader[lengthOffset + 1] = len[1]; // Length byte 2
-            outputStream.write(dataHeader);
-            outputStream.write(data, 0, numBytesRead);
-        }
-    }
-
     /**
      * Writes data chunks to a given outputstream
      *
@@ -186,7 +155,7 @@ public abstract class AbstractAFPObject implements Streamable {
      * @param maxLength the maximum length allowed for the string
      * @return a possibly truncated string
      */
-    protected String truncate(String str, int maxLength) {
+    protected static String truncate(String str, int maxLength) {
         if (str.length() > maxLength) {
             str = str.substring(0, maxLength);
             LOG.warn("truncated character string '"
index b349e1f7db852eaed72bb61c7bc2a66710a60015..83c720e581b2edf7f54f99ac0fbebc2eb1598e16 100644 (file)
@@ -90,7 +90,7 @@ public abstract class AbstractNamedAFPObject extends AbstractTripletStructuredOb
         return nameBytes;
     }
 
-    /** {@inheritDoc} */
+    @Override
     protected void copySF(byte[] data, byte type, byte category) {
         super.copySF(data, type, category);
         byte[] nameData = getNameBytes();
index 4b269086e240779e677b40bfc1ddb54f308c033d..662b344c172f6d104c17af1af2faf2e111c8ee7c 100644 (file)
@@ -22,7 +22,6 @@ package org.apache.fop.afp.modca;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.util.Collection;
-import java.util.Iterator;
 import java.util.List;
 
 import org.apache.fop.afp.modca.Registry.ObjectType;
@@ -35,10 +34,10 @@ import org.apache.fop.afp.modca.triplets.Triplet;
 /**
  * A MODCA structured object base class providing support for Triplets
  */
-public class AbstractTripletStructuredObject extends AbstractStructuredObject {
+public abstract class AbstractTripletStructuredObject extends AbstractStructuredObject {
 
     /** list of object triplets */
-    protected List/*<Triplet>*/ triplets = new java.util.ArrayList/*<Triplet>*/();
+    protected List<AbstractTriplet> triplets = new java.util.ArrayList<AbstractTriplet>();
 
     /**
      * Returns the triplet data length
@@ -47,12 +46,8 @@ public class AbstractTripletStructuredObject extends AbstractStructuredObject {
      */
     protected int getTripletDataLength() {
         int dataLength = 0;
-        if (hasTriplets()) {
-            Iterator it = triplets.iterator();
-            while (it.hasNext()) {
-                AbstractTriplet triplet = (AbstractTriplet)it.next();
-                dataLength += triplet.getDataLength();
-            }
+        for (Triplet triplet : triplets) {
+            dataLength += triplet.getDataLength();
         }
         return dataLength;
     }
@@ -85,11 +80,9 @@ public class AbstractTripletStructuredObject extends AbstractStructuredObject {
      * @param tripletId the triplet identifier
      */
     private AbstractTriplet getTriplet(byte tripletId) {
-        Iterator it = getTriplets().iterator();
-        while (it.hasNext()) {
-            AbstractTriplet triplet = (AbstractTriplet)it.next();
-            if (triplet.getId() == tripletId) {
-                return triplet;
+        for (AbstractTriplet trip : triplets) {
+            if (trip.getId() == tripletId) {
+                return trip;
             }
         }
         return null;
@@ -110,7 +103,7 @@ public class AbstractTripletStructuredObject extends AbstractStructuredObject {
      *
      * @param triplet the triplet to add
      */
-    protected void addTriplet(Triplet triplet) {
+    protected void addTriplet(AbstractTriplet triplet) {
         triplets.add(triplet);
     }
 
@@ -119,14 +112,14 @@ public class AbstractTripletStructuredObject extends AbstractStructuredObject {
      *
      * @param tripletCollection a collection of triplets
      */
-    public void addTriplets(Collection/*<Triplet>*/ tripletCollection) {
+    public void addTriplets(Collection<AbstractTriplet> tripletCollection) {
         if (tripletCollection != null) {
             triplets.addAll(tripletCollection);
         }
     }
 
     /** @return the triplet list pertaining to this resource */
-    protected List/*<Triplet>*/ getTriplets() {
+    protected List<AbstractTriplet> getTriplets() {
         return triplets;
     }
 
index 46b4a31f285840dda520ed88b4fdc8f928713a5b..60ee88d9049f64ca90355670ed66074dd0eedc18 100644 (file)
@@ -57,7 +57,6 @@ public class IncludeObject extends AbstractNamedAFPObject {
     /** the object referenced is of type image */
     public static final byte TYPE_IMAGE = (byte)0xFB;
 
-
     /** the object type referenced (default is other) */
     private byte objectType = TYPE_OTHER;
 
@@ -68,7 +67,7 @@ public class IncludeObject extends AbstractNamedAFPObject {
     private int yoaOset = 0;
 
     /** the orientation of the referenced object */
-    private int oaOrent = 0;
+    private ObjectAreaRotation oaOrent = ObjectAreaRotation.RIGHT_HANDED_0;
 
     /** the X-axis origin defined in the object */
     private int xocaOset = -1;
@@ -94,13 +93,7 @@ public class IncludeObject extends AbstractNamedAFPObject {
      *            The orientation (0,90, 180, 270)
      */
     public void setObjectAreaOrientation(int orientation) {
-        if (orientation == 0 || orientation == 90 || orientation == 180
-            || orientation == 270) {
-            this.oaOrent = orientation;
-        } else {
-            throw new IllegalArgumentException(
-                "The orientation must be one of the values 0, 90, 180, 270");
-        }
+        this.oaOrent = ObjectAreaRotation.objectAreaRotationFor(orientation);
     }
 
     /**
@@ -151,87 +144,16 @@ public class IncludeObject extends AbstractNamedAFPObject {
         data[17] = 0x00; // reserved
         data[18] = objectType;
 
-        //XoaOset (object area)
-        if (xoaOset > -1) {
-            byte[] x = BinaryUtils.convert(xoaOset, 3);
-            data[19] = x[0];
-            data[20] = x[1];
-            data[21] = x[2];
-        } else {
-            data[19] = (byte)0xFF;
-            data[20] = (byte)0xFF;
-            data[21] = (byte)0xFF;
-        }
+        writeOsetTo(data, 19, xoaOset);
 
-        // YoaOset (object area)
-        if (yoaOset > -1) {
-            byte[] y = BinaryUtils.convert(yoaOset, 3);
-            data[22] = y[0];
-            data[23] = y[1];
-            data[24] = y[2];
-        } else {
-            data[22] = (byte)0xFF;
-            data[23] = (byte)0xFF;
-            data[24] = (byte)0xFF;
-        }
+        writeOsetTo(data, 22, yoaOset);
 
-        // XoaOrent/YoaOrent
-        switch (oaOrent) {
-            case -1: // use x/y axis orientation defined in object
-                data[25] = (byte)0xFF; // x axis rotation
-                data[26] = (byte)0xFF; //
-                data[27] = (byte)0xFF; // y axis rotation
-                data[28] = (byte)0xFF;
-                break;
-            case 90:
-                data[25] = 0x2D;
-                data[26] = 0x00;
-                data[27] = 0x5A;
-                data[28] = 0x00;
-                break;
-            case 180:
-                data[25] = 0x5A;
-                data[25] = 0x00;
-                data[27] = (byte)0x87;
-                data[28] = 0x00;
-                break;
-            case 270:
-                data[25] = (byte)0x87;
-                data[26] = 0x00;
-                data[27] = 0x00;
-                data[28] = 0x00;
-                break;
-            default: // 0 degrees
-                data[25] = 0x00;
-                data[26] = 0x00;
-                data[27] = 0x2D;
-                data[28] = 0x00;
-                break;
-        }
+        oaOrent.writeTo(data, 25);
 
-        // XocaOset (object content)
-        if (xocaOset > -1) {
-            byte[] x = BinaryUtils.convert(xocaOset, 3);
-            data[29] = x[0];
-            data[30] = x[1];
-            data[31] = x[2];
-        } else {
-            data[29] = (byte)0xFF;
-            data[30] = (byte)0xFF;
-            data[31] = (byte)0xFF;
-        }
+        writeOsetTo(data, 29, xocaOset);
+
+        writeOsetTo(data, 32, yocaOset);
 
-        // YocaOset (object content)
-        if (yocaOset > -1) {
-            byte[] y = BinaryUtils.convert(yocaOset, 3);
-            data[32] = y[0];
-            data[33] = y[1];
-            data[34] = y[2];
-        } else {
-            data[32] = (byte)0xFF;
-            data[33] = (byte)0xFF;
-            data[34] = (byte)0xFF;
-        }
         // RefCSys (Reference coordinate system)
         data[35] = 0x01; // Page or overlay coordinate system
 
@@ -242,6 +164,19 @@ public class IncludeObject extends AbstractNamedAFPObject {
         writeTriplets(os);
     }
 
+    private static void writeOsetTo(byte[] out, int offset, int oset) {
+        if (oset > -1) {
+            byte[] y = BinaryUtils.convert(oset, 3);
+            out[offset] = y[0];
+            out[offset + 1] = y[1];
+            out[offset + 2] = y[2];
+        } else {
+            out[offset] = (byte)0xFF;
+            out[offset + 1] = (byte)0xFF;
+            out[offset + 2] = (byte)0xFF;
+        }
+    }
+
     private String getObjectTypeName() {
         String objectTypeName = null;
         if (objectType == TYPE_PAGE_SEGMENT) {
@@ -299,4 +234,69 @@ public class IncludeObject extends AbstractNamedAFPObject {
         addTriplet(new MeasurementUnitsTriplet(xRes, xRes));
     }
 
-}
\ No newline at end of file
+    /**
+     * Represents the 4 bytes that specify the area rotation reference coordinate system
+     *
+     */
+    private enum ObjectAreaRotation {
+
+        RIGHT_HANDED_0(Rotation.ROTATION_0, Rotation.ROTATION_90),
+        RIGHT_HANDED_90(Rotation.ROTATION_90, Rotation.ROTATION_180),
+        RIGHT_HANDED_180(Rotation.ROTATION_180, Rotation.ROTATION_270),
+        RIGHT_HANDED_270(Rotation.ROTATION_270, Rotation.ROTATION_0);
+
+        /**
+         * The object area’s X-axis rotation from the X axis of the reference coordinate system
+         */
+        private final Rotation xoaOrent;
+        /**
+         * The object area’s Y-axis rotation from the Y axis of the reference coordinate system
+         */
+        private final Rotation yoaOrent;
+
+        public void writeTo(byte[] out, int offset) {
+            xoaOrent.writeTo(out, offset);
+            yoaOrent.writeTo(out, offset + 2);
+        }
+
+        ObjectAreaRotation(Rotation xoaOrent, Rotation yoaOrent) {
+            this.xoaOrent = xoaOrent;
+            this.yoaOrent = yoaOrent;
+        }
+
+        private static ObjectAreaRotation objectAreaRotationFor(int orientation) {
+            switch (orientation) {
+                case 0: return RIGHT_HANDED_0;
+                case 90: return RIGHT_HANDED_90;
+                case 180: return RIGHT_HANDED_180;
+                case 270: return RIGHT_HANDED_270;
+                default: throw new IllegalArgumentException(
+                "The orientation must be one of the values 0, 90, 180, 270");
+            }
+        }
+    }
+
+    /**
+     * Represents a rotation value
+     *
+     */
+    private enum Rotation {
+
+        ROTATION_0(0),
+        ROTATION_90(0x2D),
+        ROTATION_180(0x5A),
+        ROTATION_270(0x87);
+
+        private final byte firstByte;
+
+        public void writeTo(byte[] out, int offset) {
+            out[offset] = firstByte;
+            out[offset + 1] = (byte)0;
+        }
+
+        Rotation(int firstByte) {
+            this.firstByte = (byte) firstByte;
+        }
+    }
+
+}
index ab2c52143e9ef659fa8cbbdf4271073a3596782d..706ceae3b411a3bc189377338925b3277e3f7336 100644 (file)
@@ -104,7 +104,7 @@ public class TagLogicalElement extends AbstractTripletStructuredObject {
         setAttributeValue(value);
         setAttributeQualifier(tleID, 1);
 
-        byte[] data = new byte[SF_HEADER.length];
+        byte[] data = new byte[SF_HEADER_LENGTH];
         copySF(data, Type.ATTRIBUTE, Category.PROCESS_ELEMENT);
 
         int tripletDataLength = getTripletDataLength();
index d6a6f8367f503d20afc103e4a268f4b9bf51d965..01e3a365e261dd2337d1c0a365cb72e51ee54aad 100644 (file)
@@ -28,7 +28,6 @@ import org.apache.fop.fonts.DejaVuLGCSerifTest;
 import org.apache.fop.image.loader.batik.ImageLoaderTestCase;
 import org.apache.fop.image.loader.batik.ImagePreloaderTestCase;
 import org.apache.fop.intermediate.IFMimickingTestCase;
-import org.apache.fop.render.afp.AFPTestSuite;
 import org.apache.fop.render.extensions.prepress.PageBoundariesTest;
 import org.apache.fop.render.extensions.prepress.PageScaleTest;
 import org.apache.fop.render.pdf.PDFAConformanceTestCase;
@@ -53,13 +52,14 @@ public class StandardTestSuite {
         //$JUnit-BEGIN$
         suite.addTest(BasicDriverTestSuite.suite());
         suite.addTest(UtilityCodeTestSuite.suite());
+        suite.addTest(org.apache.fop.afp.AFPTestSuite.suite());
         suite.addTest(new TestSuite(PDFAConformanceTestCase.class));
         suite.addTest(new TestSuite(PDFEncodingTestCase.class));
         suite.addTest(new TestSuite(PDFCMapTestCase.class));
         suite.addTest(new TestSuite(PDFsRGBSettingsTestCase.class));
         suite.addTest(new TestSuite(DejaVuLGCSerifTest.class));
         suite.addTest(new TestSuite(MODCAParserTestCase.class));
-        suite.addTest(AFPTestSuite.suite());
+        suite.addTest(org.apache.fop.render.afp.AFPTestSuite.suite());
         suite.addTest(PSTestSuite.suite());
         suite.addTest(RichTextFormatTestSuite.suite());
         suite.addTest(new TestSuite(ImageLoaderTestCase.class));
diff --git a/test/java/org/apache/fop/afp/AFPTestSuite.java b/test/java/org/apache/fop/afp/AFPTestSuite.java
new file mode 100644 (file)
index 0000000..c32eab6
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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$ */
+
+package org.apache.fop.afp;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import org.apache.fop.afp.modca.AbstractStructuredObjectTestCase;
+import org.apache.fop.afp.modca.AbstractTripletStructuredObjectTestCase;
+import org.apache.fop.afp.modca.IncludeObjectTestCase;
+
+/**
+ * Test suite for FOP's AFP classes.
+ */
+public class AFPTestSuite {
+    /**
+     * Builds the test suite
+     * @return the test suite
+     */
+    public static Test suite() {
+        TestSuite suite = new TestSuite("Test suite for FOP's AFP classes");
+        //$JUnit-BEGIN$
+        suite.addTest(new TestSuite(IncludeObjectTestCase.class));
+        //$JUnit-END$
+        return suite;
+    }
+}
diff --git a/test/java/org/apache/fop/afp/modca/AbstractAFPObjectTestCase.java b/test/java/org/apache/fop/afp/modca/AbstractAFPObjectTestCase.java
new file mode 100644 (file)
index 0000000..815939d
--- /dev/null
@@ -0,0 +1,245 @@
+/*
+ * 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$ */
+
+package org.apache.fop.afp.modca;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.apache.fop.afp.Streamable;
+
+/**
+ * Tests the {@link AbstractAFPObject} class.
+ */
+public abstract class  AbstractAFPObjectTestCase<S extends  AbstractAFPObject>
+        extends TestCase {
+
+    private S sut;
+
+    protected final S getSut() {
+        return sut;
+    }
+
+    protected final void setSut(S sut) {
+        if ( this.sut == null) {
+            this.sut = sut;
+        }
+    }
+
+
+    private byte[] header = new byte[] {
+            0x5A, // Structured field identifier
+            0x00, // Length byte 1
+            0x10, // Length byte 2
+            0x00, // Structured field id byte 1
+            0x00, // Structured field id byte 2
+            0x00, // Structured field id byte 3
+            0x00, // Flags
+            0x00, // Reserved
+            0x00 // Reserved
+    };
+
+
+    public void testCopySFStatic() {
+        byte[] actual = new byte[9];
+        Arrays.fill(actual, (byte)-1);
+
+        S.copySF(actual, (byte)0, (byte)0, (byte)0);
+
+        assertTrue(Arrays.equals(actual, header));
+
+        byte[] expected2 =  new byte[9];
+        System.arraycopy(header, 0, expected2, 0, header.length);
+
+        final byte clazz = (byte) 0x01;
+        final byte type = (byte) 0x02;
+        final byte catagory = (byte) 0x03;
+        expected2[3] = clazz;
+        expected2[4] = type;
+        expected2[5] = catagory;
+
+        AbstractAFPObject.copySF(actual, clazz, type, catagory);
+
+        assertTrue(Arrays.equals(actual, expected2));
+    }
+
+    public void testCopySF() {
+        byte[] expected = new byte[9];
+        S.copySF(expected, (byte) 0xD3, (byte)0, (byte)0);
+
+        byte[] actual = new byte[9];
+        Arrays.fill(actual, (byte)-1);
+
+        getSut().copySF(actual, (byte)0, (byte)0);
+
+        assertTrue(Arrays.equals(actual, expected));
+
+        byte[] expected2 =  new byte[9];
+        System.arraycopy(expected, 0, expected2, 0, expected.length);
+
+        final byte type = (byte)1;
+        final byte catagory = (byte)2;
+        expected2[4] = type;
+        expected2[5] = catagory;
+
+        getSut().copySF(actual, type, catagory);
+
+        assertTrue(Arrays.equals(actual, expected2));
+    }
+
+    /**
+     *
+     */
+    public void testwriteObjects() {
+       final byte[][] expected = {{(byte)0, (byte)1}, {(byte)2, (byte)3}, {(byte)4, (byte)5}};
+
+        List<Streamable> objects = new ArrayList<Streamable>() {
+            {
+           add(StreamableObject.instance(expected[0]));
+           add(StreamableObject.instance(expected[1]));
+           add(StreamableObject.instance(expected[2]));
+       } };
+
+       ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+       try {
+           getSut().writeObjects(objects, baos);
+       } catch (IOException e) {
+           fail();
+       }
+
+       byte[] actual = baos.toByteArray();
+
+       int index = 0;
+       for (int i = 0; i < expected.length; i++) {
+           for (int j = 0; j < expected[i].length; j++) {
+               assertTrue("" + index, actual[index] == expected[i][j]);
+               index++;
+           }
+       }
+    }
+
+    /**
+     *
+     */
+    public void testTruncate() {
+        String expected = "abc";
+        assertTrue(AbstractAFPObject.truncate(expected, 4)  == expected);
+        assertTrue(AbstractAFPObject.truncate(expected, 3) == expected);
+        assertEquals(AbstractAFPObject.truncate(expected + "d", 3), expected);
+        assertEquals(AbstractAFPObject.truncate(expected, 0), "");
+        try {
+            assertTrue(AbstractAFPObject.truncate(null, 4) == null);
+            fail();
+        } catch (NullPointerException e) {
+            // PASS
+        }
+    }
+
+    /**
+     *
+     */
+    public void testWriteChunksToStream() throws IOException {
+        final byte[] data = new byte[256];
+        int counter = 0;
+        for (int i = 0; i < data.length; i++) {
+            data[i] = (byte) counter++;
+        }
+
+        byte[] header = new byte[9];
+        // Test when chunk size % data.length == 0
+        testWithGivenChunkSize(data, header, 16);
+
+        // test when chunk size % data.length != 0
+        testWithGivenChunkSize(data, header, 10);
+
+        // test with an odd number...
+        testWithGivenChunkSize(data, header, 13);
+    }
+
+    private void testWithGivenChunkSize(byte[] data, byte[] header, int chunkSize)
+            throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        S.writeChunksToStream(data, header, 0, chunkSize, baos);
+        byte[] testData = baos.toByteArray();
+
+        int numberOfFullDataChunks = data.length / chunkSize;
+        int lastChunkSize = data.length % chunkSize;
+        int lengthOfTestData = numberOfFullDataChunks * (chunkSize + header.length);
+        lengthOfTestData += lastChunkSize == 0 ? 0 : header.length + lastChunkSize;
+
+        putLengthInHeader(header, chunkSize);
+
+        assertEquals(lengthOfTestData, testData.length);
+        int testIndex = 0;
+        int expectedIndex = 0;
+        for (int i = 0; i < numberOfFullDataChunks; i++) {
+            checkHeaderAndData(header, data, testData, expectedIndex, testIndex, chunkSize);
+            expectedIndex += chunkSize + header.length;
+            testIndex += chunkSize;
+        }
+
+        putLengthInHeader(header, lastChunkSize);
+        // check last chunk
+        if (lastChunkSize != 0) {
+            checkHeaderAndData(header, data, testData, expectedIndex, testIndex, lastChunkSize);
+        }
+    }
+
+    private void putLengthInHeader(byte[] header, int chunkSize) {
+        header[0] = 0;
+        header[1] = (byte) (chunkSize + header.length);
+    }
+
+    private void checkHeaderAndData(byte[] header, byte[] data, byte[] testData, int expectedIndex,
+            int testIndex, int chunkSize) {
+        for (int i = 0; i < header.length; i++) {
+            assertEquals(testData[expectedIndex++], header[i]);
+        }
+        for (int i = 0; i < chunkSize; i++) {
+            assertEquals(testData[expectedIndex++], data[i + testIndex]);
+        }
+    }
+
+    /**
+     *
+     */
+    private static class StreamableObject implements Streamable {
+        private byte[] bytes;
+
+        StreamableObject(byte[] bytes) {
+            this.bytes = new byte[bytes.length];
+            System.arraycopy(bytes, 0, this.bytes, 0, bytes.length);
+        }
+
+        private static Streamable instance(byte[] bytes) {
+            return new StreamableObject(bytes);
+        }
+
+        public void writeToStream(OutputStream os) throws IOException {
+            os.write(bytes);
+        }
+    }
+}
diff --git a/test/java/org/apache/fop/afp/modca/AbstractNamedAFPObjectTestCase.java b/test/java/org/apache/fop/afp/modca/AbstractNamedAFPObjectTestCase.java
new file mode 100644 (file)
index 0000000..975c269
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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$ */
+
+package org.apache.fop.afp.modca;
+
+import java.util.Arrays;
+
+/**
+ * Tests the {@linkplain AbstractAFPObject} class.
+ */
+public abstract class AbstractNamedAFPObjectTestCase<S extends  AbstractNamedAFPObject>
+        extends AbstractAFPObjectTestCase<S> {
+
+    public void testCopySF() {
+
+        final S sut = getSut();
+
+        byte[] expected = new byte[17];
+        S.copySF(expected, (byte) 0xD3, (byte)0, (byte)0);
+
+        byte[] nameData = sut.getNameBytes();
+        System.arraycopy(nameData, 0, expected, 9, nameData.length);
+
+        byte[] actual = new byte[17];
+        Arrays.fill(actual, (byte)-1);
+
+        getSut().copySF(actual, (byte)0, (byte)0);
+
+        assertTrue(Arrays.equals(actual, expected));
+
+        byte[] expected2 =  new byte[17];
+        System.arraycopy(expected, 0, expected2, 0, expected.length);
+        System.arraycopy(nameData, 0, expected, 9, nameData.length);
+
+        final byte type = (byte)1;
+        final byte catagory = (byte)2;
+        expected2[4] = type;
+        expected2[5] = catagory;
+
+        getSut().copySF(actual, type, catagory);
+
+        assertTrue(Arrays.equals(actual, expected2));
+    }
+}
diff --git a/test/java/org/apache/fop/afp/modca/AbstractStructuredObjectTestCase.java b/test/java/org/apache/fop/afp/modca/AbstractStructuredObjectTestCase.java
new file mode 100644 (file)
index 0000000..e5bf70c
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * 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$ */
+
+package org.apache.fop.afp.modca;
+
+import java.io.IOException;
+
+public abstract class AbstractStructuredObjectTestCase<S extends  AbstractStructuredObject> extends AbstractAFPObjectTestCase<S> {
+
+    /**
+     * Test writeStart() - test that the contract is maintained with
+     * {@link AbstractStructuredObject}.
+     *
+     * @throws IOException
+     */
+    public void testwriteStart() throws IOException {
+    }
+
+    /**
+     * Test writeEnd() - test that the contract is maintained with {@link AbstractStructuredObject}.
+     *
+     * @throws IOException
+     */
+    public void testWriteEnd() throws IOException {
+    }
+
+    /**
+     * Test writeContent() - test that the contract is maintained with
+     * {@link AbstractStructuredObject}.
+     *
+     * @throws IOException
+     */
+    public void testWriteContent() throws IOException {
+    }
+
+    /**
+     * Test writeToStream() - test that the contract is maintained with
+     * {@link AbstractStructuredObject}.
+     *
+     * @throws IOException
+     */
+    public void testWriteToStream() throws IOException {
+        testwriteStart();
+        testWriteEnd();
+        testWriteContent();
+    }
+}
diff --git a/test/java/org/apache/fop/afp/modca/AbstractTripletStructuredObjectTestCase.java b/test/java/org/apache/fop/afp/modca/AbstractTripletStructuredObjectTestCase.java
new file mode 100644 (file)
index 0000000..ebb0f42
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * 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$ */
+
+package org.apache.fop.afp.modca;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.fop.afp.modca.triplets.AbstractTriplet;
+import org.apache.fop.afp.modca.triplets.AttributeQualifierTriplet;
+import org.apache.fop.afp.modca.triplets.CommentTriplet;
+import org.apache.fop.afp.modca.triplets.ObjectAreaSizeTriplet;
+import org.apache.fop.afp.modca.triplets.Triplet;
+
+/**
+ * Test {@link AbstractTripletStructuredObject}
+ */
+public abstract class AbstractTripletStructuredObjectTestCase<S extends AbstractTripletStructuredObject>
+        extends AbstractStructuredObjectTestCase<AbstractTripletStructuredObject> {
+
+    private static final List<AbstractTriplet> TRIPLETS;
+
+    static {
+        List<AbstractTriplet> triplets = new ArrayList<AbstractTriplet>();
+
+        triplets.add(new CommentTriplet((byte) 0x01, "test comment"));
+
+        triplets.add(new AttributeQualifierTriplet(1, 1));
+
+        triplets.add(new ObjectAreaSizeTriplet(10, 20));
+
+        TRIPLETS = Collections.unmodifiableList(triplets);
+    }
+
+    private AbstractTripletStructuredObject emptyStructuredObject
+            = new AbstractTripletStructuredObject() { };
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        AbstractTripletStructuredObject sut = getSut();
+
+        for (AbstractTriplet triplet : TRIPLETS) {
+            sut.addTriplet(triplet);
+        }
+    }
+
+
+    /**
+     * Test getTripletLength() - ensure a sum of all enclosing object lengths is returned.
+     */
+    public void testGetTripletLength() {
+
+        int dataLength = 0;
+        for (Triplet t : TRIPLETS) {
+            dataLength += t.getDataLength();
+        }
+        assertEquals(dataLength, getSut().getTripletDataLength());
+        assertEquals(0, emptyStructuredObject.getTripletDataLength());
+    }
+
+    /**
+     * Test hasTriplets()
+     */
+    public void testHasTriplets() {
+        assertTrue(getSut().hasTriplets());
+        assertFalse(emptyStructuredObject.hasTriplets());
+    }
+
+    /**
+     * Test writeTriplets() - Ensure the triplets are written properly.
+     *
+     * @throws IOException -
+     */
+    public void testWriteObjects() throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        for (AbstractTriplet triplet : TRIPLETS) {
+            triplet.writeToStream(baos);
+        }
+        byte[] expected = baos.toByteArray();
+        baos.reset();
+        getSut().writeTriplets(baos);
+        assertTrue(Arrays.equals(expected, baos.toByteArray()));
+
+        baos.reset();
+        // Ensure it doesn't die if no data has been added
+        emptyStructuredObject.writeTriplets(baos);
+        byte[] emptyArray = baos.toByteArray();
+        assertTrue(Arrays.equals(emptyArray, new byte[0]));
+    }
+
+    /**
+     * Test hasTriplet() - ensure both positive and negative values are returned.
+     */
+    public void testHasTriplet() {
+        for (AbstractTriplet triplet : TRIPLETS) {
+            assertTrue(getSut().hasTriplet(triplet.getId()));
+            assertFalse(emptyStructuredObject.hasTriplet(triplet.getId()));
+        }
+        CommentTriplet notInSystem = new CommentTriplet((byte) 0x30, "This should return false");
+        assertFalse(getSut().hasTriplet(notInSystem.getId()));
+    }
+
+    /**
+     * Test addTriplet() - mostly tested above, but check boundary cases
+     */
+    public void testAddTriplet() {
+        // ensure null doesn't kill it... not sure what else to test
+        getSut().addTriplet(null);
+    }
+
+    /**
+     * Test addTriplets() - ensure all triplets are added.
+     */
+    public void testAddTriplets() {
+        // Tested on empty object
+        List<AbstractTriplet> expectedList = TRIPLETS;
+        emptyStructuredObject.addTriplets(expectedList);
+        // checks equals() on each member of both lists
+        assertEquals(expectedList, emptyStructuredObject.getTriplets());
+
+        // Add a list to an already populated list
+        getSut().addTriplets(expectedList);
+
+        List<AbstractTriplet> newExpected = new ArrayList<AbstractTriplet>(expectedList);
+        newExpected.addAll(expectedList);
+        assertEquals(newExpected, getSut().getTriplets());
+
+        // Ensure null doesn't throw exception
+        emptyStructuredObject.addTriplets(null);
+    }
+
+}
\ No newline at end of file
diff --git a/test/java/org/apache/fop/afp/modca/IncludeObjectTestCase.java b/test/java/org/apache/fop/afp/modca/IncludeObjectTestCase.java
new file mode 100644 (file)
index 0000000..5e28f00
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * 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:$ */
+
+package org.apache.fop.afp.modca;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.apache.fop.afp.util.BinaryUtils;
+
+/**
+ * Test {@link IncludeObject}
+ */
+public class IncludeObjectTestCase extends AbstractNamedAFPObjectTestCase<IncludeObject> {
+
+    @Override
+    public void setUp() throws Exception {
+        setSut(new IncludeObject("8__chars"));
+        super.setUp();
+    }
+
+    /**
+     * Test writeToStream()
+     * @throws IOException -
+     */
+    public void testWriteToStream() throws IOException {
+        final IncludeObject sut = getSut();
+
+        byte[] expected = defaultIncludeObjectBytes(sut.getTripletDataLength(), sut.getNameBytes());
+
+        testWriteToStreamHelper(sut, expected);
+    }
+
+    /**
+     * Test writeToStream() - the orientation of the referenced object is a right-
+     * handed with a 180 x-axis
+     * @throws IOException -
+     */
+    public void testWriteToStreamForOrientation() throws IOException {
+        final IncludeObject sut = getSut();
+
+        byte[] expected = defaultIncludeObjectBytes(sut.getTripletDataLength(), sut.getNameBytes());
+
+        expected[25] = (byte)0x5A;
+        expected[26] = (byte)0x00;
+        expected[27] = (byte)0x87;
+        expected[28] = (byte)0x00;
+
+        sut.setObjectAreaOrientation(180);
+
+        testWriteToStreamHelper(sut, expected);
+    }
+
+    private void testWriteToStreamHelper(IncludeObject sut, byte[] expected) throws IOException {
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+        sut.writeToStream(baos);
+
+        byte[] actual = baos.toByteArray();
+
+        assertTrue(Arrays.equals(actual, expected));
+    }
+
+    private byte[] defaultIncludeObjectBytes(int tripletDataLength, byte[] nameData) {
+
+        byte[] expected = new byte[36];
+
+        byte[] header = new byte[] {
+                0x5A, // Structured field identifier
+                0x00, // Length byte 1
+                0x10, // Length byte 2
+                (byte)0xD3, // Structured field id byte 1
+                (byte)0xAF, // Structured field id byte 2 - type 'input'
+                (byte)0xC3, // Structured field id byte 3 - category 'data resource'
+                0x00, // Flags
+                0x00, // Reserved
+                0x00, // Reserved
+        };
+
+        System.arraycopy(header, 0, expected, 0, header.length);
+
+        byte[] lengthBytes = BinaryUtils.convert(35 + tripletDataLength, 2); //Ignore first byte
+        expected[1] = lengthBytes[0];
+        expected[2] = lengthBytes[1];
+
+        System.arraycopy(nameData, 0, expected, 9, nameData.length);
+
+        expected[18] = (byte)0x92; // object type 'other'
+
+        expected[27] = (byte)0x2D; // orientation of the reference object
+        writeOsetTo(expected, 29, -1); // the X-axis origin defined in the object
+        writeOsetTo(expected, 32, -1); // the Y-axis origin defined in the object
+
+        expected[35] = 0x01; // Page or overlay coordinate system
+
+        return expected;
+    }
+
+    private static void writeOsetTo(byte[] out, int offset, int oset) {
+        if (oset > -1) {
+            byte[] y = BinaryUtils.convert(oset, 3);
+            out[offset] = y[0];
+            out[offset + 1] = y[1];
+            out[offset + 2] = y[2];
+        } else {
+            out[offset] = (byte)0xFF;
+            out[offset + 1] = (byte)0xFF;
+            out[offset + 2] = (byte)0xFF;
+        }
+    }
+}
\ No newline at end of file