PageRenderTime 91ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/components/forks/poi/src/loci/poi/hpsf/PropertySet.java

http://github.com/openmicroscopy/bioformats
Java | 721 lines | 240 code | 97 blank | 384 comment | 20 complexity | c696e553c12e15e63de30c0e97a23a85 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, Apache-2.0, BSD-2-Clause, MPL-2.0-no-copyleft-exception
  1. /*
  2. * #%L
  3. * Fork of Apache Jakarta POI.
  4. * %%
  5. * Copyright (C) 2008 - 2013 Open Microscopy Environment:
  6. * - Board of Regents of the University of Wisconsin-Madison
  7. * - Glencoe Software, Inc.
  8. * - University of Dundee
  9. * %%
  10. * Licensed under the Apache License, Version 2.0 (the "License");
  11. * you may not use this file except in compliance with the License.
  12. * You may obtain a copy of the License at
  13. *
  14. * http://www.apache.org/licenses/LICENSE-2.0
  15. *
  16. * Unless required by applicable law or agreed to in writing, software
  17. * distributed under the License is distributed on an "AS IS" BASIS,
  18. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  19. * See the License for the specific language governing permissions and
  20. * limitations under the License.
  21. * #L%
  22. */
  23. /* ====================================================================
  24. Licensed to the Apache Software Foundation (ASF) under one or more
  25. contributor license agreements. See the NOTICE file distributed with
  26. this work for additional information regarding copyright ownership.
  27. The ASF licenses this file to You under the Apache License, Version 2.0
  28. (the "License"); you may not use this file except in compliance with
  29. the License. You may obtain a copy of the License at
  30. http://www.apache.org/licenses/LICENSE-2.0
  31. Unless required by applicable law or agreed to in writing, software
  32. distributed under the License is distributed on an "AS IS" BASIS,
  33. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  34. See the License for the specific language governing permissions and
  35. limitations under the License.
  36. ==================================================================== */
  37. package loci.poi.hpsf;
  38. import java.io.IOException;
  39. import java.io.InputStream;
  40. import java.io.UnsupportedEncodingException;
  41. import java.util.ArrayList;
  42. import java.util.List;
  43. import loci.poi.hpsf.wellknown.SectionIDMap;
  44. import loci.poi.util.LittleEndian;
  45. /**
  46. * <p>Represents a property set in the Horrible Property Set Format
  47. * (HPSF). These are usually metadata of a Microsoft Office
  48. * document.</p>
  49. *
  50. * <p>An application that wants to access these metadata should create
  51. * an instance of this class or one of its subclasses by calling the
  52. * factory method {@link PropertySetFactory#create} and then retrieve
  53. * the information its needs by calling appropriate methods.</p>
  54. *
  55. * <p>{@link PropertySetFactory#create} does its work by calling one
  56. * of the constructors {@link PropertySet#PropertySet(InputStream)} or
  57. * {@link PropertySet#PropertySet(byte[])}. If the constructor's
  58. * argument is not in the Horrible Property Set Format, i.e. not a
  59. * property set stream, or if any other error occurs, an appropriate
  60. * exception is thrown.</p>
  61. *
  62. * <p>A {@link PropertySet} has a list of {@link Section}s, and each
  63. * {@link Section} has a {@link Property} array. Use {@link
  64. * #getSections} to retrieve the {@link Section}s, then call {@link
  65. * Section#getProperties} for each {@link Section} to get hold of the
  66. * {@link Property} arrays.</p> Since the vast majority of {@link
  67. * PropertySet}s contains only a single {@link Section}, the
  68. * convenience method {@link #getProperties} returns the properties of
  69. * a {@link PropertySet}'s {@link Section} (throwing a {@link
  70. * NoSingleSectionException} if the {@link PropertySet} contains more
  71. * (or less) than exactly one {@link Section}).</p>
  72. *
  73. * @author Rainer Klute <a
  74. * href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
  75. * @author Drew Varner (Drew.Varner hanginIn sc.edu)
  76. * @version $Id: PropertySet.java 489730 2006-12-22 19:18:16Z bayard $
  77. * @since 2002-02-09
  78. */
  79. public class PropertySet
  80. {
  81. /**
  82. * <p>The "byteOrder" field must equal this value.</p>
  83. */
  84. static final byte[] BYTE_ORDER_ASSERTION =
  85. new byte[] {(byte) 0xFE, (byte) 0xFF};
  86. /**
  87. * <p>Specifies this {@link PropertySet}'s byte order. See the
  88. * HPFS documentation for details!</p>
  89. */
  90. protected int byteOrder;
  91. /**
  92. * <p>Returns the property set stream's low-level "byte order"
  93. * field. It is always <tt>0xFFFE</tt> .</p>
  94. *
  95. * @return The property set stream's low-level "byte order" field.
  96. */
  97. public int getByteOrder()
  98. {
  99. return byteOrder;
  100. }
  101. /**
  102. * <p>The "format" field must equal this value.</p>
  103. */
  104. static final byte[] FORMAT_ASSERTION =
  105. new byte[]{(byte) 0x00, (byte) 0x00};
  106. /**
  107. * <p>Specifies this {@link PropertySet}'s format. See the HPFS
  108. * documentation for details!</p>
  109. */
  110. protected int format;
  111. /**
  112. * <p>Returns the property set stream's low-level "format"
  113. * field. It is always <tt>0x0000</tt> .</p>
  114. *
  115. * @return The property set stream's low-level "format" field.
  116. */
  117. public int getFormat()
  118. {
  119. return format;
  120. }
  121. /**
  122. * <p>Specifies the version of the operating system that created
  123. * this {@link PropertySet}. See the HPFS documentation for
  124. * details!</p>
  125. */
  126. protected int osVersion;
  127. /**
  128. * <p>If the OS version field holds this value the property set stream was
  129. * created on a 16-bit Windows system.</p>
  130. */
  131. public static final int OS_WIN16 = 0x0000;
  132. /**
  133. * <p>If the OS version field holds this value the property set stream was
  134. * created on a Macintosh system.</p>
  135. */
  136. public static final int OS_MACINTOSH = 0x0001;
  137. /**
  138. * <p>If the OS version field holds this value the property set stream was
  139. * created on a 32-bit Windows system.</p>
  140. */
  141. public static final int OS_WIN32 = 0x0002;
  142. /**
  143. * <p>Returns the property set stream's low-level "OS version"
  144. * field.</p>
  145. *
  146. * @return The property set stream's low-level "OS version" field.
  147. */
  148. public int getOSVersion()
  149. {
  150. return osVersion;
  151. }
  152. /**
  153. * <p>Specifies this {@link PropertySet}'s "classID" field. See
  154. * the HPFS documentation for details!</p>
  155. */
  156. protected ClassID classID;
  157. /**
  158. * <p>Returns the property set stream's low-level "class ID"
  159. * field.</p>
  160. *
  161. * @return The property set stream's low-level "class ID" field.
  162. */
  163. public ClassID getClassID()
  164. {
  165. return classID;
  166. }
  167. /**
  168. * <p>Returns the number of {@link Section}s in the property
  169. * set.</p>
  170. *
  171. * @return The number of {@link Section}s in the property set.
  172. */
  173. public int getSectionCount()
  174. {
  175. return sections.size();
  176. }
  177. /**
  178. * <p>The sections in this {@link PropertySet}.</p>
  179. */
  180. protected List sections;
  181. /**
  182. * <p>Returns the {@link Section}s in the property set.</p>
  183. *
  184. * @return The {@link Section}s in the property set.
  185. */
  186. public List getSections()
  187. {
  188. return sections;
  189. }
  190. /**
  191. * <p>Creates an empty (uninitialized) {@link PropertySet}.</p>
  192. *
  193. * <p><strong>Please note:</strong> For the time being this
  194. * constructor is protected since it is used for internal purposes
  195. * only, but expect it to become public once the property set's
  196. * writing functionality is implemented.</p>
  197. */
  198. protected PropertySet()
  199. { }
  200. /**
  201. * <p>Creates a {@link PropertySet} instance from an {@link
  202. * InputStream} in the Horrible Property Set Format.</p>
  203. *
  204. * <p>The constructor reads the first few bytes from the stream
  205. * and determines whether it is really a property set stream. If
  206. * it is, it parses the rest of the stream. If it is not, it
  207. * resets the stream to its beginning in order to let other
  208. * components mess around with the data and throws an
  209. * exception.</p>
  210. *
  211. * @param stream Holds the data making out the property set
  212. * stream.
  213. * @throws MarkUnsupportedException if the stream does not support
  214. * the {@link InputStream#markSupported} method.
  215. * @throws IOException if the {@link InputStream} cannot not be
  216. * accessed as needed.
  217. * @exception NoPropertySetStreamException if the input stream does not
  218. * contain a property set.
  219. * @exception UnsupportedEncodingException if a character encoding is not
  220. * supported.
  221. */
  222. public PropertySet(final InputStream stream)
  223. throws NoPropertySetStreamException, MarkUnsupportedException,
  224. IOException, UnsupportedEncodingException
  225. {
  226. if (isPropertySetStream(stream))
  227. {
  228. final int avail = stream.available();
  229. final byte[] buffer = new byte[avail];
  230. stream.read(buffer, 0, buffer.length);
  231. init(buffer, 0, buffer.length);
  232. }
  233. else
  234. throw new NoPropertySetStreamException();
  235. }
  236. /**
  237. * <p>Creates a {@link PropertySet} instance from a byte array
  238. * that represents a stream in the Horrible Property Set
  239. * Format.</p>
  240. *
  241. * @param stream The byte array holding the stream data.
  242. * @param offset The offset in <var>stream</var> where the stream
  243. * data begin. If the stream data begin with the first byte in the
  244. * array, the <var>offset</var> is 0.
  245. * @param length The length of the stream data.
  246. * @throws NoPropertySetStreamException if the byte array is not a
  247. * property set stream.
  248. *
  249. * @exception UnsupportedEncodingException if the codepage is not supported.
  250. */
  251. public PropertySet(final byte[] stream, final int offset, final int length)
  252. throws NoPropertySetStreamException, UnsupportedEncodingException
  253. {
  254. if (isPropertySetStream(stream, offset, length))
  255. init(stream, offset, length);
  256. else
  257. throw new NoPropertySetStreamException();
  258. }
  259. /**
  260. * <p>Creates a {@link PropertySet} instance from a byte array
  261. * that represents a stream in the Horrible Property Set
  262. * Format.</p>
  263. *
  264. * @param stream The byte array holding the stream data. The
  265. * complete byte array contents is the stream data.
  266. * @throws NoPropertySetStreamException if the byte array is not a
  267. * property set stream.
  268. *
  269. * @exception UnsupportedEncodingException if the codepage is not supported.
  270. */
  271. public PropertySet(final byte[] stream)
  272. throws NoPropertySetStreamException, UnsupportedEncodingException
  273. {
  274. this(stream, 0, stream.length);
  275. }
  276. /**
  277. * <p>Checks whether an {@link InputStream} is in the Horrible
  278. * Property Set Format.</p>
  279. *
  280. * @param stream The {@link InputStream} to check. In order to
  281. * perform the check, the method reads the first bytes from the
  282. * stream. After reading, the stream is reset to the position it
  283. * had before reading. The {@link InputStream} must support the
  284. * {@link InputStream#mark} method.
  285. * @return <code>true</code> if the stream is a property set
  286. * stream, else <code>false</code>.
  287. * @throws MarkUnsupportedException if the {@link InputStream}
  288. * does not support the {@link InputStream#mark} method.
  289. * @exception IOException if an I/O error occurs
  290. */
  291. public static boolean isPropertySetStream(final InputStream stream)
  292. throws MarkUnsupportedException, IOException
  293. {
  294. /*
  295. * Read at most this many bytes.
  296. */
  297. final int BUFFER_SIZE = 50;
  298. /*
  299. * Mark the current position in the stream so that we can
  300. * reset to this position if the stream does not contain a
  301. * property set.
  302. */
  303. if (!stream.markSupported())
  304. throw new MarkUnsupportedException(stream.getClass().getName());
  305. stream.mark(BUFFER_SIZE);
  306. /*
  307. * Read a couple of bytes from the stream.
  308. */
  309. final byte[] buffer = new byte[BUFFER_SIZE];
  310. final int bytes =
  311. stream.read(buffer, 0,
  312. Math.min(buffer.length, stream.available()));
  313. final boolean isPropertySetStream =
  314. isPropertySetStream(buffer, 0, bytes);
  315. stream.reset();
  316. return isPropertySetStream;
  317. }
  318. /**
  319. * <p>Checks whether a byte array is in the Horrible Property Set
  320. * Format.</p>
  321. *
  322. * @param src The byte array to check.
  323. * @param offset The offset in the byte array.
  324. * @param length The significant number of bytes in the byte
  325. * array. Only this number of bytes will be checked.
  326. * @return <code>true</code> if the byte array is a property set
  327. * stream, <code>false</code> if not.
  328. */
  329. public static boolean isPropertySetStream(final byte[] src,
  330. final int offset,
  331. final int length)
  332. {
  333. /* FIXME (3): Ensure that at most "length" bytes are read. */
  334. /*
  335. * Read the header fields of the stream. They must always be
  336. * there.
  337. */
  338. int o = offset;
  339. final int byteOrder = LittleEndian.getUShort(src, o);
  340. o += LittleEndian.SHORT_SIZE;
  341. byte[] temp = new byte[LittleEndian.SHORT_SIZE];
  342. LittleEndian.putShort(temp, (short) byteOrder);
  343. if (!Util.equal(temp, BYTE_ORDER_ASSERTION))
  344. return false;
  345. final int format = LittleEndian.getUShort(src, o);
  346. o += LittleEndian.SHORT_SIZE;
  347. temp = new byte[LittleEndian.SHORT_SIZE];
  348. LittleEndian.putShort(temp, (short) format);
  349. if (!Util.equal(temp, FORMAT_ASSERTION))
  350. return false;
  351. // final long osVersion = LittleEndian.getUInt(src, offset);
  352. o += LittleEndian.INT_SIZE;
  353. // final ClassID classID = new ClassID(src, offset);
  354. o += ClassID.LENGTH;
  355. final long sectionCount = LittleEndian.getUInt(src, o);
  356. o += LittleEndian.INT_SIZE;
  357. if (sectionCount < 1)
  358. return false;
  359. return true;
  360. }
  361. /**
  362. * <p>Initializes this {@link PropertySet} instance from a byte
  363. * array. The method assumes that it has been checked already that
  364. * the byte array indeed represents a property set stream. It does
  365. * no more checks on its own.</p>
  366. *
  367. * @param src Byte array containing the property set stream
  368. * @param offset The property set stream starts at this offset
  369. * from the beginning of <var>src</var>
  370. * @param length Length of the property set stream.
  371. * @throws UnsupportedEncodingException if HPSF does not (yet) support the
  372. * property set's character encoding.
  373. */
  374. private void init(final byte[] src, final int offset, final int length)
  375. throws UnsupportedEncodingException
  376. {
  377. /* FIXME (3): Ensure that at most "length" bytes are read. */
  378. /*
  379. * Read the stream's header fields.
  380. */
  381. int o = offset;
  382. byteOrder = LittleEndian.getUShort(src, o);
  383. o += LittleEndian.SHORT_SIZE;
  384. format = LittleEndian.getUShort(src, o);
  385. o += LittleEndian.SHORT_SIZE;
  386. osVersion = (int) LittleEndian.getUInt(src, o);
  387. o += LittleEndian.INT_SIZE;
  388. classID = new ClassID(src, o);
  389. o += ClassID.LENGTH;
  390. final int sectionCount = LittleEndian.getInt(src, o);
  391. o += LittleEndian.INT_SIZE;
  392. if (sectionCount <= 0)
  393. throw new HPSFRuntimeException("Section count " + sectionCount +
  394. " must be greater than 0.");
  395. /*
  396. * Read the sections, which are following the header. They
  397. * start with an array of section descriptions. Each one
  398. * consists of a format ID telling what the section contains
  399. * and an offset telling how many bytes from the start of the
  400. * stream the section begins.
  401. */
  402. /*
  403. * Most property sets have only one section. The Document
  404. * Summary Information stream has 2. Everything else is a rare
  405. * exception and is no longer fostered by Microsoft.
  406. */
  407. sections = new ArrayList(sectionCount);
  408. /*
  409. * Loop over the section descriptor array. Each descriptor
  410. * consists of a ClassID and a DWord, and we have to increment
  411. * "offset" accordingly.
  412. */
  413. for (int i = 0; i < sectionCount; i++)
  414. {
  415. final Section s = new Section(src, o);
  416. o += ClassID.LENGTH + LittleEndian.INT_SIZE;
  417. sections.add(s);
  418. }
  419. }
  420. /**
  421. * <p>Checks whether this {@link PropertySet} represents a Summary
  422. * Information.</p>
  423. *
  424. * @return <code>true</code> if this {@link PropertySet}
  425. * represents a Summary Information, else <code>false</code>.
  426. */
  427. public boolean isSummaryInformation()
  428. {
  429. return Util.equal(((Section) sections.get(0)).getFormatID().getBytes(),
  430. SectionIDMap.SUMMARY_INFORMATION_ID);
  431. }
  432. /**
  433. * <p>Checks whether this {@link PropertySet} is a Document
  434. * Summary Information.</p>
  435. *
  436. * @return <code>true</code> if this {@link PropertySet}
  437. * represents a Document Summary Information, else <code>false</code>.
  438. */
  439. public boolean isDocumentSummaryInformation()
  440. {
  441. return Util.equal(((Section) sections.get(0)).getFormatID().getBytes(),
  442. SectionIDMap.DOCUMENT_SUMMARY_INFORMATION_ID[0]);
  443. }
  444. /**
  445. * <p>Convenience method returning the {@link Property} array
  446. * contained in this property set. It is a shortcut for getting
  447. * the {@link PropertySet}'s {@link Section}s list and then
  448. * getting the {@link Property} array from the first {@link
  449. * Section}.</p>
  450. *
  451. * @return The properties of the only {@link Section} of this
  452. * {@link PropertySet}.
  453. * @throws NoSingleSectionException if the {@link PropertySet} has
  454. * more or less than one {@link Section}.
  455. */
  456. public Property[] getProperties()
  457. throws NoSingleSectionException
  458. {
  459. return getFirstSection().getProperties();
  460. }
  461. /**
  462. * <p>Convenience method returning the value of the property with
  463. * the specified ID. If the property is not available,
  464. * <code>null</code> is returned and a subsequent call to {@link
  465. * #wasNull} will return <code>true</code> .</p>
  466. *
  467. * @param id The property ID
  468. * @return The property value
  469. * @throws NoSingleSectionException if the {@link PropertySet} has
  470. * more or less than one {@link Section}.
  471. */
  472. protected Object getProperty(final int id) throws NoSingleSectionException
  473. {
  474. return getFirstSection().getProperty(id);
  475. }
  476. /**
  477. * <p>Convenience method returning the value of a boolean property
  478. * with the specified ID. If the property is not available,
  479. * <code>false</code> is returned. A subsequent call to {@link
  480. * #wasNull} will return <code>true</code> to let the caller
  481. * distinguish that case from a real property value of
  482. * <code>false</code>.</p>
  483. *
  484. * @param id The property ID
  485. * @return The property value
  486. * @throws NoSingleSectionException if the {@link PropertySet} has
  487. * more or less than one {@link Section}.
  488. */
  489. protected boolean getPropertyBooleanValue(final int id)
  490. throws NoSingleSectionException
  491. {
  492. return getFirstSection().getPropertyBooleanValue(id);
  493. }
  494. /**
  495. * <p>Convenience method returning the value of the numeric
  496. * property with the specified ID. If the property is not
  497. * available, 0 is returned. A subsequent call to {@link #wasNull}
  498. * will return <code>true</code> to let the caller distinguish
  499. * that case from a real property value of 0.</p>
  500. *
  501. * @param id The property ID
  502. * @return The propertyIntValue value
  503. * @throws NoSingleSectionException if the {@link PropertySet} has
  504. * more or less than one {@link Section}.
  505. */
  506. protected int getPropertyIntValue(final int id)
  507. throws NoSingleSectionException
  508. {
  509. return getFirstSection().getPropertyIntValue(id);
  510. }
  511. /**
  512. * <p>Checks whether the property which the last call to {@link
  513. * #getPropertyIntValue} or {@link #getProperty} tried to access
  514. * was available or not. This information might be important for
  515. * callers of {@link #getPropertyIntValue} since the latter
  516. * returns 0 if the property does not exist. Using {@link
  517. * #wasNull}, the caller can distiguish this case from a
  518. * property's real value of 0.</p>
  519. *
  520. * @return <code>true</code> if the last call to {@link
  521. * #getPropertyIntValue} or {@link #getProperty} tried to access a
  522. * property that was not available, else <code>false</code>.
  523. * @throws NoSingleSectionException if the {@link PropertySet} has
  524. * more than one {@link Section}.
  525. */
  526. public boolean wasNull() throws NoSingleSectionException
  527. {
  528. return getFirstSection().wasNull();
  529. }
  530. /**
  531. * <p>Gets the {@link PropertySet}'s first section.</p>
  532. *
  533. * @return The {@link PropertySet}'s first section.
  534. */
  535. public Section getFirstSection()
  536. {
  537. if (getSectionCount() < 1)
  538. throw new MissingSectionException("Property set does not contain any sections.");
  539. return ((Section) sections.get(0));
  540. }
  541. /**
  542. * <p>If the {@link PropertySet} has only a single section this
  543. * method returns it.</p>
  544. *
  545. * @return The singleSection value
  546. */
  547. public Section getSingleSection()
  548. {
  549. final int sectionCount = getSectionCount();
  550. if (sectionCount != 1)
  551. throw new NoSingleSectionException
  552. ("Property set contains " + sectionCount + " sections.");
  553. return ((Section) sections.get(0));
  554. }
  555. /**
  556. * <p>Returns <code>true</code> if the <code>PropertySet</code> is equal
  557. * to the specified parameter, else <code>false</code>.</p>
  558. *
  559. * @param o the object to compare this <code>PropertySet</code> with
  560. *
  561. * @return <code>true</code> if the objects are equal, <code>false</code>
  562. * if not
  563. */
  564. public boolean equals(final Object o)
  565. {
  566. if (o == null || !(o instanceof PropertySet))
  567. return false;
  568. final PropertySet ps = (PropertySet) o;
  569. int byteOrder1 = ps.getByteOrder();
  570. int byteOrder2 = getByteOrder();
  571. ClassID classID1 = ps.getClassID();
  572. ClassID classID2 = getClassID();
  573. int format1 = ps.getFormat();
  574. int format2 = getFormat();
  575. int osVersion1 = ps.getOSVersion();
  576. int osVersion2 = getOSVersion();
  577. int sectionCount1 = ps.getSectionCount();
  578. int sectionCount2 = getSectionCount();
  579. if (byteOrder1 != byteOrder2 ||
  580. !classID1.equals(classID2) ||
  581. format1 != format2 ||
  582. osVersion1 != osVersion2 ||
  583. sectionCount1 != sectionCount2)
  584. return false;
  585. /* Compare the sections: */
  586. return Util.equals(getSections(), ps.getSections());
  587. }
  588. /**
  589. * @see Object#hashCode()
  590. */
  591. public int hashCode()
  592. {
  593. throw new UnsupportedOperationException("FIXME: Not yet implemented.");
  594. }
  595. /**
  596. * @see Object#toString()
  597. */
  598. public String toString()
  599. {
  600. final StringBuffer b = new StringBuffer();
  601. final int sectionCount = getSectionCount();
  602. b.append(getClass().getName());
  603. b.append('[');
  604. b.append("byteOrder: ");
  605. b.append(getByteOrder());
  606. b.append(", classID: ");
  607. b.append(getClassID());
  608. b.append(", format: ");
  609. b.append(getFormat());
  610. b.append(", OSVersion: ");
  611. b.append(getOSVersion());
  612. b.append(", sectionCount: ");
  613. b.append(sectionCount);
  614. b.append(", sections: [\n");
  615. final List sections = getSections();
  616. for (int i = 0; i < sectionCount; i++)
  617. b.append(((Section) sections.get(i)).toString());
  618. b.append(']');
  619. b.append(']');
  620. return b.toString();
  621. }
  622. }