You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DirectoryNode.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.poifs.filesystem;
  16. import java.io.FileNotFoundException;
  17. import java.io.IOException;
  18. import java.io.InputStream;
  19. import java.util.ArrayList;
  20. import java.util.HashMap;
  21. import java.util.Iterator;
  22. import java.util.List;
  23. import java.util.Map;
  24. import java.util.Set;
  25. import java.util.Spliterator;
  26. import org.apache.poi.hpsf.ClassID;
  27. import org.apache.poi.poifs.dev.POIFSViewable;
  28. import org.apache.poi.poifs.property.DirectoryProperty;
  29. import org.apache.poi.poifs.property.DocumentProperty;
  30. import org.apache.poi.poifs.property.Property;
  31. /**
  32. * Simple implementation of DirectoryEntry
  33. */
  34. public class DirectoryNode
  35. extends EntryNode
  36. implements DirectoryEntry, POIFSViewable, Iterable<Entry>
  37. {
  38. // Map of Entry instances, keyed by their names
  39. private final Map<String,Entry> _byname = new HashMap<>();
  40. // Our list of entries, kept sorted to preserve order
  41. private final ArrayList<Entry> _entries = new ArrayList<>();
  42. // the POIFSFileSystem we belong to
  43. private final POIFSFileSystem _filesystem;
  44. // the path described by this document
  45. private final POIFSDocumentPath _path;
  46. /**
  47. * create a DirectoryNode. This method is not public by design; it
  48. * is intended strictly for the internal use of this package
  49. *
  50. * @param property the DirectoryProperty for this DirectoryEntry
  51. * @param filesystem the {@link POIFSFileSystem} we belong to
  52. * @param parent the parent of this entry
  53. */
  54. DirectoryNode(final DirectoryProperty property,
  55. final POIFSFileSystem filesystem,
  56. final DirectoryNode parent)
  57. {
  58. super(property, parent);
  59. this._filesystem = filesystem;
  60. if (parent == null)
  61. {
  62. _path = new POIFSDocumentPath();
  63. }
  64. else
  65. {
  66. _path = new POIFSDocumentPath(parent._path, new String[]
  67. {
  68. property.getName()
  69. });
  70. }
  71. Iterator<Property> iter = property.getChildren();
  72. while (iter.hasNext())
  73. {
  74. Property child = iter.next();
  75. Entry childNode;
  76. if (child.isDirectory())
  77. {
  78. DirectoryProperty childDir = (DirectoryProperty) child;
  79. childNode = new DirectoryNode(childDir, _filesystem, this);
  80. }
  81. else
  82. {
  83. childNode = new DocumentNode((DocumentProperty) child, this);
  84. }
  85. _entries.add(childNode);
  86. _byname.put(childNode.getName(), childNode);
  87. }
  88. }
  89. /**
  90. * @return this directory's path representation
  91. */
  92. public POIFSDocumentPath getPath()
  93. {
  94. return _path;
  95. }
  96. /**
  97. * @return the filesystem that this belongs to
  98. */
  99. public POIFSFileSystem getFileSystem()
  100. {
  101. return _filesystem;
  102. }
  103. /**
  104. * open a document in the directory's entry's list of entries
  105. *
  106. * @param documentName the name of the document to be opened
  107. *
  108. * @return a newly opened DocumentInputStream
  109. *
  110. * @throws IOException if the document does not exist or the
  111. * name is that of a DirectoryEntry
  112. */
  113. public DocumentInputStream createDocumentInputStream(
  114. final String documentName)
  115. throws IOException
  116. {
  117. return createDocumentInputStream(getEntry(documentName));
  118. }
  119. /**
  120. * open a document in the directory's entry's list of entries
  121. *
  122. * @param document the document to be opened
  123. *
  124. * @return a newly opened DocumentInputStream or DocumentInputStream
  125. *
  126. * @throws IOException if the document does not exist or the
  127. * name is that of a DirectoryEntry
  128. */
  129. public DocumentInputStream createDocumentInputStream(
  130. final Entry document)
  131. throws IOException
  132. {
  133. if (!document.isDocumentEntry()) {
  134. throw new IOException("Entry '" + document.getName()
  135. + "' is not a DocumentEntry");
  136. }
  137. DocumentEntry entry = (DocumentEntry)document;
  138. return new DocumentInputStream(entry);
  139. }
  140. /**
  141. * create a new DocumentEntry
  142. *
  143. * @param document the new document
  144. *
  145. * @return the new DocumentEntry
  146. *
  147. * @throws IOException if the document can't be created
  148. */
  149. DocumentEntry createDocument(final POIFSDocument document)
  150. throws IOException
  151. {
  152. DocumentProperty property = document.getDocumentProperty();
  153. DocumentNode rval = new DocumentNode(property, this);
  154. (( DirectoryProperty ) getProperty()).addChild(property);
  155. _filesystem.addDocument(document);
  156. _entries.add(rval);
  157. _byname.put(property.getName(), rval);
  158. return rval;
  159. }
  160. /**
  161. * Change a contained Entry's name
  162. *
  163. * @param oldName the original name
  164. * @param newName the new name
  165. *
  166. * @return true if the operation succeeded, else false
  167. */
  168. boolean changeName(final String oldName, final String newName)
  169. {
  170. boolean rval = false;
  171. EntryNode child = ( EntryNode ) _byname.get(oldName);
  172. if (child != null)
  173. {
  174. rval = (( DirectoryProperty ) getProperty())
  175. .changeName(child.getProperty(), newName);
  176. if (rval)
  177. {
  178. _byname.remove(oldName);
  179. _byname.put(child.getProperty().getName(), child);
  180. }
  181. }
  182. return rval;
  183. }
  184. /**
  185. * Delete an entry
  186. *
  187. * @param entry the EntryNode to be deleted
  188. *
  189. * @return true if the entry was deleted, else false
  190. */
  191. boolean deleteEntry(final EntryNode entry)
  192. {
  193. boolean rval =
  194. (( DirectoryProperty ) getProperty())
  195. .deleteChild(entry.getProperty());
  196. if (rval)
  197. {
  198. _entries.remove(entry);
  199. _byname.remove(entry.getName());
  200. try {
  201. _filesystem.remove(entry);
  202. } catch (IOException e) {
  203. // TODO Work out how to report this, given we can't change the method signature...
  204. throw new RuntimeException(e);
  205. }
  206. }
  207. return rval;
  208. }
  209. /* ********** START implementation of DirectoryEntry ********** */
  210. /**
  211. * get an iterator of the Entry instances contained directly in
  212. * this instance (in other words, children only; no grandchildren
  213. * etc.)
  214. *
  215. * @return iterator; never null, but hasNext() may return false
  216. * immediately (i.e., this DirectoryEntry is empty). All
  217. * objects retrieved by next() are guaranteed to be
  218. * implementations of Entry.
  219. */
  220. @Override
  221. public Iterator<Entry> getEntries()
  222. {
  223. return _entries.iterator();
  224. }
  225. /**
  226. * get the names of all the Entries contained directly in this
  227. * instance (in other words, names of children only; no grandchildren
  228. * etc).
  229. *
  230. * @return the names of all the entries that may be retrieved with
  231. * getEntry(String), which may be empty (if this
  232. * DirectoryEntry is empty)
  233. */
  234. @Override
  235. public Set<String> getEntryNames()
  236. {
  237. return _byname.keySet();
  238. }
  239. /**
  240. * is this DirectoryEntry empty?
  241. *
  242. * @return true if this instance contains no Entry instances
  243. */
  244. @Override
  245. public boolean isEmpty()
  246. {
  247. return _entries.isEmpty();
  248. }
  249. /**
  250. * find out how many Entry instances are contained directly within
  251. * this DirectoryEntry
  252. *
  253. * @return number of immediately (no grandchildren etc.) contained
  254. * Entry instances
  255. */
  256. @Override
  257. public int getEntryCount()
  258. {
  259. return _entries.size();
  260. }
  261. @Override
  262. public boolean hasEntry( String name )
  263. {
  264. return name != null && _byname.containsKey( name );
  265. }
  266. /**
  267. * get a specified Entry by name
  268. *
  269. * @param name the name of the Entry to obtain.
  270. *
  271. * @return the specified Entry, if it is directly contained in
  272. * this DirectoryEntry
  273. *
  274. * @throws FileNotFoundException if no Entry with the specified
  275. * name exists in this DirectoryEntry
  276. */
  277. @Override
  278. public Entry getEntry(final String name) throws FileNotFoundException {
  279. Entry rval = null;
  280. if (name != null) {
  281. rval = _byname.get(name);
  282. }
  283. if (rval == null) {
  284. // throw more useful exceptions for known wrong file-extensions
  285. if(_byname.containsKey("Workbook")) {
  286. throw new IllegalArgumentException("The document is really a XLS file");
  287. } else if(_byname.containsKey("PowerPoint Document")) {
  288. throw new IllegalArgumentException("The document is really a PPT file");
  289. } else if(_byname.containsKey("VisioDocument")) {
  290. throw new IllegalArgumentException("The document is really a VSD file");
  291. }
  292. // either a null name was given, or there is no such name
  293. throw new FileNotFoundException("no such entry: \"" + name
  294. + "\", had: " + _byname.keySet());
  295. }
  296. return rval;
  297. }
  298. /**
  299. * create a new DocumentEntry
  300. *
  301. * @param name the name of the new DocumentEntry
  302. * @param stream the InputStream from which to create the new
  303. * DocumentEntry
  304. *
  305. * @return the new DocumentEntry
  306. *
  307. * @throws IOException if the document can't be created
  308. */
  309. @Override
  310. public DocumentEntry createDocument(final String name,
  311. final InputStream stream)
  312. throws IOException
  313. {
  314. return createDocument(new POIFSDocument(name, _filesystem, stream));
  315. }
  316. /**
  317. * create a new DocumentEntry; the data will be provided later
  318. *
  319. * @param name the name of the new DocumentEntry
  320. * @param size the size of the new DocumentEntry
  321. * @param writer the writer of the new DocumentEntry
  322. *
  323. * @return the new DocumentEntry
  324. *
  325. * @throws IOException if the document can't be created
  326. */
  327. @Override
  328. public DocumentEntry createDocument(final String name, final int size,
  329. final POIFSWriterListener writer)
  330. throws IOException
  331. {
  332. return createDocument(new POIFSDocument(name, size, _filesystem, writer));
  333. }
  334. /**
  335. * create a new DirectoryEntry
  336. *
  337. * @param name the name of the new DirectoryEntry
  338. *
  339. * @return the new DirectoryEntry
  340. *
  341. * @throws IOException if the directory can't be created
  342. */
  343. @Override
  344. public DirectoryEntry createDirectory(final String name)
  345. throws IOException
  346. {
  347. DirectoryProperty property = new DirectoryProperty(name);
  348. DirectoryNode rval = new DirectoryNode(property, _filesystem, this);
  349. _filesystem.addDirectory(property);
  350. (( DirectoryProperty ) getProperty()).addChild(property);
  351. _entries.add(rval);
  352. _byname.put(name, rval);
  353. return rval;
  354. }
  355. /**
  356. * Set the contents of a document, creating if needed,
  357. * otherwise updating. Returns the created / updated DocumentEntry
  358. *
  359. * @param name the name of the new or existing DocumentEntry
  360. * @param stream the InputStream from which to populate the DocumentEntry
  361. *
  362. * @return the new or updated DocumentEntry
  363. *
  364. * @throws IOException if the document can't be created or its content be replaced
  365. */
  366. @SuppressWarnings("WeakerAccess")
  367. public DocumentEntry createOrUpdateDocument(final String name,
  368. final InputStream stream)
  369. throws IOException
  370. {
  371. if (! hasEntry(name)) {
  372. return createDocument(name, stream);
  373. } else {
  374. DocumentNode existing = (DocumentNode)getEntry(name);
  375. POIFSDocument nDoc = new POIFSDocument(existing);
  376. nDoc.replaceContents(stream);
  377. return existing;
  378. }
  379. }
  380. /**
  381. * Gets the storage clsid of the directory entry
  382. *
  383. * @return storage Class ID
  384. */
  385. @Override
  386. public ClassID getStorageClsid()
  387. {
  388. return getProperty().getStorageClsid();
  389. }
  390. /**
  391. * Sets the storage clsid for the directory entry
  392. *
  393. * @param clsidStorage storage Class ID
  394. */
  395. @Override
  396. public void setStorageClsid(ClassID clsidStorage)
  397. {
  398. getProperty().setStorageClsid(clsidStorage);
  399. }
  400. /* ********** END implementation of DirectoryEntry ********** */
  401. /* ********** START implementation of Entry ********** */
  402. /**
  403. * is this a DirectoryEntry?
  404. *
  405. * @return true if the Entry is a DirectoryEntry, else false
  406. */
  407. @Override
  408. public boolean isDirectoryEntry()
  409. {
  410. return true;
  411. }
  412. /* ********** END implementation of Entry ********** */
  413. /* ********** START extension of Entry ********** */
  414. /**
  415. * extensions use this method to verify internal rules regarding
  416. * deletion of the underlying store.
  417. *
  418. * @return true if it's ok to delete the underlying store, else
  419. * false
  420. */
  421. @Override
  422. protected boolean isDeleteOK()
  423. {
  424. // if this directory is empty, we can delete it
  425. return isEmpty();
  426. }
  427. /* ********** END extension of Entry ********** */
  428. /* ********** START begin implementation of POIFSViewable ********** */
  429. /**
  430. * Get an array of objects, some of which may implement
  431. * POIFSViewable
  432. *
  433. * @return an array of Object; may not be null, but may be empty
  434. */
  435. @Override
  436. public Object [] getViewableArray()
  437. {
  438. return new Object[ 0 ];
  439. }
  440. /**
  441. * Get an Iterator of objects, some of which may implement
  442. * POIFSViewable
  443. *
  444. * @return an Iterator; may not be null, but may have an empty
  445. * back end store
  446. */
  447. @Override
  448. public Iterator<Object> getViewableIterator() {
  449. List<Object> components = new ArrayList<>();
  450. components.add(getProperty());
  451. components.addAll(_entries);
  452. return components.iterator();
  453. }
  454. /**
  455. * Give viewers a hint as to whether to call getViewableArray or
  456. * getViewableIterator
  457. *
  458. * @return true if a viewer should call getViewableArray, false if
  459. * a viewer should call getViewableIterator
  460. */
  461. @Override
  462. public boolean preferArray()
  463. {
  464. return false;
  465. }
  466. /**
  467. * Provides a short description of the object, to be used when a
  468. * POIFSViewable object has not provided its contents.
  469. *
  470. * @return short description
  471. */
  472. @Override
  473. public String getShortDescription()
  474. {
  475. return getName();
  476. }
  477. /**
  478. * Returns an Iterator over all the entries
  479. */
  480. @Override
  481. public Iterator<Entry> iterator() {
  482. return getEntries();
  483. }
  484. /**
  485. * Returns a Spliterator over all the entries
  486. *
  487. * @since POI 5.2.0
  488. */
  489. @Override
  490. public Spliterator<Entry> spliterator() {
  491. return _entries.spliterator();
  492. }
  493. /* ********** END begin implementation of POIFSViewable ********** */
  494. } // end public class DirectoryNode