1    /* ====================================================================
2     * The Apache Software License, Version 1.1
3     *
4     * Copyright (c) 2002 The Apache Software Foundation.  All rights
5     * reserved.
6     *
7     * Redistribution and use in source and binary forms, with or without
8     * modification, are permitted provided that the following conditions
9     * are met:
10    *
11    * 1. Redistributions of source code must retain the above copyright
12    *    notice, this list of conditions and the following disclaimer.
13    *
14    * 2. Redistributions in binary form must reproduce the above copyright
15    *    notice, this list of conditions and the following disclaimer in
16    *    the documentation and/or other materials provided with the
17    *    distribution.
18    *
19    * 3. The end-user documentation included with the redistribution,
20    *    if any, must include the following acknowledgment:
21    *       "This product includes software developed by the
22    *        Apache Software Foundation (http://www.apache.org/)."
23    *    Alternately, this acknowledgment may appear in the software itself,
24    *    if and wherever such third-party acknowledgments normally appear.
25    *
26    * 4. The names "Apache" and "Apache Software Foundation" and
27    *    "Apache POI" must not be used to endorse or promote products
28    *    derived from this software without prior written permission. For
29    *    written permission, please contact apache@apache.org.
30    *
31    * 5. Products derived from this software may not be called "Apache",
32    *    "Apache POI", nor may "Apache" appear in their name, without
33    *    prior written permission of the Apache Software Foundation.
34    *
35    * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36    * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37    * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38    * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39    * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40    * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41    * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42    * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43    * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44    * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45    * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46    * SUCH DAMAGE.
47    * ====================================================================
48    *
49    * This software consists of voluntary contributions made by many
50    * individuals on behalf of the Apache Software Foundation.  For more
51    * information on the Apache Software Foundation, please see
52    * <http://www.apache.org/>.
53    */
54   
55   package org.apache.poi.hssf.record;
56   
57   import org.apache.poi.util.BinaryTree;
58   import org.apache.poi.util.LittleEndianConsts;
59   
60   import java.util.List;
61   import java.util.ArrayList;
62   
63   /**
64    * This class handles serialization of SST records.  It utilizes the record processor
65    * class write individual records. This has been refactored from the SSTRecord class.
66    *
67    * @author Glen Stampoultzis (glens at apache.org)
68    */
69   class SSTSerializer
70   {
71   
72       private List recordLengths;
73       private BinaryTree strings;
74       private int numStrings;
75       private int numUniqueStrings;
76       private SSTRecordHeader sstRecordHeader;
77   
78       public SSTSerializer( List recordLengths, BinaryTree strings, int numStrings, int numUniqueStrings )
79       {
80           this.recordLengths = recordLengths;
81           this.strings = strings;
82           this.numStrings = numStrings;
83           this.numUniqueStrings = numUniqueStrings;
84           this.sstRecordHeader = new SSTRecordHeader(numStrings, numUniqueStrings);
85       }
86   
87       /**
88        * Create a byte array consisting of an SST record and any
89        * required Continue records, ready to be written out.
90        * <p>
91        * If an SST record and any subsequent Continue records are read
92        * in to create this instance, this method should produce a byte
93        * array that is identical to the byte array produced by
94        * concatenating the input records' data.
95        *
96        * @return the byte array
97        */
98       public int serialize( int offset, byte[] data )
99       {
100          int record_size = getRecordSize();
101          int record_length_index = 0;
102  
103          if ( calculateUnicodeSize() > SSTRecord.MAX_DATA_SPACE )
104              serializeLargeRecord( record_size, record_length_index, data, offset );
105          else
106              serializeSingleSSTRecord( data, offset, record_length_index );
107          return record_size;
108      }
109  
110      private int calculateUnicodeSize()
111      {
112          int retval = 0;
113  
114          for ( int k = 0; k < strings.size(); k++ )
115          {
116              retval += getUnicodeString(k).getRecordSize();
117          }
118          return retval;
119      }
120  
121      // we can probably simplify this later...this calculates the size
122      // w/o serializing but still is a bit slow
123      public int getRecordSize()
124      {
125          recordLengths = new ArrayList();
126          int retval = 0;
127          int unicodesize = calculateUnicodeSize();
128  
129          if ( unicodesize > SSTRecord.MAX_DATA_SPACE )
130          {
131              retval = calcRecordSizesForLongStrings( unicodesize );
132          }
133          else
134          {
135              // short data: write one simple SST record
136              retval = SSTRecord.SST_RECORD_OVERHEAD + unicodesize;
137              recordLengths.add( new Integer( unicodesize ) );
138          }
139          return retval;
140      }
141  
142      private int calcRecordSizesForLongStrings( int unicodesize )
143      {
144          int retval;
145          UnicodeString unistr = null;
146          int stringreminant = 0;
147          int unipos = 0;
148          boolean lastneedcontinue = false;
149          int stringbyteswritten = 0;
150          boolean finished = false;
151          boolean first_record = true;
152          int totalWritten = 0;
153  
154          while ( !finished )
155          {
156              int record = 0;
157              int pos = 0;
158  
159              if ( first_record )
160              {
161  
162                  // writing SST record
163                  record = SSTRecord.MAX_RECORD_SIZE;
164                  pos = 12;
165                  first_record = false;
166                  recordLengths.add( new Integer( record - SSTRecord.STD_RECORD_OVERHEAD ) );
167              }
168              else
169              {
170  
171                  // writing continue record
172                  pos = 0;
173                  int to_be_written = ( unicodesize - stringbyteswritten ) + ( lastneedcontinue ? 1 : 0 );
174                  int size = Math.min( SSTRecord.MAX_RECORD_SIZE - SSTRecord.STD_RECORD_OVERHEAD, to_be_written );
175  
176                  if ( size == to_be_written )
177                  {
178                      finished = true;
179                  }
180                  record = size + SSTRecord.STD_RECORD_OVERHEAD;
181                  recordLengths.add( new Integer( size ) );
182                  pos = 4;
183              }
184              if ( lastneedcontinue )
185              {
186                  int available = SSTRecord.MAX_RECORD_SIZE - pos;
187  
188                  if ( stringreminant <= available )
189                  {
190  
191                      // write reminant
192                      stringbyteswritten += stringreminant - 1;
193                      pos += stringreminant;
194                      lastneedcontinue = false;
195                  }
196                  else
197                  {
198  
199                      // write as much of the remnant as possible
200                      int toBeWritten = unistr.maxBrokenLength( available );
201  
202                      if ( available != toBeWritten )
203                      {
204                          int shortrecord = record - ( available - toBeWritten );
205                          recordLengths.set( recordLengths.size() - 1,
206                                  new Integer( shortrecord - SSTRecord.STD_RECORD_OVERHEAD ) );
207                          record = shortrecord;
208                      }
209                      stringbyteswritten += toBeWritten - 1;
210                      pos += toBeWritten;
211                      stringreminant -= toBeWritten - 1;
212                      lastneedcontinue = true;
213                  }
214              }
215              for ( ; unipos < strings.size(); unipos++ )
216              {
217                  int available = SSTRecord.MAX_RECORD_SIZE - pos;
218                  Integer intunipos = new Integer( unipos );
219  
220                  unistr = ( (UnicodeString) strings.get( intunipos ) );
221                  if ( unistr.getRecordSize() <= available )
222                  {
223                      stringbyteswritten += unistr.getRecordSize();
224                      pos += unistr.getRecordSize();
225                  }
226                  else
227                  {
228                      if ( available >= SSTRecord.STRING_MINIMAL_OVERHEAD )
229                      {
230                          int toBeWritten =
231                                  unistr.maxBrokenLength( available );
232  
233                          stringbyteswritten += toBeWritten;
234                          stringreminant =
235                                  ( unistr.getRecordSize() - toBeWritten )
236                                  + LittleEndianConsts.BYTE_SIZE;
237                          if ( available != toBeWritten )
238                          {
239                              int shortrecord = record
240                                      - ( available - toBeWritten );
241  
242                              recordLengths.set(
243                                      recordLengths.size() - 1,
244                                      new Integer(
245                                              shortrecord - SSTRecord.STD_RECORD_OVERHEAD ) );
246                              record = shortrecord;
247                          }
248                          lastneedcontinue = true;
249                          unipos++;
250                      }
251                      else
252                      {
253                          int shortrecord = record - available;
254  
255                          recordLengths.set( recordLengths.size() - 1,
256                                  new Integer( shortrecord - SSTRecord.STD_RECORD_OVERHEAD ) );
257                          record = shortrecord;
258                      }
259                      break;
260                  }
261              }
262              totalWritten += record;
263          }
264          retval = totalWritten;
265  
266          return retval;
267      }
268  
269  
270      private void serializeSingleSSTRecord( byte[] data, int offset, int record_length_index )
271      {
272          // short data: write one simple SST record
273  
274          int len = ( (Integer) recordLengths.get( record_length_index++ ) ).intValue();
275          int recordSize = SSTRecord.SST_RECORD_OVERHEAD + len - SSTRecord.STD_RECORD_OVERHEAD;
276          sstRecordHeader.writeSSTHeader( data, 0 + offset, recordSize );
277          int pos = SSTRecord.SST_RECORD_OVERHEAD;
278  
279          for ( int k = 0; k < strings.size(); k++ )
280          {
281  //            UnicodeString unistr = ( (UnicodeString) strings.get( new Integer( k ) ) );
282              System.arraycopy( getUnicodeString(k).serialize(), 0, data, pos + offset, getUnicodeString(k).getRecordSize() );
283              pos += getUnicodeString(k).getRecordSize();
284          }
285      }
286  
287      /**
288       * Large records are serialized to an SST and to one or more CONTINUE records.  Joy.  They have the special
289       * characteristic that they can change the option field when a single string is split across to a
290       * CONTINUE record.
291       */
292      private void serializeLargeRecord( int record_size, int record_length_index, byte[] buffer, int offset )
293      {
294  
295          byte[] stringReminant = null;
296          int stringIndex = 0;
297          boolean lastneedcontinue = false;
298          boolean first_record = true;
299          int totalWritten = 0;
300  
301          while ( totalWritten != record_size )
302          {
303              int recordLength = ( (Integer) recordLengths.get( record_length_index++ ) ).intValue();
304              RecordProcessor recordProcessor = new RecordProcessor( buffer,
305                      recordLength, numStrings, numUniqueStrings );
306  
307              // write the appropriate header
308              recordProcessor.writeRecordHeader( offset, totalWritten, recordLength, first_record );
309              first_record = false;
310  
311              // now, write the rest of the data into the current
312              // record space
313              if ( lastneedcontinue )
314              {
315                  lastneedcontinue = stringReminant.length > recordProcessor.getAvailable();
316                  // the last string in the previous record was not written out completely
317                  stringReminant = recordProcessor.writeStringRemainder( lastneedcontinue,
318                          stringReminant, offset, totalWritten );
319              }
320  
321              // last string's remnant, if any, is cleaned up as best as can be done ... now let's try and write
322              // some more strings
323              for ( ; stringIndex < strings.size(); stringIndex++ )
324              {
325                  UnicodeString unistr = getUnicodeString( stringIndex );
326  
327                  if ( unistr.getRecordSize() <= recordProcessor.getAvailable() )
328                  {
329                      recordProcessor.writeWholeString( unistr, offset, totalWritten );
330                  }
331                  else
332                  {
333  
334                      // can't write the entire string out
335                      if ( recordProcessor.getAvailable() >= SSTRecord.STRING_MINIMAL_OVERHEAD )
336                      {
337  
338                          // we can write some of it
339                          stringReminant = recordProcessor.writePartString( unistr, offset, totalWritten );
340                          lastneedcontinue = true;
341                          stringIndex++;
342                      }
343                      break;
344                  }
345              }
346              totalWritten += recordLength + SSTRecord.STD_RECORD_OVERHEAD;
347          }
348      }
349  
350      private UnicodeString getUnicodeString( int index )
351      {
352          Integer intunipos = new Integer( index );
353          return ( (UnicodeString) strings.get( intunipos ) );
354      }
355  
356  }
357