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          // the header is now properly initialized. Make a list of
272          // writers (the header block, followed by the documents, the
273          // property table, the small block store, the small block
274          // allocation table, the block allocation table, and the
275          // extended block allocation table blocks)
276          List writers = new ArrayList();
277  
278          writers.add(header_block_writer);
279          writers.addAll(_documents);
280          writers.add(_property_table);
281          writers.add(sbtw);
282          writers.add(sbtw.getSBAT());
283          writers.add(bat);
284          for (int j = 0; j < xbat_blocks.length; j++)
285          {
286              writers.add(xbat_blocks[ j ]);
287          }
288  
289          // now, write everything out
290          iter = writers.iterator();
291          while (iter.hasNext())
292          {
293              BlockWritable writer = ( BlockWritable ) iter.next();
294  
295              writer.writeBlocks(stream);
296          }
297      }
298  
299      /**
300       * read in a file and write it back out again
301       *
302       * @param args names of the files; arg[ 0 ] is the input file,
303       *             arg[ 1 ] is the output file
304       *
305       * @exception IOException
306       */
307  
308      public static void main(String args[])
309          throws IOException
310      {
311          if (args.length != 2)
312          {
313              System.err.println(
314                  "two arguments required: input filename and output filename");
315              System.exit(1);
316          }
317          FileInputStream  istream = new FileInputStream(args[ 0 ]);
318          FileOutputStream ostream = new FileOutputStream(args[ 1 ]);
319  
320          new POIFSFileSystem(istream).writeFilesystem(ostream);
321          istream.close();
322          ostream.close();
323      }
324  
325      /**
326       * get the root entry
327       *
328       * @return the root entry
329       */
330  
331      public DirectoryEntry getRoot()
332      {
333          if (_root == null)
334          {
335              _root = new DirectoryNode(_property_table.getRoot(), this, null);
336          }
337          return _root;
338      }
339  
340      /**
341       * open a document in the root entry's list of entries
342       *
343       * @param documentName the name of the document to be opened
344       *
345       * @return a newly opened DocumentInputStream
346       *
347       * @exception IOException if the document does not exist or the
348       *            name is that of a DirectoryEntry
349       */
350  
351      public DocumentInputStream createDocumentInputStream(
352              final String documentName)
353          throws IOException
354      {
355          Entry document = getRoot().getEntry(documentName);
356  
357          if (!document.isDocumentEntry())
358          {
359              throw new IOException("Entry '" + documentName
360                                    + "' is not a DocumentEntry");
361          }
362          return new DocumentInputStream(( DocumentEntry ) document);
363      }
364  
365      /**
366       * add a new POIFSDocument
367       *
368       * @param document the POIFSDocument being added
369       */
370  
371      void addDocument(final POIFSDocument document)
372      {
373          _documents.add(document);
374          _property_table.addProperty(document.getDocumentProperty());
375      }
376  
377      /**
378       * add a new DirectoryProperty
379       *
380       * @param directory the DirectoryProperty being added
381       */
382  
383      void addDirectory(final DirectoryProperty directory)
384      {
385          _property_table.addProperty(directory);
386      }
387  
388      /**
389       * remove an entry
390       *
391       * @param entry to be removed
392       */
393  
394      void remove(EntryNode entry)
395      {
396          _property_table.removeProperty(entry.getProperty());
397          if (entry.isDocumentEntry())
398          {
399              _documents.remove((( DocumentNode ) entry).getDocument());
400          }
401      }
402  
403      private void processProperties(final BlockList small_blocks,
404                                     final BlockList big_blocks,
405                                     final Iterator properties,
406                                     final DirectoryNode dir)
407          throws IOException
408      {
409          while (properties.hasNext())
410          {
411              Property      property = ( Property ) properties.next();
412              String        name     = property.getName();
413              DirectoryNode parent   = (dir == null)
414                                       ? (( DirectoryNode ) getRoot())
415                                       : dir;
416  
417              if (property.isDirectory())
418              {
419                  DirectoryNode new_dir =
420                      ( DirectoryNode ) parent.createDirectory(name);
421  
422                  processProperties(
423                      small_blocks, big_blocks,
424                      (( DirectoryProperty ) property).getChildren(), new_dir);
425              }
426              else
427              {
428                  int           startBlock = property.getStartBlock();
429                  int           size       = property.getSize();
430                  POIFSDocument document   = null;
431  
432                  if (property.shouldUseSmallBlocks())
433                  {
434                      document =
435                          new POIFSDocumentfetchBlocksnamesmall_blocks                     .fetchBlocks(startBlock), size);
436                  }
437                  else
438                  {
439                      document =
440                          new POIFSDocument(name,
441                                            big_blocks.fetchBlocks(startBlock),
442                                            size);
443                  }
444                  parent.createDocument(document);
445              }
446          }
447      }
448  
449      /* ********** START begin implementation of POIFSViewable ********** */
450  
451      /**
452       * Get an array of objects, some of which may implement
453       * POIFSViewable
454       *
455       * @return an array of Object; may not be null, but may be empty
456       */
457  
458      public Object [] getViewableArray()
459      {
460          if (preferArray())
461          {
462              return (( POIFSViewable ) getRoot()).getViewableArray();
463          }
464          else
465          {
466              return new Object[ 0 ];
467          }
468      }
469  
470      /**
471       * Get an Iterator of objects, some of which may implement
472       * POIFSViewable
473       *
474       * @return an Iterator; may not be null, but may have an empty
475       * back end store
476       */
477  
478      public Iterator getViewableIterator()
479      {
480          if (!preferArray())
481          {
482              return (( POIFSViewable ) getRoot()).getViewableIterator();
483          }
484          else
485          {
486              return Collections.EMPTY_LIST.iterator();
487          }
488      }
489  
490      /**
491       * Give viewers a hint as to whether to call getViewableArray or
492       * getViewableIterator
493       *
494       * @return true if a viewer should call getViewableArray, false if
495       *         a viewer should call getViewableIterator
496       */
497  
498      public boolean preferArray()
499      {
500          return (( POIFSViewable ) getRoot()).preferArray();
501      }
502  
503      /**
504       * Provides a short description of the object, to be used when a
505       * POIFSViewable object has not provided its contents.
506       *
507       * @return short description
508       */
509  
510      public String getShortDescription()
511      {
512          return "POIFS FileSystem";
513      }
514  
515      /* **********  END  begin implementation of POIFSViewable ********** */
516  }   // end public class POIFSFileSystem
517  
518