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