1    
2    /* ====================================================================
3     * The Apache Software License, Version 1.1
4     *
5     * Copyright (c) 2002 The Apache Software Foundation.  All rights
6     * reserved.
7     *
8     * Redistribution and use in source and binary forms, with or without
9     * modification, are permitted provided that the following conditions
10    * are met:
11    *
12    * 1. Redistributions of source code must retain the above copyright
13    *    notice, this list of conditions and the following disclaimer.
14    *
15    * 2. Redistributions in binary form must reproduce the above copyright
16    *    notice, this list of conditions and the following disclaimer in
17    *    the documentation and/or other materials provided with the
18    *    distribution.
19    *
20    * 3. The end-user documentation included with the redistribution,
21    *    if any, must include the following acknowledgment:
22    *       "This product includes software developed by the
23    *        Apache Software Foundation (http://www.apache.org/)."
24    *    Alternately, this acknowledgment may appear in the software itself,
25    *    if and wherever such third-party acknowledgments normally appear.
26    *
27    * 4. The names "Apache" and "Apache Software Foundation" and
28    *    "Apache POI" must not be used to endorse or promote products
29    *    derived from this software without prior written permission. For
30    *    written permission, please contact apache@apache.org.
31    *
32    * 5. Products derived from this software may not be called "Apache",
33    *    "Apache POI", nor may "Apache" appear in their name, without
34    *    prior written permission of the Apache Software Foundation.
35    *
36    * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37    * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38    * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39    * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
40    * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41    * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42    * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43    * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44    * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45    * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46    * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47    * SUCH DAMAGE.
48    * ====================================================================
49    *
50    * This software consists of voluntary contributions made by many
51    * individuals on behalf of the Apache Software Foundation.  For more
52    * information on the Apache Software Foundation, please see
53    * <http://www.apache.org/>.
54    */
55   
56   package org.apache.poi.poifs.filesystem;
57   
58   import java.io.*;
59   
60   import java.util.*;
61   
62   import org.apache.poi.poifs.common.POIFSConstants;
63   import org.apache.poi.poifs.dev.POIFSViewable;
64   import org.apache.poi.poifs.property.DirectoryProperty;
65   import org.apache.poi.poifs.property.DocumentProperty;
66   import org.apache.poi.poifs.property.Property;
67   import org.apache.poi.poifs.property.PropertyTable;
68   import org.apache.poi.poifs.storage.BATBlock;
69   import org.apache.poi.poifs.storage.BlockAllocationTableReader;
70   import org.apache.poi.poifs.storage.BlockAllocationTableWriter;
71   import org.apache.poi.poifs.storage.BlockList;
72   import org.apache.poi.poifs.storage.BlockWritable;
73   import org.apache.poi.poifs.storage.HeaderBlockReader;
74   import org.apache.poi.poifs.storage.HeaderBlockWriter;
75   import org.apache.poi.poifs.storage.RawDataBlock;
76   import org.apache.poi.poifs.storage.RawDataBlockList;
77   import org.apache.poi.poifs.storage.SmallBlockTableReader;
78   import org.apache.poi.poifs.storage.SmallBlockTableWriter;
79   import org.apache.poi.poifs.storage.SmallDocumentBlock;
80   
81   /**
82    * This is the main class of the POIFS system; it manages the entire
83    * life cycle of the filesystem.
84    *
85    * @author Marc Johnson (mjohnson at apache dot org)
86    */
87   
88   public class POIFSFileSystem
89       implements POIFSViewable
90   {
91       private PropertyTable _property_table;
92       private List          _documents;
93       private DirectoryNode _root;
94   
95       /**
96        * Constructor, intended for writing
97        */
98   
99       public POIFSFileSystem()
100      {
101          _property_table = new PropertyTable();
102          _documents      = new ArrayList();
103          _root           = null;
104      }
105  
106      /**
107       * Create a POIFSFileSystem from an InputStream
108       *
109       * @param stream the InputStream from which to read the data
110       *
111       * @exception IOException on errors reading, or on invalid data
112       */
113  
114      public POIFSFileSystem(final InputStream stream)
115          throws IOException
116      {
117          this();
118  
119          // read the header block from the stream
120          HeaderBlockReader header_block_reader = new HeaderBlockReader(stream);
121  
122          // read the rest of the stream into blocks
123          RawDataBlockList  data_blocks         = new RawDataBlockList(stream);
124  
125          // set up the block allocation table (necessary for the
126          // data_blocks to be manageable
127          new BlockAllocationTableReader(header_block_reader.getBATCount(),
128                                         header_block_reader.getBATArray(),
129                                         header_block_reader.getXBATCount(),
130                                         header_block_reader.getXBATIndex(),
131                                         data_blocks);
132  
133          // get property table from the document
134          PropertyTable properties =
135              new PropertyTable(header_block_reader.getPropertyStart(),
136                                data_blocks);
137  
138          // init documents
139          processProperties(SmallBlockTableReader
140              .getSgetRootumentBlocks(data_blocks, properties
141                  .getRgetSBATStartheader_block_reader                .getSgetChildren, data_blocks, properties.getRoot()
142                          .getChildren(), null);
143      }
144  
145      /**
146       * Create a new document to be added to the root directory
147       *
148       * @param stream the InputStream from which the document's data
149       *               will be obtained
150       * @param name the name of the new POIFSDocument
151       *
152       * @return the new DocumentEntry
153       *
154       * @exception IOException on error creating the new POIFSDocument
155       */
156  
157      public DocumentEntry createDocument(final InputStream stream,
158                                          final String name)
159          throws IOException
160      {
161          return getRoot().createDocument(name, stream);
162      }
163  
164      /**
165       * create a new DocumentEntry in the root entry; the data will be
166       * provided later
167       *
168       * @param name the name of the new DocumentEntry
169       * @param size the size of the new DocumentEntry
170       * @param writer the writer of the new DocumentEntry
171       *
172       * @return the new DocumentEntry
173       *
174       * @exception IOException
175       */
176  
177      public DocumentEntry createDocument(final String name, final int size,
178                                          final POIFSWriterListener writer)
179          throws IOException
180      {
181          return getRoot().createDocument(name, size, writer);
182      }
183  
184      /**
185       * create a new DirectoryEntry in the root directory
186       *
187       * @param name the name of the new DirectoryEntry
188       *
189       * @return the new DirectoryEntry
190       *
191       * @exception IOException on name duplication
192       */
193  
194      public DirectoryEntry createDirectory(final String name)
195          throws IOException
196      {
197          return getRoot().createDirectory(name);
198      }
199  
200      /**
201       * Write the filesystem out
202       *
203       * @param stream the OutputStream to which the filesystem will be
204       *               written
205       *
206       * @exception IOException thrown on errors writing to the stream
207       */
208  
209      public void writeFilesystem(final OutputStream stream)
210          throws IOException
211      {
212  
213          // get the property table ready
214          _property_table.preWrite();
215  
216          // create the small block store, and the SBAT
217          SmallBlockTableWriter      sbtw       =
218              new SmallBlockTableWriter(_documents, _property_table.getRoot());
219  
220          // create the block allocation table
221          BlockAllocationTableWriter bat        =
222              new BlockAllocationTableWriter();
223  
224          // create a list of BATManaged objects: the documents plus the
225          // property table and the small block table
226          List                       bm_objects = new ArrayList();
227  
228          bm_objects.addAll(_documents);
229          bm_objects.add(_property_table);
230          bm_objects.add(sbtw);
231          bm_objects.add(sbtw.getSBAT());
232  
233          // walk the list, allocating space for each and assigning each
234          // a starting block number
235          Iterator iter = bm_objects.iterator();
236  
237          while (iter.hasNext())
238          {
239              BATManaged bmo         = ( BATManaged ) iter.next();
240              int        block_count = bmo.countBlocks();
241  
242              if (block_count != 0)
243              {
244                  bmo.setStartBlock(bat.allocateSpace(block_count));
245              }
246              else
247              {
248  
249                  // Either the BATManaged object is empty or its data
250                  // is composed of SmallBlocks; in either case,
251                  // allocating space in the BAT is inappropriate
252              }
253          }
254  
255          // allocate space for the block allocation table and take its
256          // starting block
257          int               batStartBlock       = bat.createBlocks();
258  
259          // get the extended block allocation table blocks
260          HeaderBlockWriter header_block_writer = new HeaderBlockWriter();
261          BATBlock[]        xbat_blocks         =
262              header_block_writer.setBATBlocks(bat.countBlocks(),
263                                               batStartBlock);
264  
265          // set the property table start block
266          header_block_writer.setPropertyStart(_property_table.getStartBlock());
267  
268          // set the small block allocation table start block
269          header_block_writer.setSBATStart(sbtw.getSBAT().getStartBlock());
270  
271          // set the small block allocation table block count
272          header_block_writer.setSBATBlockCount(sbtw.getSBATBlockCount());
273  
274          // the header is now properly initialized. Make a list of
275          // writers (the header block, followed by the documents, the
276          // property table, the small block store, the small block
277          // allocation table, the block allocation table, and the
278          // extended block allocation table blocks)
279          List writers = new ArrayList();
280  
281          writers.add(header_block_writer);
282          writers.addAll(_documents);
283          writers.add(_property_table);
284          writers.add(sbtw);
285          writers.add(sbtw.getSBAT());
286          writers.add(bat);
287          for (int j = 0; j < xbat_blocks.length; j++)
288          {
289              writers.add(xbat_blocks[ j ]);
290          }
291  
292          // now, write everything out
293          iter = writers.iterator();
294          while (iter.hasNext())
295          {
296              BlockWritable writer = ( BlockWritable ) iter.next();
297  
298              writer.writeBlocks(stream);
299          }
300      }
301  
302      /**
303       * read in a file and write it back out again
304       *
305       * @param args names of the files; arg[ 0 ] is the input file,
306       *             arg[ 1 ] is the output file
307       *
308       * @exception IOException
309       */
310  
311      public static void main(String args[])
312          throws IOException
313      {
314          if (args.length != 2)
315          {
316              System.err.println(
317                  "two arguments required: input filename and output filename");
318              System.exit(1);
319          }
320          FileInputStream  istream = new FileInputStream(args[ 0 ]);
321          FileOutputStream ostream = new FileOutputStream(args[ 1 ]);
322  
323          new POIFSFileSystem(istream).writeFilesystem(ostream);
324          istream.close();
325          ostream.close();
326      }
327  
328      /**
329       * get the root entry
330       *
331       * @return the root entry
332       */
333  
334      public DirectoryEntry getRoot()
335      {
336          if (_root == null)
337          {
338              _root = new DirectoryNode(_property_table.getRoot(), this, null);
339          }
340          return _root;
341      }
342  
343      /**
344       * open a document in the root entry's list of entries
345       *
346       * @param documentName the name of the document to be opened
347       *
348       * @return a newly opened DocumentInputStream
349       *
350       * @exception IOException if the document does not exist or the
351       *            name is that of a DirectoryEntry
352       */
353  
354      public DocumentInputStream createDocumentInputStream(
355              final String documentName)
356          throws IOException
357      {
358          Entry document = getRoot().getEntry(documentName);
359  
360          if (!document.isDocumentEntry())
361          {
362              throw new IOException("Entry '" + documentName
363                                    + "' is not a DocumentEntry");
364          }
365          return new DocumentInputStream(( DocumentEntry ) document);
366      }
367  
368      /**
369       * add a new POIFSDocument
370       *
371       * @param document the POIFSDocument being added
372       */
373  
374      void addDocument(final POIFSDocument document)
375      {
376          _documents.add(document);
377          _property_table.addProperty(document.getDocumentProperty());
378      }
379  
380      /**
381       * add a new DirectoryProperty
382       *
383       * @param directory the DirectoryProperty being added
384       */
385  
386      void addDirectory(final DirectoryProperty directory)
387      {
388          _property_table.addProperty(directory);
389      }
390  
391      /**
392       * remove an entry
393       *
394       * @param entry to be removed
395       */
396  
397      void remove(EntryNode entry)
398      {
399          _property_table.removeProperty(entry.getProperty());
400          if (entry.isDocumentEntry())
401          {
402              _documents.remove((( DocumentNode ) entry).getDocument());
403          }
404      }
405  
406      private void processProperties(final BlockList small_blocks,
407                                     final BlockList big_blocks,
408                                     final Iterator properties,
409                                     final DirectoryNode dir)
410          throws IOException
411      {
412          while (properties.hasNext())
413          {
414              Property      property = ( Property ) properties.next();
415              String        name     = property.getName();
416              DirectoryNode parent   = (dir == null)
417                                       ? (( DirectoryNode ) getRoot())
418                                       : dir;
419  
420              if (property.isDirectory())
421              {
422                  DirectoryNode new_dir =
423                      ( DirectoryNode ) parent.createDirectory(name);
424  
425                  processProperties(
426                      small_blocks, big_blocks,
427                      (( DirectoryProperty ) property).getChildren(), new_dir);
428              }
429              else
430              {
431                  int           startBlock = property.getStartBlock();
432                  int           size       = property.getSize();
433                  POIFSDocument document   = null;
434  
435                  if (property.shouldUseSmallBlocks())
436                  {
437                      document =
438                          new POIFSDocumentfetchBlocksnamesmall_blocks                     .fetchBlocks(startBlock), size);
439                  }
440                  else
441                  {
442                      document =
443                          new POIFSDocument(name,
444                                            big_blocks.fetchBlocks(startBlock),
445                                            size);
446                  }
447                  parent.createDocument(document);
448              }
449          }
450      }
451  
452      /* ********** START begin implementation of POIFSViewable ********** */
453  
454      /**
455       * Get an array of objects, some of which may implement
456       * POIFSViewable
457       *
458       * @return an array of Object; may not be null, but may be empty
459       */
460  
461      public Object [] getViewableArray()
462      {
463          if (preferArray())
464          {
465              return (( POIFSViewable ) getRoot()).getViewableArray();
466          }
467          else
468          {
469              return new Object[ 0 ];
470          }
471      }
472  
473      /**
474       * Get an Iterator of objects, some of which may implement
475       * POIFSViewable
476       *
477       * @return an Iterator; may not be null, but may have an empty
478       * back end store
479       */
480  
481      public Iterator getViewableIterator()
482      {
483          if (!preferArray())
484          {
485              return (( POIFSViewable ) getRoot()).getViewableIterator();
486          }
487          else
488          {
489              return Collections.EMPTY_LIST.iterator();
490          }
491      }
492  
493      /**
494       * Give viewers a hint as to whether to call getViewableArray or
495       * getViewableIterator
496       *
497       * @return true if a viewer should call getViewableArray, false if
498       *         a viewer should call getViewableIterator
499       */
500  
501      public boolean preferArray()
502      {
503          return (( POIFSViewable ) getRoot()).preferArray();
504      }
505  
506      /**
507       * Provides a short description of the object, to be used when a
508       * POIFSViewable object has not provided its contents.
509       *
510       * @return short description
511       */
512  
513      public String getShortDescription()
514      {
515          return "POIFS FileSystem";
516      }
517  
518      /* **********  END  begin implementation of POIFSViewable ********** */
519  }   // end public class POIFSFileSystem
520  
521