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.DirectoryProperty;
027: import org.apache.poi.poifs.property.DocumentProperty;
028: import org.apache.poi.poifs.property.Property;
029: import org.apache.poi.poifs.property.PropertyTable;
030: import org.apache.poi.poifs.storage.BATBlock;
031: import org.apache.poi.poifs.storage.BlockAllocationTableReader;
032: import org.apache.poi.poifs.storage.BlockAllocationTableWriter;
033: import org.apache.poi.poifs.storage.BlockList;
034: import org.apache.poi.poifs.storage.BlockWritable;
035: import org.apache.poi.poifs.storage.HeaderBlockReader;
036: import org.apache.poi.poifs.storage.HeaderBlockWriter;
037: import org.apache.poi.poifs.storage.RawDataBlock;
038: import org.apache.poi.poifs.storage.RawDataBlockList;
039: import org.apache.poi.poifs.storage.SmallBlockTableReader;
040: import org.apache.poi.poifs.storage.SmallBlockTableWriter;
041: import org.apache.poi.poifs.storage.SmallDocumentBlock;
042:
043: /**
044: * This is the main class of the POIFS system; it manages the entire
045: * life cycle of the filesystem.
046: *
047: * @author Marc Johnson (mjohnson at apache dot org)
048: */
049:
050: public class POIFSFileSystem implements POIFSViewable {
051: private PropertyTable _property_table;
052: private List _documents;
053: private DirectoryNode _root;
054:
055: /**
056: * Constructor, intended for writing
057: */
058:
059: public POIFSFileSystem() {
060: _property_table = new PropertyTable();
061: _documents = new ArrayList();
062: _root = null;
063: }
064:
065: /**
066: * Create a POIFSFileSystem from an InputStream
067: *
068: * @param stream the InputStream from which to read the data
069: *
070: * @exception IOException on errors reading, or on invalid data
071: */
072:
073: public POIFSFileSystem(final InputStream stream) throws IOException {
074: this ();
075:
076: // read the header block from the stream
077: HeaderBlockReader header_block_reader = new HeaderBlockReader(
078: stream);
079:
080: // read the rest of the stream into blocks
081: RawDataBlockList data_blocks = new RawDataBlockList(stream);
082:
083: // set up the block allocation table (necessary for the
084: // data_blocks to be manageable
085: new BlockAllocationTableReader(header_block_reader
086: .getBATCount(), header_block_reader.getBATArray(),
087: header_block_reader.getXBATCount(), header_block_reader
088: .getXBATIndex(), data_blocks);
089:
090: // get property table from the document
091: PropertyTable properties = new PropertyTable(
092: header_block_reader.getPropertyStart(), data_blocks);
093:
094: // init documents
095: processProperties(SmallBlockTableReader.getSmallDocumentBlocks(
096: data_blocks, properties.getRoot(), header_block_reader
097: .getSBATStart()), data_blocks, properties
098: .getRoot().getChildren(), null);
099: }
100:
101: /**
102: * Create a new document to be added to the root directory
103: *
104: * @param stream the InputStream from which the document's data
105: * will be obtained
106: * @param name the name of the new POIFSDocument
107: *
108: * @return the new DocumentEntry
109: *
110: * @exception IOException on error creating the new POIFSDocument
111: */
112:
113: public DocumentEntry createDocument(final InputStream stream,
114: final String name) throws IOException {
115: return getRoot().createDocument(name, stream);
116: }
117:
118: /**
119: * create a new DocumentEntry in the root entry; the data will be
120: * provided later
121: *
122: * @param name the name of the new DocumentEntry
123: * @param size the size of the new DocumentEntry
124: * @param writer the writer of the new DocumentEntry
125: *
126: * @return the new DocumentEntry
127: *
128: * @exception IOException
129: */
130:
131: public DocumentEntry createDocument(final String name,
132: final int size, final POIFSWriterListener writer)
133: throws IOException {
134: return getRoot().createDocument(name, size, writer);
135: }
136:
137: /**
138: * create a new DirectoryEntry in the root directory
139: *
140: * @param name the name of the new DirectoryEntry
141: *
142: * @return the new DirectoryEntry
143: *
144: * @exception IOException on name duplication
145: */
146:
147: public DirectoryEntry createDirectory(final String name)
148: throws IOException {
149: return getRoot().createDirectory(name);
150: }
151:
152: /**
153: * Write the filesystem out
154: *
155: * @param stream the OutputStream to which the filesystem will be
156: * written
157: *
158: * @exception IOException thrown on errors writing to the stream
159: */
160:
161: public void writeFilesystem(final OutputStream stream)
162: throws IOException {
163:
164: // get the property table ready
165: _property_table.preWrite();
166:
167: // create the small block store, and the SBAT
168: SmallBlockTableWriter sbtw = new SmallBlockTableWriter(
169: _documents, _property_table.getRoot());
170:
171: // create the block allocation table
172: BlockAllocationTableWriter bat = new BlockAllocationTableWriter();
173:
174: // create a list of BATManaged objects: the documents plus the
175: // property table and the small block table
176: List bm_objects = new ArrayList();
177:
178: bm_objects.addAll(_documents);
179: bm_objects.add(_property_table);
180: bm_objects.add(sbtw);
181: bm_objects.add(sbtw.getSBAT());
182:
183: // walk the list, allocating space for each and assigning each
184: // a starting block number
185: Iterator iter = bm_objects.iterator();
186:
187: while (iter.hasNext()) {
188: BATManaged bmo = (BATManaged) iter.next();
189: int block_count = bmo.countBlocks();
190:
191: if (block_count != 0) {
192: bmo.setStartBlock(bat.allocateSpace(block_count));
193: } else {
194:
195: // Either the BATManaged object is empty or its data
196: // is composed of SmallBlocks; in either case,
197: // allocating space in the BAT is inappropriate
198: }
199: }
200:
201: // allocate space for the block allocation table and take its
202: // starting block
203: int batStartBlock = bat.createBlocks();
204:
205: // get the extended block allocation table blocks
206: HeaderBlockWriter header_block_writer = new HeaderBlockWriter();
207: BATBlock[] xbat_blocks = header_block_writer.setBATBlocks(bat
208: .countBlocks(), batStartBlock);
209:
210: // set the property table start block
211: header_block_writer.setPropertyStart(_property_table
212: .getStartBlock());
213:
214: // set the small block allocation table start block
215: header_block_writer
216: .setSBATStart(sbtw.getSBAT().getStartBlock());
217:
218: // set the small block allocation table block count
219: header_block_writer.setSBATBlockCount(sbtw.getSBATBlockCount());
220:
221: // the header is now properly initialized. Make a list of
222: // writers (the header block, followed by the documents, the
223: // property table, the small block store, the small block
224: // allocation table, the block allocation table, and the
225: // extended block allocation table blocks)
226: List writers = new ArrayList();
227:
228: writers.add(header_block_writer);
229: writers.addAll(_documents);
230: writers.add(_property_table);
231: writers.add(sbtw);
232: writers.add(sbtw.getSBAT());
233: writers.add(bat);
234: for (int j = 0; j < xbat_blocks.length; j++) {
235: writers.add(xbat_blocks[j]);
236: }
237:
238: // now, write everything out
239: iter = writers.iterator();
240: while (iter.hasNext()) {
241: BlockWritable writer = (BlockWritable) iter.next();
242:
243: writer.writeBlocks(stream);
244: }
245: }
246:
247: /**
248: * read in a file and write it back out again
249: *
250: * @param args names of the files; arg[ 0 ] is the input file,
251: * arg[ 1 ] is the output file
252: *
253: * @exception IOException
254: */
255:
256: public static void main(String args[]) throws IOException {
257: if (args.length != 2) {
258: System.err
259: .println("two arguments required: input filename and output filename");
260: System.exit(1);
261: }
262: FileInputStream istream = new FileInputStream(args[0]);
263: FileOutputStream ostream = new FileOutputStream(args[1]);
264:
265: new POIFSFileSystem(istream).writeFilesystem(ostream);
266: istream.close();
267: ostream.close();
268: }
269:
270: /**
271: * get the root entry
272: *
273: * @return the root entry
274: */
275:
276: public DirectoryEntry getRoot() {
277: if (_root == null) {
278: _root = new DirectoryNode(_property_table.getRoot(), this ,
279: null);
280: }
281: return _root;
282: }
283:
284: /**
285: * open a document in the root entry's list of entries
286: *
287: * @param documentName the name of the document to be opened
288: *
289: * @return a newly opened DocumentInputStream
290: *
291: * @exception IOException if the document does not exist or the
292: * name is that of a DirectoryEntry
293: */
294:
295: public DocumentInputStream createDocumentInputStream(
296: final String documentName) throws IOException {
297: Entry document = getRoot().getEntry(documentName);
298:
299: if (!document.isDocumentEntry()) {
300: throw new IOException("Entry '" + documentName
301: + "' is not a DocumentEntry");
302: }
303: return new DocumentInputStream((DocumentEntry) document);
304: }
305:
306: /**
307: * add a new POIFSDocument
308: *
309: * @param document the POIFSDocument being added
310: */
311:
312: void addDocument(final POIFSDocument document) {
313: _documents.add(document);
314: _property_table.addProperty(document.getDocumentProperty());
315: }
316:
317: /**
318: * add a new DirectoryProperty
319: *
320: * @param directory the DirectoryProperty being added
321: */
322:
323: void addDirectory(final DirectoryProperty directory) {
324: _property_table.addProperty(directory);
325: }
326:
327: /**
328: * remove an entry
329: *
330: * @param entry to be removed
331: */
332:
333: void remove(EntryNode entry) {
334: _property_table.removeProperty(entry.getProperty());
335: if (entry.isDocumentEntry()) {
336: _documents.remove(((DocumentNode) entry).getDocument());
337: }
338: }
339:
340: private void processProperties(final BlockList small_blocks,
341: final BlockList big_blocks, final Iterator properties,
342: final DirectoryNode dir) throws IOException {
343: while (properties.hasNext()) {
344: Property property = (Property) properties.next();
345: String name = property.getName();
346: DirectoryNode parent = (dir == null) ? ((DirectoryNode) getRoot())
347: : dir;
348:
349: if (property.isDirectory()) {
350: DirectoryNode new_dir = (DirectoryNode) parent
351: .createDirectory(name);
352:
353: new_dir.setStorageClsid(property.getStorageClsid());
354:
355: processProperties(small_blocks, big_blocks,
356: ((DirectoryProperty) property).getChildren(),
357: new_dir);
358: } else {
359: int startBlock = property.getStartBlock();
360: int size = property.getSize();
361: POIFSDocument document = null;
362:
363: if (property.shouldUseSmallBlocks()) {
364: document = new POIFSDocument(name, small_blocks
365: .fetchBlocks(startBlock), size);
366: } else {
367: document = new POIFSDocument(name, big_blocks
368: .fetchBlocks(startBlock), size);
369: }
370: parent.createDocument(document);
371: }
372: }
373: }
374:
375: /* ********** START begin implementation of POIFSViewable ********** */
376:
377: /**
378: * Get an array of objects, some of which may implement
379: * POIFSViewable
380: *
381: * @return an array of Object; may not be null, but may be empty
382: */
383:
384: public Object[] getViewableArray() {
385: if (preferArray()) {
386: return ((POIFSViewable) getRoot()).getViewableArray();
387: } else {
388: return new Object[0];
389: }
390: }
391:
392: /**
393: * Get an Iterator of objects, some of which may implement
394: * POIFSViewable
395: *
396: * @return an Iterator; may not be null, but may have an empty
397: * back end store
398: */
399:
400: public Iterator getViewableIterator() {
401: if (!preferArray()) {
402: return ((POIFSViewable) getRoot()).getViewableIterator();
403: } else {
404: return Collections.EMPTY_LIST.iterator();
405: }
406: }
407:
408: /**
409: * Give viewers a hint as to whether to call getViewableArray or
410: * getViewableIterator
411: *
412: * @return true if a viewer should call getViewableArray, false if
413: * a viewer should call getViewableIterator
414: */
415:
416: public boolean preferArray() {
417: return ((POIFSViewable) getRoot()).preferArray();
418: }
419:
420: /**
421: * Provides a short description of the object, to be used when a
422: * POIFSViewable object has not provided its contents.
423: *
424: * @return short description
425: */
426:
427: public String getShortDescription() {
428: return "POIFS FileSystem";
429: }
430:
431: /* ********** END begin implementation of POIFSViewable ********** */
432: } // end public class POIFSFileSystem
|