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   
59   import java.util.List;
60   import java.util.ArrayList;
61   import java.util.Map;
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       // todo: make private again
73       private List recordLengths;
74       private BinaryTree strings;
75   
76       private int numStrings;
77       private int numUniqueStrings;
78       private SSTRecordHeader sstRecordHeader;
79   
80       public SSTSerializer( List recordLengths, BinaryTree strings, int numStrings, int numUniqueStrings )
81       {
82           this.recordLengths = recordLengths;
83           this.strings = strings;
84           this.numStrings = numStrings;
85           this.numUniqueStrings = numUniqueStrings;
86           this.sstRecordHeader = new SSTRecordHeader( numStrings, numUniqueStrings );
87       }
88   
89       /**
90        * Create a byte array consisting of an SST record and any
91        * required Continue records, ready to be written out.
92        * <p>
93        * If an SST record and any subsequent Continue records are read
94        * in to create this instance, this method should produce a byte
95        * array that is identical to the byte array produced by
96        * concatenating the input records' data.
97        *
98        * @return the byte array
99        */
100      public int serialize( int record_size, int offset, byte[] data )
101      {
102          int record_length_index = 0;
103  
104          if ( calculateUnicodeSize() > SSTRecord.MAX_DATA_SPACE )
105              serializeLargeRecord( record_size, record_length_index, data, offset );
106          else
107              serializeSingleSSTRecord( data, offset, record_length_index );
108          return record_size;
109      }
110  
111  
112  
113      /**
114       * Calculates the total unicode size for all the strings.
115       *
116       * @return the total size.
117       */
118      public static int calculateUnicodeSize(Map strings)
119      {
120          int retval = 0;
121  
122          for ( int k = 0; k < strings.size(); k++ )
123          {
124              retval += getUnicodeString( strings, k ).getRecordSize();
125          }
126          return retval;
127      }
128  
129      public int calculateUnicodeSize()
130      {
131          return calculateUnicodeSize(strings);
132      }
133  
134      /**
135       * This case is chosen when an SST record does not span over to a continue record.
136       *
137       */
138      private void serializeSingleSSTRecord( byte[] data, int offset, int record_length_index )
139      {
140          int len = ( (Integer) recordLengths.get( record_length_index ) ).intValue();
141          int recordSize = SSTRecord.SST_RECORD_OVERHEAD + len - SSTRecord.STD_RECORD_OVERHEAD;
142          sstRecordHeader.writeSSTHeader( data, 0 + offset, recordSize );
143          int pos = SSTRecord.SST_RECORD_OVERHEAD;
144  
145          for ( int k = 0; k < strings.size(); k++ )
146          {
147              System.arraycopy( getUnicodeString( k ).serialize(), 0, data, pos + offset, getUnicodeString( k ).getRecordSize() );
148              pos += getUnicodeString( k ).getRecordSize();
149          }
150      }
151  
152      /**
153       * Large records are serialized to an SST and to one or more CONTINUE records.  Joy.  They have the special
154       * characteristic that they can change the option field when a single string is split across to a
155       * CONTINUE record.
156       */
157      private void serializeLargeRecord( int record_size, int record_length_index, byte[] buffer, int offset )
158      {
159  
160          byte[] stringReminant = null;
161          int stringIndex = 0;
162          boolean lastneedcontinue = false;
163          boolean first_record = true;
164          int totalWritten = 0;
165  
166          while ( totalWritten != record_size )
167          {
168              int recordLength = ( (Integer) recordLengths.get( record_length_index++ ) ).intValue();
169              RecordProcessor recordProcessor = new RecordProcessor( buffer,
170                      recordLength, numStrings, numUniqueStrings );
171  
172              // write the appropriate header
173              recordProcessor.writeRecordHeader( offset, totalWritten, recordLength, first_record );
174              first_record = false;
175  
176              // now, write the rest of the data into the current
177              // record space
178              if ( lastneedcontinue )
179              {
180                  lastneedcontinue = stringReminant.length > recordProcessor.getAvailable();
181                  // the last string in the previous record was not written out completely
182                  stringReminant = recordProcessor.writeStringRemainder( lastneedcontinue,
183                          stringReminant, offset, totalWritten );
184              }
185  
186              // last string's remnant, if any, is cleaned up as best as can be done ... now let's try and write
187              // some more strings
188              for ( ; stringIndex < strings.size(); stringIndex++ )
189              {
190                  UnicodeString unistr = getUnicodeString( stringIndex );
191  
192                  if ( unistr.getRecordSize() <= recordProcessor.getAvailable() )
193                  {
194                      recordProcessor.writeWholeString( unistr, offset, totalWritten );
195                  }
196                  else
197                  {
198  
199                      // can't write the entire string out
200                      if ( recordProcessor.getAvailable() >= SSTRecord.STRING_MINIMAL_OVERHEAD )
201                      {
202  
203                          // we can write some of it
204                          stringReminant = recordProcessor.writePartString( unistr, offset, totalWritten );
205                          lastneedcontinue = true;
206                          stringIndex++;
207                      }
208                      break;
209                  }
210              }
211              totalWritten += recordLength + SSTRecord.STD_RECORD_OVERHEAD;
212          }
213      }
214  
215      private UnicodeString getUnicodeString( int index )
216      {
217          return getUnicodeString(strings, index);
218      }
219  
220      private static UnicodeString getUnicodeString( Map strings, int index )
221      {
222          Integer intunipos = new Integer( index );
223          return ( (UnicodeString) strings.get( intunipos ) );
224      }
225  
226      public int getRecordSize()
227      {
228          SSTRecordSizeCalculator calculator = new SSTRecordSizeCalculator(strings);
229          int recordSize = calculator.getRecordSize();
230          recordLengths = calculator.getRecordLengths();
231          return recordSize;
232      }
233  
234      public List getRecordLengths()
235      {
236          return recordLengths;
237      }
238  }
239