896 lines
31 KiB
Java
896 lines
31 KiB
Java
/* ====================================================================
|
|
Licensed to the Apache Software Foundation (ASF) under one or more
|
|
contributor license agreements. See the NOTICE file distributed with
|
|
this work for additional information regarding copyright ownership.
|
|
The ASF licenses this file to You under the Apache License, Version 2.0
|
|
(the "License"); you may not use this file except in compliance with
|
|
the License. You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
==================================================================== */
|
|
|
|
package org.apache.poi.hpsf;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.io.UnsupportedEncodingException;
|
|
import java.nio.charset.Charset;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
|
|
import org.apache.poi.EmptyFileException;
|
|
import org.apache.poi.hpsf.wellknown.PropertyIDMap;
|
|
import org.apache.poi.hpsf.wellknown.SectionIDMap;
|
|
import org.apache.poi.poifs.filesystem.DirectoryEntry;
|
|
import org.apache.poi.poifs.filesystem.Entry;
|
|
import org.apache.poi.util.IOUtils;
|
|
import org.apache.poi.util.LittleEndian;
|
|
import org.apache.poi.util.LittleEndianConsts;
|
|
import org.apache.poi.util.NotImplemented;
|
|
|
|
/**
|
|
* Represents a property set in the Horrible Property Set Format
|
|
* (HPSF). These are usually metadata of a Microsoft Office
|
|
* document.<p>
|
|
*
|
|
* An application that wants to access these metadata should create
|
|
* an instance of this class or one of its subclasses by calling the
|
|
* factory method {@link PropertySetFactory#create} and then retrieve
|
|
* the information its needs by calling appropriate methods.<p>
|
|
*
|
|
* {@link PropertySetFactory#create} does its work by calling one
|
|
* of the constructors {@link PropertySet#PropertySet(InputStream)} or
|
|
* {@link PropertySet#PropertySet(byte[])}. If the constructor's
|
|
* argument is not in the Horrible Property Set Format, i.e. not a
|
|
* property set stream, or if any other error occurs, an appropriate
|
|
* exception is thrown.<p>
|
|
*
|
|
* A {@link PropertySet} has a list of {@link Section}s, and each
|
|
* {@link Section} has a {@link Property} array. Use {@link
|
|
* #getSections} to retrieve the {@link Section}s, then call {@link
|
|
* Section#getProperties} for each {@link Section} to get hold of the
|
|
* {@link Property} arrays.<p>
|
|
*
|
|
* Since the vast majority of {@link PropertySet}s contains only a single
|
|
* {@link Section}, the convenience method {@link #getProperties} returns
|
|
* the properties of a {@link PropertySet}'s {@link Section} (throwing a
|
|
* {@link NoSingleSectionException} if the {@link PropertySet} contains
|
|
* more (or less) than exactly one {@link Section}).
|
|
*/
|
|
public class PropertySet {
|
|
/**
|
|
* If the OS version field holds this value the property set stream was
|
|
* created on a 16-bit Windows system.
|
|
*/
|
|
public static final int OS_WIN16 = 0x0000;
|
|
|
|
/**
|
|
* If the OS version field holds this value the property set stream was
|
|
* created on a Macintosh system.
|
|
*/
|
|
public static final int OS_MACINTOSH = 0x0001;
|
|
|
|
/**
|
|
* If the OS version field holds this value the property set stream was
|
|
* created on a 32-bit Windows system.
|
|
*/
|
|
public static final int OS_WIN32 = 0x0002;
|
|
|
|
/**
|
|
* The "byteOrder" field must equal this value.
|
|
*/
|
|
private static final int BYTE_ORDER_ASSERTION = 0xFFFE;
|
|
|
|
/**
|
|
* The "format" field must equal this value.
|
|
*/
|
|
private static final int FORMAT_ASSERTION = 0x0000;
|
|
|
|
/**
|
|
* The length of the property set stream header.
|
|
*/
|
|
private static final int OFFSET_HEADER =
|
|
LittleEndianConsts.SHORT_SIZE + /* Byte order */
|
|
LittleEndianConsts.SHORT_SIZE + /* Format */
|
|
LittleEndianConsts.INT_SIZE + /* OS version */
|
|
ClassID.LENGTH + /* Class ID */
|
|
LittleEndianConsts.INT_SIZE; /* Section count */
|
|
|
|
|
|
/**
|
|
* Specifies this {@link PropertySet}'s byte order. See the
|
|
* HPFS documentation for details!
|
|
*/
|
|
private int byteOrder;
|
|
|
|
/**
|
|
* Specifies this {@link PropertySet}'s format. See the HPFS
|
|
* documentation for details!
|
|
*/
|
|
private int format;
|
|
|
|
/**
|
|
* Specifies the version of the operating system that created this
|
|
* {@link PropertySet}. See the HPFS documentation for details!
|
|
*/
|
|
private int osVersion;
|
|
|
|
/**
|
|
* Specifies this {@link PropertySet}'s "classID" field. See
|
|
* the HPFS documentation for details!
|
|
*/
|
|
private ClassID classID;
|
|
|
|
/**
|
|
* The sections in this {@link PropertySet}.
|
|
*/
|
|
private final List<Section> sections = new LinkedList<Section>();
|
|
|
|
|
|
/**
|
|
* Constructs a {@code PropertySet} instance. Its
|
|
* primary task is to initialize the field with their proper values.
|
|
* It also sets fields that might change to reasonable defaults.
|
|
*/
|
|
public PropertySet() {
|
|
/* Initialize the "byteOrder" field. */
|
|
byteOrder = BYTE_ORDER_ASSERTION;
|
|
|
|
/* Initialize the "format" field. */
|
|
format = FORMAT_ASSERTION;
|
|
|
|
/* Initialize "osVersion" field as if the property has been created on
|
|
* a Win32 platform, whether this is the case or not. */
|
|
osVersion = (OS_WIN32 << 16) | 0x0A04;
|
|
|
|
/* Initialize the "classID" field. */
|
|
classID = new ClassID();
|
|
|
|
/* Initialize the sections. Since property set must have at least
|
|
* one section it is added right here. */
|
|
addSection(new MutableSection());
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Creates a {@link PropertySet} instance from an {@link
|
|
* InputStream} in the Horrible Property Set Format.<p>
|
|
*
|
|
* The constructor reads the first few bytes from the stream
|
|
* and determines whether it is really a property set stream. If
|
|
* it is, it parses the rest of the stream. If it is not, it
|
|
* resets the stream to its beginning in order to let other
|
|
* components mess around with the data and throws an
|
|
* exception.
|
|
*
|
|
* @param stream Holds the data making out the property set
|
|
* stream.
|
|
* @throws MarkUnsupportedException
|
|
* if the stream does not support the {@link InputStream#markSupported} method.
|
|
* @throws IOException
|
|
* if the {@link InputStream} cannot be accessed as needed.
|
|
* @exception NoPropertySetStreamException
|
|
* if the input stream does not contain a property set.
|
|
* @exception UnsupportedEncodingException
|
|
* if a character encoding is not supported.
|
|
*/
|
|
public PropertySet(final InputStream stream)
|
|
throws NoPropertySetStreamException, MarkUnsupportedException,
|
|
IOException, UnsupportedEncodingException {
|
|
if (!isPropertySetStream(stream)) {
|
|
throw new NoPropertySetStreamException();
|
|
}
|
|
|
|
final byte[] buffer = IOUtils.toByteArray(stream);
|
|
init(buffer, 0, buffer.length);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Creates a {@link PropertySet} instance from a byte array that
|
|
* represents a stream in the Horrible Property Set Format.
|
|
*
|
|
* @param stream The byte array holding the stream data.
|
|
* @param offset The offset in {@code stream} where the stream
|
|
* data begin. If the stream data begin with the first byte in the
|
|
* array, the {@code offset} is 0.
|
|
* @param length The length of the stream data.
|
|
* @throws NoPropertySetStreamException if the byte array is not a
|
|
* property set stream.
|
|
*
|
|
* @exception UnsupportedEncodingException if the codepage is not supported.
|
|
*/
|
|
public PropertySet(final byte[] stream, final int offset, final int length)
|
|
throws NoPropertySetStreamException, UnsupportedEncodingException {
|
|
if (!isPropertySetStream(stream, offset, length)) {
|
|
throw new NoPropertySetStreamException();
|
|
}
|
|
init(stream, offset, length);
|
|
}
|
|
|
|
/**
|
|
* Creates a {@link PropertySet} instance from a byte array
|
|
* that represents a stream in the Horrible Property Set Format.
|
|
*
|
|
* @param stream The byte array holding the stream data. The
|
|
* complete byte array contents is the stream data.
|
|
* @throws NoPropertySetStreamException if the byte array is not a
|
|
* property set stream.
|
|
*
|
|
* @exception UnsupportedEncodingException if the codepage is not supported.
|
|
*/
|
|
public PropertySet(final byte[] stream)
|
|
throws NoPropertySetStreamException, UnsupportedEncodingException {
|
|
this(stream, 0, stream.length);
|
|
}
|
|
|
|
/**
|
|
* Constructs a {@code PropertySet} by doing a deep copy of
|
|
* an existing {@code PropertySet}. All nested elements, i.e.
|
|
* {@code Section}s and {@code Property} instances, will be their
|
|
* counterparts in the new {@code PropertySet}.
|
|
*
|
|
* @param ps The property set to copy
|
|
*/
|
|
public PropertySet(PropertySet ps) {
|
|
setByteOrder(ps.getByteOrder());
|
|
setFormat(ps.getFormat());
|
|
setOSVersion(ps.getOSVersion());
|
|
setClassID(ps.getClassID());
|
|
for (final Section section : ps.getSections()) {
|
|
sections.add(new MutableSection(section));
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @return The property set stream's low-level "byte order" field. It is always {@code 0xFFFE}.
|
|
*/
|
|
public int getByteOrder() {
|
|
return byteOrder;
|
|
}
|
|
|
|
/**
|
|
* Returns the property set stream's low-level "byte order" field.
|
|
*
|
|
* @param byteOrder The property set stream's low-level "byte order" field.
|
|
*/
|
|
public void setByteOrder(int byteOrder) {
|
|
this.byteOrder = byteOrder;
|
|
}
|
|
|
|
/**
|
|
* @return The property set stream's low-level "format" field. It is always {@code 0x0000}.
|
|
*/
|
|
public int getFormat() {
|
|
return format;
|
|
}
|
|
|
|
/**
|
|
* Sets the property set stream's low-level "format" field.
|
|
*
|
|
* @param format The property set stream's low-level "format" field.
|
|
*/
|
|
public void setFormat(int format) {
|
|
this.format = format;
|
|
}
|
|
|
|
/**
|
|
* @return The property set stream's low-level "OS version" field.
|
|
*/
|
|
public int getOSVersion() {
|
|
return osVersion;
|
|
}
|
|
|
|
/**
|
|
* Sets the property set stream's low-level "OS version" field.
|
|
*
|
|
* @param osVersion The property set stream's low-level "OS version" field.
|
|
*/
|
|
public void setOSVersion(int osVersion) {
|
|
this.osVersion = osVersion;
|
|
}
|
|
|
|
|
|
/**
|
|
* @return The property set stream's low-level "class ID" field.
|
|
*/
|
|
public ClassID getClassID() {
|
|
return classID;
|
|
}
|
|
|
|
/**
|
|
* Sets the property set stream's low-level "class ID" field.
|
|
*
|
|
* @param classID The property set stream's low-level "class ID" field.
|
|
*/
|
|
public void setClassID(ClassID classID) {
|
|
this.classID = classID;
|
|
}
|
|
|
|
/**
|
|
* @return The number of {@link Section}s in the property set.
|
|
*/
|
|
public int getSectionCount() {
|
|
return sections.size();
|
|
}
|
|
|
|
/**
|
|
* @return The unmodifiable list of {@link Section}s in the property set.
|
|
*/
|
|
public List<Section> getSections() {
|
|
return Collections.unmodifiableList(sections);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Adds a section to this property set.
|
|
*
|
|
* @param section The {@link Section} to add. It will be appended
|
|
* after any sections that are already present in the property set
|
|
* and thus become the last section.
|
|
*/
|
|
public void addSection(final Section section) {
|
|
sections.add(section);
|
|
}
|
|
|
|
/**
|
|
* Removes all sections from this property set.
|
|
*/
|
|
public void clearSections() {
|
|
sections.clear();
|
|
}
|
|
|
|
/**
|
|
* The id to name mapping of the properties in this set.
|
|
*
|
|
* @return the id to name mapping of the properties in this set or {@code null} if not applicable
|
|
*/
|
|
public PropertyIDMap getPropertySetIDMap() {
|
|
return null;
|
|
}
|
|
|
|
|
|
/**
|
|
* Checks whether an {@link InputStream} is in the Horrible
|
|
* Property Set Format.
|
|
*
|
|
* @param stream The {@link InputStream} to check. In order to
|
|
* perform the check, the method reads the first bytes from the
|
|
* stream. After reading, the stream is reset to the position it
|
|
* had before reading. The {@link InputStream} must support the
|
|
* {@link InputStream#mark} method.
|
|
* @return {@code true} if the stream is a property set
|
|
* stream, else {@code false}.
|
|
* @throws MarkUnsupportedException if the {@link InputStream}
|
|
* does not support the {@link InputStream#mark} method.
|
|
* @exception IOException if an I/O error occurs
|
|
*/
|
|
public static boolean isPropertySetStream(final InputStream stream)
|
|
throws MarkUnsupportedException, IOException {
|
|
/*
|
|
* Read at most this many bytes.
|
|
*/
|
|
final int BUFFER_SIZE = 50;
|
|
|
|
/*
|
|
* Read a couple of bytes from the stream.
|
|
*/
|
|
try {
|
|
final byte[] buffer = IOUtils.peekFirstNBytes(stream, BUFFER_SIZE);
|
|
final boolean isPropertySetStream = isPropertySetStream(buffer, 0, buffer.length);
|
|
return isPropertySetStream;
|
|
} catch (EmptyFileException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Checks whether a byte array is in the Horrible Property Set Format.
|
|
*
|
|
* @param src The byte array to check.
|
|
* @param offset The offset in the byte array.
|
|
* @param length The significant number of bytes in the byte
|
|
* array. Only this number of bytes will be checked.
|
|
* @return {@code true} if the byte array is a property set
|
|
* stream, {@code false} if not.
|
|
*/
|
|
public static boolean isPropertySetStream(final byte[] src, final int offset, final int length) {
|
|
/* FIXME (3): Ensure that at most "length" bytes are read. */
|
|
|
|
/*
|
|
* Read the header fields of the stream. They must always be
|
|
* there.
|
|
*/
|
|
int o = offset;
|
|
final int byteOrder = LittleEndian.getUShort(src, o);
|
|
o += LittleEndianConsts.SHORT_SIZE;
|
|
if (byteOrder != BYTE_ORDER_ASSERTION) {
|
|
return false;
|
|
}
|
|
final int format = LittleEndian.getUShort(src, o);
|
|
o += LittleEndianConsts.SHORT_SIZE;
|
|
if (format != FORMAT_ASSERTION) {
|
|
return false;
|
|
}
|
|
// final long osVersion = LittleEndian.getUInt(src, offset);
|
|
o += LittleEndianConsts.INT_SIZE;
|
|
// final ClassID classID = new ClassID(src, offset);
|
|
o += ClassID.LENGTH;
|
|
final long sectionCount = LittleEndian.getUInt(src, o);
|
|
return (sectionCount >= 0);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Initializes this {@link PropertySet} instance from a byte
|
|
* array. The method assumes that it has been checked already that
|
|
* the byte array indeed represents a property set stream. It does
|
|
* no more checks on its own.
|
|
*
|
|
* @param src Byte array containing the property set stream
|
|
* @param offset The property set stream starts at this offset
|
|
* from the beginning of {@code src}
|
|
* @param length Length of the property set stream.
|
|
* @throws UnsupportedEncodingException if HPSF does not (yet) support the
|
|
* property set's character encoding.
|
|
*/
|
|
private void init(final byte[] src, final int offset, final int length)
|
|
throws UnsupportedEncodingException {
|
|
/* FIXME (3): Ensure that at most "length" bytes are read. */
|
|
|
|
/*
|
|
* Read the stream's header fields.
|
|
*/
|
|
int o = offset;
|
|
byteOrder = LittleEndian.getUShort(src, o);
|
|
o += LittleEndianConsts.SHORT_SIZE;
|
|
format = LittleEndian.getUShort(src, o);
|
|
o += LittleEndianConsts.SHORT_SIZE;
|
|
osVersion = (int) LittleEndian.getUInt(src, o);
|
|
o += LittleEndianConsts.INT_SIZE;
|
|
classID = new ClassID(src, o);
|
|
o += ClassID.LENGTH;
|
|
final int sectionCount = LittleEndian.getInt(src, o);
|
|
o += LittleEndianConsts.INT_SIZE;
|
|
if (sectionCount < 0) {
|
|
throw new HPSFRuntimeException("Section count " + sectionCount + " is negative.");
|
|
}
|
|
|
|
/*
|
|
* Read the sections, which are following the header. They
|
|
* start with an array of section descriptions. Each one
|
|
* consists of a format ID telling what the section contains
|
|
* and an offset telling how many bytes from the start of the
|
|
* stream the section begins.
|
|
*
|
|
* Most property sets have only one section. The Document
|
|
* Summary Information stream has 2. Everything else is a rare
|
|
* exception and is no longer fostered by Microsoft.
|
|
*/
|
|
|
|
/*
|
|
* Loop over the section descriptor array. Each descriptor
|
|
* consists of a ClassID and a DWord, and we have to increment
|
|
* "offset" accordingly.
|
|
*/
|
|
for (int i = 0; i < sectionCount; i++) {
|
|
final Section s = new MutableSection(src, o);
|
|
o += ClassID.LENGTH + LittleEndianConsts.INT_SIZE;
|
|
sections.add(s);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Writes the property set to an output stream.
|
|
*
|
|
* @param out the output stream to write the section to
|
|
* @exception IOException if an error when writing to the output stream
|
|
* occurs
|
|
* @exception WritingNotSupportedException if HPSF does not yet support
|
|
* writing a property's variant type.
|
|
*/
|
|
public void write(final OutputStream out)
|
|
throws WritingNotSupportedException, IOException {
|
|
/* Write the number of sections in this property set stream. */
|
|
final int nrSections = getSectionCount();
|
|
|
|
/* Write the property set's header. */
|
|
TypeWriter.writeToStream(out, (short) getByteOrder());
|
|
TypeWriter.writeToStream(out, (short) getFormat());
|
|
TypeWriter.writeToStream(out, getOSVersion());
|
|
TypeWriter.writeToStream(out, getClassID());
|
|
TypeWriter.writeToStream(out, nrSections);
|
|
int offset = OFFSET_HEADER;
|
|
|
|
/* Write the section list, i.e. the references to the sections. Each
|
|
* entry in the section list consist of the section's class ID and the
|
|
* section's offset relative to the beginning of the stream. */
|
|
offset += nrSections * (ClassID.LENGTH + LittleEndianConsts.INT_SIZE);
|
|
final int sectionsBegin = offset;
|
|
for (final Section section : getSections()) {
|
|
final ClassID formatID = section.getFormatID();
|
|
if (formatID == null) {
|
|
throw new NoFormatIDException();
|
|
}
|
|
TypeWriter.writeToStream(out, section.getFormatID());
|
|
TypeWriter.writeUIntToStream(out, offset);
|
|
try {
|
|
offset += section.getSize();
|
|
} catch (HPSFRuntimeException ex) {
|
|
final Throwable cause = ex.getReason();
|
|
if (cause instanceof UnsupportedEncodingException) {
|
|
throw new IllegalPropertySetDataException(cause);
|
|
}
|
|
throw ex;
|
|
}
|
|
}
|
|
|
|
/* Write the sections themselves. */
|
|
offset = sectionsBegin;
|
|
for (final Section section : getSections()) {
|
|
offset += section.write(out);
|
|
}
|
|
|
|
/* Indicate that we're done */
|
|
out.close();
|
|
}
|
|
|
|
/**
|
|
* Writes a property set to a document in a POI filesystem directory.
|
|
*
|
|
* @param dir The directory in the POI filesystem to write the document to.
|
|
* @param name The document's name. If there is already a document with the
|
|
* same name in the directory the latter will be overwritten.
|
|
*
|
|
* @throws WritingNotSupportedException if the filesystem doesn't support writing
|
|
* @throws IOException if the old entry can't be deleted or the new entry be written
|
|
*/
|
|
public void write(final DirectoryEntry dir, final String name)
|
|
throws WritingNotSupportedException, IOException {
|
|
/* If there is already an entry with the same name, remove it. */
|
|
if (dir.hasEntry(name)) {
|
|
final Entry e = dir.getEntry(name);
|
|
e.delete();
|
|
}
|
|
|
|
/* Create the new entry. */
|
|
dir.createDocument(name, toInputStream());
|
|
}
|
|
|
|
/**
|
|
* Returns the contents of this property set stream as an input stream.
|
|
* The latter can be used for example to write the property set into a POIFS
|
|
* document. The input stream represents a snapshot of the property set.
|
|
* If the latter is modified while the input stream is still being
|
|
* read, the modifications will not be reflected in the input stream but in
|
|
* the {@link MutablePropertySet} only.
|
|
*
|
|
* @return the contents of this property set stream
|
|
*
|
|
* @throws WritingNotSupportedException if HPSF does not yet support writing
|
|
* of a property's variant type.
|
|
* @throws IOException if an I/O exception occurs.
|
|
*/
|
|
public InputStream toInputStream() throws IOException, WritingNotSupportedException {
|
|
final ByteArrayOutputStream psStream = new ByteArrayOutputStream();
|
|
try {
|
|
write(psStream);
|
|
} finally {
|
|
psStream.close();
|
|
}
|
|
final byte[] streamData = psStream.toByteArray();
|
|
return new ByteArrayInputStream(streamData);
|
|
}
|
|
|
|
/**
|
|
* Fetches the property with the given ID, then does its
|
|
* best to return it as a String
|
|
*
|
|
* @param propertyId the property id
|
|
*
|
|
* @return The property as a String, or null if unavailable
|
|
*/
|
|
protected String getPropertyStringValue(final int propertyId) {
|
|
Object propertyValue = getProperty(propertyId);
|
|
return getPropertyStringValue(propertyValue);
|
|
}
|
|
|
|
/**
|
|
* Return the string representation of a property value
|
|
*
|
|
* @param propertyValue the property value
|
|
*
|
|
* @return The property value as a String, or null if unavailable
|
|
*/
|
|
public static String getPropertyStringValue(final Object propertyValue) {
|
|
// Normal cases
|
|
if (propertyValue == null) {
|
|
return null;
|
|
}
|
|
if (propertyValue instanceof String) {
|
|
return (String)propertyValue;
|
|
}
|
|
|
|
// Do our best with some edge cases
|
|
if (propertyValue instanceof byte[]) {
|
|
byte[] b = (byte[])propertyValue;
|
|
switch (b.length) {
|
|
case 0:
|
|
return "";
|
|
case 1:
|
|
return Byte.toString(b[0]);
|
|
case 2:
|
|
return Integer.toString( LittleEndian.getUShort(b) );
|
|
case 4:
|
|
return Long.toString( LittleEndian.getUInt(b) );
|
|
default:
|
|
// Maybe it's a string? who knows!
|
|
return new String(b, Charset.forName("ASCII"));
|
|
}
|
|
}
|
|
return propertyValue.toString();
|
|
}
|
|
|
|
/**
|
|
* Checks whether this {@link PropertySet} represents a Summary Information.
|
|
*
|
|
* @return {@code true} if this {@link PropertySet}
|
|
* represents a Summary Information, else {@code false}.
|
|
*/
|
|
public boolean isSummaryInformation() {
|
|
return matchesSummary(SectionIDMap.SUMMARY_INFORMATION_ID);
|
|
}
|
|
|
|
/**
|
|
* Checks whether this {@link PropertySet} is a Document Summary Information.
|
|
*
|
|
* @return {@code true} if this {@link PropertySet}
|
|
* represents a Document Summary Information, else {@code false}.
|
|
*/
|
|
public boolean isDocumentSummaryInformation() {
|
|
return matchesSummary(SectionIDMap.DOCUMENT_SUMMARY_INFORMATION_ID[0]);
|
|
}
|
|
|
|
private boolean matchesSummary(byte[] summaryBytes) {
|
|
return !sections.isEmpty() &&
|
|
Arrays.equals(getFirstSection().getFormatID().getBytes(), summaryBytes);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Convenience method returning the {@link Property} array contained in this
|
|
* property set. It is a shortcut for getting he {@link PropertySet}'s
|
|
* {@link Section}s list and then getting the {@link Property} array from the
|
|
* first {@link Section}.
|
|
*
|
|
* @return The properties of the only {@link Section} of this
|
|
* {@link PropertySet}.
|
|
* @throws NoSingleSectionException if the {@link PropertySet} has
|
|
* more or less than one {@link Section}.
|
|
*/
|
|
public Property[] getProperties() throws NoSingleSectionException {
|
|
return getFirstSection().getProperties();
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Convenience method returning the value of the property with the specified ID.
|
|
* If the property is not available, {@code null} is returned and a subsequent
|
|
* call to {@link #wasNull} will return {@code true}.
|
|
*
|
|
* @param id The property ID
|
|
* @return The property value
|
|
* @throws NoSingleSectionException if the {@link PropertySet} has
|
|
* more or less than one {@link Section}.
|
|
*/
|
|
protected Object getProperty(final int id) throws NoSingleSectionException {
|
|
return getFirstSection().getProperty(id);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Convenience method returning the value of a boolean property with the
|
|
* specified ID. If the property is not available, {@code false} is returned.
|
|
* A subsequent call to {@link #wasNull} will return {@code true} to let the
|
|
* caller distinguish that case from a real property value of {@code false}.
|
|
*
|
|
* @param id The property ID
|
|
* @return The property value
|
|
* @throws NoSingleSectionException if the {@link PropertySet} has
|
|
* more or less than one {@link Section}.
|
|
*/
|
|
protected boolean getPropertyBooleanValue(final int id) throws NoSingleSectionException {
|
|
return getFirstSection().getPropertyBooleanValue(id);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Convenience method returning the value of the numeric
|
|
* property with the specified ID. If the property is not
|
|
* available, 0 is returned. A subsequent call to {@link #wasNull}
|
|
* will return {@code true} to let the caller distinguish
|
|
* that case from a real property value of 0.
|
|
*
|
|
* @param id The property ID
|
|
* @return The propertyIntValue value
|
|
* @throws NoSingleSectionException if the {@link PropertySet} has
|
|
* more or less than one {@link Section}.
|
|
*/
|
|
protected int getPropertyIntValue(final int id) throws NoSingleSectionException {
|
|
return getFirstSection().getPropertyIntValue(id);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Checks whether the property which the last call to {@link
|
|
* #getPropertyIntValue} or {@link #getProperty} tried to access
|
|
* was available or not. This information might be important for
|
|
* callers of {@link #getPropertyIntValue} since the latter
|
|
* returns 0 if the property does not exist. Using {@link
|
|
* #wasNull}, the caller can distiguish this case from a
|
|
* property's real value of 0.
|
|
*
|
|
* @return {@code true} if the last call to {@link
|
|
* #getPropertyIntValue} or {@link #getProperty} tried to access a
|
|
* property that was not available, else {@code false}.
|
|
* @throws NoSingleSectionException if the {@link PropertySet} has
|
|
* more than one {@link Section}.
|
|
*/
|
|
public boolean wasNull() throws NoSingleSectionException {
|
|
return getFirstSection().wasNull();
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Gets the {@link PropertySet}'s first section.
|
|
*
|
|
* @return The {@link PropertySet}'s first section.
|
|
*/
|
|
public Section getFirstSection() {
|
|
if (sections.isEmpty()) {
|
|
throw new MissingSectionException("Property set does not contain any sections.");
|
|
}
|
|
return sections.get(0);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* If the {@link PropertySet} has only a single section this method returns it.
|
|
*
|
|
* @return The singleSection value
|
|
*/
|
|
public Section getSingleSection() {
|
|
final int sectionCount = getSectionCount();
|
|
if (sectionCount != 1) {
|
|
throw new NoSingleSectionException("Property set contains " + sectionCount + " sections.");
|
|
}
|
|
return sections.get(0);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Returns {@code true} if the {@code PropertySet} is equal
|
|
* to the specified parameter, else {@code false}.
|
|
*
|
|
* @param o the object to compare this {@code PropertySet} with
|
|
*
|
|
* @return {@code true} if the objects are equal, {@code false}
|
|
* if not
|
|
*/
|
|
@Override
|
|
public boolean equals(final Object o) {
|
|
if (o == null || !(o instanceof PropertySet)) {
|
|
return false;
|
|
}
|
|
final PropertySet ps = (PropertySet) o;
|
|
int byteOrder1 = ps.getByteOrder();
|
|
int byteOrder2 = getByteOrder();
|
|
ClassID classID1 = ps.getClassID();
|
|
ClassID classID2 = getClassID();
|
|
int format1 = ps.getFormat();
|
|
int format2 = getFormat();
|
|
int osVersion1 = ps.getOSVersion();
|
|
int osVersion2 = getOSVersion();
|
|
int sectionCount1 = ps.getSectionCount();
|
|
int sectionCount2 = getSectionCount();
|
|
if (byteOrder1 != byteOrder2 ||
|
|
!classID1.equals(classID2) ||
|
|
format1 != format2 ||
|
|
osVersion1 != osVersion2 ||
|
|
sectionCount1 != sectionCount2) {
|
|
return false;
|
|
}
|
|
|
|
/* Compare the sections: */
|
|
return getSections().containsAll(ps.getSections());
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @see Object#hashCode()
|
|
*/
|
|
@NotImplemented
|
|
@Override
|
|
public int hashCode() {
|
|
throw new UnsupportedOperationException("FIXME: Not yet implemented.");
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @see Object#toString()
|
|
*/
|
|
@Override
|
|
public String toString() {
|
|
final StringBuilder b = new StringBuilder();
|
|
final int sectionCount = getSectionCount();
|
|
b.append(getClass().getName());
|
|
b.append('[');
|
|
b.append("byteOrder: ");
|
|
b.append(getByteOrder());
|
|
b.append(", classID: ");
|
|
b.append(getClassID());
|
|
b.append(", format: ");
|
|
b.append(getFormat());
|
|
b.append(", OSVersion: ");
|
|
b.append(getOSVersion());
|
|
b.append(", sectionCount: ");
|
|
b.append(sectionCount);
|
|
b.append(", sections: [\n");
|
|
for (Section section: getSections()) {
|
|
b.append(section);
|
|
}
|
|
b.append(']');
|
|
b.append(']');
|
|
return b.toString();
|
|
}
|
|
|
|
|
|
protected void remove1stProperty(long id) {
|
|
getFirstSection().removeProperty(id);
|
|
}
|
|
|
|
protected void set1stProperty(long id, String value) {
|
|
getFirstSection().setProperty((int)id, value);
|
|
}
|
|
|
|
protected void set1stProperty(long id, int value) {
|
|
getFirstSection().setProperty((int)id, value);
|
|
}
|
|
|
|
protected void set1stProperty(long id, boolean value) {
|
|
getFirstSection().setProperty((int)id, value);
|
|
}
|
|
|
|
protected void set1stProperty(long id, byte[] value) {
|
|
getFirstSection().setProperty((int)id, value);
|
|
}
|
|
}
|