FileDocCategorySizeDatePackage
MutableSection.javaAPI DocApache Poi 3.0.122624Mon Jan 01 12:39:34 GMT 2007org.apache.poi.hpsf

MutableSection

public class MutableSection extends Section

Adds writing capability to the {@link Section} class.

Please be aware that this class' functionality will be merged into the {@link Section} class at a later time, so the API will change.

version
$Id: MutableSection.java 489730 2006-12-22 19:18:16Z bayard $
since
2002-02-20

Fields Summary
private boolean
dirty

If the "dirty" flag is true, the section's size must be (re-)calculated before the section is written.

private List
preprops

List to assemble the properties. Unfortunately a wrong decision has been taken when specifying the "properties" field as an Property[]. It should have been a {@link java.util.List}.

private byte[]
sectionBytes

Contains the bytes making out the section. This byte array is established when the section's size is calculated and can be reused later. It is valid only if the "dirty" flag is false.

Constructors Summary
public MutableSection()

Creates an empty mutable section.




              
     
    
        dirty = true;
        formatID = null;
        offset = -1;
        preprops = new LinkedList();
    
public MutableSection(Section s)

Constructs a MutableSection by doing a deep copy of an existing Section. All nested Property instances, will be their mutable counterparts in the new MutableSection.

param
s The section set to copy

        setFormatID(s.getFormatID());
        final Property[] pa = s.getProperties();
        final MutableProperty[] mpa = new MutableProperty[pa.length];
        for (int i = 0; i < pa.length; i++)
            mpa[i] = new MutableProperty(pa[i]);
        setProperties(mpa);
        setDictionary(s.getDictionary());
    
Methods Summary
private intcalcSize()

Calculates the section's size. It is the sum of the lengths of the section's header (8), the properties list (16 times the number of properties) and the properties themselves.

return
the section's length in bytes.
throws
WritingNotSupportedException
throws
IOException

        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        write(out);
        out.close();
        /* Pad to multiple of 4 bytes so that even the Windows shell (explorer)
         * shows custom properties. */
        sectionBytes = Util.pad4(out.toByteArray());
        return sectionBytes.length;
    
public voidclear()

Removes all properties from the section including 0 (dictionary) and 1 (codepage).

        final Property[] properties = getProperties();
        for (int i = 0; i < properties.length; i++)
        {
            final Property p = properties[i];
            removeProperty(p.getID());
        }
    
public org.apache.poi.hpsf.Property[]getProperties()

Gets this section's properties.

return
this section's properties.

        properties = (Property[]) preprops.toArray(new Property[0]);
        return properties;
    
public java.lang.ObjectgetProperty(long id)

Gets a property.

param
id The ID of the property to get
return
The property or null if there is no such property

        /* Calling getProperties() ensures that properties and preprops are in
         * sync.</p> */
        getProperties();
        return super.getProperty(id);
    
public intgetPropertyCount()

Overwrites the super class' method to cope with a redundancy: the property count is maintained in a separate member variable, but shouldn't.

return
The number of properties in this section

        return preprops.size();
    
public intgetSize()

Returns the section's size.

return
the section's size.

        if (dirty)
        {
            try
            {
                size = calcSize();
                dirty = false;
            }
            catch (HPSFRuntimeException ex)
            {
                throw ex;
            }
            catch (Exception ex)
            {
                throw new HPSFRuntimeException(ex);
            }
        }
        return size;
    
public voidremoveProperty(long id)

Removes a property.

param
id The ID of the property to be removed

        for (final Iterator i = preprops.iterator(); i.hasNext();)
            if (((Property) i.next()).getID() == id)
            {
                i.remove();
                break;
            }
        dirty = true;
    
public voidsetCodepage(int codepage)

Sets the codepage.

param
codepage the codepage

        setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
                new Integer(codepage));
    
public voidsetDictionary(java.util.Map dictionary)

Sets the section's dictionary. All keys in the dictionary must be {@link java.lang.Long} instances, all values must be {@link java.lang.String}s. This method overwrites the properties with IDs 0 and 1 since they are reserved for the dictionary and the dictionary's codepage. Setting these properties explicitly might have surprising effects. An application should never do this but always use this method.

param
dictionary The dictionary
exception
IllegalPropertySetDataException if the dictionary's key and value types are not correct.
see
Section#getDictionary()

        if (dictionary != null)
        {
            for (final Iterator i = dictionary.keySet().iterator();
                 i.hasNext();)
                if (!(i.next() instanceof Long))
                    throw new IllegalPropertySetDataException
                        ("Dictionary keys must be of type Long.");
            for (final Iterator i = dictionary.values().iterator();
                 i.hasNext();)
                if (!(i.next() instanceof String))
                    throw new IllegalPropertySetDataException
                        ("Dictionary values must be of type String.");
            this.dictionary = dictionary;

            /* Set the dictionary property (ID 0). Please note that the second
             * parameter in the method call below is unused because dictionaries
             * don't have a type. */
            setProperty(PropertyIDMap.PID_DICTIONARY, -1, dictionary);

            /* If the codepage property (ID 1) for the strings (keys and
             * values) used in the dictionary is not yet defined, set it to
             * Unicode. */
            final Integer codepage =
                (Integer) getProperty(PropertyIDMap.PID_CODEPAGE);
            if (codepage == null)
                setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
                            new Integer(Constants.CP_UNICODE));
        }
        else
            /* Setting the dictionary to null means to remove property 0.
             * However, it does not mean to remove property 1 (codepage). */
            removeProperty(PropertyIDMap.PID_DICTIONARY);
    
public voidsetFormatID(org.apache.poi.hpsf.ClassID formatID)

Sets the section's format ID.

param
formatID The section's format ID
see
#setFormatID(byte[])
see
Section#getFormatID

        this.formatID = formatID;
    
public voidsetFormatID(byte[] formatID)

Sets the section's format ID.

param
formatID The section's format ID as a byte array. It components are in big-endian format.
see
#setFormatID(ClassID)
see
Section#getFormatID

        ClassID fid = getFormatID();
        if (fid == null)
        {
            fid = new ClassID();
            setFormatID(fid);
        }
        fid.setBytes(formatID);
    
public voidsetProperties(org.apache.poi.hpsf.Property[] properties)

Sets this section's properties. Any former values are overwritten.

param
properties This section's new properties.

        this.properties = properties;
        preprops = new LinkedList();
        for (int i = 0; i < properties.length; i++)
            preprops.add(properties[i]);
        dirty = true;
    
public voidsetProperty(int id, long variantType, java.lang.Object value)

Sets the value and the variant type of the property with the specified ID. If a property with this ID is not yet present in the section, it will be added. An already present property with the specified ID will be overwritten. A default mapping will be used to choose the property's type.

param
id The property's ID.
param
variantType The property's variant type.
param
value The property's value.
see
#setProperty(int, String)
see
#getProperty
see
Variant

        final MutableProperty p = new MutableProperty();
        p.setID(id);
        p.setType(variantType);
        p.setValue(value);
        setProperty(p);
        dirty = true;
    
public voidsetProperty(org.apache.poi.hpsf.Property p)

Sets a property.

param
p The property to be set.
see
#setProperty(int, long, Object)
see
#getProperty
see
Variant

        final long id = p.getID();
        removeProperty(id);
        preprops.add(p);
        dirty = true;
    
public voidsetProperty(int id, java.lang.Object value)

Sets a property.

param
id The property ID.
param
value The property's value. The value's class must be one of those supported by HPSF.

        if (value instanceof String)
            setProperty(id, (String) value);
        else if (value instanceof Long)
            setProperty(id, ((Long) value).longValue());
        else if (value instanceof Integer)
            setProperty(id, ((Integer) value).intValue());
        else if (value instanceof Short)
            setProperty(id, ((Short) value).intValue());
        else if (value instanceof Boolean)
            setProperty(id, ((Boolean) value).booleanValue());
        else if (value instanceof Date)
            setProperty(id, Variant.VT_FILETIME, value);
        else
            throw new HPSFRuntimeException(
                    "HPSF does not support properties of type " +
                    value.getClass().getName() + ".");
    
public voidsetProperty(int id, java.lang.String value)

Sets the string value of the property with the specified ID.

param
id The property's ID
param
value The property's value. It will be written as a Unicode string.
see
#setProperty(int, long, Object)
see
#getProperty

        setProperty(id, Variant.VT_LPWSTR, value);
        dirty = true;
    
public voidsetProperty(int id, int value)

Sets the int value of the property with the specified ID.

param
id The property's ID
param
value The property's value.
see
#setProperty(int, long, Object)
see
#getProperty

        setProperty(id, Variant.VT_I4, new Integer(value));
        dirty = true;
    
public voidsetProperty(int id, long value)

Sets the long value of the property with the specified ID.

param
id The property's ID
param
value The property's value.
see
#setProperty(int, long, Object)
see
#getProperty

        setProperty(id, Variant.VT_I8, new Long(value));
        dirty = true;
    
public voidsetProperty(int id, boolean value)

Sets the boolean value of the property with the specified ID.

param
id The property's ID
param
value The property's value.
see
#setProperty(int, long, Object)
see
#getProperty

        setProperty(id, Variant.VT_BOOL, new Boolean(value));
        dirty = true;
    
protected voidsetPropertyBooleanValue(int id, boolean value)

Sets the value of the boolean property with the specified ID.

param
id The property's ID
param
value The property's value
see
#setProperty(int, long, Object)
see
#getProperty
see
Variant

        setProperty(id, Variant.VT_BOOL, new Boolean(value));
    
public intwrite(java.io.OutputStream out)

Writes this section into an output stream.

Internally this is done by writing into three byte array output streams: one for the properties, one for the property list and one for the section as such. The two former are appended to the latter when they have received all their data.

param
out The stream to write into.
return
The number of bytes written, i.e. the section's size.
exception
IOException if an I/O error occurs
exception
WritingNotSupportedException if HPSF does not yet support writing a property's variant type.

        /* Check whether we have already generated the bytes making out the
         * section. */
        if (!dirty && sectionBytes != null)
        {
            out.write(sectionBytes);
            return sectionBytes.length;
        }

        /* The properties are written to this stream. */
        final ByteArrayOutputStream propertyStream =
            new ByteArrayOutputStream();

        /* The property list is established here. After each property that has
         * been written to "propertyStream", a property list entry is written to
         * "propertyListStream". */
        final ByteArrayOutputStream propertyListStream =
            new ByteArrayOutputStream();
 
        /* Maintain the current position in the list. */
        int position = 0;

        /* Increase the position variable by the size of the property list so
         * that it points behind the property list and to the beginning of the
         * properties themselves. */
        position += 2 * LittleEndian.INT_SIZE +
                    getPropertyCount() * 2 * LittleEndian.INT_SIZE;

        /* Writing the section's dictionary it tricky. If there is a dictionary
         * (property 0) the codepage property (property 1) must be set, too. */
        int codepage = -1;
        if (getProperty(PropertyIDMap.PID_DICTIONARY) != null)
        {
            final Object p1 = getProperty(PropertyIDMap.PID_CODEPAGE);
            if (p1 != null)
            {
                if (!(p1 instanceof Integer))
                    throw new IllegalPropertySetDataException
                        ("The codepage property (ID = 1) must be an " +
                         "Integer object.");
            }
            else
                /* Warning: The codepage property is not set although a
                 * dictionary is present. In order to cope with this problem we
                 * add the codepage property and set it to Unicode. */
                setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
                            new Integer(Constants.CP_UNICODE));
            codepage = getCodepage();
        }

        /* Sort the property list by their property IDs: */
        Collections.sort(preprops, new Comparator()
            {
                public int compare(final Object o1, final Object o2)
                {
                    final Property p1 = (Property) o1;
                    final Property p2 = (Property) o2;
                    if (p1.getID() < p2.getID())
                        return -1;
                    else if (p1.getID() == p2.getID())
                        return 0;
                    else
                        return 1;
                }
            });

        /* Write the properties and the property list into their respective
         * streams: */
        for (final ListIterator i = preprops.listIterator(); i.hasNext();)
        {
            final MutableProperty p = (MutableProperty) i.next();
            final long id = p.getID();
            
            /* Write the property list entry. */
            TypeWriter.writeUIntToStream(propertyListStream, p.getID());
            TypeWriter.writeUIntToStream(propertyListStream, position);

            /* If the property ID is not equal 0 we write the property and all
             * is fine. However, if it equals 0 we have to write the section's
             * dictionary which has an implicit type only and an explicit
             * value. */
            if (id != 0)
                /* Write the property and update the position to the next
                 * property. */
                position += p.write(propertyStream, getCodepage());
            else
            {
                if (codepage == -1)
                    throw new IllegalPropertySetDataException
                        ("Codepage (property 1) is undefined.");
                position += writeDictionary(propertyStream, dictionary,
                                            codepage);
            }
        }
        propertyStream.close();
        propertyListStream.close();

        /* Write the section: */
        byte[] pb1 = propertyListStream.toByteArray();
        byte[] pb2 = propertyStream.toByteArray();
        
        /* Write the section's length: */
        TypeWriter.writeToStream(out, LittleEndian.INT_SIZE * 2 +
                                      pb1.length + pb2.length);
        
        /* Write the section's number of properties: */
        TypeWriter.writeToStream(out, getPropertyCount());
        
        /* Write the property list: */
        out.write(pb1);
        
        /* Write the properties: */
        out.write(pb2);

        int streamLength = LittleEndian.INT_SIZE * 2 + pb1.length + pb2.length;
        return streamLength;
    
private static intwriteDictionary(java.io.OutputStream out, java.util.Map dictionary, int codepage)

Writes the section's dictionary.

param
out The output stream to write to.
param
dictionary The dictionary.
param
codepage The codepage to be used to write the dictionary items.
return
The number of bytes written
exception
IOException if an I/O exception occurs.

        int length = TypeWriter.writeUIntToStream(out, dictionary.size());
        for (final Iterator i = dictionary.keySet().iterator(); i.hasNext();)
        {
            final Long key = (Long) i.next();
            final String value = (String) dictionary.get(key);

            if (codepage == Constants.CP_UNICODE)
            {
                /* Write the dictionary item in Unicode. */
                int sLength = value.length() + 1;
                if (sLength % 2 == 1)
                    sLength++;
                length += TypeWriter.writeUIntToStream(out, key.longValue());
                length += TypeWriter.writeUIntToStream(out, sLength);
                final byte[] ca =
                    value.getBytes(VariantSupport.codepageToEncoding(codepage));
                for (int j = 2; j < ca.length; j += 2)
                {
                    out.write(ca[j+1]);
                    out.write(ca[j]);
                    length += 2;
                }
                sLength -= value.length();
                while (sLength > 0)
                {
                    out.write(0x00);
                    out.write(0x00);
                    length += 2;
                    sLength--;
                }
            }
            else
            {
                /* Write the dictionary item in another codepage than
                 * Unicode. */
                length += TypeWriter.writeUIntToStream(out, key.longValue());
                length += TypeWriter.writeUIntToStream(out, value.length() + 1);
                final byte[] ba =
                    value.getBytes(VariantSupport.codepageToEncoding(codepage));
                for (int j = 0; j < ba.length; j++)
                {
                    out.write(ba[j]);
                    length++;
                }
                out.write(0x00);
                length++;
            }
        }
        return length;