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.poifs.filesystem;
019:
020: import java.io.*;
021:
022: import java.util.*;
023:
024: import org.apache.poi.poifs.common.POIFSConstants;
025: import org.apache.poi.poifs.dev.POIFSViewable;
026: import org.apache.poi.poifs.property.DocumentProperty;
027: import org.apache.poi.poifs.property.Property;
028: import org.apache.poi.poifs.storage.BlockWritable;
029: import org.apache.poi.poifs.storage.ListManagedBlock;
030: import org.apache.poi.poifs.storage.DocumentBlock;
031: import org.apache.poi.poifs.storage.RawDataBlock;
032: import org.apache.poi.poifs.storage.SmallDocumentBlock;
033: import org.apache.poi.util.HexDump;
034:
035: /**
036: * This class manages a document in the POIFS filesystem.
037: *
038: * @author Marc Johnson (mjohnson at apache dot org)
039: */
040:
041: public class POIFSDocument implements BATManaged, BlockWritable,
042: POIFSViewable {
043: private DocumentProperty _property;
044: private int _size;
045:
046: // one of these stores will be valid
047: private SmallBlockStore _small_store;
048: private BigBlockStore _big_store;
049:
050: /**
051: * Constructor from large blocks
052: *
053: * @param name the name of the POIFSDocument
054: * @param blocks the big blocks making up the POIFSDocument
055: * @param length the actual length of the POIFSDocument
056: *
057: * @exception IOException
058: */
059:
060: public POIFSDocument(final String name,
061: final RawDataBlock[] blocks, final int length)
062: throws IOException {
063: _size = length;
064: _big_store = new BigBlockStore(blocks);
065: _property = new DocumentProperty(name, _size);
066: _small_store = new SmallBlockStore(new BlockWritable[0]);
067: _property.setDocument(this );
068: }
069:
070: /**
071: * Constructor from small blocks
072: *
073: * @param name the name of the POIFSDocument
074: * @param blocks the small blocks making up the POIFSDocument
075: * @param length the actual length of the POIFSDocument
076: */
077:
078: public POIFSDocument(final String name,
079: final SmallDocumentBlock[] blocks, final int length) {
080: _size = length;
081: try {
082: _big_store = new BigBlockStore(new RawDataBlock[0]);
083: } catch (IOException ignored) {
084:
085: // can't happen with that constructor
086: }
087: _property = new DocumentProperty(name, _size);
088: _small_store = new SmallBlockStore(blocks);
089: _property.setDocument(this );
090: }
091:
092: /**
093: * Constructor from small blocks
094: *
095: * @param name the name of the POIFSDocument
096: * @param blocks the small blocks making up the POIFSDocument
097: * @param length the actual length of the POIFSDocument
098: *
099: * @exception IOException
100: */
101:
102: public POIFSDocument(final String name,
103: final ListManagedBlock[] blocks, final int length)
104: throws IOException {
105: _size = length;
106: _property = new DocumentProperty(name, _size);
107: _property.setDocument(this );
108: if (Property.isSmall(_size)) {
109: _big_store = new BigBlockStore(new RawDataBlock[0]);
110: _small_store = new SmallBlockStore(blocks);
111: } else {
112: _big_store = new BigBlockStore(blocks);
113: _small_store = new SmallBlockStore(new BlockWritable[0]);
114: }
115: }
116:
117: /**
118: * Constructor
119: *
120: * @param name the name of the POIFSDocument
121: * @param stream the InputStream we read data from
122: *
123: * @exception IOException thrown on read errors
124: */
125:
126: public POIFSDocument(final String name, final InputStream stream)
127: throws IOException {
128: List blocks = new ArrayList();
129:
130: _size = 0;
131: while (true) {
132: DocumentBlock block = new DocumentBlock(stream);
133: int blockSize = block.size();
134:
135: if (blockSize > 0) {
136: blocks.add(block);
137: _size += blockSize;
138: }
139: if (block.partiallyRead()) {
140: break;
141: }
142: }
143: DocumentBlock[] bigBlocks = (DocumentBlock[]) blocks
144: .toArray(new DocumentBlock[0]);
145:
146: _big_store = new BigBlockStore(bigBlocks);
147: _property = new DocumentProperty(name, _size);
148: _property.setDocument(this );
149: if (_property.shouldUseSmallBlocks()) {
150: _small_store = new SmallBlockStore(SmallDocumentBlock
151: .convert(bigBlocks, _size));
152: _big_store = new BigBlockStore(new DocumentBlock[0]);
153: } else {
154: _small_store = new SmallBlockStore(new BlockWritable[0]);
155: }
156: }
157:
158: /**
159: * Constructor
160: *
161: * @param name the name of the POIFSDocument
162: * @param size the length of the POIFSDocument
163: * @param path the path of the POIFSDocument
164: * @param writer the writer who will eventually write the document
165: * contents
166: *
167: * @exception IOException thrown on read errors
168: */
169:
170: public POIFSDocument(final String name, final int size,
171: final POIFSDocumentPath path,
172: final POIFSWriterListener writer) throws IOException {
173: _size = size;
174: _property = new DocumentProperty(name, _size);
175: _property.setDocument(this );
176: if (_property.shouldUseSmallBlocks()) {
177: _small_store = new SmallBlockStore(path, name, size, writer);
178: _big_store = new BigBlockStore(new Object[0]);
179: } else {
180: _small_store = new SmallBlockStore(new BlockWritable[0]);
181: _big_store = new BigBlockStore(path, name, size, writer);
182: }
183: }
184:
185: /**
186: * return the array of SmallDocumentBlocks used
187: *
188: * @return array of SmallDocumentBlocks; may be empty, cannot be null
189: */
190:
191: public BlockWritable[] getSmallBlocks() {
192: return _small_store.getBlocks();
193: }
194:
195: /**
196: * @return size of the document
197: */
198:
199: public int getSize() {
200: return _size;
201: }
202:
203: /**
204: * read data from the internal stores
205: *
206: * @param buffer the buffer to write to
207: * @param offset the offset into our storage to read from
208: */
209:
210: void read(final byte[] buffer, final int offset) {
211: if (_property.shouldUseSmallBlocks()) {
212: SmallDocumentBlock.read(_small_store.getBlocks(), buffer,
213: offset);
214: } else {
215: DocumentBlock.read(_big_store.getBlocks(), buffer, offset);
216: }
217: }
218:
219: /**
220: * Get the DocumentProperty
221: *
222: * @return the instance's DocumentProperty
223: */
224:
225: DocumentProperty getDocumentProperty() {
226: return _property;
227: }
228:
229: /* ********** START implementation of BlockWritable ********** */
230:
231: /**
232: * Write the storage to an OutputStream
233: *
234: * @param stream the OutputStream to which the stored data should
235: * be written
236: *
237: * @exception IOException on problems writing to the specified
238: * stream
239: */
240:
241: public void writeBlocks(final OutputStream stream)
242: throws IOException {
243: _big_store.writeBlocks(stream);
244: }
245:
246: /* ********** END implementation of BlockWritable ********** */
247: /* ********** START implementation of BATManaged ********** */
248:
249: /**
250: * Return the number of BigBlock's this instance uses
251: *
252: * @return count of BigBlock instances
253: */
254:
255: public int countBlocks() {
256: return _big_store.countBlocks();
257: }
258:
259: /**
260: * Set the start block for this instance
261: *
262: * @param index index into the array of blocks making up the
263: * filesystem
264: */
265:
266: public void setStartBlock(final int index) {
267: _property.setStartBlock(index);
268: }
269:
270: /* ********** END implementation of BATManaged ********** */
271: /* ********** START begin implementation of POIFSViewable ********** */
272:
273: /**
274: * Get an array of objects, some of which may implement
275: * POIFSViewable
276: *
277: * @return an array of Object; may not be null, but may be empty
278: */
279:
280: public Object[] getViewableArray() {
281: Object[] results = new Object[1];
282: String result;
283:
284: try {
285: ByteArrayOutputStream output = new ByteArrayOutputStream();
286: BlockWritable[] blocks = null;
287:
288: if (_big_store.isValid()) {
289: blocks = _big_store.getBlocks();
290: } else if (_small_store.isValid()) {
291: blocks = _small_store.getBlocks();
292: }
293: if (blocks != null) {
294: for (int k = 0; k < blocks.length; k++) {
295: blocks[k].writeBlocks(output);
296: }
297: byte[] data = output.toByteArray();
298:
299: if (data.length > _property.getSize()) {
300: byte[] tmp = new byte[_property.getSize()];
301:
302: System.arraycopy(data, 0, tmp, 0, tmp.length);
303: data = tmp;
304: }
305: output = new ByteArrayOutputStream();
306: HexDump.dump(data, 0, output, 0);
307: result = output.toString();
308: } else {
309: result = "<NO DATA>";
310: }
311: } catch (IOException e) {
312: result = e.getMessage();
313: }
314: results[0] = result;
315: return results;
316: }
317:
318: /**
319: * Get an Iterator of objects, some of which may implement
320: * POIFSViewable
321: *
322: * @return an Iterator; may not be null, but may have an empty
323: * back end store
324: */
325:
326: public Iterator getViewableIterator() {
327: return Collections.EMPTY_LIST.iterator();
328: }
329:
330: /**
331: * Give viewers a hint as to whether to call getViewableArray or
332: * getViewableIterator
333: *
334: * @return true if a viewer should call getViewableArray, false if
335: * a viewer should call getViewableIterator
336: */
337:
338: public boolean preferArray() {
339: return true;
340: }
341:
342: /**
343: * Provides a short description of the object, to be used when a
344: * POIFSViewable object has not provided its contents.
345: *
346: * @return short description
347: */
348:
349: public String getShortDescription() {
350: StringBuffer buffer = new StringBuffer();
351:
352: buffer.append("Document: \"").append(_property.getName())
353: .append("\"");
354: buffer.append(" size = ").append(getSize());
355: return buffer.toString();
356: }
357:
358: /* ********** END begin implementation of POIFSViewable ********** */
359: private class SmallBlockStore {
360: private SmallDocumentBlock[] smallBlocks;
361: private POIFSDocumentPath path;
362: private String name;
363: private int size;
364: private POIFSWriterListener writer;
365:
366: /**
367: * Constructor
368: *
369: * @param blocks blocks to construct the store from
370: */
371:
372: SmallBlockStore(final Object[] blocks) {
373: smallBlocks = new SmallDocumentBlock[blocks.length];
374: for (int j = 0; j < blocks.length; j++) {
375: smallBlocks[j] = (SmallDocumentBlock) blocks[j];
376: }
377: this .path = null;
378: this .name = null;
379: this .size = -1;
380: this .writer = null;
381: }
382:
383: /**
384: * Constructor for a small block store that will be written
385: * later
386: *
387: * @param path path of the document
388: * @param name name of the document
389: * @param size length of the document
390: * @param writer the object that will eventually write the document
391: */
392:
393: SmallBlockStore(final POIFSDocumentPath path,
394: final String name, final int size,
395: final POIFSWriterListener writer) {
396: smallBlocks = new SmallDocumentBlock[0];
397: this .path = path;
398: this .name = name;
399: this .size = size;
400: this .writer = writer;
401: }
402:
403: /**
404: * @return true if this store is a valid source of data
405: */
406:
407: boolean isValid() {
408: return ((smallBlocks.length > 0) || (writer != null));
409: }
410:
411: /**
412: * @return the SmallDocumentBlocks
413: */
414:
415: BlockWritable[] getBlocks() {
416: if (isValid() && (writer != null)) {
417: ByteArrayOutputStream stream = new ByteArrayOutputStream(
418: size);
419: DocumentOutputStream dstream = new DocumentOutputStream(
420: stream, size);
421:
422: writer.processPOIFSWriterEvent(new POIFSWriterEvent(
423: dstream, path, name, size));
424: smallBlocks = SmallDocumentBlock.convert(stream
425: .toByteArray(), size);
426: }
427: return smallBlocks;
428: }
429: } // end private class SmallBlockStore
430:
431: private class BigBlockStore {
432: private DocumentBlock[] bigBlocks;
433: private POIFSDocumentPath path;
434: private String name;
435: private int size;
436: private POIFSWriterListener writer;
437:
438: /**
439: * Constructor
440: *
441: * @param blocks the blocks making up the store
442: *
443: * @exception IOException on I/O error
444: */
445:
446: BigBlockStore(final Object[] blocks) throws IOException {
447: bigBlocks = new DocumentBlock[blocks.length];
448: for (int j = 0; j < blocks.length; j++) {
449: if (blocks[j] instanceof DocumentBlock) {
450: bigBlocks[j] = (DocumentBlock) blocks[j];
451: } else {
452: bigBlocks[j] = new DocumentBlock(
453: (RawDataBlock) blocks[j]);
454: }
455: }
456: this .path = null;
457: this .name = null;
458: this .size = -1;
459: this .writer = null;
460: }
461:
462: /**
463: * Constructor for a big block store that will be written
464: * later
465: *
466: * @param path path of the document
467: * @param name name of the document
468: * @param size length of the document
469: * @param writer the object that will eventually write the
470: * document
471: */
472:
473: BigBlockStore(final POIFSDocumentPath path, final String name,
474: final int size, final POIFSWriterListener writer) {
475: bigBlocks = new DocumentBlock[0];
476: this .path = path;
477: this .name = name;
478: this .size = size;
479: this .writer = writer;
480: }
481:
482: /**
483: * @return true if this store is a valid source of data
484: */
485:
486: boolean isValid() {
487: return ((bigBlocks.length > 0) || (writer != null));
488: }
489:
490: /**
491: * @return the DocumentBlocks
492: */
493:
494: DocumentBlock[] getBlocks() {
495: if (isValid() && (writer != null)) {
496: ByteArrayOutputStream stream = new ByteArrayOutputStream(
497: size);
498: DocumentOutputStream dstream = new DocumentOutputStream(
499: stream, size);
500:
501: writer.processPOIFSWriterEvent(new POIFSWriterEvent(
502: dstream, path, name, size));
503: bigBlocks = DocumentBlock.convert(stream.toByteArray(),
504: size);
505: }
506: return bigBlocks;
507: }
508:
509: /**
510: * write the blocks to a stream
511: *
512: * @param stream the stream to which the data is to be written
513: *
514: * @exception IOException on error
515: */
516:
517: void writeBlocks(OutputStream stream) throws IOException {
518: if (isValid()) {
519: if (writer != null) {
520: DocumentOutputStream dstream = new DocumentOutputStream(
521: stream, size);
522:
523: writer
524: .processPOIFSWriterEvent(new POIFSWriterEvent(
525: dstream, path, name, size));
526: dstream.writeFiller(countBlocks()
527: * POIFSConstants.BIG_BLOCK_SIZE,
528: DocumentBlock.getFillByte());
529: } else {
530: for (int k = 0; k < bigBlocks.length; k++) {
531: bigBlocks[k].writeBlocks(stream);
532: }
533: }
534: }
535: }
536:
537: /**
538: * @return number of big blocks making up this document
539: */
540:
541: int countBlocks() {
542: int rval = 0;
543:
544: if (isValid()) {
545: if (writer != null) {
546: rval = (size + POIFSConstants.BIG_BLOCK_SIZE - 1)
547: / POIFSConstants.BIG_BLOCK_SIZE;
548: } else {
549: rval = bigBlocks.length;
550: }
551: }
552: return rval;
553: }
554: } // end private class BigBlockStore
555: } // end class POIFSDocument
|