#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
This commit is contained in:
Andreas Beeker 2017-05-14 22:25:33 +00:00
parent c45c64ae3a
commit 90dca20bd2
17 changed files with 487 additions and 624 deletions

View File

@ -36,7 +36,6 @@ import org.apache.poi.hpsf.MutablePropertySet;
import org.apache.poi.hpsf.NoPropertySetStreamException; import org.apache.poi.hpsf.NoPropertySetStreamException;
import org.apache.poi.hpsf.PropertySet; import org.apache.poi.hpsf.PropertySet;
import org.apache.poi.hpsf.PropertySetFactory; import org.apache.poi.hpsf.PropertySetFactory;
import org.apache.poi.hpsf.Util;
import org.apache.poi.hpsf.WritingNotSupportedException; import org.apache.poi.hpsf.WritingNotSupportedException;
import org.apache.poi.poifs.eventfilesystem.POIFSReader; import org.apache.poi.poifs.eventfilesystem.POIFSReader;
import org.apache.poi.poifs.eventfilesystem.POIFSReaderEvent; import org.apache.poi.poifs.eventfilesystem.POIFSReaderEvent;
@ -352,13 +351,11 @@ public class CopyCompare
/* According to the definition of the processPOIFSReaderEvent method /* According to the definition of the processPOIFSReaderEvent method
* we cannot pass checked exceptions to the caller. The following * we cannot pass checked exceptions to the caller. The following
* lines check whether a checked exception occured and throws an * lines check whether a checked exception occurred and throws an
* unchecked exception. The message of that exception is that of * unchecked exception. The message of that exception is that of
* the underlying checked exception. */ * the underlying checked exception. */
if (t != null) { if (t != null) {
throw new HPSFRuntimeException throw new HPSFRuntimeException("Could not read file \"" + path + "/" + name, t);
("Could not read file \"" + path + "/" + name +
"\". Reason: " + Util.toString(t));
} }
} }

View File

@ -36,7 +36,6 @@ import org.apache.poi.hpsf.NoPropertySetStreamException;
import org.apache.poi.hpsf.PropertySet; import org.apache.poi.hpsf.PropertySet;
import org.apache.poi.hpsf.PropertySetFactory; import org.apache.poi.hpsf.PropertySetFactory;
import org.apache.poi.hpsf.SummaryInformation; import org.apache.poi.hpsf.SummaryInformation;
import org.apache.poi.hpsf.Util;
import org.apache.poi.hpsf.Variant; import org.apache.poi.hpsf.Variant;
import org.apache.poi.hpsf.WritingNotSupportedException; import org.apache.poi.hpsf.WritingNotSupportedException;
import org.apache.poi.hpsf.wellknown.PropertyIDMap; import org.apache.poi.hpsf.wellknown.PropertyIDMap;
@ -211,9 +210,7 @@ public class WriteAuthorAndTitle
* unchecked exception. The message of that exception is that of * unchecked exception. The message of that exception is that of
* the underlying checked exception. */ * the underlying checked exception. */
if (t != null) { if (t != null) {
throw new HPSFRuntimeException throw new HPSFRuntimeException("Could not read file \"" + path + "/" + name, t);
("Could not read file \"" + path + "/" + name +
"\". Reason: " + Util.toString(t));
} }
} }

View File

@ -18,24 +18,40 @@ package org.apache.poi.hpsf;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Date;
import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianByteArrayInputStream; import org.apache.poi.util.LittleEndianByteArrayInputStream;
import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianConsts;
class Filetime { public class Filetime {
private static final int SIZE = LittleEndian.INT_SIZE * 2; /**
* 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 _dwHighDateTime;
private int _dwLowDateTime; private int _dwLowDateTime;
Filetime() {}
Filetime() {}
Filetime( int low, int high ) { Filetime( int low, int high ) {
_dwLowDateTime = low; _dwLowDateTime = low;
_dwHighDateTime = high; _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 ) { void read( LittleEndianByteArrayInputStream lei ) {
_dwLowDateTime = lei.readInt(); _dwLowDateTime = lei.readInt();
@ -53,8 +69,7 @@ class Filetime {
byte[] toByteArray() { byte[] toByteArray() {
byte[] result = new byte[SIZE]; byte[] result = new byte[SIZE];
LittleEndian.putInt( result, 0 * LittleEndianConsts.INT_SIZE, _dwLowDateTime ); LittleEndian.putInt( result, 0 * LittleEndianConsts.INT_SIZE, _dwLowDateTime );
LittleEndian LittleEndian.putInt( result, 1 * LittleEndianConsts.INT_SIZE, _dwHighDateTime );
.putInt( result, 1 * LittleEndianConsts.INT_SIZE, _dwHighDateTime );
return result; return result;
} }
@ -63,4 +78,49 @@ class Filetime {
LittleEndian.putInt( _dwHighDateTime, out ); LittleEndian.putInt( _dwHighDateTime, out );
return SIZE; 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);
}
} }

View File

@ -22,7 +22,11 @@ import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset; 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.hpsf.wellknown.PropertyIDMap;
import org.apache.poi.util.CodePageUtil; import org.apache.poi.util.CodePageUtil;
@ -377,15 +381,18 @@ public class Property {
*/ */
@Override @Override
public String toString() { public String toString() {
return toString(Property.DEFAULT_CODEPAGE); return toString(Property.DEFAULT_CODEPAGE, null);
} }
public String toString(int codepage) { public String toString(int codepage, PropertyIDMap idMap) {
final StringBuffer b = new StringBuffer(); final StringBuilder b = new StringBuilder();
b.append("Property["); b.append("Property[");
b.append("id: "); b.append("id: ");
b.append(getID()); b.append(id);
String idName = getNameFromID(); String idName = (idMap == null) ? null : idMap.get(id);
if (idName == null) {
idName = PropertyIDMap.getFallbackProperties().get(id);
}
if (idName != null) { if (idName != null) {
b.append(" ("); b.append(" (");
b.append(idName); b.append(idName);
@ -399,6 +406,7 @@ public class Property {
final Object value = getValue(); final Object value = getValue();
b.append(", value: "); b.append(", value: ");
if (value instanceof String) { if (value instanceof String) {
b.append((String)value);
b.append("\n"); b.append("\n");
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
try { try {
@ -407,13 +415,11 @@ public class Property {
LOG.log(POILogger.WARN, "can't serialize string", e); LOG.log(POILogger.WARN, "can't serialize string", e);
} }
b.append(" [");
// skip length field // skip length field
if(bos.size() > 2*LittleEndianConsts.INT_SIZE) { if(bos.size() > 2*LittleEndianConsts.INT_SIZE) {
final String hex = HexDump.dump(bos.toByteArray(), -2*LittleEndianConsts.INT_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(hex);
} }
b.append("]");
} else if (value instanceof byte[]) { } else if (value instanceof byte[]) {
b.append("\n"); b.append("\n");
byte[] bytes = (byte[])value; byte[] bytes = (byte[])value;
@ -421,7 +427,32 @@ public class Property {
String hex = HexDump.dump(bytes, 0L, 0); String hex = HexDump.dump(bytes, 0L, 0);
b.append(hex); 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"); b.append("null");
} else { } else {
b.append(value.toString()); b.append(value.toString());
@ -458,45 +489,6 @@ public class Property {
return null; 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. * Writes the property to an output stream.
* *

View File

@ -871,7 +871,7 @@ public class PropertySet {
b.append(sectionCount); b.append(sectionCount);
b.append(", sections: [\n"); b.append(", sections: [\n");
for (Section section: getSections()) { for (Section section: getSections()) {
b.append(section); b.append(section.toString(getPropertySetIDMap()));
} }
b.append(']'); b.append(']');
b.append(']'); b.append(']');

View File

@ -55,29 +55,19 @@ public class Section {
* The section's format ID, {@link #getFormatID}. * The section's format ID, {@link #getFormatID}.
*/ */
private ClassID formatID; 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 * Contains the bytes making out the section. This byte array is
* established when the section's size is calculated and can be reused * 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. * The offset of the section in the stream.
*/ */
private final long _offset; private final long _offset;
/**
* The section's size in bytes.
*/
private int size;
/** /**
* This section's properties. * This section's properties.
*/ */
@ -126,7 +116,6 @@ public class Section {
* @exception UnsupportedEncodingException if the section's codepage is not * @exception UnsupportedEncodingException if the section's codepage is not
* supported. * supported.
*/ */
@SuppressWarnings("unchecked")
public Section(final byte[] src, final int offset) throws UnsupportedEncodingException { public Section(final byte[] src, final int offset) throws UnsupportedEncodingException {
/* /*
* Read the format ID. * Read the format ID.
@ -154,7 +143,7 @@ public class Section {
/* /*
* Read the section length. * Read the section length.
*/ */
size = (int)leis.readUInt(); int size = (int)Math.min(leis.readUInt(), src.length-_offset);
/* /*
* Read the number of properties. * Read the number of properties.
@ -213,6 +202,7 @@ public class Section {
/* Read the codepage number. */ /* Read the codepage number. */
codepage = leis.readUShort(); codepage = leis.readUShort();
setCodepage(codepage);
} }
@ -222,6 +212,10 @@ public class Section {
long off = me.getKey(); long off = me.getKey();
long id = me.getValue(); long id = me.getValue();
if (id == PropertyIDMap.PID_CODEPAGE) {
continue;
}
int pLen = propLen(offset2Id, off, size); int pLen = propLen(offset2Id, off, size);
leis.setReadIndex((int)(this._offset + off)); leis.setReadIndex((int)(this._offset + off));
@ -239,12 +233,13 @@ public class Section {
LOG.log(POILogger.INFO, "Dictionary fallback failed - ignoring property"); LOG.log(POILogger.INFO, "Dictionary fallback failed - ignoring property");
} }
}; };
} else if (id == PropertyIDMap.PID_CODEPAGE) {
setCodepage(codepage);
} else { } else {
setProperty(new MutableProperty(id, leis, pLen, codepage)); 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) { public void setProperties(final Property[] properties) {
this.properties.clear(); this.properties.clear();
for (Property p : properties) { 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()); Property old = properties.get(p.getID());
if (old == null || !old.equals(p)) { if (old == null || !old.equals(p)) {
properties.put(p.getID(), p); properties.put(p.getID(), p);
dirty = true; sectionBytes.reset();
} }
} }
@ -543,17 +537,17 @@ public class Section {
* @return the section's size in bytes. * @return the section's size in bytes.
*/ */
public int getSize() { public int getSize() {
if (dirty) { int size = sectionBytes.size();
try { if (size > 0) {
size = calcSize(); return size;
dirty = false; }
} catch (HPSFRuntimeException ex) { try {
throw ex; return calcSize();
} catch (Exception ex) { } catch (HPSFRuntimeException ex) {
throw new HPSFRuntimeException(ex); throw ex;
} } catch (Exception ex) {
throw new HPSFRuntimeException(ex);
} }
return size;
} }
/** /**
@ -566,16 +560,19 @@ public class Section {
* @throws IOException * @throws IOException
*/ */
private int calcSize() throws WritingNotSupportedException, IOException { private int calcSize() throws WritingNotSupportedException, IOException {
final ByteArrayOutputStream out = new ByteArrayOutputStream(); sectionBytes.reset();
write(out); write(sectionBytes);
out.close(); padSectionBytes();
/* Pad to multiple of 4 bytes so that even the Windows shell (explorer) return sectionBytes.size();
* shows custom properties. */
sectionBytes = Util.pad4(out.toByteArray());
return sectionBytes.length;
} }
private void padSectionBytes() {
byte[] padArray = { 0, 0, 0 };
/* Pad to multiple of 4 bytes so that even the Windows shell (explorer)
* shows custom properties. */
int pad = (4 - (sectionBytes.size() & 0x3)) & 0x3;
sectionBytes.write(padArray, 0, pad);
}
/** /**
@ -623,12 +620,8 @@ public class Section {
* Removes all properties from the section including 0 (dictionary) and * Removes all properties from the section including 0 (dictionary) and
* 1 (codepage). * 1 (codepage).
*/ */
public void clear() public void clear() {
{ for (Property p : getProperties()) {
final Property[] properties = getProperties();
for (int i = 0; i < properties.length; i++)
{
final Property p = properties[i];
removeProperty(p.getID()); removeProperty(p.getID());
} }
} }
@ -639,17 +632,17 @@ public class Section {
* *
* <ul> * <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, * <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 * <li>The properties have different values. The order of the properties
* is irrelevant.</li> * is irrelevant.
* *
* </ul> * </ul>
* *
@ -695,7 +688,9 @@ public class Section {
* @param id The ID of the property to be removed * @param id The ID of the property to be removed
*/ */
public void removeProperty(final long id) { 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 { public int write(final OutputStream out) throws WritingNotSupportedException, IOException {
/* Check whether we have already generated the bytes making out the /* Check whether we have already generated the bytes making out the
* section. */ * section. */
if (!dirty && sectionBytes != null) { if (sectionBytes.size() > 0) {
out.write(sectionBytes); sectionBytes.writeTo(out);
return sectionBytes.length; return sectionBytes.size();
} }
/* Writing the section's dictionary it tricky. If there is a dictionary /* Writing the section's dictionary it tricky. If there is a dictionary
@ -971,6 +966,10 @@ public class Section {
*/ */
@Override @Override
public String toString() { public String toString() {
return toString(null);
}
public String toString(PropertyIDMap idMap) {
final StringBuffer b = new StringBuffer(); final StringBuffer b = new StringBuffer();
final Property[] pa = getProperties(); final Property[] pa = getProperties();
b.append("\n\n\n"); b.append("\n\n\n");
@ -990,7 +989,7 @@ public class Section {
codepage = Property.DEFAULT_CODEPAGE; codepage = Property.DEFAULT_CODEPAGE;
} }
for (Property p : pa) { for (Property p : pa) {
b.append(p.toString(codepage)); b.append(p.toString(codepage, idMap));
b.append(",\n"); b.append(",\n");
} }
b.append(']'); b.append(']');

View File

@ -351,7 +351,7 @@ public final class SummaryInformation extends SpecialPropertySet {
if (d == null) { if (d == null) {
return 0; 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. * @param time The time to set.
*/ */
public void setEditTime(final long time) { 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); getFirstSection().setProperty(PropertyIDMap.PID_EDITTIME, Variant.VT_FILETIME, d);
} }

View File

@ -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();
}
}
}

View File

@ -207,7 +207,7 @@ public class VariantSupport extends Variant {
case Variant.VT_FILETIME: case Variant.VT_FILETIME:
Filetime filetime = (Filetime) typedPropertyValue.getValue(); Filetime filetime = (Filetime) typedPropertyValue.getValue();
return Util.filetimeToDate( (int) filetime.getHigh(), (int) filetime.getLow() ); return filetime.getJavaValue();
case Variant.VT_LPSTR: case Variant.VT_LPSTR:
CodePageString cpString = (CodePageString) typedPropertyValue.getValue(); CodePageString cpString = (CodePageString) typedPropertyValue.getValue();
@ -413,13 +413,8 @@ public class VariantSupport extends Variant {
break; break;
case Variant.VT_FILETIME: case Variant.VT_FILETIME:
if (value instanceof Date) { Filetime filetimeValue = (value instanceof Date) ? new Filetime((Date)value) : new Filetime();
long filetime = Util.dateToFileTime((Date) value); length = filetimeValue.write( out );
int high = (int) ((filetime >> 32) & 0x00000000FFFFFFFFL);
int low = (int) (filetime & 0x00000000FFFFFFFFL);
Filetime filetimeValue = new Filetime( low, high);
length = filetimeValue.write( out );
}
break; break;
default: default:

View File

@ -17,9 +17,14 @@
package org.apache.poi.hpsf.wellknown; package org.apache.poi.hpsf.wellknown;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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 * 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 * should treat them as unmodifiable, copy them and modifiy the
* copies. * 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 * The following definitions are for property IDs in the first
@ -317,6 +322,26 @@ public class PropertyIDMap extends HashMap<Long,String> {
* details! * details!
*/ */
private static PropertyIDMap summaryInformationProperties; 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 * Contains the summary information property ID values and
@ -324,144 +349,181 @@ public class PropertyIDMap extends HashMap<Long,String> {
* details! * details!
*/ */
private static PropertyIDMap documentSummaryInformationProperties; 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}. * Contains the fallback property ID values and associated strings.
* * This is only used for lookups and not for initializing a property set
* @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) private static PropertyIDMap fallbackProperties;
{ private static final Object[][] fallbackIdValues = {
super(initialCapacity, loadFactor); { (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. * Creates a {@link PropertyIDMap} backed by another map.
* *
* @param map The instance to be created is backed by this map. * @param map The instance to be created is backed by this map.
*/ */
public PropertyIDMap(final Map<Long,String> map) private PropertyIDMap(Object[][] idValues) {
{ Map<Long,String> m = new HashMap<Long,String>(idValues.length);
super(map); for (Object[] idValue : idValues) {
m.put((Long)idValue[0], (String)idValue[1]);
}
idMap = Collections.unmodifiableMap(m);
} }
/**
* 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);
}
/**
* 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));
}
/** /**
* @return the Summary Information properties singleton * @return the Summary Information properties singleton
*/ */
public static synchronized PropertyIDMap getSummaryInformationProperties() public static synchronized PropertyIDMap getSummaryInformationProperties() {
{ if (summaryInformationProperties == null) {
if (summaryInformationProperties == null) summaryInformationProperties = new PropertyIDMap(summaryInformationIdValues);
{
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; return summaryInformationProperties;
} }
/** /**
* Returns the Document Summary Information properties
* singleton.
*
* @return The Document Summary Information properties singleton. * @return The Document Summary Information properties singleton.
*/ */
public static synchronized PropertyIDMap getDocumentSummaryInformationProperties() public static synchronized PropertyIDMap getDocumentSummaryInformationProperties() {
{ if (documentSummaryInformationProperties == null) {
if (documentSummaryInformationProperties == null) documentSummaryInformationProperties = new PropertyIDMap(documentSummaryInformationIdValues);
{
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; return documentSummaryInformationProperties;
} }
/**
* 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);
}
@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);
}
@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();
}
@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. * For the most basic testing.
* *
* @param args The command-line arguments * @param args The command-line arguments
*/ */
public static void main(final String[] args) public static void main(final String[] args) {
{
PropertyIDMap s1 = getSummaryInformationProperties(); PropertyIDMap s1 = getSummaryInformationProperties();
PropertyIDMap s2 = getDocumentSummaryInformationProperties(); PropertyIDMap s2 = getDocumentSummaryInformationProperties();
System.out.println("s1: " + s1); System.out.println("s1: " + s1);

View File

@ -25,7 +25,7 @@ import java.util.Locale;
import org.apache.poi.hmef.Attachment; import org.apache.poi.hmef.Attachment;
import org.apache.poi.hmef.HMEFMessage; import org.apache.poi.hmef.HMEFMessage;
import org.apache.poi.hpsf.Util; import org.apache.poi.hpsf.Filetime;
import org.apache.poi.hsmf.datatypes.MAPIProperty; import org.apache.poi.hsmf.datatypes.MAPIProperty;
import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LocaleUtil; import org.apache.poi.util.LocaleUtil;
@ -53,7 +53,7 @@ public final class MAPIDateAttribute extends MAPIAttribute {
super(property, type, data); super(property, type, data);
// The value is a 64 bit Windows Filetime // The value is a 64 bit Windows Filetime
this.data = Util.filetimeToDate( this.data = Filetime.filetimeToDate(
LittleEndian.getLong(data, 0) LittleEndian.getLong(data, 0)
); );
} }

View File

@ -28,7 +28,7 @@ import java.util.Locale;
import org.apache.poi.hmef.Attachment; import org.apache.poi.hmef.Attachment;
import org.apache.poi.hmef.HMEFMessage; import org.apache.poi.hmef.HMEFMessage;
import org.apache.poi.hpsf.Util; import org.apache.poi.hpsf.Filetime;
import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LocaleUtil; import org.apache.poi.util.LocaleUtil;
import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogFactory;
@ -52,7 +52,7 @@ public final class TNEFDateAttribute extends TNEFAttribute {
byte[] binData = getData(); byte[] binData = getData();
if(binData.length == 8) { if(binData.length == 8) {
// The value is a 64 bit Windows Filetime // The value is a 64 bit Windows Filetime
this.data = Util.filetimeToDate( this.data = Filetime.filetimeToDate(
LittleEndian.getLong(getData(), 0) LittleEndian.getLong(getData(), 0)
); );
} else if(binData.length == 14) { } else if(binData.length == 14) {

View File

@ -17,7 +17,6 @@
package org.apache.poi.hpsf.basic; package org.apache.poi.hpsf.basic;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -28,10 +27,13 @@ import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.List; import java.util.List;
import org.apache.poi.POIDataSamples; import org.apache.poi.POIDataSamples;
import org.apache.poi.hpsf.ClassID;
import org.apache.poi.hpsf.DocumentSummaryInformation; import org.apache.poi.hpsf.DocumentSummaryInformation;
import org.apache.poi.hpsf.Filetime;
import org.apache.poi.hpsf.HPSFException; import org.apache.poi.hpsf.HPSFException;
import org.apache.poi.hpsf.MarkUnsupportedException; import org.apache.poi.hpsf.MarkUnsupportedException;
import org.apache.poi.hpsf.NoPropertySetStreamException; import org.apache.poi.hpsf.NoPropertySetStreamException;
@ -48,33 +50,24 @@ import org.junit.Test;
*/ */
public final class TestBasic { public final class TestBasic {
private static final String POI_FS = "TestGermanWord90.doc"; private static final POIDataSamples samples = POIDataSamples.getHPSFInstance();
private static final String[] POI_FILES = new String[]
{
"\005SummaryInformation",
"\005DocumentSummaryInformation",
"WordDocument",
"\001CompObj",
"1Table"
};
private static final int BYTE_ORDER = 0xfffe;
private static final int FORMAT = 0x0000;
private static final int OS_VERSION = 0x00020A04;
private static final byte[] CLASS_ID =
{
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
};
private static final int[] SECTION_COUNT =
{1, 2};
private static final boolean[] IS_SUMMARY_INFORMATION =
{true, false};
private static final boolean[] IS_DOCUMENT_SUMMARY_INFORMATION =
{false, true};
private POIFile[] poiFiles; private static final String[] POI_FILES = {
"\005SummaryInformation",
"\005DocumentSummaryInformation",
"WordDocument",
"\001CompObj",
"1Table"
};
private static final int BYTE_ORDER = 0xfffe;
private static final int FORMAT = 0x0000;
private static final int OS_VERSION = 0x00020A04;
private static final ClassID CLASS_ID = new ClassID("{00000000-0000-0000-0000-000000000000}");
private static final int[] SECTION_COUNT = {1, 2};
private static final boolean[] IS_SUMMARY_INFORMATION = {true, false};
private static final boolean[] IS_DOCUMENT_SUMMARY_INFORMATION = {false, true};
private List<POIFile> poiFiles;
/** /**
@ -84,10 +77,8 @@ public final class TestBasic {
* @exception IOException if any other I/O exception occurs. * @exception IOException if any other I/O exception occurs.
*/ */
@Before @Before
public void setUp() throws IOException public void setUp() throws IOException {
{ final File data = samples.getFile("TestGermanWord90.doc");
POIDataSamples samples = POIDataSamples.getHPSFInstance();
final File data = samples.getFile(POI_FS);
poiFiles = Util.readPOIFiles(data); poiFiles = Util.readPOIFiles(data);
} }
@ -96,11 +87,11 @@ public final class TestBasic {
* are expected to be in a certain order.</p> * are expected to be in a certain order.</p>
*/ */
@Test @Test
public void testReadFiles() public void testReadFiles() {
{
String[] expected = POI_FILES; String[] expected = POI_FILES;
for (int i = 0; i < expected.length; i++) for (int i = 0; i < expected.length; i++) {
assertEquals(poiFiles[i].getName(), expected[i]); assertEquals(poiFiles.get(i).getName(), expected[i]);
}
} }
/** /**
@ -119,30 +110,22 @@ public final class TestBasic {
*/ */
@Test @Test
public void testCreatePropertySets() public void testCreatePropertySets()
throws UnsupportedEncodingException, IOException throws UnsupportedEncodingException, IOException {
{ Class<?>[] expected = {
Class<?>[] expected = new Class[] SummaryInformation.class,
{ DocumentSummaryInformation.class,
SummaryInformation.class, NoPropertySetStreamException.class,
DocumentSummaryInformation.class, NoPropertySetStreamException.class,
NoPropertySetStreamException.class, NoPropertySetStreamException.class
NoPropertySetStreamException.class, };
NoPropertySetStreamException.class for (int i = 0; i < expected.length; i++) {
}; InputStream in = new ByteArrayInputStream(poiFiles.get(i).getBytes());
for (int i = 0; i < expected.length; i++)
{
InputStream in = new ByteArrayInputStream(poiFiles[i].getBytes());
Object o; Object o;
try try {
{
o = PropertySetFactory.create(in); o = PropertySetFactory.create(in);
} } catch (NoPropertySetStreamException ex) {
catch (NoPropertySetStreamException ex)
{
o = ex; o = ex;
} } catch (MarkUnsupportedException ex) {
catch (MarkUnsupportedException ex)
{
o = ex; o = ex;
} }
in.close(); in.close();
@ -159,17 +142,15 @@ public final class TestBasic {
* @exception HPSFException if any HPSF exception occurs * @exception HPSFException if any HPSF exception occurs
*/ */
@Test @Test
public void testPropertySetMethods() throws IOException, HPSFException public void testPropertySetMethods() throws IOException, HPSFException {
{
/* Loop over the two property sets. */ /* Loop over the two property sets. */
for (int i = 0; i < 2; i++) for (int i = 0; i < 2; i++) {
{ byte[] b = poiFiles.get(i).getBytes();
byte[] b = poiFiles[i].getBytes();
PropertySet ps = PropertySetFactory.create(new ByteArrayInputStream(b)); PropertySet ps = PropertySetFactory.create(new ByteArrayInputStream(b));
assertEquals(BYTE_ORDER, ps.getByteOrder()); assertEquals(BYTE_ORDER, ps.getByteOrder());
assertEquals(FORMAT, ps.getFormat()); assertEquals(FORMAT, ps.getFormat());
assertEquals(OS_VERSION, ps.getOSVersion()); assertEquals(OS_VERSION, ps.getOSVersion());
assertArrayEquals(CLASS_ID, ps.getClassID().getBytes()); assertEquals(CLASS_ID, ps.getClassID());
assertEquals(SECTION_COUNT[i], ps.getSectionCount()); assertEquals(SECTION_COUNT[i], ps.getSectionCount());
assertEquals(IS_SUMMARY_INFORMATION[i], ps.isSummaryInformation()); assertEquals(IS_SUMMARY_INFORMATION[i], ps.isSummaryInformation());
assertEquals(IS_DOCUMENT_SUMMARY_INFORMATION[i], ps.isDocumentSummaryInformation()); assertEquals(IS_DOCUMENT_SUMMARY_INFORMATION[i], ps.isDocumentSummaryInformation());
@ -185,11 +166,9 @@ public final class TestBasic {
* @exception HPSFException if any HPSF exception occurs * @exception HPSFException if any HPSF exception occurs
*/ */
@Test @Test
public void testSectionMethods() throws IOException, HPSFException public void testSectionMethods() throws IOException, HPSFException {
{ InputStream is = new ByteArrayInputStream(poiFiles.get(0).getBytes());
final SummaryInformation si = (SummaryInformation) final SummaryInformation si = (SummaryInformation)PropertySetFactory.create(is);
PropertySetFactory.create(new ByteArrayInputStream
(poiFiles[0].getBytes()));
final List<Section> sections = si.getSections(); final List<Section> sections = si.getSections();
final Section s = sections.get(0); final Section s = sections.get(0);
assertEquals(s.getFormatID(), SectionIDMap.SUMMARY_INFORMATION_ID); assertEquals(s.getFormatID(), SectionIDMap.SUMMARY_INFORMATION_ID);
@ -198,4 +177,16 @@ public final class TestBasic {
assertEquals("Titel", s.getProperty(2)); assertEquals("Titel", s.getProperty(2));
assertEquals(1764, s.getSize()); assertEquals(1764, s.getSize());
} }
@Test
public void bug52117LastPrinted() throws IOException, HPSFException {
File f = samples.getFile("TestBug52117.doc");
POIFile poiFile = Util.readPOIFiles(f, new String[]{POI_FILES[0]}).get(0);
InputStream in = new ByteArrayInputStream(poiFile.getBytes());
SummaryInformation si = (SummaryInformation)PropertySetFactory.create(in);
Date lastPrinted = si.getLastPrinted();
long editTime = si.getEditTime();
assertTrue(Filetime.isUndefined(lastPrinted));
assertEquals(1800000000L, editTime);
}
} }

View File

@ -17,14 +17,17 @@
package org.apache.poi.hpsf.basic; package org.apache.poi.hpsf.basic;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.List;
import junit.framework.TestCase;
import org.apache.poi.POIDataSamples; import org.apache.poi.POIDataSamples;
import org.apache.poi.hpsf.DocumentSummaryInformation; import org.apache.poi.hpsf.DocumentSummaryInformation;
@ -35,27 +38,29 @@ import org.apache.poi.hpsf.PropertySet;
import org.apache.poi.hpsf.PropertySetFactory; import org.apache.poi.hpsf.PropertySetFactory;
import org.apache.poi.hpsf.SummaryInformation; import org.apache.poi.hpsf.SummaryInformation;
import org.apache.poi.hpsf.Variant; import org.apache.poi.hpsf.Variant;
import org.junit.Before;
import org.junit.Test;
/** /**
* <p>Test case for OLE2 files with empty properties. An empty property's type * Test case for OLE2 files with empty properties.
* is {@link Variant#VT_EMPTY}.</p> * An empty property's type is {@link Variant#VT_EMPTY}.
*/ */
public final class TestEmptyProperties extends TestCase { public final class TestEmptyProperties {
private static final POIDataSamples samples = POIDataSamples.getHPSFInstance();
/** /**
* <p>This test file's summary information stream contains some empty * This test file's summary information stream contains some empty properties.
* properties.</p>
*/ */
private static final String POI_FS = "TestCorel.shw"; private static final String POI_FS = "TestCorel.shw";
private static final String[] POI_FILES = new String[] private static final String[] POI_FILES = {
{ "PerfectOffice_MAIN",
"PerfectOffice_MAIN", "\005SummaryInformation",
"\005SummaryInformation", "Main"
"Main" };
};
private POIFile[] poiFiles; private List<POIFile> poiFiles;
/** /**
* <p>Read a the test file from the "data" directory.</p> * <p>Read a the test file from the "data" directory.</p>
@ -64,24 +69,21 @@ public final class TestEmptyProperties extends TestCase {
* does not exist * does not exist
* @exception IOException if an I/O exception occurs * @exception IOException if an I/O exception occurs
*/ */
@Override @Before
public void setUp() throws FileNotFoundException, IOException public void setUp() throws IOException {
{
POIDataSamples samples = POIDataSamples.getHPSFInstance();
final File data = samples.getFile(POI_FS); final File data = samples.getFile(POI_FS);
poiFiles = Util.readPOIFiles(data); poiFiles = Util.readPOIFiles(data);
} }
/** /**
* <p>Checks the names of the files in the POI filesystem. They * Checks the names of the files in the POI filesystem. They
* are expected to be in a certain order.</p> * are expected to be in a certain order.
*/ */
public void testReadFiles() @Test
{ public void testReadFiles() {
String[] expected = POI_FILES; String[] expected = POI_FILES;
for (int i = 0; i < expected.length; i++) for (int i = 0; i < expected.length; i++)
assertEquals(poiFiles[i].getName(), expected[i]); assertEquals(poiFiles.get(i).getName(), expected[i]);
} }
/** /**
@ -98,29 +100,22 @@ public final class TestEmptyProperties extends TestCase {
* @exception UnsupportedEncodingException if a character encoding is not * @exception UnsupportedEncodingException if a character encoding is not
* supported. * supported.
*/ */
@Test
public void testCreatePropertySets() public void testCreatePropertySets()
throws UnsupportedEncodingException, IOException throws UnsupportedEncodingException, IOException {
{ Class<?>[] expected = {
Class<?>[] expected = new Class[] NoPropertySetStreamException.class,
{ SummaryInformation.class,
NoPropertySetStreamException.class, NoPropertySetStreamException.class
SummaryInformation.class, };
NoPropertySetStreamException.class for (int i = 0; i < expected.length; i++) {
}; InputStream in = new ByteArrayInputStream(poiFiles.get(i).getBytes());
for (int i = 0; i < expected.length; i++)
{
InputStream in = new ByteArrayInputStream(poiFiles[i].getBytes());
Object o; Object o;
try try {
{
o = PropertySetFactory.create(in); o = PropertySetFactory.create(in);
} } catch (NoPropertySetStreamException ex) {
catch (NoPropertySetStreamException ex)
{
o = ex; o = ex;
} } catch (MarkUnsupportedException ex) {
catch (MarkUnsupportedException ex)
{
o = ex; o = ex;
} }
in.close(); in.close();
@ -136,11 +131,10 @@ public final class TestEmptyProperties extends TestCase {
* @exception IOException if an I/O exception occurs * @exception IOException if an I/O exception occurs
* @exception HPSFException if an HPSF operation fails * @exception HPSFException if an HPSF operation fails
*/ */
public void testPropertySetMethods() throws IOException, HPSFException @Test
{ public void testPropertySetMethods() throws IOException, HPSFException {
byte[] b = poiFiles[1].getBytes(); byte[] b = poiFiles.get(1).getBytes();
PropertySet ps = PropertySet ps = PropertySetFactory.create(new ByteArrayInputStream(b));
PropertySetFactory.create(new ByteArrayInputStream(b));
SummaryInformation s = (SummaryInformation) ps; SummaryInformation s = (SummaryInformation) ps;
assertNull(s.getTitle()); assertNull(s.getTitle());
assertNull(s.getSubject()); assertNull(s.getSubject());

View File

@ -17,13 +17,14 @@
package org.apache.poi.hpsf.basic; package org.apache.poi.hpsf.basic;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import junit.framework.TestCase;
import org.apache.poi.POIDataSamples; import org.apache.poi.POIDataSamples;
import org.apache.poi.hpsf.DocumentSummaryInformation; import org.apache.poi.hpsf.DocumentSummaryInformation;
import org.apache.poi.hpsf.HPSFException; import org.apache.poi.hpsf.HPSFException;
@ -32,30 +33,30 @@ import org.apache.poi.hpsf.PropertySetFactory;
import org.apache.poi.hpsf.Section; import org.apache.poi.hpsf.Section;
import org.apache.poi.hpsf.SummaryInformation; import org.apache.poi.hpsf.SummaryInformation;
import org.apache.poi.util.CodePageUtil; import org.apache.poi.util.CodePageUtil;
import org.junit.Before;
import org.junit.Test;
/** /**
* <p>Tests whether Unicode string can be read from a * Tests whether Unicode string can be read from a DocumentSummaryInformation.
* DocumentSummaryInformation.</p>
*/ */
public class TestUnicode extends TestCase { public class TestUnicode {
static final String POI_FS = "TestUnicode.xls"; static final String POI_FS = "TestUnicode.xls";
static final String[] POI_FILES = new String[] static final String[] POI_FILES = {
{ "\005DocumentSummaryInformation",
"\005DocumentSummaryInformation", };
};
File data; File data;
POIFile[] poiFiles; POIFile[] poiFiles;
/** /**
* <p>Read a the test file from the "data" directory.</p> * Read a the test file from the "data" directory.
* *
* @exception FileNotFoundException if the file to be read does not exist. * @exception FileNotFoundException if the file to be read does not exist.
* @exception IOException if any other I/O exception occurs * @exception IOException if any other I/O exception occurs
*/ */
@Override @Before
protected void setUp() { public void setUp() {
POIDataSamples samples = POIDataSamples.getHPSFInstance(); POIDataSamples samples = POIDataSamples.getHPSFInstance();
data = samples.getFile(POI_FS); data = samples.getFile(POI_FS);
} }
@ -63,31 +64,25 @@ public class TestUnicode extends TestCase {
/** /**
* <p>Tests the {@link PropertySet} methods. The test file has two * Tests the {@link PropertySet} methods. The test file has two
* property set: the first one is a {@link SummaryInformation}, * property set: the first one is a {@link SummaryInformation},
* the second one is a {@link DocumentSummaryInformation}.</p> * the second one is a {@link DocumentSummaryInformation}.
* *
* @exception IOException if an I/O exception occurs * @exception IOException if an I/O exception occurs
* @exception HPSFException if an HPSF exception occurs * @exception HPSFException if an HPSF exception occurs
*/ */
public void testPropertySetMethods() throws IOException, HPSFException @Test
{ public void testPropertySetMethods() throws IOException, HPSFException {
POIFile poiFile = Util.readPOIFiles(data, POI_FILES)[0]; POIFile poiFile = Util.readPOIFiles(data, POI_FILES).get(0);
byte[] b = poiFile.getBytes(); byte[] b = poiFile.getBytes();
PropertySet ps = PropertySet ps = PropertySetFactory.create(new ByteArrayInputStream(b));
PropertySetFactory.create(new ByteArrayInputStream(b));
assertTrue(ps.isDocumentSummaryInformation()); assertTrue(ps.isDocumentSummaryInformation());
assertEquals(ps.getSectionCount(), 2); assertEquals(ps.getSectionCount(), 2);
Section s = ps.getSections().get(1); Section s = ps.getSections().get(1);
assertEquals(s.getProperty(1), assertEquals(s.getProperty(1), CodePageUtil.CP_UTF16);
Integer.valueOf(CodePageUtil.CP_UTF16)); assertEquals(s.getProperty(2), -96070278);
assertEquals(s.getProperty(2), assertEquals(s.getProperty(3), "MCon_Info zu Office bei Schreiner");
Integer.valueOf(-96070278)); assertEquals(s.getProperty(4), "petrovitsch@schreiner-online.de");
assertEquals(s.getProperty(3), assertEquals(s.getProperty(5), "Petrovitsch, Wilhelm");
"MCon_Info zu Office bei Schreiner");
assertEquals(s.getProperty(4),
"petrovitsch@schreiner-online.de");
assertEquals(s.getProperty(5),
"Petrovitsch, Wilhelm");
} }
} }

View File

@ -237,7 +237,7 @@ public class TestWrite {
try { try {
psa[0] = PropertySetFactory.create(event.getStream()); psa[0] = PropertySetFactory.create(event.getStream());
} catch (Exception ex) { } catch (Exception ex) {
fail(org.apache.poi.hpsf.Util.toString(ex)); fail(ex.getMessage());
} }
}}, }},
SummaryInformation.DEFAULT_STREAM_NAME SummaryInformation.DEFAULT_STREAM_NAME
@ -340,7 +340,7 @@ public class TestWrite {
try { try {
PropertySetFactory.create(event.getStream()); PropertySetFactory.create(event.getStream());
} catch (Exception ex) { } catch (Exception ex) {
fail(org.apache.poi.hpsf.Util.toString(ex)); fail(ex.getMessage());
} }
} }
} }

View File

@ -18,17 +18,13 @@
package org.apache.poi.hpsf.basic; package org.apache.poi.hpsf.basic;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Properties;
import org.apache.poi.hpsf.PropertySet; import org.apache.poi.hpsf.PropertySet;
import org.apache.poi.poifs.eventfilesystem.POIFSReader; import org.apache.poi.poifs.eventfilesystem.POIFSReader;
@ -43,31 +39,6 @@ import org.apache.poi.util.IOUtils;
*/ */
final class Util { final class Util {
/**
* <p>Reads all files from a POI filesystem and returns them as an
* array of {@link POIFile} instances. This method loads all files
* into memory and thus does not cope well with large POI
* filessystems.</p>
*
* @param poiFs The name of the POI filesystem as seen by the
* operating system. (This is the "filename".)
*
* @return The POI files. The elements are ordered in the same way
* as the files in the POI filesystem.
*
* @exception FileNotFoundException if the file containing the POI
* filesystem does not exist
*
* @exception IOException if an I/O exception occurs
*/
public static POIFile[] readPOIFiles(final File poiFs)
throws FileNotFoundException, IOException
{
return readPOIFiles(poiFs, null);
}
/** /**
* <p>Reads a set of files from a POI filesystem and returns them * <p>Reads a set of files from a POI filesystem and returns them
* as an array of {@link POIFile} instances. This method loads all * as an array of {@link POIFile} instances. This method loads all
@ -87,42 +58,34 @@ final class Util {
* *
* @exception IOException if an I/O exception occurs * @exception IOException if an I/O exception occurs
*/ */
public static POIFile[] readPOIFiles(final File poiFs, public static List<POIFile> readPOIFiles(final File poiFs, final String... poiFiles)
final String[] poiFiles) throws FileNotFoundException, IOException {
throws FileNotFoundException, IOException
{
final List<POIFile> files = new ArrayList<POIFile>(); final List<POIFile> files = new ArrayList<POIFile>();
POIFSReader r = new POIFSReader(); POIFSReader r = new POIFSReader();
POIFSReaderListener pfl = new POIFSReaderListener() POIFSReaderListener pfl = new POIFSReaderListener() {
{
@Override @Override
public void processPOIFSReaderEvent(final POIFSReaderEvent event) public void processPOIFSReaderEvent(final POIFSReaderEvent event) {
{ try {
try
{
final POIFile f = new POIFile(); final POIFile f = new POIFile();
f.setName(event.getName()); f.setName(event.getName());
f.setPath(event.getPath()); f.setPath(event.getPath());
final InputStream in = event.getStream(); final InputStream in = event.getStream();
final ByteArrayOutputStream out = f.setBytes(IOUtils.toByteArray(in));
new ByteArrayOutputStream(); in.close();
IOUtils.copy(in, out);
out.close();
f.setBytes(out.toByteArray());
files.add(f); files.add(f);
} } catch (IOException ex) {
catch (IOException ex)
{
throw new RuntimeException(ex); throw new RuntimeException(ex);
} }
} }
}; };
if (poiFiles == null) if (poiFiles.length == 0) {
/* Register the listener for all POI files. */ /* Register the listener for all POI files. */
r.registerListener(pfl); r.registerListener(pfl);
else } else {
for (String poiFile : poiFiles) for (String poiFile : poiFiles) {
r.registerListener(pfl, poiFile); r.registerListener(pfl, poiFile);
}
}
/* Read the POI filesystem. */ /* Read the POI filesystem. */
FileInputStream stream = new FileInputStream(poiFs); FileInputStream stream = new FileInputStream(poiFs);
@ -131,10 +94,7 @@ final class Util {
} finally { } finally {
stream.close(); stream.close();
} }
POIFile[] result = new POIFile[files.size()]; return files;
for (int i = 0; i < result.length; i++)
result[i] = files.get(i);
return result;
} }
@ -155,18 +115,7 @@ final class Util {
* *
* @exception IOException if an I/O exception occurs * @exception IOException if an I/O exception occurs
*/ */
public static List<POIFile> readPropertySets(final File poiFs) public static List<POIFile> readPropertySets(final File poiFs) throws IOException {
throws FileNotFoundException, IOException {
FileInputStream stream = new FileInputStream(poiFs);
try {
return readPropertySets(stream);
} finally {
stream.close();
}
}
public static List<POIFile> readPropertySets(final InputStream poiFs)
throws FileNotFoundException, IOException {
final List<POIFile> files = new ArrayList<POIFile>(7); final List<POIFile> files = new ArrayList<POIFile>(7);
final POIFSReader r = new POIFSReader(); final POIFSReader r = new POIFSReader();
POIFSReaderListener pfl = new POIFSReaderListener() { POIFSReaderListener pfl = new POIFSReaderListener() {
@ -191,28 +140,13 @@ final class Util {
r.registerListener(pfl); r.registerListener(pfl);
/* Read the POI filesystem. */ /* Read the POI filesystem. */
r.read(poiFs); InputStream is = new FileInputStream(poiFs);
try {
r.read(is);
} finally {
is.close();
}
return files; return files;
} }
/**
* <p>Prints the system properties to System.out.</p>
*/
public static void printSystemProperties()
{
final Properties p = System.getProperties();
final List<String> names = new LinkedList<String>();
for (String name : p.stringPropertyNames())
names.add(name);
Collections.sort(names);
for (String name : names) {
String value = p.getProperty(name);
System.out.println(name + ": " + value);
}
System.out.println("Current directory: " +
System.getProperty("user.dir"));
}
} }