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.

Property.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  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.UnsupportedEncodingException;
  17. import java.util.HashMap;
  18. import java.util.Map;
  19. import org.apache.poi.util.HexDump;
  20. import org.apache.poi.util.LittleEndian;
  21. import org.apache.poi.util.POILogFactory;
  22. import org.apache.poi.util.POILogger;
  23. /**
  24. * <p>A property in a {@link Section} of a {@link PropertySet}.</p>
  25. *
  26. * <p>The property's <strong>ID</strong> gives the property a meaning
  27. * in the context of its {@link Section}. Each {@link Section} spans
  28. * its own name space of property IDs.</p>
  29. *
  30. * <p>The property's <strong>type</strong> determines how its
  31. * <strong>value </strong> is interpreted. For example, if the type is
  32. * {@link Variant#VT_LPSTR} (byte string), the value consists of a
  33. * DWord telling how many bytes the string contains. The bytes follow
  34. * immediately, including any null bytes that terminate the
  35. * string. The type {@link Variant#VT_I4} denotes a four-byte integer
  36. * value, {@link Variant#VT_FILETIME} some date and time (of a
  37. * file).</p>
  38. *
  39. * <p>Please note that not all {@link Variant} types yet. This might change
  40. * over time but largely depends on your feedback so that the POI team knows
  41. * which variant types are really needed. So please feel free to submit error
  42. * reports or patches for the types you need.</p>
  43. *
  44. * <p>Microsoft documentation: <a
  45. * href="http://msdn.microsoft.com/library/en-us/stg/stg/property_set_display_name_dictionary.asp?frame=true">
  46. * Property Set Display Name Dictionary</a>.
  47. *
  48. * @author Rainer Klute <a
  49. * href="mailto:klute@rainer-klute.de">&lt;klute@rainer-klute.de&gt;</a>
  50. * @author Drew Varner (Drew.Varner InAndAround sc.edu)
  51. * @see Section
  52. * @see Variant
  53. */
  54. public class Property
  55. {
  56. /** <p>The property's ID.</p> */
  57. protected long id;
  58. /**
  59. * <p>Returns the property's ID.</p>
  60. *
  61. * @return The ID value
  62. */
  63. public long getID()
  64. {
  65. return id;
  66. }
  67. /** <p>The property's type.</p> */
  68. protected long type;
  69. /**
  70. * <p>Returns the property's type.</p>
  71. *
  72. * @return The type value
  73. */
  74. public long getType()
  75. {
  76. return type;
  77. }
  78. /** <p>The property's value.</p> */
  79. protected Object value;
  80. /**
  81. * <p>Returns the property's value.</p>
  82. *
  83. * @return The property's value
  84. */
  85. public Object getValue()
  86. {
  87. return value;
  88. }
  89. /**
  90. * <p>Creates a property.</p>
  91. *
  92. * @param id the property's ID.
  93. * @param type the property's type, see {@link Variant}.
  94. * @param value the property's value. Only certain types are allowed, see
  95. * {@link Variant}.
  96. */
  97. public Property(final long id, final long type, final Object value)
  98. {
  99. this.id = id;
  100. this.type = type;
  101. this.value = value;
  102. }
  103. /**
  104. * <p>Creates a {@link Property} instance by reading its bytes
  105. * from the property set stream.</p>
  106. *
  107. * @param id The property's ID.
  108. * @param src The bytes the property set stream consists of.
  109. * @param offset The property's type/value pair's offset in the
  110. * section.
  111. * @param length The property's type/value pair's length in bytes.
  112. * @param codepage The section's and thus the property's
  113. * codepage. It is needed only when reading string values.
  114. * @exception UnsupportedEncodingException if the specified codepage is not
  115. * supported.
  116. */
  117. public Property(final long id, final byte[] src, final long offset,
  118. final int length, final int codepage)
  119. throws UnsupportedEncodingException
  120. {
  121. this.id = id;
  122. /*
  123. * ID 0 is a special case since it specifies a dictionary of
  124. * property IDs and property names.
  125. */
  126. if (id == 0)
  127. {
  128. value = readDictionary(src, offset, length, codepage);
  129. return;
  130. }
  131. int o = (int) offset;
  132. type = LittleEndian.getUInt(src, o);
  133. o += LittleEndian.INT_SIZE;
  134. try
  135. {
  136. value = VariantSupport.read(src, o, length, (int) type, codepage);
  137. }
  138. catch (UnsupportedVariantTypeException ex)
  139. {
  140. VariantSupport.writeUnsupportedTypeMessage(ex);
  141. value = ex.getValue();
  142. }
  143. }
  144. /**
  145. * <p>Creates an empty property. It must be filled using the set method to
  146. * be usable.</p>
  147. */
  148. protected Property()
  149. { }
  150. /**
  151. * <p>Reads a dictionary.</p>
  152. *
  153. * @param src The byte array containing the bytes making out the dictionary.
  154. * @param offset At this offset within <var>src </var> the dictionary
  155. * starts.
  156. * @param length The dictionary contains at most this many bytes.
  157. * @param codepage The codepage of the string values.
  158. * @return The dictonary
  159. * @throws UnsupportedEncodingException if the dictionary's codepage is not
  160. * (yet) supported.
  161. */
  162. protected Map readDictionary(final byte[] src, final long offset,
  163. final int length, final int codepage)
  164. throws UnsupportedEncodingException
  165. {
  166. /* Check whether "offset" points into the "src" array". */
  167. if (offset < 0 || offset > src.length)
  168. throw new HPSFRuntimeException
  169. ("Illegal offset " + offset + " while HPSF stream contains " +
  170. length + " bytes.");
  171. int o = (int) offset;
  172. /*
  173. * Read the number of dictionary entries.
  174. */
  175. final long nrEntries = LittleEndian.getUInt(src, o);
  176. o += LittleEndian.INT_SIZE;
  177. final Map m = new HashMap((int) nrEntries, (float) 1.0);
  178. try
  179. {
  180. for (int i = 0; i < nrEntries; i++)
  181. {
  182. /* The key. */
  183. final Long id = Long.valueOf(LittleEndian.getUInt(src, o));
  184. o += LittleEndian.INT_SIZE;
  185. /* The value (a string). The length is the either the
  186. * number of (two-byte) characters if the character set is Unicode
  187. * or the number of bytes if the character set is not Unicode.
  188. * The length includes terminating 0x00 bytes which we have to strip
  189. * off to create a Java string. */
  190. long sLength = LittleEndian.getUInt(src, o);
  191. o += LittleEndian.INT_SIZE;
  192. /* Read the string. */
  193. final StringBuffer b = new StringBuffer();
  194. switch (codepage)
  195. {
  196. case -1:
  197. {
  198. /* Without a codepage the length is equal to the number of
  199. * bytes. */
  200. b.append(new String(src, o, (int) sLength));
  201. break;
  202. }
  203. case Constants.CP_UNICODE:
  204. {
  205. /* The length is the number of characters, i.e. the number
  206. * of bytes is twice the number of the characters. */
  207. final int nrBytes = (int) (sLength * 2);
  208. final byte[] h = new byte[nrBytes];
  209. for (int i2 = 0; i2 < nrBytes; i2 += 2)
  210. {
  211. h[i2] = src[o + i2 + 1];
  212. h[i2 + 1] = src[o + i2];
  213. }
  214. b.append(new String(h, 0, nrBytes,
  215. VariantSupport.codepageToEncoding(codepage)));
  216. break;
  217. }
  218. default:
  219. {
  220. /* For encodings other than Unicode the length is the number
  221. * of bytes. */
  222. b.append(new String(src, o, (int) sLength,
  223. VariantSupport.codepageToEncoding(codepage)));
  224. break;
  225. }
  226. }
  227. /* Strip 0x00 characters from the end of the string: */
  228. while (b.length() > 0 && b.charAt(b.length() - 1) == 0x00)
  229. b.setLength(b.length() - 1);
  230. if (codepage == Constants.CP_UNICODE)
  231. {
  232. if (sLength % 2 == 1)
  233. sLength++;
  234. o += (sLength + sLength);
  235. }
  236. else
  237. o += sLength;
  238. m.put(id, b.toString());
  239. }
  240. }
  241. catch (RuntimeException ex)
  242. {
  243. final POILogger l = POILogFactory.getLogger(getClass());
  244. l.log(POILogger.WARN,
  245. "The property set's dictionary contains bogus data. "
  246. + "All dictionary entries starting with the one with ID "
  247. + id + " will be ignored.", ex);
  248. }
  249. return m;
  250. }
  251. /**
  252. * <p>Returns the property's size in bytes. This is always a multiple of
  253. * 4.</p>
  254. *
  255. * @return the property's size in bytes
  256. *
  257. * @exception WritingNotSupportedException if HPSF does not yet support the
  258. * property's variant type.
  259. */
  260. protected int getSize() throws WritingNotSupportedException
  261. {
  262. int length = VariantSupport.getVariantLength(type);
  263. if (length >= 0)
  264. return length; /* Fixed length */
  265. if (length == -2)
  266. /* Unknown length */
  267. throw new WritingNotSupportedException(type, null);
  268. /* Variable length: */
  269. final int PADDING = 4; /* Pad to multiples of 4. */
  270. switch ((int) type)
  271. {
  272. case Variant.VT_LPSTR:
  273. {
  274. int l = ((String) value).length() + 1;
  275. int r = l % PADDING;
  276. if (r > 0)
  277. l += PADDING - r;
  278. length += l;
  279. break;
  280. }
  281. case Variant.VT_EMPTY:
  282. break;
  283. default:
  284. throw new WritingNotSupportedException(type, value);
  285. }
  286. return length;
  287. }
  288. /**
  289. * <p>Compares two properties.</p> <p>Please beware that a property with
  290. * ID == 0 is a special case: It does not have a type, and its value is the
  291. * section's dictionary. Another special case are strings: Two properties
  292. * may have the different types Variant.VT_LPSTR and Variant.VT_LPWSTR;</p>
  293. *
  294. * @see Object#equals(java.lang.Object)
  295. */
  296. public boolean equals(final Object o)
  297. {
  298. if (!(o instanceof Property)) {
  299. return false;
  300. }
  301. final Property p = (Property) o;
  302. final Object pValue = p.getValue();
  303. final long pId = p.getID();
  304. if (id != pId || (id != 0 && !typesAreEqual(type, p.getType())))
  305. return false;
  306. if (value == null && pValue == null)
  307. return true;
  308. if (value == null || pValue == null)
  309. return false;
  310. /* It's clear now that both values are non-null. */
  311. final Class<?> valueClass = value.getClass();
  312. final Class<?> pValueClass = pValue.getClass();
  313. if (!(valueClass.isAssignableFrom(pValueClass)) &&
  314. !(pValueClass.isAssignableFrom(valueClass)))
  315. return false;
  316. if (value instanceof byte[])
  317. return Util.equal((byte[]) value, (byte[]) pValue);
  318. return value.equals(pValue);
  319. }
  320. private boolean typesAreEqual(final long t1, final long t2)
  321. {
  322. if (t1 == t2 ||
  323. (t1 == Variant.VT_LPSTR && t2 == Variant.VT_LPWSTR) ||
  324. (t2 == Variant.VT_LPSTR && t1 == Variant.VT_LPWSTR)) {
  325. return true;
  326. }
  327. return false;
  328. }
  329. /**
  330. * @see Object#hashCode()
  331. */
  332. public int hashCode()
  333. {
  334. long hashCode = 0;
  335. hashCode += id;
  336. hashCode += type;
  337. if (value != null)
  338. hashCode += value.hashCode();
  339. final int returnHashCode = (int) (hashCode & 0x0ffffffffL );
  340. return returnHashCode;
  341. }
  342. /**
  343. * @see Object#toString()
  344. */
  345. public String toString()
  346. {
  347. final StringBuffer b = new StringBuffer();
  348. b.append(getClass().getName());
  349. b.append('[');
  350. b.append("id: ");
  351. b.append(getID());
  352. b.append(", type: ");
  353. b.append(getType());
  354. final Object value = getValue();
  355. b.append(", value: ");
  356. b.append(value.toString());
  357. if (value instanceof String)
  358. {
  359. final String s = (String) value;
  360. final int l = s.length();
  361. final byte[] bytes = new byte[l * 2];
  362. for (int i = 0; i < l; i++)
  363. {
  364. final char c = s.charAt(i);
  365. final byte high = (byte) ((c & 0x00ff00) >> 8);
  366. final byte low = (byte) ((c & 0x0000ff) >> 0);
  367. bytes[i * 2] = high;
  368. bytes[i * 2 + 1] = low;
  369. }
  370. final String hex = HexDump.dump(bytes, 0L, 0);
  371. b.append(" [");
  372. b.append(hex);
  373. b.append("]");
  374. }
  375. b.append(']');
  376. return b.toString();
  377. }
  378. }