1    
2    /* ====================================================================
3     * The Apache Software License, Version 1.1
4     *
5     * Copyright (c) 2002 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" and
28    *    "Apache POI" must not be used to endorse or promote products
29    *    derived from this software without prior written permission. For
30    *    written permission, please contact apache@apache.org.
31    *
32    * 5. Products derived from this software may not be called "Apache",
33    *    "Apache POI", nor may "Apache" appear in their name, without
34    *    prior written 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   
56   package org.apache.poi.hssf.record;
57   
58   import org.apache.poi.util.LittleEndian;
59   import org.apache.poi.util.StringUtil;
60   
61   /**
62    * Title: Unicode String<P>
63    * Description:  Unicode String record.  We implement these as a record, although
64    *               they are really just standard fields that are in several records.
65    *               It is considered more desirable then repeating it in all of them.<P>
66    * REFERENCE:  PG 264 Microsoft Excel 97 Developer's Kit (ISBN: 1-57231-498-2)<P>
67    * @author  Andrew C. Oliver
68    * @author Marc Johnson (mjohnson at apache dot org)
69    * @version 2.0-pre
70    */
71   
72   public class UnicodeString
73       extends Record
74       implements Comparable
75   {
76       public final static short sid = 0xFFF;
77       private short             field_1_charCount;     // = 0;
78       private byte              field_2_optionflags;   // = 0;
79       private String            field_3_string;        // = null;
80   
81       public int hashCode()
82       {
83           return field_1_charCount;
84       }
85   
86       public boolean equals(Object o)
87       {
88           if ((o == null) || (o.getClass() != this.getClass()))
89           {
90               return false;
91           }
92           UnicodeString other = ( UnicodeString ) o;
93   
94           return ((field_1_charCount == other.field_1_charCount)
95                   && (field_2_optionflags == other.field_2_optionflags)
96                   && field_3_string.equals(other.field_3_string));
97       }
98   
99       public UnicodeString()
100      {
101      }
102  
103      /**
104       * construct a unicode string record and fill its fields, ID is ignored
105       * @param id - ignored
106       * @param size - size of the data
107       * @param data - the bytes of the string/fields
108       */
109  
110      public UnicodeString(short id, short size, byte [] data)
111      {
112          super(id, size, data);
113      }
114  
115      /**
116       * construct a unicode string from a string fragment + data
117       */
118  
119      public UnicodeString(short id, short size, byte [] data, String prefix)
120      {
121          this(id, size, data);
122          field_3_string = prefix + field_3_string;
123          setCharCount();
124      }
125  
126      /**
127       * NO OP
128       */
129  
130      protected void validateSid(short id)
131      {
132  
133          // included only for interface compliance
134      }
135  
136      protected void fillFields(byte [] data, short size)
137      {
138          field_1_charCount   = LittleEndian.getShort(data, 0);
139          field_2_optionflags = data[ 2 ];
140          if ((field_2_optionflags & 1) == 0)
141          {
142              field_3_string = new String(data, 3, getCharCount());
143          }
144          else
145          {
146              char[] array = new char[ getCharCount() ];
147  
148              for (int j = 0; j < array.length; j++)
149              {
150                  array[ j ] = ( char ) LittleEndian.getShort(data,
151                                                              3 + (j * 2));
152              }
153              field_3_string = new String(array);
154          }
155      }
156  
157      /**
158       * get the number of characters in the string
159       *
160       *
161       * @return number of characters
162       *
163       */
164  
165      public short getCharCount()
166      {
167          return field_1_charCount;
168      }
169  
170      /**
171       * set the number of characters in the string
172       * @param cc - number of characters
173       */
174  
175      public void setCharCount(short cc)
176      {
177          field_1_charCount = cc;
178      }
179  
180      /**
181       * sets the number of characters to whaterver number of characters is in the string
182       * currently.  effectively setCharCount(getString.length()).
183       * @see #setString(String)
184       * @see #getString()
185       */
186  
187      public void setCharCount()
188      {
189          field_1_charCount = ( short ) field_3_string.length();
190      }
191  
192      /**
193       * get the option flags which among other things return if this is a 16-bit or
194       * 8 bit string
195       *
196       * @return optionflags bitmask
197       *
198       */
199  
200      public byte getOptionFlags()
201      {
202          return field_2_optionflags;
203      }
204  
205      /**
206       * set the option flags which among other things return if this is a 16-bit or
207       * 8 bit string
208       *
209       * @param of  optionflags bitmask
210       *
211       */
212  
213      public void setOptionFlags(byte of)
214      {
215          field_2_optionflags = of;
216      }
217  
218      /**
219       * get the actual string this contains as a java String object
220       *
221       *
222       * @return String
223       *
224       */
225  
226      public String getString()
227      {
228          return field_3_string;
229      }
230  
231      /**
232       * set the actual string this contains
233       * @param string  the text
234       */
235  
236      public void setString(String string)
237      {
238          field_3_string = string;
239          if (getCharCount() < field_3_string.length())
240          {
241              setCharCount();
242          }
243      }
244  
245      /**
246       * unlike the real records we return the same as "getString()" rather than debug info
247       * @see #getDebugInfo()
248       * @return String value of the record
249       */
250  
251      public String toString()
252      {
253          return getString();
254      }
255  
256      /**
257       * return a character representation of the fields of this record
258       *
259       *
260       * @return String of output for biffviewer etc.
261       *
262       */
263  
264      public String getDebugInfo()
265      {
266          StringBuffer buffer = new StringBuffer();
267  
268          buffer.append("[UNICODESTRING]\n");
269          buffer.append("    .charcount       = ")
270              .append(Integer.toHexString(getCharCount())).append("\n");
271          buffer.append("    .optionflags     = ")
272              .append(Integer.toHexString(getOptionFlags())).append("\n");
273          buffer.append("    .string          = ").append(getString())
274              .append("\n");
275          buffer.append("[/UNICODESTRING]\n");
276          return buffer.toString();
277      }
278  
279      public int serialize(int offset, byte [] data)
280      {
281          int charsize = 1;
282  
283          if (getOptionFlags() == 1)
284          {
285              charsize = 2;
286          }
287  
288          // byte[] retval = new byte[ 3 + (getString().length() * charsize) ];
289          LittleEndian.putShort(data, 0 + offset, getCharCount());
290          data[ 2 + offset ] = getOptionFlags();
291  
292  //        System.out.println("Unicode: We've got "+retval[2]+" for our option flag");
293          if (getOptionFlags() == 0)
294          {
295              StringUtil.putCompressedUnicode(getString(), data, 0x3 + offset);
296          }
297          else
298          {
299              StringUtil.putUncompressedUnicode(getString(), data,
300                                                0x3 + offset);
301          }
302          return getRecordSize();
303      }
304  
305      public int getRecordSize()
306      {
307          int charsize = 1;
308  
309          if (getOptionFlags() == 1)
310          {
311              charsize = 2;
312          }
313          return 3 + (getString().length() * charsize);
314      }
315  
316      public short getSid()
317      {
318          return this.sid;
319      }
320  
321      /**
322       * called by the constructor, should set class level fields.  Should throw
323       * runtime exception for bad/icomplete data.
324       *
325       * @param data raw data
326       * @param size size of data
327       * @param offset of the records data (provided a big array of the file)
328       */
329  
330      protected void fillFields(byte [] data, short size, int offset)
331      {
332      }
333  
334      public int compareTo(Object obj)
335      {
336          UnicodeString str = ( UnicodeString ) obj;
337  
338          return this.getString().compareTo(str.getString());
339      }
340  
341      int maxBrokenLength(final int proposedBrokenLength)
342      {
343          int rval = proposedBrokenLength;
344  
345          if ((field_2_optionflags & 1) == 1)
346          {
347              int proposedStringLength = proposedBrokenLength - 3;
348  
349              if ((proposedStringLength % 2) == 1)
350              {
351                  proposedStringLength--;
352              }
353              rval = proposedStringLength + 3;
354          }
355          return rval;
356      }
357  
358  //    public boolean equals(Object obj) {
359  //        if (!(obj instanceof UnicodeString)) return false;
360  //        
361  //        UnicodeString str = (UnicodeString)obj;
362  //        
363  //        
364  //       return this.getString().equals(str.getString());
365  //    }    
366  }
367