From 4e855eadb11271d9b774cc3b3df60fe595d15251 Mon Sep 17 00:00:00 2001 From: Rainer Klute Date: Sat, 23 Aug 2003 15:12:22 +0000 Subject: [PATCH] - Fixed a bug that occured when reading a section with properties that are not stored with ascending offsets in the property list. - 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 | 64 ++- .../hpsf/ReadingNotSupportedException.java | 27 ++ src/java/org/apache/poi/hpsf/Section.java | 157 +++++--- src/java/org/apache/poi/hpsf/TypeReader.java | 142 ------- src/java/org/apache/poi/hpsf/TypeWriter.java | 219 +++++++++++ .../hpsf/UnsupportedVariantTypeException.java | 123 ++++++ src/java/org/apache/poi/hpsf/Util.java | 6 + src/java/org/apache/poi/hpsf/Variant.java | 59 +++ .../org/apache/poi/hpsf/VariantSupport.java | 370 ++++++++++++++++++ .../hpsf/WritingNotSupportedException.java | 27 ++ 10 files changed, 994 insertions(+), 200 deletions(-) create mode 100644 src/java/org/apache/poi/hpsf/ReadingNotSupportedException.java create mode 100644 src/java/org/apache/poi/hpsf/TypeWriter.java create mode 100644 src/java/org/apache/poi/hpsf/UnsupportedVariantTypeException.java create mode 100644 src/java/org/apache/poi/hpsf/VariantSupport.java create mode 100644 src/java/org/apache/poi/hpsf/WritingNotSupportedException.java diff --git a/src/java/org/apache/poi/hpsf/Property.java b/src/java/org/apache/poi/hpsf/Property.java index 227f69d16..1131d4593 100644 --- a/src/java/org/apache/poi/hpsf/Property.java +++ b/src/java/org/apache/poi/hpsf/Property.java @@ -63,7 +63,10 @@ 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.

*/ 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."); } + + + /** + *

Keeps a list of those variant types for those an "unsupported" message + * has already been issued.

+ */ + 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 index 000000000..5fa7334e3 --- /dev/null +++ b/src/java/org/apache/poi/hpsf/ReadingNotSupportedException.java @@ -0,0 +1,27 @@ +package org.apache.poi.hpsf; + +/** + *

This exception is thrown when trying to read a (yet) unsupported variant + * type.

+ * + * @author Rainer Klute <klute@rainer-klute.de> + * @since 2003-08-08 + * @version $Id$ + */ +public class ReadingNotSupportedException + extends UnsupportedVariantTypeException +{ + + /** + *

Constructor

+ * + * @param variantType + * @param value + */ + public ReadingNotSupportedException(long variantType, Object value) + { + super(variantType, value); + } + +} diff --git a/src/java/org/apache/poi/hpsf/Section.java b/src/java/org/apache/poi/hpsf/Section.java index abeed3a7c..8ec477103 100644 --- a/src/java/org/apache/poi/hpsf/Section.java +++ b/src/java/org/apache/poi/hpsf/Section.java @@ -54,10 +54,15 @@ */ 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; /** *

Represents a section in a {@link PropertySet}.

@@ -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); + } - if (id == PropertyIDMap.PID_CODEPAGE) + /* Sort the property list by ascending offsets: */ + Collections.sort(propertyList); + + /* 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 codepage if the property ID is 1. */ - /* 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 + /** + *

Represents an entry in the property list and holds a property's ID and + * its offset from the section's beginning.

+ */ + class PropertyListEntry implements Comparable + { + int id; + int offset; + int length; + + /** + *

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.

+ * + * @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; + } + } + + + /** *

Returns the value of the property with the specified ID. If * the property is not available, null is returned diff --git a/src/java/org/apache/poi/hpsf/TypeReader.java b/src/java/org/apache/poi/hpsf/TypeReader.java index ed079cc6d..a778da3c5 100644 --- a/src/java/org/apache/poi/hpsf/TypeReader.java +++ b/src/java/org/apache/poi/hpsf/TypeReader.java @@ -62,7 +62,6 @@ */ package org.apache.poi.hpsf; -import org.apache.poi.util.LittleEndian; /** *

Reader for specific data types.

@@ -75,145 +74,4 @@ import org.apache.poi.util.LittleEndian; */ public class TypeReader { - - /** - *

Reads a variant data type from a byte array.

- * - * @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 index 000000000..123253d3b --- /dev/null +++ b/src/java/org/apache/poi/hpsf/TypeWriter.java @@ -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 + * . + * + * 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; + +/** + *

Class for writing little-endian data and more.

+ * + * @author Rainer Klute (klute@rainer-klute.de) + * @version $Id$ + * @since 2003-02-20 + */ +public class TypeWriter +{ + + /** + *

Writes a two-byte value (short) to an output stream.

+ * + * @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; + } + + + + /** + *

Writes a four-byte value to an output stream.

+ * + * @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; + + } + + + + /** + *

Writes an unsigned two-byte value to an output stream.

+ * + * @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); + } + + + + /** + *

Writes an unsigned four-byte value to an output stream.

+ * + * @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); + } + + + + /** + *

Writes a 16-byte {@link ClassID} to an output stream.

+ * + * @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; + } + + + + /** + *

Writes an array of {@link Property} instances to an output stream + * according to the Horrible Property Stream Format.

+ * + * @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 index 000000000..6cad52662 --- /dev/null +++ b/src/java/org/apache/poi/hpsf/UnsupportedVariantTypeException.java @@ -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 + * . + */ +package org.apache.poi.hpsf; + +import org.apache.poi.util.HexDump; + +/** + *

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.

+ * + *

Obviously this class should disappear some day.

+ * + * @author Rainer Klute <klute@rainer-klute.de> + * @since 2003-08-05 + * @version $Id$ + */ +public abstract class UnsupportedVariantTypeException extends HPSFException +{ + + private Object value; + + private long variantType; + + + + /** + *

Constructor.

+ * + * @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 " + + "! Thank you!"); + this.variantType = variantType; + this.value = value; + } + + + + /** + *

Returns the offending variant type.

+ * + * @return the offending variant type. + */ + public long getVariantType() + { + return variantType; + } + + + + /** + *

Return the value who's variant type is not yet supported.

+ * + * @return the value who's variant type is not yet supported + */ + public Object getValue() + { + return value; + } + +} diff --git a/src/java/org/apache/poi/hpsf/Util.java b/src/java/org/apache/poi/hpsf/Util.java index 4a92633d0..b340df047 100644 --- a/src/java/org/apache/poi/hpsf/Util.java +++ b/src/java/org/apache/poi/hpsf/Util.java @@ -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); + } /** diff --git a/src/java/org/apache/poi/hpsf/Variant.java b/src/java/org/apache/poi/hpsf/Variant.java index bb3b39a15..54f41255b 100644 --- a/src/java/org/apache/poi/hpsf/Variant.java +++ b/src/java/org/apache/poi/hpsf/Variant.java @@ -54,6 +54,9 @@ */ package org.apache.poi.hpsf; +import java.util.HashMap; +import java.util.Map; + /** *

The Variant types as defined by Microsoft's COM. I * found this information in . + * + * 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; + +/** + *

Supports reading and writing of variant data.

+ * + *

FIXME: Reading and writing must be made more uniform than + * it is now. The following items should be resolved: + * + *

    + * + *
  • Reading requires a length parameter that is 4 byte greater than the + * actual data, because the variant type field is included.

  • + * + *
  • Reading reads from a byte array while writing writes to an byte array + * output stream.

  • + * + *
      + * + * @author Rainer Klute <klute@rainer-klute.de> + * @since 08.08.2003 + * @version $Id$ + */ +public class VariantSupport extends Variant +{ + + /** + *

      Reads a variant data type from a byte array.

      + * + * @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; + } + + + + /** + *

      Writes a variant value to an output stream.

      + * + * @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); + } + } + } + + + + /** + *

      Converts a string into a 0x00-terminated character sequence padded + * with 0x00 bytes to a multiple of 4.

      + * + * @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 index 000000000..0d8c54227 --- /dev/null +++ b/src/java/org/apache/poi/hpsf/WritingNotSupportedException.java @@ -0,0 +1,27 @@ +package org.apache.poi.hpsf; + +/** + *

      This exception is thrown when trying to write a (yet) unsupported variant + * type.

      + * + * @author Rainer Klute <klute@rainer-klute.de> + * @since 2003-08-08 + * @version $Id$ + */ +public class WritingNotSupportedException + extends UnsupportedVariantTypeException +{ + + /** + *

      Constructor

      + * + * @param variantType + * @param value + */ + public WritingNotSupportedException(long variantType, Object value) + { + super(variantType, value); + } + +}