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.dev.POIFSViewable;
63   import org.apache.poi.poifs.property.DirectoryProperty;
64   import org.apache.poi.poifs.property.DocumentProperty;
65   import org.apache.poi.poifs.property.Property;
66   
67   /**
68    * Simple implementation of DirectoryEntry
69    *
70    * @author Marc Johnson (mjohnson at apache dot org)
71    */
72   
73   public class DirectoryNode
74       extends EntryNode
75       implements DirectoryEntry, POIFSViewable
76   {
77   
78       // Map of Entry instances, keyed by their names
79       private Map               _entries;
80   
81       // the POIFSFileSystem we belong to
82       private POIFSFileSystem   _filesystem;
83   
84       // the path described by this document
85       private POIFSDocumentPath _path;
86   
87       /**
88        * create a DirectoryNode. This method is not public by design; it
89        * is intended strictly for the internal use of this package
90        *
91        * @param property the DirectoryProperty for this DirectoryEntry
92        * @param filesystem the POIFSFileSystem we belong to
93        * @param parent the parent of this entry
94        */
95   
96       DirectoryNode(final DirectoryProperty property,
97                     final POIFSFileSystem filesystem,
98                     final DirectoryNode parent)
99       {
100          super(property, parent);
101          if (parent == null)
102          {
103              _path = new POIFSDocumentPath();
104          }
105          else
106          {
107              _path = new POIFSDocumentPath(parent._path, new String[]
108              {
109                  property.getName()
110              });
111          }
112          _filesystem = filesystem;
113          _entries    = new HashMap();
114          Iterator iter = property.getChildren();
115  
116          while (iter.hasNext())
117          {
118              Property child     = ( Property ) iter.next();
119              Entry    childNode = null;
120  
121              if (child.isDirectory())
122              {
123                  childNode = new DirectoryNode(( DirectoryProperty ) child,
124                                                _filesystem, this);
125              }
126              else
127              {
128                  childNode = new DocumentNode(( DocumentProperty ) child,
129                                               this);
130              }
131              _entries.put(childNode.getName(), childNode);
132          }
133      }
134  
135      /**
136       * @return this directory's path representation
137       */
138  
139      public POIFSDocumentPath getPath()
140      {
141          return _path;
142      }
143  
144      /**
145       * create a new DocumentEntry
146       *
147       * @param document the new document
148       *
149       * @return the new DocumentEntry
150       *
151       * @exception IOException
152       */
153  
154      DocumentEntry createDocument(final POIFSDocument document)
155          throws IOException
156      {
157          DocumentProperty property = document.getDocumentProperty();
158          DocumentNode     rval     = new DocumentNode(property, this);
159  
160          (( DirectoryProperty ) getProperty()).addChild(property);
161          _filesystem.addDocument(document);
162          _entries.put(property.getName(), rval);
163          return rval;
164      }
165  
166      /**
167       * Change a contained Entry's name
168       *
169       * @param oldName the original name
170       * @param newName the new name
171       *
172       * @return true if the operation succeeded, else false
173       */
174  
175      boolean changeName(final String oldName, final String newName)
176      {
177          boolean   rval  = false;
178          EntryNode child = ( EntryNode ) _entries.get(oldName);
179  
180          if (child != null)
181          {
182              rval = (( DirectoryProperty ) getProperty())
183                  .changeName(child.getProperty(), newName);
184              if (rval)
185              {
186                  _entries.remove(oldName);
187                  _entries.put(child.getProperty().getName(), child);
188              }
189          }
190          return rval;
191      }
192  
193      /**
194       * Delete an entry
195       *
196       * @param entry the EntryNode to be deleted
197       *
198       * @return true if the entry was deleted, else false
199       */
200  
201      boolean deleteEntry(final EntryNode entry)
202      {
203          boolean rval =
204              (( DirectoryProperty ) getProperty())
205                  .deleteChild(entry.getProperty());
206  
207          if (rval)
208          {
209              _entries.remove(entry.getName());
210              _filesystem.remove(entry);
211          }
212          return rval;
213      }
214  
215      /* ********** START implementation of DirectoryEntry ********** */
216  
217      /**
218       * get an iterator of the Entry instances contained directly in
219       * this instance (in other words, children only; no grandchildren
220       * etc.)
221       *
222       * @return iterator; never null, but hasNext() may return false
223       *         immediately (i.e., this DirectoryEntry is empty). All
224       *         objects retrieved by next() are guaranteed to be
225       *         implementations of Entry.
226       */
227  
228      public Iterator getEntries()
229      {
230          return _entries.values().iterator();
231      }
232  
233      /**
234       * is this DirectoryEntry empty?
235       *
236       * @return true if this instance contains no Entry instances
237       */
238  
239      public boolean isEmpty()
240      {
241          return _entries.isEmpty();
242      }
243  
244      /**
245       * find out how many Entry instances are contained directly within
246       * this DirectoryEntry
247       *
248       * @return number of immediately (no grandchildren etc.) contained
249       *         Entry instances
250       */
251  
252      public int getEntryCount()
253      {
254          return _entries.size();
255      }
256  
257      /**
258       * get a specified Entry by name
259       *
260       * @param name the name of the Entry to obtain.
261       *
262       * @return the specified Entry, if it is directly contained in
263       *         this DirectoryEntry
264       *
265       * @exception FileNotFoundException if no Entry with the specified
266       *            name exists in this DirectoryEntry
267       */
268  
269      public Entry getEntry(final String name)
270          throws FileNotFoundException
271      {
272          Entry rval = null;
273  
274          if (name != null)
275          {
276              rval = ( Entry ) _entries.get(name);
277          }
278          if (rval == null)
279          {
280  
281              // either a null name was given, or there is no such name
282              throw new FileNotFoundException("no such entry: \"" + name
283                                              + "\"");
284          }
285          return rval;
286      }
287  
288      /**
289       * create a new DocumentEntry
290       *
291       * @param name the name of the new DocumentEntry
292       * @param stream the InputStream from which to create the new
293       *               DocumentEntry
294       *
295       * @return the new DocumentEntry
296       *
297       * @exception IOException
298       */
299  
300      public DocumentEntry createDocument(final String name,
301                                          final InputStream stream)
302          throws IOException
303      {
304          return createDocument(new POIFSDocument(name, stream));
305      }
306  
307      /**
308       * create a new DocumentEntry; the data will be provided later
309       *
310       * @param name the name of the new DocumentEntry
311       * @param size the size of the new DocumentEntry
312       * @param writer the writer of the new DocumentEntry
313       *
314       * @return the new DocumentEntry
315       *
316       * @exception IOException
317       */
318  
319      public DocumentEntry createDocument(final String name, final int size,
320                                          final POIFSWriterListener writer)
321          throws IOException
322      {
323          return createDocument(new POIFSDocument(name, size, _path, writer));
324      }
325  
326      /**
327       * create a new DirectoryEntry
328       *
329       * @param name the name of the new DirectoryEntry
330       *
331       * @return the new DirectoryEntry
332       *
333       * @exception IOException
334       */
335  
336      public DirectoryEntry createDirectory(final String name)
337          throws IOException
338      {
339          DirectoryProperty property = new DirectoryProperty(name);
340          DirectoryNode     rval     = new DirectoryNode(property, _filesystem,
341                                           this);
342  
343          (( DirectoryProperty ) getProperty()).addChild(property);
344          _filesystem.addDirectory(property);
345          _entries.put(name, rval);
346          return rval;
347      }
348  
349      /* **********  END  implementation of DirectoryEntry ********** */
350      /* ********** START implementation of Entry ********** */
351  
352      /**
353       * is this a DirectoryEntry?
354       *
355       * @return true if the Entry is a DirectoryEntry, else false
356       */
357  
358      public boolean isDirectoryEntry()
359      {
360          return true;
361      }
362  
363      /* **********  END  implementation of Entry ********** */
364      /* ********** START extension of Entry ********** */
365  
366      /**
367       * extensions use this method to verify internal rules regarding
368       * deletion of the underlying store.
369       *
370       * @return true if it's ok to delete the underlying store, else
371       *         false
372       */
373  
374      protected boolean isDeleteOK()
375      {
376  
377          // if this directory is empty, we can delete it
378          return isEmpty();
379      }
380  
381      /* **********  END  extension of Entry ********** */
382      /* ********** START begin implementation of POIFSViewable ********** */
383  
384      /**
385       * Get an array of objects, some of which may implement
386       * POIFSViewable
387       *
388       * @return an array of Object; may not be null, but may be empty
389       */
390  
391      public Object [] getViewableArray()
392      {
393          return new Object[ 0 ];
394      }
395  
396      /**
397       * Get an Iterator of objects, some of which may implement
398       * POIFSViewable
399       *
400       * @return an Iterator; may not be null, but may have an empty
401       * back end store
402       */
403  
404      public Iterator getViewableIterator()
405      {
406          List components = new ArrayList();
407  
408          components.add(getProperty());
409          SortedMap sortedEntries = new TreeMap(_entries);
410          Iterator  iter          = sortedEntries.values().iterator();
411  
412          while (iter.hasNext())
413          {
414              components.add(iter.next());
415          }
416          return components.iterator();
417      }
418  
419      /**
420       * Give viewers a hint as to whether to call getViewableArray or
421       * getViewableIterator
422       *
423       * @return true if a viewer should call getViewableArray, false if
424       *         a viewer should call getViewableIterator
425       */
426  
427      public boolean preferArray()
428      {
429          return false;
430      }
431  
432      /**
433       * Provides a short description of the object, to be used when a
434       * POIFSViewable object has not provided its contents.
435       *
436       * @return short description
437       */
438  
439      public String getShortDescription()
440      {
441          return getName();
442      }
443  
444      /* **********  END  begin implementation of POIFSViewable ********** */
445  }   // end public class DirectoryNode
446  
447