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