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.util.*; 58 import org.apache.poi.util.LittleEndian; 59 import org.apache.poi.hpsf.wellknown.*; 60 61 /** 62 * <p>Represents a section in a {@link PropertySet}.</p> 63 * 64 * @author Rainer Klute (klute@rainer-klute.de) 65 * @author Drew Varner (Drew.Varner allUpIn sc.edu) 66 * @version $Id: Section.java,v 1.9 2003/01/29 18:01:18 klute Exp $ 67 * @since 2002-02-09 68 */ 69 public class Section 70 { 71 72 /** 73 * <p>Maps property IDs to section-private PID strings. These 74 * strings can be found in the property with ID 0.</p> 75 */ 76 protected Map dictionary; 77 78 private ClassID formatID; 79 80 81 /** 82 * <p>Returns the format ID. The format ID is the "type" of the 83 * section. For example, if the format ID of the first {@link 84 * Section} contains the bytes specified by 85 * <code>org.apache.poi.hpsf.wellknown.SectionIDMap.SUMMARY_INFORMATION_ID</code> 86 * the section (and thus the property set) is a 87 * SummaryInformation.</p> 88 * 89 * @return The format ID 90 */ 91 public ClassID getFormatID() 92 { 93 return formatID; 94 } 95 96 97 98 private long offset; 99 100 101 /** 102 * <p>Returns the offset of the section in the stream.</p> 103 * 104 * @return The offset of the section in the stream. 105 */ 106 public long getOffset() 107 { 108 return offset; 109 } 110 111 112 113 private int size; 114 115 116 /** 117 * <p>Returns the section's size in bytes.</p> 118 * 119 * @return The section's size in bytes. 120 */ 121 public int getSize() 122 { 123 return size; 124 } 125 126 127 128 private int propertyCount; 129 130 131 /** 132 * <p>Returns the number of properties in this section.</p> 133 * 134 * @return The number of properties in this section. 135 */ 136 public int getPropertyCount() 137 { 138 return propertyCount; 139 } 140 141 142 143 private Property[] properties; 144 145 146 /** 147 * <p>Returns this section's properties.</p> 148 * 149 * @return This section's properties. 150 */ 151 public Property[] getProperties() 152 { 153 return properties; 154 } 155 156 157 158 /** 159 * <p>Creates a {@link Section} instance from a byte array.</p> 160 * 161 * @param src Contains the complete property set stream. 162 * @param offset The position in the stream that points to the 163 * section's format ID. 164 */ 165 public Section(final byte[] src, int offset) 166 { 167 /* 168 * Read the format ID. 169 */ 170 formatID = new ClassID(src, offset); 171 offset += ClassID.LENGTH; 172 173 /* 174 * Read the offset from the stream's start and positions to 175 * the section header. 176 */ 177 this.offset = LittleEndian.getUInt(src, offset); 178 offset = (int)this.offset; 179 180 /* 181 * Read the section length. 182 */ 183 size = (int)LittleEndian.getUInt(src, offset); 184 offset += LittleEndian.INT_SIZE; 185 186 /* 187 * Read the number of properties. 188 */ 189 propertyCount = (int)LittleEndian.getUInt(src, offset); 190 offset += LittleEndian.INT_SIZE; 191 192 /* 193 * Read the properties. The offset is positioned at the first 194 * entry of the property list. The problem is that we have to 195 * read the property with ID 1 before we read other 196 * properties, at least before other properties containing 197 * strings. The reason is that property 1 specifies the 198 * codepage. If it is 1200, all strings are in Unicode. In 199 * other words: Before we can read any strings we have to know 200 * whether they are in Unicode or not. Unfortunately property 201 * 1 is not guaranteed to be the first in a section. 202 * 203 * The algorithm below reads the properties in two passes: The 204 * first one looks for property ID 1 and extracts the codepage 205 * number. The seconds pass reads the other properties. 206 */ 207 properties = new Property[propertyCount]; 208 Property propertyOne; 209 210 /* Pass 1: Look for the codepage. */ 211 int codepage = -1; 212 int pass1Offset = offset; 213 for (int i = 0; i < properties.length; i++) 214 { 215 /* Read the property ID. */ 216 final int id = (int) LittleEndian.getUInt(src, pass1Offset); 217 pass1Offset += LittleEndian.INT_SIZE; 218 219 /* Offset from the section's start. */ 220 final int sOffset = (int) LittleEndian.getUInt(src, pass1Offset); 221 pass1Offset += LittleEndian.INT_SIZE; 222 223 /* Calculate the length of the property. */ 224 int length; 225 if (i == properties.length - 1) 226 length = (int) (src.length - this.offset - sOffset); 227 else 228 length = (int) 229 LittleEndian.getUInt(src, pass1Offset + 230 LittleEndian.INT_SIZE) - sOffset; 231 232 if (id == PropertyIDMap.PID_CODEPAGE) 233 { 234 /* Read the codepage if the property ID is 1. */ 235 236 /* Read the property's value type. It must be 237 * VT_I2. */ 238 int o = (int) (this.offset + sOffset); 239 final long type = LittleEndian.getUInt(src, o); 240 o += LittleEndian.INT_SIZE; 241 242 if (type != Variant.VT_I2) 243 throw new HPSFRuntimeException 244 ("Value type of property ID 1 is not VT_I2 but " + 245 type + "."); 246 247 /* Read the codepage number. */ 248 codepage = LittleEndian.getUShort(src, o); 249 } 250 } 251 252 /* Pass 2: Read all properties, including 1. */ 253 for (int i = 0; i < properties.length; i++) 254 { 255 /* Read the property ID. */ 256 final int id = (int) LittleEndian.getUInt(src, offset); 257 offset += LittleEndian.INT_SIZE; 258 259 /* Offset from the section. */ 260 final int sOffset = (int) LittleEndian.getUInt(src, offset); 261 offset += LittleEndian.INT_SIZE; 262 263 /* Calculate the length of the property. */ 264 int length; 265 if (i == properties.length - 1) 266 length = (int) (src.length - this.offset - sOffset); 267 else 268 length = (int) 269 LittleEndian.getUInt(src, offset + LittleEndian.INT_SIZE) - 270 sOffset; 271 272 /* Create it. */ 273 properties[i] = new Property(id, src, this.offset + sOffset, 274 length, codepage); 275 } 276 277 /* 278 * Extract the dictionary (if available). 279 */ 280 dictionary = (Map) getProperty(0); 281 } 282 283 284 285 /** 286 * <p>Returns the value of the property with the specified ID. If 287 * the property is not available, <code>null</code> is returned 288 * and a subsequent call to {@link #wasNull} will return 289 * <code>true</code>.</p> 290 * 291 * @param id The property's ID 292 * 293 * @return The property's value 294 */ 295 public Object getProperty(final int id) 296 { 297 wasNull = false; 298 for (int i = 0; i < properties.length; i++) 299 if (id == properties[i].getID()) 300 return properties[i].getValue(); 301 wasNull = true; 302 return null; 303 } 304 305 306 307 /** 308 * <p>Returns the value of the numeric property with the specified 309 * ID. If the property is not available, 0 is returned. A 310 * subsequent call to {@link #wasNull} will return 311 * <code>true</code> to let the caller distinguish that case from 312 * a real property value of 0.</p> 313 * 314 * @param id The property's ID 315 * 316 * @return The property's value 317 */ 318 protected int getPropertyIntValue(final int id) 319 { 320 /* FIXME: Find out why the following is a Long instead of an 321 * Integer! */ 322 final Long i = (Long) getProperty(id); 323 if (i != null) 324 return i.intValue(); 325 else 326 return 0; 327 } 328 329 330 331 /** 332 * <p>Returns the value of the boolean property with the specified 333 * ID. If the property is not available, <code>false</code> is 334 * returned. A subsequent call to {@link #wasNull} will return 335 * <code>true</code> to let the caller distinguish that case from 336 * a real property value of <code>false</code>.</p> 337 * 338 * @param id The property's ID 339 * 340 * @return The property's value 341 */ 342 protected boolean getPropertyBooleanValue(final int id) 343 { 344 final Boolean b = (Boolean) getProperty(id); 345 if (b != null) 346 return b.booleanValue(); 347 else 348 return false; 349 } 350 351 352 353 private boolean wasNull; 354 355 356 /** 357 * <p>Checks whether the property which the last call to {@link 358 * #getPropertyIntValue} or {@link #getProperty} tried to access 359 * was available or not. This information might be important for 360 * callers of {@link #getPropertyIntValue} since the latter 361 * returns 0 if the property does not exist. Using {@link 362 * #wasNull} the caller can distiguish this case from a property's 363 * real value of 0.</p> 364 * 365 * @return <code>true</code> if the last call to {@link 366 * #getPropertyIntValue} or {@link #getProperty} tried to access a 367 * property that was not available, else <code>false</code>. 368 */ 369 public boolean wasNull() 370 { 371 return wasNull; 372 } 373 374 375 376 /** 377 * <p>Returns the PID string associated with a property ID. The ID 378 * is first looked up in the {@link Section}'s private 379 * dictionary. If it is not found there, the method calls {@link 380 * SectionIDMap#getPIDString}.</p> 381 * 382 * @param pid The property ID 383 * 384 * @return The property ID's string value 385 */ 386 public String getPIDString(final int pid) 387 { 388 String s = null; 389 if (dictionary != null) 390 s = (String) dictionary.get(new Integer(pid)); 391 if (s == null) 392 s = SectionIDMap.getPIDString(getFormatID().getBytes(), pid); 393 if (s == null) 394 s = SectionIDMap.UNDEFINED; 395 return s; 396 } 397 398 } 399