diff options
author | Andreas Beeker <kiwiwings@apache.org> | 2017-05-14 22:25:33 +0000 |
---|---|---|
committer | Andreas Beeker <kiwiwings@apache.org> | 2017-05-14 22:25:33 +0000 |
commit | 98b10cf68454fef15de8632b472c38442aaf95d5 (patch) | |
tree | 38c5b401f837ec45ae4ba572e863e46f3a8d3013 /src/java | |
parent | 965860b11607a2d610b941cdd648cc9bb7017a58 (diff) | |
download | poi-98b10cf68454fef15de8632b472c38442aaf95d5.tar.gz poi-98b10cf68454fef15de8632b472c38442aaf95d5.zip |
#52117 - Invalid "last printed" summary field value - added helper method to identify undefined dates
HPSF: fixed uid listing in Section.toString()
HPSF: moved timestamp based "utility" methods to Filetime class
HPSF: preserve original datastream for unchanged property sets
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1795123 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java')
-rw-r--r-- | src/java/org/apache/poi/hpsf/Filetime.java | 72 | ||||
-rw-r--r-- | src/java/org/apache/poi/hpsf/Property.java | 88 | ||||
-rw-r--r-- | src/java/org/apache/poi/hpsf/PropertySet.java | 2 | ||||
-rw-r--r-- | src/java/org/apache/poi/hpsf/Section.java | 103 | ||||
-rw-r--r-- | src/java/org/apache/poi/hpsf/SummaryInformation.java | 4 | ||||
-rw-r--r-- | src/java/org/apache/poi/hpsf/Util.java | 153 | ||||
-rw-r--r-- | src/java/org/apache/poi/hpsf/VariantSupport.java | 11 | ||||
-rw-r--r-- | src/java/org/apache/poi/hpsf/wellknown/PropertyIDMap.java | 274 |
8 files changed, 331 insertions, 376 deletions
diff --git a/src/java/org/apache/poi/hpsf/Filetime.java b/src/java/org/apache/poi/hpsf/Filetime.java index 2ed36afccc..ba5687a7b3 100644 --- a/src/java/org/apache/poi/hpsf/Filetime.java +++ b/src/java/org/apache/poi/hpsf/Filetime.java @@ -18,24 +18,40 @@ 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.LittleEndianByteArrayInputStream; import org.apache.poi.util.LittleEndianConsts; -class Filetime { - private static final int SIZE = LittleEndian.INT_SIZE * 2; +public class Filetime { + /** + * The difference between the Windows epoch (1601-01-01 + * 00:00:00) and the Unix epoch (1970-01-01 00:00:00) in + * milliseconds. + */ + private static final long EPOCH_DIFF = -11644473600000L; + private static final int SIZE = LittleEndian.INT_SIZE * 2; + private static final long UINT_MASK = 0x00000000FFFFFFFFL; + private static final long NANO_100 = 1000L * 10L; + private int _dwHighDateTime; private int _dwLowDateTime; - - Filetime() {} + Filetime() {} + Filetime( int low, int high ) { _dwLowDateTime = low; _dwHighDateTime = high; } + Filetime( Date date ) { + long filetime = Filetime.dateToFileTime(date); + _dwHighDateTime = (int) ((filetime >>> 32) & UINT_MASK); + _dwLowDateTime = (int) (filetime & UINT_MASK); + } + void read( LittleEndianByteArrayInputStream lei ) { _dwLowDateTime = lei.readInt(); @@ -53,8 +69,7 @@ class Filetime { byte[] toByteArray() { byte[] result = new byte[SIZE]; LittleEndian.putInt( result, 0 * LittleEndianConsts.INT_SIZE, _dwLowDateTime ); - LittleEndian - .putInt( result, 1 * LittleEndianConsts.INT_SIZE, _dwHighDateTime ); + LittleEndian.putInt( result, 1 * LittleEndianConsts.INT_SIZE, _dwHighDateTime ); return result; } @@ -63,4 +78,49 @@ class Filetime { LittleEndian.putInt( _dwHighDateTime, out ); return SIZE; } + + Date getJavaValue() { + long l = (((long)_dwHighDateTime) << 32) | (_dwLowDateTime & UINT_MASK); + return filetimeToDate( l ); + } + + /** + * Converts a Windows FILETIME into a {@link Date}. The Windows + * FILETIME structure holds a date and time associated with a + * file. The structure identifies a 64-bit integer specifying the + * number of 100-nanosecond intervals which have passed since + * January 1, 1601. + * + * @param filetime The filetime to convert. + * @return The Windows FILETIME as a {@link Date}. + */ + public static Date filetimeToDate(final long filetime) { + final long ms_since_16010101 = filetime / NANO_100; + final long ms_since_19700101 = ms_since_16010101 + EPOCH_DIFF; + return new Date(ms_since_19700101); + } + + /** + * Converts a {@link Date} into a filetime. + * + * @param date The date to be converted + * @return The filetime + * + * @see #filetimeToDate(long) + */ + 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 * NANO_100; + } + + /** + * Return {@code true} if the date is undefined + * + * @param date the date + * @return {@code true} if the date is undefined + */ + public static boolean isUndefined(Date date) { + return (date == null || dateToFileTime(date) == 0); + } } diff --git a/src/java/org/apache/poi/hpsf/Property.java b/src/java/org/apache/poi/hpsf/Property.java index 60a46b804d..e05a06dacd 100644 --- a/src/java/org/apache/poi/hpsf/Property.java +++ b/src/java/org/apache/poi/hpsf/Property.java @@ -22,7 +22,11 @@ import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; -import java.util.Arrays; +import java.util.Calendar; +import java.util.Locale; +import java.util.concurrent.TimeUnit; + +import javax.xml.bind.DatatypeConverter; import org.apache.poi.hpsf.wellknown.PropertyIDMap; import org.apache.poi.util.CodePageUtil; @@ -377,15 +381,18 @@ public class Property { */ @Override public String toString() { - return toString(Property.DEFAULT_CODEPAGE); + return toString(Property.DEFAULT_CODEPAGE, null); } - public String toString(int codepage) { - final StringBuffer b = new StringBuffer(); + public String toString(int codepage, PropertyIDMap idMap) { + final StringBuilder b = new StringBuilder(); b.append("Property["); b.append("id: "); - b.append(getID()); - String idName = getNameFromID(); + b.append(id); + String idName = (idMap == null) ? null : idMap.get(id); + if (idName == null) { + idName = PropertyIDMap.getFallbackProperties().get(id); + } if (idName != null) { b.append(" ("); b.append(idName); @@ -399,6 +406,7 @@ public class Property { final Object value = getValue(); b.append(", value: "); if (value instanceof String) { + b.append((String)value); b.append("\n"); ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { @@ -407,13 +415,11 @@ public class Property { LOG.log(POILogger.WARN, "can't serialize string", e); } - b.append(" ["); // skip length field if(bos.size() > 2*LittleEndianConsts.INT_SIZE) { final String hex = HexDump.dump(bos.toByteArray(), -2*LittleEndianConsts.INT_SIZE, 2*LittleEndianConsts.INT_SIZE); b.append(hex); } - b.append("]"); } else if (value instanceof byte[]) { b.append("\n"); byte[] bytes = (byte[])value; @@ -421,7 +427,32 @@ public class Property { String hex = HexDump.dump(bytes, 0L, 0); b.append(hex); } - } else if (type == Variant.VT_EMPTY || type == Variant.VT_NULL) { + } else if (value instanceof java.util.Date) { + java.util.Date d = (java.util.Date)value; + long filetime = Filetime.dateToFileTime(d); + if (Filetime.isUndefined(d)) { + b.append("<undefined>"); + } else if ((filetime >>> 32) == 0) { + // if the upper dword isn't set, we deal with time intervals + long l = filetime*100; + TimeUnit tu = TimeUnit.NANOSECONDS; + final long hr = tu.toHours(l); + l -= TimeUnit.HOURS.toNanos(hr); + final long min = tu.toMinutes(l); + l -= TimeUnit.MINUTES.toNanos(min); + final long sec = tu.toSeconds(l); + l -= TimeUnit.SECONDS.toNanos(sec); + final long ms = tu.toMillis(l); + + String str = String.format(Locale.ROOT, "%02d:%02d:%02d.%03d",hr,min,sec,ms); + b.append(str); + } else { + Calendar cal = Calendar.getInstance(LocaleUtil.TIMEZONE_UTC, Locale.ROOT); + cal.setTime(d); + // use ISO-8601 timestamp format + b.append(DatatypeConverter.printDateTime(cal)); + } + } else if (type == Variant.VT_EMPTY || type == Variant.VT_NULL || value == null) { b.append("null"); } else { b.append(value.toString()); @@ -458,45 +489,6 @@ public class Property { return null; } - private String getNameFromID() { - switch ((int)getID()) { - case PropertyIDMap.PID_DICTIONARY: return "PID_DICTIONARY"; - case PropertyIDMap.PID_CODEPAGE: return "PID_CODEPAGE"; - case PropertyIDMap.PID_CATEGORY: return "PID_CATEGORY"; - case PropertyIDMap.PID_PRESFORMAT: return "PID_PRESFORMAT"; - case PropertyIDMap.PID_BYTECOUNT: return "PID_BYTECOUNT"; - case PropertyIDMap.PID_LINECOUNT: return "PID_LINECOUNT"; - case PropertyIDMap.PID_PARCOUNT: return "PID_PARCOUNT"; - case PropertyIDMap.PID_SLIDECOUNT: return "PID_SLIDECOUNT"; - case PropertyIDMap.PID_NOTECOUNT: return "PID_NOTECOUNT"; - case PropertyIDMap.PID_HIDDENCOUNT: return "PID_HIDDENCOUNT"; - case PropertyIDMap.PID_MMCLIPCOUNT: return "PID_MMCLIPCOUNT"; - case PropertyIDMap.PID_SCALE: return "PID_SCALE"; - case PropertyIDMap.PID_HEADINGPAIR: return "PID_HEADINGPAIR"; - case PropertyIDMap.PID_DOCPARTS: return "PID_DOCPARTS"; - case PropertyIDMap.PID_MANAGER: return "PID_MANAGER"; - case PropertyIDMap.PID_COMPANY: return "PID_COMPANY"; - case PropertyIDMap.PID_LINKSDIRTY: return "PID_LINKSDIRTY"; - case PropertyIDMap.PID_CCHWITHSPACES: return "PID_CCHWITHSPACES"; - // 0x12 Unused - // 0x13 GKPIDDSI_SHAREDDOC - Must be False - // 0x14 GKPIDDSI_LINKBASE - Must not be written - // 0x15 GKPIDDSI_HLINKS - Must not be written - case PropertyIDMap.PID_HYPERLINKSCHANGED: return "PID_HYPERLINKSCHANGED"; - case PropertyIDMap.PID_VERSION: return "PID_VERSION"; - case PropertyIDMap.PID_DIGSIG: return "PID_DIGSIG"; - // 0x19 Unused - case PropertyIDMap.PID_CONTENTTYPE: return "PID_CONTENTTYPE"; - case PropertyIDMap.PID_CONTENTSTATUS: return "PID_CONTENTSTATUS"; - case PropertyIDMap.PID_LANGUAGE: return "PID_LANGUAGE"; - case PropertyIDMap.PID_DOCVERSION: return "PID_DOCVERSION"; - case PropertyIDMap.PID_MAX: return "PID_MAX"; - case PropertyIDMap.PID_LOCALE: return "PID_LOCALE"; - case PropertyIDMap.PID_BEHAVIOUR: return "PID_BEHAVIOUR"; - default: return null; - } - } - /** * Writes the property to an output stream. * diff --git a/src/java/org/apache/poi/hpsf/PropertySet.java b/src/java/org/apache/poi/hpsf/PropertySet.java index fb83442693..db01200bc7 100644 --- a/src/java/org/apache/poi/hpsf/PropertySet.java +++ b/src/java/org/apache/poi/hpsf/PropertySet.java @@ -871,7 +871,7 @@ public class PropertySet { b.append(sectionCount); b.append(", sections: [\n"); for (Section section: getSections()) { - b.append(section); + b.append(section.toString(getPropertySetIDMap())); } b.append(']'); b.append(']'); diff --git a/src/java/org/apache/poi/hpsf/Section.java b/src/java/org/apache/poi/hpsf/Section.java index 59150afb53..f041986402 100644 --- a/src/java/org/apache/poi/hpsf/Section.java +++ b/src/java/org/apache/poi/hpsf/Section.java @@ -55,18 +55,13 @@ public class Section { * The section's format ID, {@link #getFormatID}. */ private ClassID formatID; - /** - * If the "dirty" flag is true, the section's size must be - * (re-)calculated before the section is written. - */ - private boolean dirty = true; /** * 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. + * later. If the array is empty, the section was modified and the bytes need to be regenerated. */ - private byte[] sectionBytes; + private final ByteArrayOutputStream sectionBytes = new ByteArrayOutputStream(); /** * The offset of the section in the stream. @@ -74,11 +69,6 @@ public class Section { private final long _offset; /** - * The section's size in bytes. - */ - private int size; - - /** * This section's properties. */ private final Map<Long,Property> properties = new LinkedHashMap<Long,Property>(); @@ -126,7 +116,6 @@ public class Section { * @exception UnsupportedEncodingException if the section's codepage is not * supported. */ - @SuppressWarnings("unchecked") public Section(final byte[] src, final int offset) throws UnsupportedEncodingException { /* * Read the format ID. @@ -154,7 +143,7 @@ public class Section { /* * Read the section length. */ - size = (int)leis.readUInt(); + int size = (int)Math.min(leis.readUInt(), src.length-_offset); /* * Read the number of properties. @@ -213,6 +202,7 @@ public class Section { /* Read the codepage number. */ codepage = leis.readUShort(); + setCodepage(codepage); } @@ -222,6 +212,10 @@ public class Section { long off = me.getKey(); long id = me.getValue(); + if (id == PropertyIDMap.PID_CODEPAGE) { + continue; + } + int pLen = propLen(offset2Id, off, size); leis.setReadIndex((int)(this._offset + off)); @@ -239,12 +233,13 @@ public class Section { LOG.log(POILogger.INFO, "Dictionary fallback failed - ignoring property"); } }; - } else if (id == PropertyIDMap.PID_CODEPAGE) { - setCodepage(codepage); } else { setProperty(new MutableProperty(id, leis, pLen, codepage)); } } + + sectionBytes.write(src, (int)_offset, size); + padSectionBytes(); } /** @@ -338,9 +333,8 @@ public class Section { public void setProperties(final Property[] properties) { this.properties.clear(); for (Property p : properties) { - this.properties.put(p.getID(), p); + setProperty(p); } - dirty = true; } /** @@ -448,7 +442,7 @@ public class Section { Property old = properties.get(p.getID()); if (old == null || !old.equals(p)) { properties.put(p.getID(), p); - dirty = true; + sectionBytes.reset(); } } @@ -543,17 +537,17 @@ public class Section { * @return the section's size in bytes. */ public int getSize() { - if (dirty) { - try { - size = calcSize(); - dirty = false; - } catch (HPSFRuntimeException ex) { - throw ex; - } catch (Exception ex) { - throw new HPSFRuntimeException(ex); - } + int size = sectionBytes.size(); + if (size > 0) { + return size; + } + try { + return calcSize(); + } catch (HPSFRuntimeException ex) { + throw ex; + } catch (Exception ex) { + throw new HPSFRuntimeException(ex); } - return size; } /** @@ -566,18 +560,21 @@ public class Section { * @throws IOException */ private int calcSize() throws WritingNotSupportedException, IOException { - final ByteArrayOutputStream out = new ByteArrayOutputStream(); - write(out); - out.close(); + sectionBytes.reset(); + write(sectionBytes); + padSectionBytes(); + return sectionBytes.size(); + } + + private void padSectionBytes() { + byte[] padArray = { 0, 0, 0 }; /* Pad to multiple of 4 bytes so that even the Windows shell (explorer) * shows custom properties. */ - sectionBytes = Util.pad4(out.toByteArray()); - return sectionBytes.length; + int pad = (4 - (sectionBytes.size() & 0x3)) & 0x3; + sectionBytes.write(padArray, 0, pad); } - - /** * Checks whether the property which the last call to {@link * #getPropertyIntValue} or {@link #getProperty} tried to access @@ -623,12 +620,8 @@ public class Section { * Removes all properties from the section including 0 (dictionary) and * 1 (codepage). */ - public void clear() - { - final Property[] properties = getProperties(); - for (int i = 0; i < properties.length; i++) - { - final Property p = properties[i]; + public void clear() { + for (Property p : getProperties()) { removeProperty(p.getID()); } } @@ -639,17 +632,17 @@ public class Section { * * <ul> * - * <li>The other object is not a {@link Section}.</li> + * <li>The other object is not a {@link Section}. * - * <li>The format IDs of the two sections are not equal.</li> + * <li>The format IDs of the two sections are not equal. * * <li>The sections have a different number of properties. However, - * properties with ID 1 (codepage) are not counted.</li> + * properties with ID 1 (codepage) are not counted. * - * <li>The other object is not a {@link Section}.</li> + * <li>The other object is not a {@link Section}. * * <li>The properties have different values. The order of the properties - * is irrelevant.</li> + * is irrelevant. * * </ul> * @@ -695,7 +688,9 @@ public class Section { * @param id The ID of the property to be removed */ public void removeProperty(final long id) { - dirty |= (properties.remove(id) != null); + if (properties.remove(id) != null) { + sectionBytes.reset(); + } } /** @@ -716,9 +711,9 @@ public class Section { public int write(final OutputStream out) throws WritingNotSupportedException, IOException { /* Check whether we have already generated the bytes making out the * section. */ - if (!dirty && sectionBytes != null) { - out.write(sectionBytes); - return sectionBytes.length; + if (sectionBytes.size() > 0) { + sectionBytes.writeTo(out); + return sectionBytes.size(); } /* Writing the section's dictionary it tricky. If there is a dictionary @@ -971,6 +966,10 @@ public class Section { */ @Override public String toString() { + return toString(null); + } + + public String toString(PropertyIDMap idMap) { final StringBuffer b = new StringBuffer(); final Property[] pa = getProperties(); b.append("\n\n\n"); @@ -990,7 +989,7 @@ public class Section { codepage = Property.DEFAULT_CODEPAGE; } for (Property p : pa) { - b.append(p.toString(codepage)); + b.append(p.toString(codepage, idMap)); b.append(",\n"); } b.append(']'); diff --git a/src/java/org/apache/poi/hpsf/SummaryInformation.java b/src/java/org/apache/poi/hpsf/SummaryInformation.java index f59bd4872a..8d9e431794 100644 --- a/src/java/org/apache/poi/hpsf/SummaryInformation.java +++ b/src/java/org/apache/poi/hpsf/SummaryInformation.java @@ -351,7 +351,7 @@ public final class SummaryInformation extends SpecialPropertySet { if (d == null) { return 0; } - return Util.dateToFileTime(d); + return Filetime.dateToFileTime(d); } @@ -362,7 +362,7 @@ public final class SummaryInformation extends SpecialPropertySet { * @param time The time to set. */ public void setEditTime(final long time) { - final Date d = Util.filetimeToDate(time); + final Date d = Filetime.filetimeToDate(time); getFirstSection().setProperty(PropertyIDMap.PID_EDITTIME, Variant.VT_FILETIME, d); } diff --git a/src/java/org/apache/poi/hpsf/Util.java b/src/java/org/apache/poi/hpsf/Util.java deleted file mode 100644 index d18fe811be..0000000000 --- a/src/java/org/apache/poi/hpsf/Util.java +++ /dev/null @@ -1,153 +0,0 @@ -/* ==================================================================== - 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.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.Date; - -import org.apache.poi.util.Internal; -import org.apache.poi.util.SuppressForbidden; - -/** - * <p>Provides various static utility methods.</p> - */ -@Internal -public class Util -{ - /** - * <p>The difference between the Windows epoch (1601-01-01 - * 00:00:00) and the Unix epoch (1970-01-01 00:00:00) in - * milliseconds: 11644473600000L. (Use your favorite spreadsheet - * program to verify the correctness of this value. By the way, - * did you notice that you can tell from the epochs which - * operating system is the modern one? :-))</p> - */ - public static final long EPOCH_DIFF = 11644473600000L; - - - /** - * <p>Converts a Windows FILETIME into a {@link Date}. The Windows - * FILETIME structure holds a date and time associated with a - * file. The structure identifies a 64-bit integer specifying the - * number of 100-nanosecond intervals which have passed since - * January 1, 1601. This 64-bit value is split into the two double - * words stored in the structure.</p> - * - * @param high The higher double word of the FILETIME structure. - * @param low The lower double word of the FILETIME structure. - * @return The Windows FILETIME as a {@link Date}. - */ - public static Date filetimeToDate(final int high, final int low) - { - final long filetime = ((long) high) << 32 | (low & 0xffffffffL); - return filetimeToDate(filetime); - } - - /** - * <p>Converts a Windows FILETIME into a {@link Date}. The Windows - * FILETIME structure holds a date and time associated with a - * file. The structure identifies a 64-bit integer specifying the - * number of 100-nanosecond intervals which have passed since - * January 1, 1601.</p> - * - * @param filetime The filetime to convert. - * @return The Windows FILETIME as a {@link Date}. - */ - public static Date filetimeToDate(final long filetime) - { - final long ms_since_16010101 = filetime / (1000 * 10); - final long ms_since_19700101 = ms_since_16010101 - EPOCH_DIFF; - return new Date(ms_since_19700101); - } - - - - /** - * <p>Converts a {@link Date} into a filetime.</p> - * - * @param date The date to be converted - * @return The filetime - * - * @see #filetimeToDate(long) - * @see #filetimeToDate(int, int) - */ - 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); - } - - /** - * <p>Pads a byte array with 0x00 bytes so that its length is a multiple of - * 4.</p> - * - * @param ba The byte array to pad. - * @return The padded byte array. - */ - public static byte[] pad4(final byte[] ba) - { - final int PAD = 4; - final byte[] result; - int l = ba.length % PAD; - if (l == 0) - result = ba; - else - { - l = PAD - l; - result = new byte[ba.length + l]; - System.arraycopy(ba, 0, result, 0, ba.length); - } - return result; - } - - - /** - * <p>Returns a textual representation of a {@link Throwable}, including a - * stacktrace.</p> - * - * @param t The {@link Throwable} - * - * @return a string containing the output of a call to - * <code>t.printStacktrace()</code>. - */ - @SuppressForbidden("uses printStackTrace") - public static String toString(final Throwable t) - { - final StringWriter sw = new StringWriter(); - final PrintWriter pw = new PrintWriter(sw); - t.printStackTrace(pw); - pw.close(); - try - { - sw.close(); - return sw.toString(); - } - catch (IOException e) - { - final StringBuffer b = new StringBuffer(t.getMessage()); - b.append("\n"); - b.append("Could not create a stacktrace. Reason: "); - b.append(e.getMessage()); - return b.toString(); - } - } - -} diff --git a/src/java/org/apache/poi/hpsf/VariantSupport.java b/src/java/org/apache/poi/hpsf/VariantSupport.java index 8c351cc39a..61e6bb8db9 100644 --- a/src/java/org/apache/poi/hpsf/VariantSupport.java +++ b/src/java/org/apache/poi/hpsf/VariantSupport.java @@ -207,7 +207,7 @@ public class VariantSupport extends Variant { case Variant.VT_FILETIME: Filetime filetime = (Filetime) typedPropertyValue.getValue(); - return Util.filetimeToDate( (int) filetime.getHigh(), (int) filetime.getLow() ); + return filetime.getJavaValue(); case Variant.VT_LPSTR: CodePageString cpString = (CodePageString) typedPropertyValue.getValue(); @@ -413,13 +413,8 @@ public class VariantSupport extends Variant { break; case Variant.VT_FILETIME: - if (value instanceof Date) { - long filetime = Util.dateToFileTime((Date) value); - int high = (int) ((filetime >> 32) & 0x00000000FFFFFFFFL); - int low = (int) (filetime & 0x00000000FFFFFFFFL); - Filetime filetimeValue = new Filetime( low, high); - length = filetimeValue.write( out ); - } + Filetime filetimeValue = (value instanceof Date) ? new Filetime((Date)value) : new Filetime(); + length = filetimeValue.write( out ); break; default: diff --git a/src/java/org/apache/poi/hpsf/wellknown/PropertyIDMap.java b/src/java/org/apache/poi/hpsf/wellknown/PropertyIDMap.java index 4a7da335d0..b3aaa87a3c 100644 --- a/src/java/org/apache/poi/hpsf/wellknown/PropertyIDMap.java +++ b/src/java/org/apache/poi/hpsf/wellknown/PropertyIDMap.java @@ -17,9 +17,14 @@ package org.apache.poi.hpsf.wellknown; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Set; + +import org.apache.poi.hpsf.DocumentSummaryInformation; +import org.apache.poi.hpsf.SummaryInformation; /** * This is a dictionary which maps property ID values to property @@ -31,7 +36,7 @@ import java.util.Map; * should treat them as unmodifiable, copy them and modifiy the * copies. */ -public class PropertyIDMap extends HashMap<Long,String> { +public class PropertyIDMap implements Map<Long,String> { /* * The following definitions are for property IDs in the first @@ -317,6 +322,26 @@ public class PropertyIDMap extends HashMap<Long,String> { * details! */ private static PropertyIDMap summaryInformationProperties; + private static final Object[][] summaryInformationIdValues = { + { (long)PID_TITLE, "PID_TITLE" }, + { (long)PID_SUBJECT, "PID_SUBJECT" }, + { (long)PID_AUTHOR, "PID_AUTHOR" }, + { (long)PID_KEYWORDS, "PID_KEYWORDS" }, + { (long)PID_COMMENTS, "PID_COMMENTS" }, + { (long)PID_TEMPLATE, "PID_TEMPLATE" }, + { (long)PID_LASTAUTHOR, "PID_LASTAUTHOR" }, + { (long)PID_REVNUMBER, "PID_REVNUMBER" }, + { (long)PID_EDITTIME, "PID_EDITTIME" }, + { (long)PID_LASTPRINTED, "PID_LASTPRINTED" }, + { (long)PID_CREATE_DTM, "PID_CREATE_DTM" }, + { (long)PID_LASTSAVE_DTM, "PID_LASTSAVE_DTM" }, + { (long)PID_PAGECOUNT, "PID_PAGECOUNT" }, + { (long)PID_WORDCOUNT, "PID_WORDCOUNT" }, + { (long)PID_CHARCOUNT, "PID_CHARCOUNT" }, + { (long)PID_THUMBNAIL, "PID_THUMBNAIL" }, + { (long)PID_APPNAME, "PID_APPNAME" }, + { (long)PID_SECURITY, "PID_SECURITY" }, + }; /** * Contains the summary information property ID values and @@ -324,144 +349,181 @@ public class PropertyIDMap extends HashMap<Long,String> { * details! */ private static PropertyIDMap documentSummaryInformationProperties; - - - + private static final Object[][] documentSummaryInformationIdValues = { + { (long)PID_DICTIONARY, "PID_DICTIONARY" }, + { (long)PID_CODEPAGE, "PID_CODEPAGE" }, + { (long)PID_CATEGORY, "PID_CATEGORY" }, + { (long)PID_PRESFORMAT, "PID_PRESFORMAT" }, + { (long)PID_BYTECOUNT, "PID_BYTECOUNT" }, + { (long)PID_LINECOUNT, "PID_LINECOUNT" }, + { (long)PID_PARCOUNT, "PID_PARCOUNT" }, + { (long)PID_SLIDECOUNT, "PID_SLIDECOUNT" }, + { (long)PID_NOTECOUNT, "PID_NOTECOUNT" }, + { (long)PID_HIDDENCOUNT, "PID_HIDDENCOUNT" }, + { (long)PID_MMCLIPCOUNT, "PID_MMCLIPCOUNT" }, + { (long)PID_SCALE, "PID_SCALE" }, + { (long)PID_HEADINGPAIR, "PID_HEADINGPAIR" }, + { (long)PID_DOCPARTS, "PID_DOCPARTS" }, + { (long)PID_MANAGER, "PID_MANAGER" }, + { (long)PID_COMPANY, "PID_COMPANY" }, + { (long)PID_LINKSDIRTY, "PID_LINKSDIRTY" }, + }; + + /** - * Creates a {@link PropertyIDMap}. - * - * @param initialCapacity The initial capacity as defined for - * {@link HashMap} - * @param loadFactor The load factor as defined for {@link HashMap} - */ - public PropertyIDMap(final int initialCapacity, final float loadFactor) - { - super(initialCapacity, loadFactor); - } - - + * Contains the fallback property ID values and associated strings. + * This is only used for lookups and not for initializing a property set + */ + private static PropertyIDMap fallbackProperties; + private static final Object[][] fallbackIdValues = { + { (long)PID_DICTIONARY, "PID_DICTIONARY" }, + { (long)PID_CODEPAGE, "PID_CODEPAGE" }, + { (long)PID_CATEGORY, "PID_CATEGORY" }, + { (long)PID_PRESFORMAT, "PID_PRESFORMAT" }, + { (long)PID_BYTECOUNT, "PID_BYTECOUNT" }, + { (long)PID_LINECOUNT, "PID_LINECOUNT" }, + { (long)PID_PARCOUNT, "PID_PARCOUNT" }, + { (long)PID_SLIDECOUNT, "PID_SLIDECOUNT" }, + { (long)PID_NOTECOUNT, "PID_NOTECOUNT" }, + { (long)PID_HIDDENCOUNT, "PID_HIDDENCOUNT" }, + { (long)PID_MMCLIPCOUNT, "PID_MMCLIPCOUNT" }, + { (long)PID_SCALE, "PID_SCALE" }, + { (long)PID_HEADINGPAIR, "PID_HEADINGPAIR" }, + { (long)PID_DOCPARTS, "PID_DOCPARTS" }, + { (long)PID_MANAGER, "PID_MANAGER" }, + { (long)PID_COMPANY, "PID_COMPANY" }, + { (long)PID_LINKSDIRTY, "PID_LINKSDIRTY" }, + { (long)PID_CCHWITHSPACES, "PID_CCHWITHSPACES" }, + // 0x12 Unused + // 0x13 GKPIDDSI_SHAREDDOC - Must be False + // 0x14 GKPIDDSI_LINKBASE - Must not be written + // 0x15 GKPIDDSI_HLINKS - Must not be written + { (long)PID_HYPERLINKSCHANGED, "PID_HYPERLINKSCHANGED" }, + { (long)PID_VERSION, "PID_VERSION" }, + { (long)PID_DIGSIG, "PID_DIGSIG" }, + // 0x19 Unused + { (long)PID_CONTENTTYPE, "PID_CONTENTTYPE" }, + { (long)PID_CONTENTSTATUS, "PID_CONTENTSTATUS" }, + { (long)PID_LANGUAGE, "PID_LANGUAGE" }, + { (long)PID_DOCVERSION, "PID_DOCVERSION" }, + { (long)PID_MAX, "PID_MAX" }, + { (long)PID_LOCALE, "PID_LOCALE" }, + { (long)PID_BEHAVIOUR, "PID_BEHAVIOUR" }, + }; + + private final Map<Long,String> idMap; + /** * Creates a {@link PropertyIDMap} backed by another map. * * @param map The instance to be created is backed by this map. */ - public PropertyIDMap(final Map<Long,String> map) - { - super(map); + private PropertyIDMap(Object[][] idValues) { + Map<Long,String> m = new HashMap<Long,String>(idValues.length); + for (Object[] idValue : idValues) { + m.put((Long)idValue[0], (String)idValue[1]); + } + idMap = Collections.unmodifiableMap(m); } + /** + * @return the Summary Information properties singleton + */ + public static synchronized PropertyIDMap getSummaryInformationProperties() { + if (summaryInformationProperties == null) { + summaryInformationProperties = new PropertyIDMap(summaryInformationIdValues); + } + return summaryInformationProperties; + } + /** + * @return The Document Summary Information properties singleton. + */ + public static synchronized PropertyIDMap getDocumentSummaryInformationProperties() { + if (documentSummaryInformationProperties == null) { + documentSummaryInformationProperties = new PropertyIDMap(documentSummaryInformationIdValues); + } + return documentSummaryInformationProperties; + } /** - * Puts a ID string for an ID into the {@link - * PropertyIDMap}. - * - * @param id The ID. - * @param idString The ID string. - * @return As specified by the {@link java.util.Map} interface, this method - * returns the previous value associated with the specified - * <var>id</var>, or <code>null</code> if there was no mapping for - * key. - */ - public Object put(final long id, final String idString) - { - return put(Long.valueOf(id), idString); + * Returns a property map, which is only used as a fallback, i.e. if available, the correct map + * for {@link DocumentSummaryInformation} or {@link SummaryInformation} should be used. + */ + public static synchronized PropertyIDMap getFallbackProperties() { + if (fallbackProperties == null) { + fallbackProperties = new PropertyIDMap(fallbackIdValues); + } + return fallbackProperties; + } + + @Override + public int size() { + return idMap.size(); } + @Override + public boolean isEmpty() { + return idMap.isEmpty(); + } + @Override + public boolean containsKey(Object key) { + return idMap.containsKey(key); + } - /** - * Gets the ID string for an ID from the {@link - * PropertyIDMap}. - * - * @param id The ID. - * @return The ID string associated with <var>id</var>. - */ - public Object get(final long id) - { - return get(Long.valueOf(id)); + @Override + public boolean containsValue(Object value) { + return idMap.containsValue(value); } + @Override + public String get(Object key) { + return idMap.get(key); + } + @Override + public String put(Long key, String value) { + return idMap.put(key, value); + } - /** - * @return the Summary Information properties singleton - */ - public static synchronized PropertyIDMap getSummaryInformationProperties() - { - if (summaryInformationProperties == null) - { - PropertyIDMap m = new PropertyIDMap(18, (float) 1.0); - m.put(PID_TITLE, "PID_TITLE"); - m.put(PID_SUBJECT, "PID_SUBJECT"); - m.put(PID_AUTHOR, "PID_AUTHOR"); - m.put(PID_KEYWORDS, "PID_KEYWORDS"); - m.put(PID_COMMENTS, "PID_COMMENTS"); - m.put(PID_TEMPLATE, "PID_TEMPLATE"); - m.put(PID_LASTAUTHOR, "PID_LASTAUTHOR"); - m.put(PID_REVNUMBER, "PID_REVNUMBER"); - m.put(PID_EDITTIME, "PID_EDITTIME"); - m.put(PID_LASTPRINTED, "PID_LASTPRINTED"); - m.put(PID_CREATE_DTM, "PID_CREATE_DTM"); - m.put(PID_LASTSAVE_DTM, "PID_LASTSAVE_DTM"); - m.put(PID_PAGECOUNT, "PID_PAGECOUNT"); - m.put(PID_WORDCOUNT, "PID_WORDCOUNT"); - m.put(PID_CHARCOUNT, "PID_CHARCOUNT"); - m.put(PID_THUMBNAIL, "PID_THUMBNAIL"); - m.put(PID_APPNAME, "PID_APPNAME"); - m.put(PID_SECURITY, "PID_SECURITY"); - summaryInformationProperties = - new PropertyIDMap(Collections.unmodifiableMap(m)); - } - return summaryInformationProperties; + @Override + public String remove(Object key) { + return idMap.remove(key); } + @Override + public void putAll(Map<? extends Long, ? extends String> m) { + idMap.putAll(m); + } + @Override + public void clear() { + idMap.clear(); + } - /** - * Returns the Document Summary Information properties - * singleton. - * - * @return The Document Summary Information properties singleton. - */ - public static synchronized PropertyIDMap getDocumentSummaryInformationProperties() - { - if (documentSummaryInformationProperties == null) - { - PropertyIDMap m = new PropertyIDMap(17, (float) 1.0); - m.put(PID_DICTIONARY, "PID_DICTIONARY"); - m.put(PID_CODEPAGE, "PID_CODEPAGE"); - m.put(PID_CATEGORY, "PID_CATEGORY"); - m.put(PID_PRESFORMAT, "PID_PRESFORMAT"); - m.put(PID_BYTECOUNT, "PID_BYTECOUNT"); - m.put(PID_LINECOUNT, "PID_LINECOUNT"); - m.put(PID_PARCOUNT, "PID_PARCOUNT"); - m.put(PID_SLIDECOUNT, "PID_SLIDECOUNT"); - m.put(PID_NOTECOUNT, "PID_NOTECOUNT"); - m.put(PID_HIDDENCOUNT, "PID_HIDDENCOUNT"); - m.put(PID_MMCLIPCOUNT, "PID_MMCLIPCOUNT"); - m.put(PID_SCALE, "PID_SCALE"); - m.put(PID_HEADINGPAIR, "PID_HEADINGPAIR"); - m.put(PID_DOCPARTS, "PID_DOCPARTS"); - m.put(PID_MANAGER, "PID_MANAGER"); - m.put(PID_COMPANY, "PID_COMPANY"); - m.put(PID_LINKSDIRTY, "PID_LINKSDIRTY"); - documentSummaryInformationProperties = - new PropertyIDMap(Collections.unmodifiableMap(m)); - } - return documentSummaryInformationProperties; + @Override + public Set<Long> keySet() { + return idMap.keySet(); } + @Override + public Collection<String> values() { + return idMap.values(); + } + @Override + public Set<Entry<Long, String>> entrySet() { + return idMap.entrySet(); + } /** * For the most basic testing. * * @param args The command-line arguments */ - public static void main(final String[] args) - { + public static void main(final String[] args) { PropertyIDMap s1 = getSummaryInformationProperties(); PropertyIDMap s2 = getDocumentSummaryInformationProperties(); System.out.println("s1: " + s1); |