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.

MutableSection.java 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
  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.hpsf;
  16. import java.io.ByteArrayOutputStream;
  17. import java.io.IOException;
  18. import java.io.OutputStream;
  19. import java.util.Collections;
  20. import java.util.Comparator;
  21. import java.util.Date;
  22. import java.util.Iterator;
  23. import java.util.LinkedList;
  24. import java.util.List;
  25. import java.util.ListIterator;
  26. import java.util.Map;
  27. import org.apache.poi.hpsf.wellknown.PropertyIDMap;
  28. import org.apache.poi.util.LittleEndian;
  29. /**
  30. * <p>Adds writing capability to the {@link Section} class.</p>
  31. *
  32. * <p>Please be aware that this class' functionality will be merged into the
  33. * {@link Section} class at a later time, so the API will change.</p>
  34. */
  35. public class MutableSection extends Section
  36. {
  37. /**
  38. * <p>If the "dirty" flag is true, the section's size must be
  39. * (re-)calculated before the section is written.</p>
  40. */
  41. private boolean dirty = true;
  42. /**
  43. * <p>List to assemble the properties. Unfortunately a wrong
  44. * decision has been taken when specifying the "properties" field
  45. * as an Property[]. It should have been a {@link java.util.List}.</p>
  46. */
  47. private List<Property> preprops;
  48. /**
  49. * <p>Contains the bytes making out the section. This byte array is
  50. * established when the section's size is calculated and can be reused
  51. * later. It is valid only if the "dirty" flag is false.</p>
  52. */
  53. private byte[] sectionBytes;
  54. /**
  55. * <p>Creates an empty mutable section.</p>
  56. */
  57. public MutableSection()
  58. {
  59. dirty = true;
  60. formatID = null;
  61. offset = -1;
  62. preprops = new LinkedList<Property>();
  63. }
  64. /**
  65. * <p>Constructs a <code>MutableSection</code> by doing a deep copy of an
  66. * existing <code>Section</code>. All nested <code>Property</code>
  67. * instances, will be their mutable counterparts in the new
  68. * <code>MutableSection</code>.</p>
  69. *
  70. * @param s The section set to copy
  71. */
  72. public MutableSection(final Section s)
  73. {
  74. setFormatID(s.getFormatID());
  75. final Property[] pa = s.getProperties();
  76. final MutableProperty[] mpa = new MutableProperty[pa.length];
  77. for (int i = 0; i < pa.length; i++)
  78. mpa[i] = new MutableProperty(pa[i]);
  79. setProperties(mpa);
  80. setDictionary(s.getDictionary());
  81. }
  82. /**
  83. * <p>Sets the section's format ID.</p>
  84. *
  85. * @param formatID The section's format ID
  86. *
  87. * @see #setFormatID(byte[])
  88. * @see Section#getFormatID
  89. */
  90. public void setFormatID(final ClassID formatID)
  91. {
  92. this.formatID = formatID;
  93. }
  94. /**
  95. * <p>Sets the section's format ID.</p>
  96. *
  97. * @param formatID The section's format ID as a byte array. It components
  98. * are in big-endian format.
  99. *
  100. * @see #setFormatID(ClassID)
  101. * @see Section#getFormatID
  102. */
  103. public void setFormatID(final byte[] formatID)
  104. {
  105. ClassID fid = getFormatID();
  106. if (fid == null)
  107. {
  108. fid = new ClassID();
  109. setFormatID(fid);
  110. }
  111. fid.setBytes(formatID);
  112. }
  113. /**
  114. * <p>Sets this section's properties. Any former values are overwritten.</p>
  115. *
  116. * @param properties This section's new properties.
  117. */
  118. public void setProperties(final Property[] properties)
  119. {
  120. this.properties = properties;
  121. preprops = new LinkedList<Property>();
  122. for (int i = 0; i < properties.length; i++)
  123. preprops.add(properties[i]);
  124. dirty = true;
  125. }
  126. /**
  127. * <p>Sets the string value of the property with the specified ID.</p>
  128. *
  129. * @param id The property's ID
  130. * @param value The property's value. It will be written as a Unicode
  131. * string.
  132. *
  133. * @see #setProperty(int, long, Object)
  134. * @see #getProperty
  135. */
  136. public void setProperty(final int id, final String value)
  137. {
  138. setProperty(id, Variant.VT_LPWSTR, value);
  139. dirty = true;
  140. }
  141. /**
  142. * <p>Sets the int value of the property with the specified ID.</p>
  143. *
  144. * @param id The property's ID
  145. * @param value The property's value.
  146. *
  147. * @see #setProperty(int, long, Object)
  148. * @see #getProperty
  149. */
  150. public void setProperty(final int id, final int value)
  151. {
  152. setProperty(id, Variant.VT_I4, Integer.valueOf(value));
  153. dirty = true;
  154. }
  155. /**
  156. * <p>Sets the long value of the property with the specified ID.</p>
  157. *
  158. * @param id The property's ID
  159. * @param value The property's value.
  160. *
  161. * @see #setProperty(int, long, Object)
  162. * @see #getProperty
  163. */
  164. public void setProperty(final int id, final long value)
  165. {
  166. setProperty(id, Variant.VT_I8, Long.valueOf(value));
  167. dirty = true;
  168. }
  169. /**
  170. * <p>Sets the boolean value of the property with the specified ID.</p>
  171. *
  172. * @param id The property's ID
  173. * @param value The property's value.
  174. *
  175. * @see #setProperty(int, long, Object)
  176. * @see #getProperty
  177. */
  178. public void setProperty(final int id, final boolean value)
  179. {
  180. setProperty(id, Variant.VT_BOOL, Boolean.valueOf(value));
  181. dirty = true;
  182. }
  183. /**
  184. * <p>Sets the value and the variant type of the property with the
  185. * specified ID. If a property with this ID is not yet present in
  186. * the section, it will be added. An already present property with
  187. * the specified ID will be overwritten. A default mapping will be
  188. * used to choose the property's type.</p>
  189. *
  190. * @param id The property's ID.
  191. * @param variantType The property's variant type.
  192. * @param value The property's value.
  193. *
  194. * @see #setProperty(int, String)
  195. * @see #getProperty
  196. * @see Variant
  197. */
  198. public void setProperty(final int id, final long variantType,
  199. final Object value)
  200. {
  201. final MutableProperty p = new MutableProperty();
  202. p.setID(id);
  203. p.setType(variantType);
  204. p.setValue(value);
  205. setProperty(p);
  206. dirty = true;
  207. }
  208. /**
  209. * <p>Sets a property.</p>
  210. *
  211. * @param p The property to be set.
  212. *
  213. * @see #setProperty(int, long, Object)
  214. * @see #getProperty
  215. * @see Variant
  216. */
  217. public void setProperty(final Property p)
  218. {
  219. final long id = p.getID();
  220. removeProperty(id);
  221. preprops.add(p);
  222. dirty = true;
  223. }
  224. /**
  225. * <p>Removes a property.</p>
  226. *
  227. * @param id The ID of the property to be removed
  228. */
  229. public void removeProperty(final long id)
  230. {
  231. for (final Iterator<Property> i = preprops.iterator(); i.hasNext();)
  232. if (i.next().getID() == id)
  233. {
  234. i.remove();
  235. break;
  236. }
  237. dirty = true;
  238. }
  239. /**
  240. * <p>Sets the value of the boolean property with the specified
  241. * ID.</p>
  242. *
  243. * @param id The property's ID
  244. * @param value The property's value
  245. *
  246. * @see #setProperty(int, long, Object)
  247. * @see #getProperty
  248. * @see Variant
  249. */
  250. protected void setPropertyBooleanValue(final int id, final boolean value)
  251. {
  252. setProperty(id, Variant.VT_BOOL, Boolean.valueOf(value));
  253. }
  254. /**
  255. * <p>Returns the section's size.</p>
  256. *
  257. * @return the section's size.
  258. */
  259. public int getSize()
  260. {
  261. if (dirty)
  262. {
  263. try
  264. {
  265. size = calcSize();
  266. dirty = false;
  267. }
  268. catch (HPSFRuntimeException ex)
  269. {
  270. throw ex;
  271. }
  272. catch (Exception ex)
  273. {
  274. throw new HPSFRuntimeException(ex);
  275. }
  276. }
  277. return size;
  278. }
  279. /**
  280. * <p>Calculates the section's size. It is the sum of the lengths of the
  281. * section's header (8), the properties list (16 times the number of
  282. * properties) and the properties themselves.</p>
  283. *
  284. * @return the section's length in bytes.
  285. * @throws WritingNotSupportedException
  286. * @throws IOException
  287. */
  288. private int calcSize() throws WritingNotSupportedException, IOException
  289. {
  290. final ByteArrayOutputStream out = new ByteArrayOutputStream();
  291. write(out);
  292. out.close();
  293. /* Pad to multiple of 4 bytes so that even the Windows shell (explorer)
  294. * shows custom properties. */
  295. sectionBytes = Util.pad4(out.toByteArray());
  296. return sectionBytes.length;
  297. }
  298. /**
  299. * <p>Writes this section into an output stream.</p>
  300. *
  301. * <p>Internally this is done by writing into three byte array output
  302. * streams: one for the properties, one for the property list and one for
  303. * the section as such. The two former are appended to the latter when they
  304. * have received all their data.</p>
  305. *
  306. * @param out The stream to write into.
  307. *
  308. * @return The number of bytes written, i.e. the section's size.
  309. * @exception IOException if an I/O error occurs
  310. * @exception WritingNotSupportedException if HPSF does not yet support
  311. * writing a property's variant type.
  312. */
  313. public int write(final OutputStream out)
  314. throws WritingNotSupportedException, IOException
  315. {
  316. /* Check whether we have already generated the bytes making out the
  317. * section. */
  318. if (!dirty && sectionBytes != null)
  319. {
  320. out.write(sectionBytes);
  321. return sectionBytes.length;
  322. }
  323. /* The properties are written to this stream. */
  324. final ByteArrayOutputStream propertyStream =
  325. new ByteArrayOutputStream();
  326. /* The property list is established here. After each property that has
  327. * been written to "propertyStream", a property list entry is written to
  328. * "propertyListStream". */
  329. final ByteArrayOutputStream propertyListStream =
  330. new ByteArrayOutputStream();
  331. /* Maintain the current position in the list. */
  332. int position = 0;
  333. /* Increase the position variable by the size of the property list so
  334. * that it points behind the property list and to the beginning of the
  335. * properties themselves. */
  336. position += 2 * LittleEndian.INT_SIZE +
  337. getPropertyCount() * 2 * LittleEndian.INT_SIZE;
  338. /* Writing the section's dictionary it tricky. If there is a dictionary
  339. * (property 0) the codepage property (property 1) must be set, too. */
  340. int codepage = -1;
  341. if (getProperty(PropertyIDMap.PID_DICTIONARY) != null)
  342. {
  343. final Object p1 = getProperty(PropertyIDMap.PID_CODEPAGE);
  344. if (p1 != null)
  345. {
  346. if (!(p1 instanceof Integer))
  347. throw new IllegalPropertySetDataException
  348. ("The codepage property (ID = 1) must be an " +
  349. "Integer object.");
  350. }
  351. else
  352. /* Warning: The codepage property is not set although a
  353. * dictionary is present. In order to cope with this problem we
  354. * add the codepage property and set it to Unicode. */
  355. setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
  356. Integer.valueOf(Constants.CP_UNICODE));
  357. codepage = getCodepage();
  358. }
  359. /* Sort the property list by their property IDs: */
  360. Collections.sort(preprops, new Comparator<Property>()
  361. {
  362. public int compare(final Property p1, final Property p2)
  363. {
  364. if (p1.getID() < p2.getID())
  365. return -1;
  366. else if (p1.getID() == p2.getID())
  367. return 0;
  368. else
  369. return 1;
  370. }
  371. });
  372. /* Write the properties and the property list into their respective
  373. * streams: */
  374. for (final ListIterator<Property> i = preprops.listIterator(); i.hasNext();)
  375. {
  376. final MutableProperty p = (MutableProperty) i.next();
  377. final long id = p.getID();
  378. /* Write the property list entry. */
  379. TypeWriter.writeUIntToStream(propertyListStream, p.getID());
  380. TypeWriter.writeUIntToStream(propertyListStream, position);
  381. /* If the property ID is not equal 0 we write the property and all
  382. * is fine. However, if it equals 0 we have to write the section's
  383. * dictionary which has an implicit type only and an explicit
  384. * value. */
  385. if (id != 0)
  386. /* Write the property and update the position to the next
  387. * property. */
  388. position += p.write(propertyStream, getCodepage());
  389. else
  390. {
  391. if (codepage == -1)
  392. throw new IllegalPropertySetDataException
  393. ("Codepage (property 1) is undefined.");
  394. position += writeDictionary(propertyStream, dictionary,
  395. codepage);
  396. }
  397. }
  398. propertyStream.close();
  399. propertyListStream.close();
  400. /* Write the section: */
  401. byte[] pb1 = propertyListStream.toByteArray();
  402. byte[] pb2 = propertyStream.toByteArray();
  403. /* Write the section's length: */
  404. TypeWriter.writeToStream(out, LittleEndian.INT_SIZE * 2 +
  405. pb1.length + pb2.length);
  406. /* Write the section's number of properties: */
  407. TypeWriter.writeToStream(out, getPropertyCount());
  408. /* Write the property list: */
  409. out.write(pb1);
  410. /* Write the properties: */
  411. out.write(pb2);
  412. int streamLength = LittleEndian.INT_SIZE * 2 + pb1.length + pb2.length;
  413. return streamLength;
  414. }
  415. /**
  416. * <p>Writes the section's dictionary.</p>
  417. *
  418. * @param out The output stream to write to.
  419. * @param dictionary The dictionary.
  420. * @param codepage The codepage to be used to write the dictionary items.
  421. * @return The number of bytes written
  422. * @exception IOException if an I/O exception occurs.
  423. */
  424. private static int writeDictionary(final OutputStream out,
  425. final Map<Long,String> dictionary, final int codepage)
  426. throws IOException
  427. {
  428. int length = TypeWriter.writeUIntToStream(out, dictionary.size());
  429. for (final Iterator<Long> i = dictionary.keySet().iterator(); i.hasNext();)
  430. {
  431. final Long key = i.next();
  432. final String value = dictionary.get(key);
  433. if (codepage == Constants.CP_UNICODE)
  434. {
  435. /* Write the dictionary item in Unicode. */
  436. int sLength = value.length() + 1;
  437. if (sLength % 2 == 1)
  438. sLength++;
  439. length += TypeWriter.writeUIntToStream(out, key.longValue());
  440. length += TypeWriter.writeUIntToStream(out, sLength);
  441. final byte[] ca =
  442. value.getBytes(VariantSupport.codepageToEncoding(codepage));
  443. for (int j = 2; j < ca.length; j += 2)
  444. {
  445. out.write(ca[j+1]);
  446. out.write(ca[j]);
  447. length += 2;
  448. }
  449. sLength -= value.length();
  450. while (sLength > 0)
  451. {
  452. out.write(0x00);
  453. out.write(0x00);
  454. length += 2;
  455. sLength--;
  456. }
  457. }
  458. else
  459. {
  460. /* Write the dictionary item in another codepage than
  461. * Unicode. */
  462. length += TypeWriter.writeUIntToStream(out, key.longValue());
  463. length += TypeWriter.writeUIntToStream(out, value.length() + 1);
  464. final byte[] ba =
  465. value.getBytes(VariantSupport.codepageToEncoding(codepage));
  466. for (int j = 0; j < ba.length; j++)
  467. {
  468. out.write(ba[j]);
  469. length++;
  470. }
  471. out.write(0x00);
  472. length++;
  473. }
  474. }
  475. return length;
  476. }
  477. /**
  478. * <p>Overwrites the super class' method to cope with a redundancy:
  479. * the property count is maintained in a separate member variable, but
  480. * shouldn't.</p>
  481. *
  482. * @return The number of properties in this section
  483. */
  484. public int getPropertyCount()
  485. {
  486. return preprops.size();
  487. }
  488. /**
  489. * <p>Gets this section's properties.</p>
  490. *
  491. * @return this section's properties.
  492. */
  493. public Property[] getProperties()
  494. {
  495. properties = (Property[]) preprops.toArray(new Property[0]);
  496. return properties;
  497. }
  498. /**
  499. * <p>Gets a property.</p>
  500. *
  501. * @param id The ID of the property to get
  502. * @return The property or <code>null</code> if there is no such property
  503. */
  504. public Object getProperty(final long id)
  505. {
  506. /* Calling getProperties() ensures that properties and preprops are in
  507. * sync.</p> */
  508. getProperties();
  509. return super.getProperty(id);
  510. }
  511. /**
  512. * <p>Sets the section's dictionary. All keys in the dictionary must be
  513. * {@link java.lang.Long} instances, all values must be
  514. * {@link java.lang.String}s. This method overwrites the properties with IDs
  515. * 0 and 1 since they are reserved for the dictionary and the dictionary's
  516. * codepage. Setting these properties explicitly might have surprising
  517. * effects. An application should never do this but always use this
  518. * method.</p>
  519. *
  520. * @param dictionary The dictionary
  521. *
  522. * @exception IllegalPropertySetDataException if the dictionary's key and
  523. * value types are not correct.
  524. *
  525. * @see Section#getDictionary()
  526. */
  527. public void setDictionary(final Map<Long,String> dictionary)
  528. throws IllegalPropertySetDataException
  529. {
  530. if (dictionary != null)
  531. {
  532. this.dictionary = dictionary;
  533. /* Set the dictionary property (ID 0). Please note that the second
  534. * parameter in the method call below is unused because dictionaries
  535. * don't have a type. */
  536. setProperty(PropertyIDMap.PID_DICTIONARY, -1, dictionary);
  537. /* If the codepage property (ID 1) for the strings (keys and
  538. * values) used in the dictionary is not yet defined, set it to
  539. * Unicode. */
  540. final Integer codepage =
  541. (Integer) getProperty(PropertyIDMap.PID_CODEPAGE);
  542. if (codepage == null)
  543. setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
  544. Integer.valueOf(Constants.CP_UNICODE));
  545. }
  546. else
  547. /* Setting the dictionary to null means to remove property 0.
  548. * However, it does not mean to remove property 1 (codepage). */
  549. removeProperty(PropertyIDMap.PID_DICTIONARY);
  550. }
  551. /**
  552. * <p>Sets a property.</p>
  553. *
  554. * @param id The property ID.
  555. * @param value The property's value. The value's class must be one of those
  556. * supported by HPSF.
  557. */
  558. public void setProperty(final int id, final Object value)
  559. {
  560. if (value instanceof String)
  561. setProperty(id, (String) value);
  562. else if (value instanceof Long)
  563. setProperty(id, ((Long) value).longValue());
  564. else if (value instanceof Integer)
  565. setProperty(id, ((Integer) value).intValue());
  566. else if (value instanceof Short)
  567. setProperty(id, ((Short) value).intValue());
  568. else if (value instanceof Boolean)
  569. setProperty(id, ((Boolean) value).booleanValue());
  570. else if (value instanceof Date)
  571. setProperty(id, Variant.VT_FILETIME, value);
  572. else
  573. throw new HPSFRuntimeException(
  574. "HPSF does not support properties of type " +
  575. value.getClass().getName() + ".");
  576. }
  577. /**
  578. * <p>Removes all properties from the section including 0 (dictionary) and
  579. * 1 (codepage).</p>
  580. */
  581. public void clear()
  582. {
  583. final Property[] properties = getProperties();
  584. for (int i = 0; i < properties.length; i++)
  585. {
  586. final Property p = properties[i];
  587. removeProperty(p.getID());
  588. }
  589. }
  590. /**
  591. * <p>Sets the codepage.</p>
  592. *
  593. * @param codepage the codepage
  594. */
  595. public void setCodepage(final int codepage)
  596. {
  597. setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
  598. Integer.valueOf(codepage));
  599. }
  600. }