]> source.dussan.org Git - poi.git/commitdiff
- Fixed a bug that occured when reading a section with properties that are not stored...
authorRainer Klute <klute@apache.org>
Sat, 23 Aug 2003 15:12:22 +0000 (15:12 +0000)
committerRainer Klute <klute@apache.org>
Sat, 23 Aug 2003 15:12:22 +0000 (15:12 +0000)
- An error message is printed to standard error now when an unsupported variant type occurs. The data bytes are still available.

git-svn-id: https://svn.apache.org/repos/asf/jakarta/poi/trunk@353307 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/poi/hpsf/Property.java
src/java/org/apache/poi/hpsf/ReadingNotSupportedException.java [new file with mode: 0644]
src/java/org/apache/poi/hpsf/Section.java
src/java/org/apache/poi/hpsf/TypeReader.java
src/java/org/apache/poi/hpsf/TypeWriter.java [new file with mode: 0644]
src/java/org/apache/poi/hpsf/UnsupportedVariantTypeException.java [new file with mode: 0644]
src/java/org/apache/poi/hpsf/Util.java
src/java/org/apache/poi/hpsf/Variant.java
src/java/org/apache/poi/hpsf/VariantSupport.java [new file with mode: 0644]
src/java/org/apache/poi/hpsf/WritingNotSupportedException.java [new file with mode: 0644]

index 227f69d1634edad55a8fbfd2f8856c181cfb4464..1131d459333ee50250b8cbea9d66e236ee1bd956 100644 (file)
 package org.apache.poi.hpsf;
 
 import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
+
 import org.apache.poi.util.LittleEndian;
 
 /**
@@ -180,12 +183,12 @@ public class Property
 
         try
         {
-            value = TypeReader.read(src, o, length, (int) type);
+            value = VariantSupport.read(src, o, length, (int) type);
         }
-        catch (Throwable t)
+        catch (UnsupportedVariantTypeException ex)
         {
-            t.printStackTrace();
-            value = "*** null ***";
+            logUnsupported(ex);
+            value = ex.getValue();
         }
     }
 
@@ -196,7 +199,7 @@ public class Property
      * be usable.</p>
      */
     protected Property()
-    {}
+    { }
 
 
 
@@ -281,14 +284,61 @@ public class Property
      */
     protected int getSize()
     {
-        throw new UnsupportedOperationException("FIXME: Not yet implemented.");
+        int length = LittleEndian.INT_SIZE;
+        final int PADDING = 4; /* Pad to multiples of 4. */
+        if (type > Integer.MAX_VALUE)
+            throw new HPSFRuntimeException
+                ("Variant type " + type + " is greater than " +
+                Integer.MAX_VALUE + ".");
+        switch ((int) type)
+        {
+            case Variant.VT_LPSTR:
+            {
+                int l = ((String) value).length() + 1;
+                int r = l % PADDING;
+                if (r > 0)
+                    l += PADDING - r;
+                length += l;
+                break;
+            }
+            case Variant.VT_EMPTY:
+                break;
+            default:
+                throw new HPSFRuntimeException
+                    ("Writing is not yet implemented for variant type " +
+                     type + ". Please report this problem to the POI team!");
+        }
+        return length;
     }
 
 
 
-    public boolean equals(Object o)
+    /**
+     * @see Object#equals(java.lang.Object)
+     */
+    public boolean equals(final Object o)
     {
         throw new UnsupportedOperationException("FIXME: Not yet implemented.");
     }
 
+
+
+    /**
+     * <p>Keeps a list of those variant types for those an "unsupported" message
+     * has already been issued.</p>
+     */
+    protected static List unsupportedMessage;
+
+    private static void logUnsupported(final UnsupportedVariantTypeException ex)
+    {
+        if (unsupportedMessage == null)
+            unsupportedMessage = new LinkedList();
+        Long vt = new Long(ex.getVariantType());
+        if (!unsupportedMessage.contains(vt))
+        {
+            System.err.println(ex.getMessage());
+            unsupportedMessage.add(vt);
+        }
+    }
+
 }
diff --git a/src/java/org/apache/poi/hpsf/ReadingNotSupportedException.java b/src/java/org/apache/poi/hpsf/ReadingNotSupportedException.java
new file mode 100644 (file)
index 0000000..5fa7334
--- /dev/null
@@ -0,0 +1,27 @@
+package org.apache.poi.hpsf;
+
+/**
+ * <p>This exception is thrown when trying to read a (yet) unsupported variant
+ * type.</p>
+ *
+ * @author Rainer Klute <a
+ * href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
+ * @since 2003-08-08
+ * @version $Id$
+ */
+public class ReadingNotSupportedException
+    extends UnsupportedVariantTypeException
+{
+
+    /**
+     * <p>Constructor</p>
+     * 
+     * @param variantType
+     * @param value
+     */
+    public ReadingNotSupportedException(long variantType, Object value)
+    {
+        super(variantType, value);
+    }
+
+}
index abeed3a7cdc588eaf5bd45b061f987f0d9e07f1d..8ec4771036683dca74b675f1bf834a784c2a0cca 100644 (file)
  */
 package org.apache.poi.hpsf;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
-import org.apache.poi.util.LittleEndian;
+
 import org.apache.poi.hpsf.wellknown.PropertyIDMap;
 import org.apache.poi.hpsf.wellknown.SectionIDMap;
+import org.apache.poi.util.LittleEndian;
 
 /**
  * <p>Represents a section in a {@link PropertySet}.</p>
@@ -227,50 +232,81 @@ public class Section
 
         /*
          * Read the properties. The offset is positioned at the first
-         * entry of the property list. The problem is that we have to
-         * read the property with ID 1 before we read other
-         * properties, at least before other properties containing
-         * strings. The reason is that property 1 specifies the
-         * codepage. If it is 1200, all strings are in Unicode. In
-         * other words: Before we can read any strings we have to know
-         * whether they are in Unicode or not. Unfortunately property
-         * 1 is not guaranteed to be the first in a section.
+         * entry of the property list. There are two problems:
+         * 
+         * 1. For each property we have to find out its length. In the
+         *    property list we find each property's ID and its offset relative
+         *    to the section's beginning. Unfortunately the properties in the
+         *    property list need not to be in ascending order, so it is not
+         *    possible to calculate the length as
+         *    (offset of property(i+1) - offset of property(i)). Before we can
+         *    that we first have to sort the property list by ascending offsets.
+         * 
+         * 2. We have to read the property with ID 1 before we read other 
+         *    properties, at least before other properties containing strings.
+         *    The reason is that property 1 specifies the codepage. If it is
+         *    1200, all strings are in Unicode. In other words: Before we can
+         *    read any strings we have to know whether they are in Unicode or
+         *    not. Unfortunately property 1 is not guaranteed to be the first in
+         *    a section.
          *
-         * The algorithm below reads the properties in two passes: The
-         * first one looks for property ID 1 and extracts the codepage
-         * number. The seconds pass reads the other properties.
+         *    The algorithm below reads the properties in two passes: The first
+         *    one looks for property ID 1 and extracts the codepage number. The
+         *    seconds pass reads the other properties.
          */
         properties = new Property[propertyCount];
-
-        /* Pass 1: Look for the codepage. */
-        int codepage = -1;
+        
+        /* Pass 1: Read the property list. */
         int pass1Offset = o1;
+        List propertyList = new ArrayList(propertyCount);
+        PropertyListEntry ple;
         for (int i = 0; i < properties.length; i++)
         {
+            ple = new PropertyListEntry();
+
             /* Read the property ID. */
-            final int id = (int) LittleEndian.getUInt(src, pass1Offset);
+            ple.id = (int) LittleEndian.getUInt(src, pass1Offset);
             pass1Offset += LittleEndian.INT_SIZE;
 
             /* Offset from the section's start. */
-            final int sOffset = (int) LittleEndian.getUInt(src, pass1Offset);
+            ple.offset = (int) LittleEndian.getUInt(src, pass1Offset);
             pass1Offset += LittleEndian.INT_SIZE;
 
-            /* Calculate the length of the property. */
-//            int length;
-//            if (i == properties.length - 1)
-//                length = (int) (src.length - this.offset - sOffset);
-//            else
-//                length = (int)
-//                    LittleEndian.getUInt(src, pass1Offset +
-//                                         LittleEndian.INT_SIZE) - sOffset;
+            /* Add the entry to the property list. */
+            propertyList.add(ple);
+        }
+
+        /* Sort the property list by ascending offsets: */
+        Collections.sort(propertyList);
 
-            if (id == PropertyIDMap.PID_CODEPAGE)
-            {
-                /* Read the codepage if the property ID is 1. */
+        /* Calculate the properties' lengths. */
+        for (int i = 0; i < propertyCount - 1; i++)
+        {
+            final PropertyListEntry ple1 =
+                (PropertyListEntry) propertyList.get(i);
+            final PropertyListEntry ple2 =
+                (PropertyListEntry) propertyList.get(i + 1);
+            ple1.length = ple2.offset - ple1.offset;
+        }
+        if (propertyCount > 0)
+        {
+            ple = (PropertyListEntry) propertyList.get(propertyCount - 1);
+            ple.length = size - ple.offset;
+        }
 
+        /* Look for the codepage. */
+        int codepage = -1;
+        for (final Iterator i = propertyList.iterator();
+             codepage == -1 && i.hasNext();)
+        {
+            ple = (PropertyListEntry) i.next();
+
+            /* Read the codepage if the property ID is 1. */
+            if (ple.id == PropertyIDMap.PID_CODEPAGE)
+            {
                 /* Read the property's value type. It must be
                  * VT_I2. */
-                int o = (int) (this.offset + sOffset);
+                int o = (int) (this.offset + ple.offset);
                 final long type = LittleEndian.getUInt(src, o);
                 o += LittleEndian.INT_SIZE;
 
@@ -284,29 +320,15 @@ public class Section
             }
         }
 
-        /* Pass 2: Read all properties, including 1. */
-        for (int i = 0; i < properties.length; i++)
+        /* Pass 2: Read all properties - including the codepage property,
+         * if available. */
+        int i1 = 0;
+        for (final Iterator i = propertyList.iterator(); i.hasNext();)
         {
-            /* Read the property ID. */
-            final int id = (int) LittleEndian.getUInt(src, o1);
-            o1 += LittleEndian.INT_SIZE;
-
-            /* Offset from the section. */
-            final int sOffset = (int) LittleEndian.getUInt(src, o1);
-            o1 += LittleEndian.INT_SIZE;
-
-            /* Calculate the length of the property. */
-            int length;
-            if (i == properties.length - 1)
-                length = (int) (src.length - this.offset - sOffset);
-            else
-                length = (int)
-                    LittleEndian.getUInt(src, o1 + LittleEndian.INT_SIZE) -
-                    sOffset;
-
-            /* Create it. */
-            properties[i] = new Property(id, src, this.offset + sOffset,
-                                         length, codepage);
+            ple = (PropertyListEntry) i.next();
+            properties[i1++] = new Property(ple.id, src,
+                                            this.offset + ple.offset,
+                                            ple.length, codepage);
         }
 
         /*
@@ -317,6 +339,39 @@ public class Section
 
 
 
+    /**
+     * <p>Represents an entry in the property list and holds a property's ID and
+     * its offset from the section's beginning.</p>
+     */
+    class PropertyListEntry implements Comparable
+    {
+        int id;
+        int offset;
+        int length;
+
+        /**
+         * <p>Compares this {@link PropertyListEntry} with another one by their
+         * offsets. A {@link PropertyListEntry} is "smaller" than another one if
+         * its offset from the section's begin is smaller.</p>
+         *
+         * @see Comparable#compareTo(java.lang.Object)
+         */
+        public int compareTo(final Object o)
+        {
+            if (!(o instanceof PropertyListEntry))
+                throw new ClassCastException(o.toString());
+            final int otherOffset = ((PropertyListEntry) o).offset;
+            if (offset < otherOffset)
+                return -1;
+            else if (offset == otherOffset)
+                return 0;
+            else
+                return 1;
+        }
+    }
+
+
+
     /**
      * <p>Returns the value of the property with the specified ID. If
      * the property is not available, <code>null</code> is returned
index ed079cc6da5a8482f8810eef025b129a3c051046..a778da3c5dc740b82743fc0b9be2f6d1bb2f5679 100644 (file)
@@ -62,7 +62,6 @@
  */
 package org.apache.poi.hpsf;
 
-import org.apache.poi.util.LittleEndian;
 
 /**
  * <p>Reader for specific data types.</p>
@@ -75,145 +74,4 @@ import org.apache.poi.util.LittleEndian;
  */
 public class TypeReader
 {
-
-    /**
-     * <p>Reads a variant data type from a byte array.</p>
-     *
-     * @param src The byte array
-     * @param offset The offset in the byte array where the variant
-     * starts
-     * @param length The length of the variant including the variant
-     * type field
-     * @param type The variant type to read
-     * @return A Java object that corresponds best to the variant
-     * field. For example, a VT_I4 is returned as a {@link Long}, a
-     * VT_LPSTR as a {@link String}.
-     *
-     * @see Variant
-     */
-    public static Object read(final byte[] src, final int offset,
-                              final int length, final int type)
-    {
-        /*
-         * FIXME: Support reading more types and clean up this code!
-         */
-        Object value;
-        int o1 = offset;
-        int l1 = length - LittleEndian.INT_SIZE;
-        switch (type)
-        {
-            case Variant.VT_EMPTY:
-            {
-                value = null;
-                break;
-            }
-            case Variant.VT_I2:
-            {
-                /*
-                 * Read a short. In Java it is represented as an
-                 * Integer object.
-                 */
-                value = new Integer(LittleEndian.getUShort(src, o1));
-                break;
-            }
-            case Variant.VT_I4:
-            {
-                /*
-                 * Read a word. In Java it is represented as a
-                 * Long object.
-                 */
-                value = new Long(LittleEndian.getUInt(src, o1));
-                break;
-            }
-            case Variant.VT_FILETIME:
-            {
-                /*
-                 * Read a FILETIME object. In Java it is represented
-                 * as a Date object.
-                 */
-                final long low = LittleEndian.getUInt(src, o1);
-                o1 += LittleEndian.INT_SIZE;
-                final long high = LittleEndian.getUInt(src, o1);
-                value = Util.filetimeToDate((int) high, (int) low);
-                break;
-            }
-            case Variant.VT_LPSTR:
-            {
-                /*
-                 * Read a byte string. In Java it is represented as a
-                 * String object. The 0x00 bytes at the end must be
-                 * stripped.
-                 *
-                 * FIXME: Reading an 8-bit string should pay attention
-                 * to the codepage. Currently the byte making out the
-                 * property's value are interpreted according to the
-                 * platform's default character set.
-                 */
-                final int first = o1 + LittleEndian.INT_SIZE;
-                long last = first + LittleEndian.getUInt(src, o1) - 1;
-                o1 += LittleEndian.INT_SIZE;
-                while (src[(int) last] == 0 && first <= last)
-                    last--;
-                value = new String(src, (int) first, (int) (last - first + 1));
-                break;
-            }
-            case Variant.VT_LPWSTR:
-            {
-                /*
-                 * Read a Unicode string. In Java it is represented as
-                 * a String object. The 0x00 bytes at the end must be
-                 * stripped.
-                 */
-                final int first = o1 + LittleEndian.INT_SIZE;
-                long last = first + LittleEndian.getUInt(src, o1) - 1;
-                long l = last - first;
-                o1 += LittleEndian.INT_SIZE;
-                StringBuffer b = new StringBuffer((int) (last - first));
-                for (int i = 0; i <= l; i++)
-                {
-                    final int i1 = o1 + (i * 2);
-                    final int i2 = i1 + 1;
-                    b.append((char) ((src[i2] << 8) + src[i1]));
-                }
-                /* Strip 0x00 characters from the end of the string: */
-                while (b.charAt(b.length() - 1) == 0x00)
-                    b.setLength(b.length() - 1);
-                value = b.toString();
-                break;
-            }
-            case Variant.VT_CF:
-            {
-                final byte[] v = new byte[l1];
-                for (int i = 0; i < l1; i++)
-                    v[i] = src[(int) (o1 + i)];
-                value = v;
-                break;
-            }
-            case Variant.VT_BOOL:
-            {
-                /*
-                 * The first four bytes in src, from src[offset] to
-                 * src[offset + 3] contain the DWord for VT_BOOL, so
-                 * skip it, we don't need it.
-                 */
-                // final int first = offset + LittleEndian.INT_SIZE;
-                long bool = LittleEndian.getUInt(src, o1);
-                if (bool != 0)
-                    value = new Boolean(true);
-                else
-                    value = new Boolean(false);
-                break;
-            }
-            default:
-            {
-                final byte[] v = new byte[l1];
-                for (int i = 0; i < l1; i++)
-                    v[i] = src[(int) (o1 + i)];
-                value = v;
-                break;
-            }
-        }
-        return value;
-    }
-
 }
diff --git a/src/java/org/apache/poi/hpsf/TypeWriter.java b/src/java/org/apache/poi/hpsf/TypeWriter.java
new file mode 100644 (file)
index 0000000..123253d
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ *  ====================================================================
+ *  The Apache Software License, Version 1.1
+ *
+ *  Copyright (c) 2003 The Apache Software Foundation.  All rights
+ *  reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions
+ *  are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright
+ *  notice, this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in
+ *  the documentation and/or other materials provided with the
+ *  distribution.
+ *
+ *  3. The end-user documentation included with the redistribution,
+ *  if any, must include the following acknowledgment:
+ *  "This product includes software developed by the
+ *  Apache Software Foundation (http://www.apache.org/)."
+ *  Alternately, this acknowledgment may appear in the software itself,
+ *  if and wherever such third-party acknowledgments normally appear.
+ *
+ *  4. The names "Apache" and "Apache Software Foundation" must
+ *  not be used to endorse or promote products derived from this
+ *  software without prior written permission. For written
+ *  permission, please contact apache@apache.org.
+ *
+ *  5. Products derived from this software may not be called "Apache",
+ *  nor may "Apache" appear in their name, without prior written
+ *  permission of the Apache Software Foundation.
+ *
+ *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ *  DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ *  ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ *  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ *  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ *  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ *  SUCH DAMAGE.
+ *  ====================================================================
+ *
+ *  This software consists of voluntary contributions made by many
+ *  individuals on behalf of the Apache Software Foundation.  For more
+ *  information on the Apache Software Foundation, please see
+ *  <http://www.apache.org/>.
+ *
+ *  Portions of this software are based upon public domain software
+ *  originally written at the National Center for Supercomputing Applications,
+ *  University of Illinois, Urbana-Champaign.
+ *
+ *  Portions of this software are based upon public domain software
+ *  originally written at the National Center for Supercomputing Applications,
+ *  University of Illinois, Urbana-Champaign.
+ */
+package org.apache.poi.hpsf;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianConsts;
+
+/**
+ * <p>Class for writing little-endian data and more.</p>
+ *
+ * @author Rainer Klute (klute@rainer-klute.de)
+ * @version $Id$
+ * @since 2003-02-20
+ */
+public class TypeWriter
+{
+
+    /**
+     * <p>Writes a two-byte value (short) to an output stream.</p>
+     *
+     * @param out The stream to write to
+     * @param n The value to write
+     * @exception IOException if an I/O error occurs
+     */
+    public static int writeToStream(final OutputStream out, final short n)
+        throws IOException
+    {
+        final int length = LittleEndian.SHORT_SIZE;
+        byte[] buffer = new byte[length];
+        LittleEndian.putUShort(buffer, 0, n);
+        out.write(buffer, 0, length);
+        return length;
+    }
+
+
+
+    /**
+     * <p>Writes a four-byte value to an output stream.</p>
+     *
+     * @param out The stream to write to.
+     * @param n The value to write.
+     * @exception IOException if an I/O error occurs
+     * @return The number of bytes written to the output stream. 
+     */
+    public static int writeToStream(final OutputStream out, final int n)
+        throws IOException
+    {
+        final int l = LittleEndian.INT_SIZE;
+        final byte[] buffer = new byte[l];
+        LittleEndian.putInt(buffer, 0, n);
+        out.write(buffer, 0, l);
+        return l;
+        
+    }
+
+
+
+    /**
+     * <p>Writes an unsigned two-byte value to an output stream.</p>
+     *
+     * @param out The stream to write to
+     * @param n The value to write
+     * @exception IOException if an I/O error occurs
+     */
+    public static void writeUShortToStream(final OutputStream out, final int n)
+        throws IOException
+    {
+        int high = n & 0xFFFF0000;
+        if (high != 0)
+            throw new IllegalPropertySetDataException
+                ("Value " + n + " cannot be represented by 2 bytes.");
+        writeToStream(out, (short) n);
+    }
+
+
+
+    /**
+     * <p>Writes an unsigned four-byte value to an output stream.</p>
+     *
+     * @param out The stream to write to.
+     * @param n The value to write.
+     * @return The number of bytes that have been written to the output stream.
+     * @exception IOException if an I/O error occurs
+     */
+    public static int writeUIntToStream(final OutputStream out, final long n)
+        throws IOException
+    {
+        long high = n & 0xFFFFFFFF00000000L;
+        if (high != 0)
+            throw new IllegalPropertySetDataException
+                ("Value " + n + " cannot be represented by 4 bytes.");
+        return writeToStream(out, (int) n);
+    }
+
+
+
+    /**
+     * <p>Writes a 16-byte {@link ClassID} to an output stream.</p>
+     *
+     * @param out The stream to write to
+     * @param n The value to write
+     * @exception IOException if an I/O error occurs
+     */
+    public static int writeToStream(final OutputStream out, final ClassID n)
+        throws IOException
+    {
+        byte[] b = new byte[16];
+        n.write(b, 0);
+        out.write(b, 0, b.length);
+        return b.length;
+    }
+
+
+
+    /**
+     * <p>Writes an array of {@link Property} instances to an output stream
+     * according to the Horrible Property Stream Format.</p>
+     *
+     * @param out The stream to write to
+     * @param properties The array to write to the stream
+     * @exception IOException if an I/O error occurs
+     */
+    public static void writeToStream(final OutputStream out,
+                                     final Property[] properties)
+        throws IOException, UnsupportedVariantTypeException
+    {
+        /* If there are no properties don't write anything. */
+        if (properties == null)
+            return;
+
+        /* Write the property list. This is a list containing pairs of property
+         * ID and offset into the stream. */
+        for (int i = 0; i < properties.length; i++)
+        {
+            final Property p = (Property) properties[i];
+            writeUIntToStream(out, p.getID());
+            writeUIntToStream(out, p.getSize());
+        }
+
+        /* Write the properties themselves. */
+        for (int i = 0; i < properties.length; i++)
+        {
+            final Property p = (Property) properties[i];
+            long type = p.getType();
+            writeUIntToStream(out, type);
+            VariantSupport.write(out, (int) type, p.getValue());
+        }
+    }
+
+
+
+
+
+
+
+}
diff --git a/src/java/org/apache/poi/hpsf/UnsupportedVariantTypeException.java b/src/java/org/apache/poi/hpsf/UnsupportedVariantTypeException.java
new file mode 100644 (file)
index 0000000..6cad526
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ *  ====================================================================
+ *  The Apache Software License, Version 1.1
+ *
+ *  Copyright (c) 2000 The Apache Software Foundation.  All rights
+ *  reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions
+ *  are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright
+ *  notice, this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in
+ *  the documentation and/or other materials provided with the
+ *  distribution.
+ *
+ *  3. The end-user documentation included with the redistribution,
+ *  if any, must include the following acknowledgment:
+ *  "This product includes software developed by the
+ *  Apache Software Foundation (http://www.apache.org/)."
+ *  Alternately, this acknowledgment may appear in the software itself,
+ *  if and wherever such third-party acknowledgments normally appear.
+ *
+ *  4. The names "Apache" and "Apache Software Foundation" must
+ *  not be used to endorse or promote products derived from this
+ *  software without prior written permission. For written
+ *  permission, please contact apache@apache.org.
+ *
+ *  5. Products derived from this software may not be called "Apache",
+ *  nor may "Apache" appear in their name, without prior written
+ *  permission of the Apache Software Foundation.
+ *
+ *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ *  DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ *  ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ *  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ *  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ *  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ *  SUCH DAMAGE.
+ *  ====================================================================
+ *
+ *  This software consists of voluntary contributions made by many
+ *  individuals on behalf of the Apache Software Foundation.  For more
+ *  information on the Apache Software Foundation, please see
+ *  <http://www.apache.org/>.
+ */
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.HexDump;
+
+/**
+ * <p>This exception is thrown if HPSF encounters a variant type that isn't
+ * supported yet. Although a variant type is unsupported the value can still be
+ * retrieved using the {@link #getValue} method.</p>
+ * 
+ * <p>Obviously this class should disappear some day.</p>
+ *
+ * @author Rainer Klute <a
+ * href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
+ * @since 2003-08-05
+ * @version $Id$
+ */
+public abstract class UnsupportedVariantTypeException extends HPSFException
+{
+
+    private Object value;
+
+    private long variantType;
+
+
+
+    /**
+     * <p>Constructor.</p>
+     * 
+     * @param variantType The unsupported variant type
+     * @param value The value who's variant type is not yet supported
+     */
+    public UnsupportedVariantTypeException(final long variantType,
+                                           final Object value)
+    {
+        super("HPSF does not yet support the variant type " + variantType + 
+              " (" + Variant.getVariantName(variantType) + ", " +
+              HexDump.toHex((int) variantType) + "). If you want support for " +
+              "this variant type in one of the next POI releases please " +
+              "submit a request for enhancement (RFE) to " +
+              "<http://nagoya.apache.org/bugzilla/>! Thank you!");
+        this.variantType = variantType;
+        this.value = value;
+    }
+
+
+
+    /**
+     * <p>Returns the offending variant type.</p>
+     *
+     * @return the offending variant type.
+     */
+    public long getVariantType()
+    {
+        return variantType;
+    }
+
+
+
+    /**
+     * <p>Return the value who's variant type is not yet supported.</p>
+     *
+     * @return the value who's variant type is not yet supported
+     */
+    public Object getValue()
+    {
+        return value;
+    }
+
+}
index 4a92633d0b09a5bf72cfb3a7858380b89b6b04da..b340df047914115ab24102b9980efa0b7866a232 100644 (file)
@@ -191,6 +191,12 @@ public class Util
         return new Date(ms_since_19700101);
     }
 
+    public static long dateToFileTime(final Date date)
+    {
+        long ms_since_19700101 = date.getTime();
+        long ms_since_16010101 = ms_since_19700101 + EPOCH_DIFF;
+        return ms_since_16010101 * (1000 * 10);
+    }
 
 
     /**
index bb3b39a15b0d17d2e233c3d25d511c7374797f6d..54f41255b2196bda666582d3c5b5118a7829f2c4 100644 (file)
@@ -54,6 +54,9 @@
  */
 package org.apache.poi.hpsf;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * <p>The <em>Variant</em> types as defined by Microsoft's COM. I
  * found this information in <a
@@ -373,4 +376,60 @@ public class Variant
      */
     public static final int VT_TYPEMASK = 0xFFF;
 
+
+
+    public static final Map m = new HashMap();
+
+    static
+    {
+        m.put(new Integer(0), "VT_EMPTY");
+        m.put(new Integer(1), "VT_NULL");
+        m.put(new Integer(2), "VT_I2");
+        m.put(new Integer(3), "VT_I4");
+        m.put(new Integer(4), "VT_R4");
+        m.put(new Integer(5), "VT_R8");
+        m.put(new Integer(6), "VT_CY");
+        m.put(new Integer(7), "VT_DATE");
+        m.put(new Integer(8), "VT_BSTR");
+        m.put(new Integer(9), "VT_DISPATCH");
+        m.put(new Integer(10), "VT_ERROR");
+        m.put(new Integer(11), "VT_BOOL");
+        m.put(new Integer(12), "VT_VARIANT");
+        m.put(new Integer(13), "VT_UNKNOWN");
+        m.put(new Integer(14), "VT_DECIMAL");
+        m.put(new Integer(16), "VT_I1");
+        m.put(new Integer(17), "VT_UI1");
+        m.put(new Integer(18), "VT_UI2");
+        m.put(new Integer(19), "VT_UI4");
+        m.put(new Integer(20), "VT_I8");
+        m.put(new Integer(21), "VT_UI8");
+        m.put(new Integer(22), "VT_INT");
+        m.put(new Integer(23), "VT_UINT");
+        m.put(new Integer(24), "VT_VOID");
+        m.put(new Integer(25), "VT_HRESULT");
+        m.put(new Integer(26), "VT_PTR");
+        m.put(new Integer(27), "VT_SAFEARRAY");
+        m.put(new Integer(28), "VT_CARRAY");
+        m.put(new Integer(29), "VT_USERDEFINED");
+        m.put(new Integer(30), "VT_LPSTR");
+        m.put(new Integer(31), "VT_LPWSTR");
+        m.put(new Integer(64), "VT_FILETIME");
+        m.put(new Integer(65), "VT_BLOB");
+        m.put(new Integer(66), "VT_STREAM");
+        m.put(new Integer(67), "VT_STORAGE");
+        m.put(new Integer(68), "VT_STREAMED_OBJECT");
+        m.put(new Integer(69), "VT_STORED_OBJECT");
+        m.put(new Integer(70), "VT_BLOB_OBJECT");
+        m.put(new Integer(71), "VT_CF");
+        m.put(new Integer(72), "VT_CLSID");
+    }
+
+
+
+    public static String getVariantName(final long variantType)
+    {
+        String name = (String) m.get(new Integer((int) variantType));
+        return name != null ? name : "unknown variant type";
+    }
+
 }
\ No newline at end of file
diff --git a/src/java/org/apache/poi/hpsf/VariantSupport.java b/src/java/org/apache/poi/hpsf/VariantSupport.java
new file mode 100644 (file)
index 0000000..f67eec4
--- /dev/null
@@ -0,0 +1,370 @@
+/*
+ *  ====================================================================
+ *  The Apache Software License, Version 1.1
+ *
+ *  Copyright (c) 2000 The Apache Software Foundation.  All rights
+ *  reserved.
+ *
+ *  Redistribution and use in source and binary forms, with or without
+ *  modification, are permitted provided that the following conditions
+ *  are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright
+ *  notice, this list of conditions and the following disclaimer.
+ *
+ *  2. Redistributions in binary form must reproduce the above copyright
+ *  notice, this list of conditions and the following disclaimer in
+ *  the documentation and/or other materials provided with the
+ *  distribution.
+ *
+ *  3. The end-user documentation included with the redistribution,
+ *  if any, must include the following acknowledgment:
+ *  "This product includes software developed by the
+ *  Apache Software Foundation (http://www.apache.org/)."
+ *  Alternately, this acknowledgment may appear in the software itself,
+ *  if and wherever such third-party acknowledgments normally appear.
+ *
+ *  4. The names "Apache" and "Apache Software Foundation" must
+ *  not be used to endorse or promote products derived from this
+ *  software without prior written permission. For written
+ *  permission, please contact apache@apache.org.
+ *
+ *  5. Products derived from this software may not be called "Apache",
+ *  nor may "Apache" appear in their name, without prior written
+ *  permission of the Apache Software Foundation.
+ *
+ *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ *  DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ *  ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ *  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ *  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ *  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ *  SUCH DAMAGE.
+ *  ====================================================================
+ *
+ *  This software consists of voluntary contributions made by many
+ *  individuals on behalf of the Apache Software Foundation.  For more
+ *  information on the Apache Software Foundation, please see
+ *  <http://www.apache.org/>.
+ *
+ *  Portions of this software are based upon public domain software
+ *  originally written at the National Center for Supercomputing Applications,
+ *  University of Illinois, Urbana-Champaign.
+ *
+ *  Portions of this software are based upon public domain software
+ *  originally written at the National Center for Supercomputing Applications,
+ *  University of Illinois, Urbana-Champaign.
+ */
+package org.apache.poi.hpsf;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Date;
+
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianConsts;
+
+/**
+ * <p>Supports reading and writing of variant data.</p>
+ * 
+ * <p><strong>FIXME:</strong> Reading and writing must be made more uniform than
+ * it is now. The following items should be resolved:
+ * 
+ * <ul>
+ *
+ * <li><p>Reading requires a length parameter that is 4 byte greater than the
+ * actual data, because the variant type field is included. </p></li>
+ *
+ * <li><p>Reading reads from a byte array while writing writes to an byte array
+ * output stream.</p></li>
+ *
+ * <ul>
+ *
+ * @author Rainer Klute <a
+ * href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
+ * @since 08.08.2003
+ * @version $Id$
+ */
+public class VariantSupport extends Variant
+{
+
+    /**
+     * <p>Reads a variant data type from a byte array.</p>
+     *
+     * @param src The byte array
+     * @param offset The offset in the byte array where the variant
+     * starts
+     * @param length The length of the variant including the variant
+     * type field
+     * @param type The variant type to read
+     * @return A Java object that corresponds best to the variant
+     * field. For example, a VT_I4 is returned as a {@link Long}, a
+     * VT_LPSTR as a {@link String}.
+     * @exception UnsupportedVariantTypeException if HPSF does not (yet)
+     * support the variant type which is to be read 
+     *
+     * @see Variant
+     */
+    public static Object read(final byte[] src, final int offset,
+                              final int length, final long type)
+        throws ReadingNotSupportedException
+    {
+        Object value;
+        int o1 = offset;
+        int l1 = length - LittleEndian.INT_SIZE;
+        switch ((int) type)
+        {
+            case Variant.VT_EMPTY:
+            {
+                value = null;
+                break;
+            }
+            case Variant.VT_I2:
+            {
+                /*
+                 * Read a short. In Java it is represented as an
+                 * Integer object.
+                 */
+                value = new Integer(LittleEndian.getUShort(src, o1));
+                break;
+            }
+            case Variant.VT_I4:
+            {
+                /*
+                 * Read a word. In Java it is represented as a
+                 * Long object.
+                 */
+                value = new Long(LittleEndian.getUInt(src, o1));
+                break;
+            }
+            case Variant.VT_FILETIME:
+            {
+                /*
+                 * Read a FILETIME object. In Java it is represented
+                 * as a Date object.
+                 */
+                final long low = LittleEndian.getUInt(src, o1);
+                o1 += LittleEndian.INT_SIZE;
+                final long high = LittleEndian.getUInt(src, o1);
+                value = Util.filetimeToDate((int) high, (int) low);
+                break;
+            }
+            case Variant.VT_LPSTR:
+            {
+                /*
+                 * Read a byte string. In Java it is represented as a
+                 * String object. The 0x00 bytes at the end must be
+                 * stripped.
+                 *
+                 * FIXME: Reading an 8-bit string should pay attention
+                 * to the codepage. Currently the byte making out the
+                 * property's value are interpreted according to the
+                 * platform's default character set.
+                 */
+                final int first = o1 + LittleEndian.INT_SIZE;
+                long last = first + LittleEndian.getUInt(src, o1) - 1;
+                o1 += LittleEndian.INT_SIZE;
+                while (src[(int) last] == 0 && first <= last)
+                    last--;
+                value = new String(src, (int) first, (int) (last - first + 1));
+                break;
+            }
+            case Variant.VT_LPWSTR:
+            {
+                /*
+                 * Read a Unicode string. In Java it is represented as
+                 * a String object. The 0x00 bytes at the end must be
+                 * stripped.
+                 */
+                final int first = o1 + LittleEndian.INT_SIZE;
+                long last = first + LittleEndian.getUInt(src, o1) - 1;
+                long l = last - first;
+                o1 += LittleEndian.INT_SIZE;
+                StringBuffer b = new StringBuffer((int) (last - first));
+                for (int i = 0; i <= l; i++)
+                {
+                    final int i1 = o1 + (i * 2);
+                    final int i2 = i1 + 1;
+                    final int high = src[i2] << 8;
+                    final int low = src[i1] & 0xff;
+                    final char c = (char) (high | low);
+                    b.append(c);
+                }
+                /* Strip 0x00 characters from the end of the string: */
+                while (b.length() > 0 && b.charAt(b.length() - 1) == 0x00)
+                    b.setLength(b.length() - 1);
+                value = b.toString();
+                break;
+            }
+            case Variant.VT_CF:
+            {
+                final byte[] v = new byte[l1];
+                for (int i = 0; i < l1; i++)
+                    v[i] = src[(int) (o1 + i)];
+                value = v;
+                break;
+            }
+            case Variant.VT_BOOL:
+            {
+                /*
+                 * The first four bytes in src, from src[offset] to
+                 * src[offset + 3] contain the DWord for VT_BOOL, so
+                 * skip it, we don't need it.
+                 */
+                // final int first = offset + LittleEndian.INT_SIZE;
+                long bool = LittleEndian.getUInt(src, o1);
+                if (bool != 0)
+                    value = new Boolean(true);
+                else
+                    value = new Boolean(false);
+                break;
+            }
+            default:
+            {
+                final byte[] v = new byte[l1];
+                for (int i = 0; i < l1; i++)
+                    v[i] = src[(int) (o1 + i)];
+                throw new ReadingNotSupportedException(type, v);
+            }
+        }
+        return value;
+    }
+
+
+
+    /**
+     * <p>Writes a variant value to an output stream.</p>
+     *
+     * @param out The stream to write the value to.
+     * @param type The variant's type.
+     * @param value The variant's value.
+     * @return The number of entities that have been written. In many cases an
+     * "entity" is a byte but this is not always the case.
+     */
+    public static int write(final OutputStream out, final long type,
+                               final Object value)
+        throws IOException, WritingNotSupportedException
+    {
+        switch ((int) type)
+        {
+            case Variant.VT_BOOL:
+            {
+                int trueOrFalse;
+                int length = 0;
+                if (((Boolean) value).booleanValue())
+                    trueOrFalse = 1;
+                else
+                    trueOrFalse = 0;
+                length += TypeWriter.writeUIntToStream(out, trueOrFalse);
+                return length;
+            }
+            case Variant.VT_LPSTR:
+            {
+                TypeWriter.writeUIntToStream
+                    (out, ((String) value).length() + 1);
+                char[] s = toPaddedCharArray((String) value);
+                /* FIXME: The following line forces characters to bytes. This
+                 * is generally wrong and should only be done according to a
+                 * codepage. Alternatively Unicode could be written (see 
+                 * Variant.VT_LPWSTR). */
+                byte[] b = new byte[s.length];
+                for (int i = 0; i < s.length; i++)
+                    b[i] = (byte) s[i];
+                out.write(b);
+                return b.length;
+            }
+            case Variant.VT_LPWSTR:
+            {
+                final int nrOfChars = ((String) value).length() + 1; 
+                TypeWriter.writeUIntToStream(out, nrOfChars);
+                char[] s = toPaddedCharArray((String) value);
+                for (int i = 0; i < s.length; i++)
+                {
+                    final int high = (int) ((s[i] & 0xff00) >> 8);
+                    final int low = (int) (s[i] & 0x00ff);
+                    final byte highb = (byte) high;
+                    final byte lowb = (byte) low;
+                    out.write(lowb);
+                    out.write(highb);
+                }
+                return nrOfChars * 2;
+            }
+            case Variant.VT_CF:
+            {
+                final byte[] b = (byte[]) value; 
+                out.write(b);
+                return b.length;
+            }
+            case Variant.VT_EMPTY:
+            {
+                TypeWriter.writeUIntToStream(out, Variant.VT_EMPTY);
+                return LittleEndianConsts.INT_SIZE;
+            }
+            case Variant.VT_I2:
+            {
+                TypeWriter.writeToStream(out, ((Integer) value).shortValue());
+                return LittleEndianConsts.SHORT_SIZE;
+            }
+            case Variant.VT_I4:
+            {
+                TypeWriter.writeToStream(out, ((Long) value).intValue());
+                return LittleEndianConsts.INT_SIZE;
+            }
+            case Variant.VT_FILETIME:
+            {
+                int length = 0;
+                long filetime = Util.dateToFileTime((Date) value);
+                int high = (int) ((filetime >> 32) & 0xFFFFFFFFL);
+                int low = (int) (filetime & 0x00000000FFFFFFFFL);
+                length += TypeWriter.writeUIntToStream(out, 0x0000000FFFFFFFFL & low);
+                length += TypeWriter.writeUIntToStream(out, 0x0000000FFFFFFFFL & high);
+                return length;
+            }
+            default:
+            {
+                throw new WritingNotSupportedException(type, value);
+             }
+        }
+    }
+
+
+
+    /**
+     * <p>Converts a string into a 0x00-terminated character sequence padded 
+     * with 0x00 bytes to a multiple of 4.</p>
+     *
+     * @param value The string to convert
+     * @return The padded character array
+     */
+    private static char[] toPaddedCharArray(final String s)
+    {
+        final int PADDING = 4;
+        int dl = s.length() + 1;
+        final int r = dl % 4;
+        if (r > 0)
+            dl += PADDING - r;
+        char[] buffer = new char[dl];
+        s.getChars(0, s.length(), buffer, 0);
+        for (int i = s.length(); i < dl; i++)
+            buffer[i] = (char) 0;
+        return buffer;
+    }
+
+
+
+    public static int getLength(final long variantType, final int lengthInBytes)
+    {
+        switch ((int) variantType)
+        {
+            case VT_LPWSTR:
+                return lengthInBytes / 2;
+            default:
+                return lengthInBytes;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/poi/hpsf/WritingNotSupportedException.java b/src/java/org/apache/poi/hpsf/WritingNotSupportedException.java
new file mode 100644 (file)
index 0000000..0d8c542
--- /dev/null
@@ -0,0 +1,27 @@
+package org.apache.poi.hpsf;
+
+/**
+ * <p>This exception is thrown when trying to write a (yet) unsupported variant
+ * type.</p>
+ *
+ * @author Rainer Klute <a
+ * href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
+ * @since 2003-08-08
+ * @version $Id$
+ */
+public class WritingNotSupportedException
+    extends UnsupportedVariantTypeException
+{
+
+    /**
+     * <p>Constructor</p>
+     * 
+     * @param variantType
+     * @param value
+     */
+    public WritingNotSupportedException(long variantType, Object value)
+    {
+        super(variantType, value);
+    }
+
+}