001: /* ====================================================================
002: Licensed to the Apache Software Foundation (ASF) under one or more
003: contributor license agreements. See the NOTICE file distributed with
004: this work for additional information regarding copyright ownership.
005: The ASF licenses this file to You under the Apache License, Version 2.0
006: (the "License"); you may not use this file except in compliance with
007: the License. You may obtain a copy of the License at
008:
009: http://www.apache.org/licenses/LICENSE-2.0
010:
011: Unless required by applicable law or agreed to in writing, software
012: distributed under the License is distributed on an "AS IS" BASIS,
013: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: See the License for the specific language governing permissions and
015: limitations under the License.
016: ==================================================================== */
017:
018: package org.apache.poi.hpsf.examples;
019:
020: import java.io.ByteArrayInputStream;
021: import java.io.ByteArrayOutputStream;
022: import java.io.File;
023: import java.io.FileInputStream;
024: import java.io.FileNotFoundException;
025: import java.io.FileOutputStream;
026: import java.io.IOException;
027: import java.io.InputStream;
028: import java.io.OutputStream;
029: import java.io.UnsupportedEncodingException;
030: import java.util.HashMap;
031: import java.util.Iterator;
032: import java.util.Map;
033:
034: import org.apache.poi.hpsf.HPSFRuntimeException;
035: import org.apache.poi.hpsf.MarkUnsupportedException;
036: import org.apache.poi.hpsf.MutablePropertySet;
037: import org.apache.poi.hpsf.NoPropertySetStreamException;
038: import org.apache.poi.hpsf.PropertySet;
039: import org.apache.poi.hpsf.PropertySetFactory;
040: import org.apache.poi.hpsf.Util;
041: import org.apache.poi.hpsf.WritingNotSupportedException;
042: import org.apache.poi.poifs.eventfilesystem.POIFSReader;
043: import org.apache.poi.poifs.eventfilesystem.POIFSReaderEvent;
044: import org.apache.poi.poifs.eventfilesystem.POIFSReaderListener;
045: import org.apache.poi.poifs.filesystem.DirectoryEntry;
046: import org.apache.poi.poifs.filesystem.DocumentEntry;
047: import org.apache.poi.poifs.filesystem.DocumentInputStream;
048: import org.apache.poi.poifs.filesystem.Entry;
049: import org.apache.poi.poifs.filesystem.POIFSDocumentPath;
050: import org.apache.poi.poifs.filesystem.POIFSFileSystem;
051: import org.apache.poi.util.TempFile;
052:
053: /**
054: * <p>This class copies a POI file system to a new file and compares the copy
055: * with the original.</p>
056: *
057: * <p>Property set streams are copied logically, i.e. the application
058: * establishes a {@link org.apache.poi.hpsf.PropertySet} of an original property
059: * set, creates a {@link org.apache.poi.hpsf.MutablePropertySet} from the
060: * {@link org.apache.poi.hpsf.PropertySet} and writes the
061: * {@link org.apache.poi.hpsf.MutablePropertySet} to the destination POI file
062: * system. - Streams which are no property set streams are copied bit by
063: * bit.</p>
064: *
065: * <p>The comparison of the POI file systems is done logically. That means that
066: * the two disk files containing the POI file systems do not need to be
067: * exactly identical. However, both POI file systems must contain the same
068: * files, and most of these files must be bitwise identical. Property set
069: * streams, however, are compared logically: they must have the same sections
070: * with the same attributs, and the sections must contain the same properties.
071: * Details like the ordering of the properties do not matter.</p>
072: *
073: * @author Rainer Klute <a
074: * href="mailto:klute@rainer-klute.de"><klute@rainer-klute.de></a>
075: * @version $Id: CopyCompare.java 489730 2006-12-22 19:18:16Z bayard $
076: * @since 2003-09-19
077: */
078: public class CopyCompare {
079: /**
080: * <p>Runs the example program. The application expects one or two
081: * arguments:</p>
082: *
083: * <ol>
084: *
085: * <li><p>The first argument is the disk file name of the POI filesystem to
086: * copy.</p></li>
087: *
088: * <li><p>The second argument is optional. If it is given, it is the name of
089: * a disk file the copy of the POI filesystem will be written to. If it is
090: * not given, the copy will be written to a temporary file which will be
091: * deleted at the end of the program.</p></li>
092: *
093: * </ol>
094: *
095: * @param args Command-line arguments.
096: * @exception MarkUnsupportedException if a POI document stream does not
097: * support the mark() operation.
098: * @exception NoPropertySetStreamException if the application tries to
099: * create a property set from a POI document stream that is not a property
100: * set stream.
101: * @exception IOException if any I/O exception occurs.
102: * @exception UnsupportedEncodingException if a character encoding is not
103: * supported.
104: */
105: public static void main(final String[] args)
106: throws NoPropertySetStreamException,
107: MarkUnsupportedException, UnsupportedEncodingException,
108: IOException {
109: String originalFileName = null;
110: String copyFileName = null;
111:
112: /* Check the command-line arguments. */
113: if (args.length == 1) {
114: originalFileName = args[0];
115: File f = TempFile.createTempFile("CopyOfPOIFileSystem-",
116: ".ole2");
117: f.deleteOnExit();
118: copyFileName = f.getAbsolutePath();
119: } else if (args.length == 2) {
120: originalFileName = args[0];
121: copyFileName = args[1];
122: } else {
123: System.err.println("Usage: " + CopyCompare.class.getName()
124: + "originPOIFS [copyPOIFS]");
125: System.exit(1);
126: }
127:
128: /* Read the origin POIFS using the eventing API. The real work is done
129: * in the class CopyFile which is registered here as a POIFSReader. */
130: final POIFSReader r = new POIFSReader();
131: final CopyFile cf = new CopyFile(copyFileName);
132: r.registerListener(cf);
133: r.read(new FileInputStream(originalFileName));
134:
135: /* Write the new POIFS to disk. */
136: cf.close();
137:
138: /* Read all documents from the original POI file system and compare them
139: * with the equivalent document from the copy. */
140: final POIFSFileSystem opfs = new POIFSFileSystem(
141: new FileInputStream(originalFileName));
142: final POIFSFileSystem cpfs = new POIFSFileSystem(
143: new FileInputStream(copyFileName));
144:
145: final DirectoryEntry oRoot = opfs.getRoot();
146: final DirectoryEntry cRoot = cpfs.getRoot();
147: final StringBuffer messages = new StringBuffer();
148: if (equal(oRoot, cRoot, messages))
149: System.out.println("Equal");
150: else
151: System.out.println("Not equal: " + messages.toString());
152: }
153:
154: /**
155: * <p>Compares two {@link DirectoryEntry} instances of a POI file system.
156: * The directories must contain the same streams with the same names and
157: * contents.</p>
158: *
159: * @param d1 The first directory.
160: * @param d2 The second directory.
161: * @param msg The method may append human-readable comparison messages to
162: * this string buffer.
163: * @return <code>true</code> if the directories are equal, else
164: * <code>false</code>.
165: * @exception MarkUnsupportedException if a POI document stream does not
166: * support the mark() operation.
167: * @exception NoPropertySetStreamException if the application tries to
168: * create a property set from a POI document stream that is not a property
169: * set stream.
170: * @throws UnsupportedEncodingException
171: * @exception IOException if any I/O exception occurs.
172: */
173: private static boolean equal(final DirectoryEntry d1,
174: final DirectoryEntry d2, final StringBuffer msg)
175: throws NoPropertySetStreamException,
176: MarkUnsupportedException, UnsupportedEncodingException,
177: IOException {
178: boolean equal = true;
179: /* Iterate over d1 and compare each entry with its counterpart in d2. */
180: for (final Iterator i = d1.getEntries(); equal && i.hasNext();) {
181: final Entry e1 = (Entry) i.next();
182: final String n1 = e1.getName();
183: Entry e2 = null;
184: try {
185: e2 = d2.getEntry(n1);
186: } catch (FileNotFoundException ex) {
187: msg.append("Document \"" + e1
188: + "\" exists, document \"" + e2
189: + "\" does not.\n");
190: equal = false;
191: break;
192: }
193:
194: if (e1.isDirectoryEntry() && e2.isDirectoryEntry())
195: equal = equal((DirectoryEntry) e1, (DirectoryEntry) e2,
196: msg);
197: else if (e1.isDocumentEntry() && e2.isDocumentEntry())
198: equal = equal((DocumentEntry) e1, (DocumentEntry) e2,
199: msg);
200: else {
201: msg
202: .append("One of \""
203: + e1
204: + "\" and \""
205: + e2
206: + "\" is a "
207: + "document while the other one is a directory.\n");
208: equal = false;
209: }
210: }
211:
212: /* Iterate over d2 just to make sure that there are no entries in d2
213: * that are not in d1. */
214: for (final Iterator i = d2.getEntries(); equal && i.hasNext();) {
215: final Entry e2 = (Entry) i.next();
216: final String n2 = e2.getName();
217: Entry e1 = null;
218: try {
219: e1 = d1.getEntry(n2);
220: } catch (FileNotFoundException ex) {
221: msg.append("Document \"" + e2
222: + "\" exitsts, document \"" + e1
223: + "\" does not.\n");
224: equal = false;
225: break;
226: }
227: }
228: return equal;
229: }
230:
231: /**
232: * <p>Compares two {@link DocumentEntry} instances of a POI file system.
233: * Documents that are not property set streams must be bitwise identical.
234: * Property set streams must be logically equal.</p>
235: *
236: * @param d1 The first document.
237: * @param d2 The second document.
238: * @param msg The method may append human-readable comparison messages to
239: * this string buffer.
240: * @return <code>true</code> if the documents are equal, else
241: * <code>false</code>.
242: * @exception MarkUnsupportedException if a POI document stream does not
243: * support the mark() operation.
244: * @exception NoPropertySetStreamException if the application tries to
245: * create a property set from a POI document stream that is not a property
246: * set stream.
247: * @throws UnsupportedEncodingException
248: * @exception IOException if any I/O exception occurs.
249: */
250: private static boolean equal(final DocumentEntry d1,
251: final DocumentEntry d2, final StringBuffer msg)
252: throws NoPropertySetStreamException,
253: MarkUnsupportedException, UnsupportedEncodingException,
254: IOException {
255: boolean equal = true;
256: final DocumentInputStream dis1 = new DocumentInputStream(d1);
257: final DocumentInputStream dis2 = new DocumentInputStream(d2);
258: if (PropertySet.isPropertySetStream(dis1)
259: && PropertySet.isPropertySetStream(dis2)) {
260: final PropertySet ps1 = PropertySetFactory.create(dis1);
261: final PropertySet ps2 = PropertySetFactory.create(dis2);
262: equal = ps1.equals(ps2);
263: if (!equal) {
264: msg.append("Property sets are not equal.\n");
265: return equal;
266: }
267: } else {
268: int i1;
269: int i2;
270: do {
271: i1 = dis1.read();
272: i2 = dis2.read();
273: if (i1 != i2) {
274: equal = false;
275: msg.append("Documents are not equal.\n");
276: break;
277: }
278: } while (equal && i1 == -1);
279: }
280: return true;
281: }
282:
283: /**
284: * <p>This class does all the work. Its method {@link
285: * #processPOIFSReaderEvent(POIFSReaderEvent)} is called for each file in
286: * the original POI file system. Except for property set streams it copies
287: * everything unmodified to the destination POI filesystem. Property set
288: * streams are copied by creating a new {@link PropertySet} from the
289: * original property set by using the {@link
290: * MutablePropertySet#MutablePropertySet(PropertySet)} constructor.</p>
291: */
292: static class CopyFile implements POIFSReaderListener {
293: String dstName;
294: OutputStream out;
295: POIFSFileSystem poiFs;
296:
297: /**
298: * <p>The constructor of a {@link CopyFile} instance creates the target
299: * POIFS. It also stores the name of the file the POIFS will be written
300: * to once it is complete.</p>
301: *
302: * @param dstName The name of the disk file the destination POIFS is to
303: * be written to.
304: */
305: public CopyFile(final String dstName) {
306: this .dstName = dstName;
307: poiFs = new POIFSFileSystem();
308: }
309:
310: /**
311: * <p>The method is called by POI's eventing API for each file in the
312: * origin POIFS.</p>
313: */
314: public void processPOIFSReaderEvent(final POIFSReaderEvent event) {
315: /* The following declarations are shortcuts for accessing the
316: * "event" object. */
317: final POIFSDocumentPath path = event.getPath();
318: final String name = event.getName();
319: final DocumentInputStream stream = event.getStream();
320:
321: Throwable t = null;
322:
323: try {
324: /* Find out whether the current document is a property set
325: * stream or not. */
326: if (PropertySet.isPropertySetStream(stream)) {
327: /* Yes, the current document is a property set stream.
328: * Let's create a PropertySet instance from it. */
329: PropertySet ps = null;
330: try {
331: ps = PropertySetFactory.create(stream);
332: } catch (NoPropertySetStreamException ex) {
333: /* This exception will not be thrown because we already
334: * checked above. */
335: }
336:
337: /* Copy the property set to the destination POI file
338: * system. */
339: copy(poiFs, path, name, ps);
340: } else
341: /* No, the current document is not a property set stream. We
342: * copy it unmodified to the destination POIFS. */
343: copy(poiFs, event.getPath(), event.getName(),
344: stream);
345: } catch (MarkUnsupportedException ex) {
346: t = ex;
347: } catch (IOException ex) {
348: t = ex;
349: } catch (WritingNotSupportedException ex) {
350: t = ex;
351: }
352:
353: /* According to the definition of the processPOIFSReaderEvent method
354: * we cannot pass checked exceptions to the caller. The following
355: * lines check whether a checked exception occured and throws an
356: * unchecked exception. The message of that exception is that of
357: * the underlying checked exception. */
358: if (t != null) {
359: throw new HPSFRuntimeException("Could not read file \""
360: + path + "/" + name + "\". Reason: "
361: + Util.toString(t));
362: }
363: }
364:
365: /**
366: * <p>Writes a {@link PropertySet} to a POI filesystem.</p>
367: *
368: * @param poiFs The POI filesystem to write to.
369: * @param path The file's path in the POI filesystem.
370: * @param name The file's name in the POI filesystem.
371: * @param ps The property set to write.
372: * @throws WritingNotSupportedException
373: * @throws IOException
374: */
375: public void copy(final POIFSFileSystem poiFs,
376: final POIFSDocumentPath path, final String name,
377: final PropertySet ps)
378: throws WritingNotSupportedException, IOException {
379: final DirectoryEntry de = getPath(poiFs, path);
380: final MutablePropertySet mps = new MutablePropertySet(ps);
381: de.createDocument(name, mps.toInputStream());
382: }
383:
384: /**
385: * <p>Copies the bytes from a {@link DocumentInputStream} to a new
386: * stream in a POI filesystem.</p>
387: *
388: * @param poiFs The POI filesystem to write to.
389: * @param path The source document's path.
390: * @param name The source document's name.
391: * @param stream The stream containing the source document.
392: * @throws IOException
393: */
394: public void copy(final POIFSFileSystem poiFs,
395: final POIFSDocumentPath path, final String name,
396: final DocumentInputStream stream) throws IOException {
397: final DirectoryEntry de = getPath(poiFs, path);
398: final ByteArrayOutputStream out = new ByteArrayOutputStream();
399: int c;
400: while ((c = stream.read()) != -1)
401: out.write(c);
402: stream.close();
403: out.close();
404: final InputStream in = new ByteArrayInputStream(out
405: .toByteArray());
406: de.createDocument(name, in);
407: }
408:
409: /**
410: * <p>Writes the POI file system to a disk file.</p>
411: *
412: * @throws FileNotFoundException
413: * @throws IOException
414: */
415: public void close() throws FileNotFoundException, IOException {
416: out = new FileOutputStream(dstName);
417: poiFs.writeFilesystem(out);
418: out.close();
419: }
420:
421: /** Contains the directory paths that have already been created in the
422: * output POI filesystem and maps them to their corresponding
423: * {@link org.apache.poi.poifs.filesystem.DirectoryNode}s. */
424: private final Map paths = new HashMap();
425:
426: /**
427: * <p>Ensures that the directory hierarchy for a document in a POI
428: * fileystem is in place. When a document is to be created somewhere in
429: * a POI filesystem its directory must be created first. This method
430: * creates all directories between the POI filesystem root and the
431: * directory the document should belong to which do not yet exist.</p>
432: *
433: * <p>Unfortunately POI does not offer a simple method to interrogate
434: * the POIFS whether a certain child node (file or directory) exists in
435: * a directory. However, since we always start with an empty POIFS which
436: * contains the root directory only and since each directory in the
437: * POIFS is created by this method we can maintain the POIFS's directory
438: * hierarchy ourselves: The {@link DirectoryEntry} of each directory
439: * created is stored in a {@link Map}. The directories' path names map
440: * to the corresponding {@link DirectoryEntry} instances.</p>
441: *
442: * @param poiFs The POI filesystem the directory hierarchy is created
443: * in, if needed.
444: * @param path The document's path. This method creates those directory
445: * components of this hierarchy which do not yet exist.
446: * @return The directory entry of the document path's parent. The caller
447: * should use this {@link DirectoryEntry} to create documents in it.
448: */
449: public DirectoryEntry getPath(final POIFSFileSystem poiFs,
450: final POIFSDocumentPath path) {
451: try {
452: /* Check whether this directory has already been created. */
453: final String s = path.toString();
454: DirectoryEntry de = (DirectoryEntry) paths.get(s);
455: if (de != null)
456: /* Yes: return the corresponding DirectoryEntry. */
457: return de;
458:
459: /* No: We have to create the directory - or return the root's
460: * DirectoryEntry. */
461: int l = path.length();
462: if (l == 0)
463: /* Get the root directory. It does not have to be created
464: * since it always exists in a POIFS. */
465: de = poiFs.getRoot();
466: else {
467: /* Create a subordinate directory. The first step is to
468: * ensure that the parent directory exists: */
469: de = getPath(poiFs, path.getParent());
470: /* Now create the target directory: */
471: de = de.createDirectory(path.getComponent(path
472: .length() - 1));
473: }
474: paths.put(s, de);
475: return de;
476: } catch (IOException ex) {
477: /* This exception will be thrown if the directory already
478: * exists. However, since we have full control about directory
479: * creation we can ensure that this will never happen. */
480: ex.printStackTrace(System.err);
481: throw new RuntimeException(ex.toString());
482: /* FIXME (2): Replace the previous line by the following once we
483: * no longer need JDK 1.3 compatibility. */
484: // throw new RuntimeException(ex);
485: }
486: }
487: }
488:
489: }
|