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 java.io.InputStream;
59   import java.io.IOException;
60   
61   import java.util.*;
62   
63   import java.lang.reflect.Constructor;
64   
65   import org.apache.poi.util.LittleEndian;
66   
67   /**
68    * Title:  Record Factory<P>
69    * Description:  Takes a stream and outputs an array of Record objects.<P>
70    *
71    * @author Andrew C. Oliver (acoliver at apache dot org)
72    * @author Marc Johnson (mjohnson at apache dot org)
73    * @author Glen Stampoultzis (glens at apache.org)
74    * @version 1.0-pre
75    */
76   
77   public class RecordFactory
78   {
79       private static int           NUM_RECORDS = 10000;
80       private static final Class[] records;
81       
82       static {
83           if (FormulaRecord.EXPERIMENTAL_FORMULA_SUPPORT_ENABLED) {
84               records = new Class[]
85               {
86                   BOFRecord.class, InterfaceHdrRecord.class, MMSRecord.class,
87                   InterfaceEndRecord.class, WriteAccessRecord.class,
88                   CodepageRecord.class, DSFRecord.class, TabIdRecord.class,
89                   FnGroupCountRecord.class, WindowProtectRecord.class,
90                   ProtectRecord.class, PasswordRecord.class, ProtectionRev4Record.class,
91                   PasswordRev4Record.class, WindowOneRecord.class, BackupRecord.class,
92                   HideObjRecord.class, DateWindow1904Record.class,
93                   PrecisionRecord.class, RefreshAllRecord.class, BookBoolRecord.class,
94                   FontRecord.class, FormatRecord.class, ExtendedFormatRecord.class,
95                   StyleRecord.class, UseSelFSRecord.class, BoundSheetRecord.class,
96                   CountryRecord.class, SSTRecord.class, ExtSSTRecord.class,
97                   EOFRecord.class, IndexRecord.class, CalcModeRecord.class,
98                   CalcCountRecord.class, RefModeRecord.class, IterationRecord.class,
99                   DeltaRecord.class, SaveRecalcRecord.class, PrintHeadersRecord.class,
100                  PrintGridlinesRecord.class, GridsetRecord.class, GutsRecord.class,
101                  DefaultRowHeightRecord.class, WSBoolRecord.class, HeaderRecord.class,
102                  FooterRecord.class, HCenterRecord.class, VCenterRecord.class,
103                  PrintSetupRecord.class, DefaultColWidthRecord.class,
104                  DimensionsRecord.class, RowRecord.class, LabelSSTRecord.class,
105                  RKRecord.class, NumberRecord.class, DBCellRecord.class,
106                  WindowTwoRecord.class, SelectionRecord.class, ContinueRecord.class,
107                  LabelRecord.class, BlankRecord.class, ColumnInfoRecord.class,
108                  MulRKRecord.class, MulBlankRecord.class, MergeCellsRecord.class,
109                  FormulaRecord.class, BoolErrRecord.class, ExternSheetRecord.class
110              };
111          } else {
112              records = new Class[]
113              {
114                  BOFRecord.class, InterfaceHdrRecord.class, MMSRecord.class,
115                  InterfaceEndRecord.class, WriteAccessRecord.class,
116                  CodepageRecord.class, DSFRecord.class, TabIdRecord.class,
117                  FnGroupCountRecord.class, WindowProtectRecord.class,
118                  ProtectRecord.class, PasswordRecord.class, ProtectionRev4Record.class,
119                  PasswordRev4Record.class, WindowOneRecord.class, BackupRecord.class,
120                  HideObjRecord.class, DateWindow1904Record.class,
121                  PrecisionRecord.class, RefreshAllRecord.class, BookBoolRecord.class,
122                  FontRecord.class, FormatRecord.class, ExtendedFormatRecord.class,
123                  StyleRecord.class, UseSelFSRecord.class, BoundSheetRecord.class,
124                  CountryRecord.class, SSTRecord.class, ExtSSTRecord.class,
125                  EOFRecord.class, IndexRecord.class, CalcModeRecord.class,
126                  CalcCountRecord.class, RefModeRecord.class, IterationRecord.class,
127                  DeltaRecord.class, SaveRecalcRecord.class, PrintHeadersRecord.class,
128                  PrintGridlinesRecord.class, GridsetRecord.class, GutsRecord.class,
129                  DefaultRowHeightRecord.class, WSBoolRecord.class, HeaderRecord.class,
130                  FooterRecord.class, HCenterRecord.class, VCenterRecord.class,
131                  PrintSetupRecord.class, DefaultColWidthRecord.class,
132                  DimensionsRecord.class, RowRecord.class, LabelSSTRecord.class,
133                  RKRecord.class, NumberRecord.class, DBCellRecord.class,
134                  WindowTwoRecord.class, SelectionRecord.class, ContinueRecord.class,
135                  LabelRecord.class, BlankRecord.class, ColumnInfoRecord.class,
136                  MulRKRecord.class, MulBlankRecord.class, MergeCellsRecord.class,
137                  BoolErrRecord.class, ExternSheetRecord.class
138              };
139              
140          }
141      }
142      private static Map           recordsMap  = recordsToMap(records);
143  
144      /**
145       * changes the default capacity (10000) to handle larger files
146       */
147  
148      public static void setCapacity(int capacity)
149      {
150          NUM_RECORDS = capacity;
151      }
152  
153      /**
154       * Create an array of records from an input stream
155       *
156       * @param in the InputStream from which the records will be
157       *           obtained
158       *
159       * @return an array of Records created from the InputStream
160       *
161       * @exception RecordFormatException on error processing the
162       *            InputStream
163       */
164  
165      public static List createRecords(InputStream in)
166          throws RecordFormatException
167      {
168          ArrayList records     = new ArrayList(NUM_RECORDS);
169          Record    last_record = null;
170  
171          try
172          {
173              short rectype = 0;
174  
175              do
176              {
177                  rectype = LittleEndian.readShort(in);
178                  if (rectype != 0)
179                  {
180                      short  recsize = LittleEndian.readShort(in);
181                      byte[] data    = new byte[ ( int ) recsize ];
182  
183                      in.read(data);
184                      Record[] recs = createRecord(rectype, recsize,
185                                                   data);   // handle MulRK records
186  
187                      if (recs.length > 1)
188                      {
189                          for (int k = 0; k < recs.length; k++)
190                          {
191                              records.add(
192                                  recs[ k ]);               // these will be number records
193                              last_record =
194                                  recs[ k ];                // do to keep the algorythm homogenous...you can't
195                          }                                 // actually continue a number record anyhow.
196                      }
197                      else
198                      {
199                          Record record = recs[ 0 ];
200  
201                          if (record != null)
202                          {
203                              if (rectype == ContinueRecord.sid)
204                              {
205                                  if (last_record == null)
206                                  {
207                                      throw new RecordFormatException(
208                                          "First record is a ContinueRecord??");
209                                  }
210                                  last_record.processContinueRecord(data);
211                              }
212                              else
213                              {
214                                  last_record = record;
215                                  records.add(record);
216                              }
217                          }
218                      }
219                  }
220              }
221              while (rectype != 0);
222          }
223          catch (IOException e)
224          {
225              throw new RecordFormatException("Error reading bytes");
226          }
227  
228          // Record[] retval = new Record[ records.size() ];
229          // retval = ( Record [] ) records.toArray(retval);
230          return records;
231      }
232  
233      public static Record [] createRecord(short rectype, short size,
234                                           byte [] data)
235      {
236          Record   retval     = null;
237          Record[] realretval = null;
238  
239          try
240          {
241              Constructor constructor =
242                  ( Constructor ) recordsMap.get(new Short(rectype));
243  
244              if (constructor != null)
245              {
246                  retval = ( Record ) constructor.newInstance(new Object[]
247                  {
248                      new Short(rectype), new Short(size), data
249                  });
250              }
251              else
252              {
253                  retval = new UnknownRecord(rectype, size, data);
254              }
255          }
256          catch (Exception introspectionException)
257          {
258              introspectionException.printStackTrace();
259              throw new RecordFormatException(
260                  "Unable to construct record instance, the following exception occured: " + introspectionException.getMessage());
261          }
262          if (retval instanceof RKRecord)
263          {
264              RKRecord     rk  = ( RKRecord ) retval;
265              NumberRecord num = new NumberRecord();
266  
267              num.setColumn(rk.getColumn());
268              num.setRow(rk.getRow());
269              num.setXFIndex(rk.getXFIndex());
270              num.setValue(rk.getRKNumber());
271              retval = num;
272          }
273          else if (retval instanceof DBCellRecord)
274          {
275              retval = null;
276          }
277          else if (retval instanceof MulRKRecord)
278          {
279              MulRKRecord mrk = ( MulRKRecord ) retval;
280  
281              realretval = new Record[ mrk.getNumColumns() ];
282              for (int k = 0; k < mrk.getNumColumns(); k++)
283              {
284                  NumberRecord nr = new NumberRecord();
285  
286                  nr.setColumn(( short ) (k + mrk.getFirstColumn()));
287                  nr.setRow(mrk.getRow());
288                  nr.setXFIndex(mrk.getXFAt(k));
289                  nr.setValue(mrk.getRKNumberAt(k));
290                  realretval[ k ] = nr;
291              }
292          }
293          else if (retval instanceof MulBlankRecord)
294          {
295              MulBlankRecord mb = ( MulBlankRecord ) retval;
296  
297              realretval = new Record[ mb.getNumColumns() ];
298              for (int k = 0; k < mb.getNumColumns(); k++)
299              {
300                  BlankRecord br = new BlankRecord();
301  
302                  br.setColumn(( short ) (k + mb.getFirstColumn()));
303                  br.setRow(mb.getRow());
304                  br.setXFIndex(mb.getXFAt(k));
305                  realretval[ k ] = br;
306              }
307          }
308          if (realretval == null)
309          {
310              realretval      = new Record[ 1 ];
311              realretval[ 0 ] = retval;
312          }
313          return realretval;
314      }
315  
316      public static short [] getAllKnownRecordSIDs()
317      {
318          short[] results = new short[ recordsMap.size() ];
319          int     i       = 0;
320  
321          for (Iterator iterator = recordsMap.keySet().iterator();
322                  iterator.hasNext(); )
323          {
324              Short sid = ( Short ) iterator.next();
325  
326              results[ i++ ] = sid.shortValue();
327          }
328          return results;
329      }
330  
331      private static Map recordsToMap(Class [] records)
332      {
333          Map         result = new HashMap();
334          Constructor constructor;
335  
336          for (int i = 0; i < records.length; i++)
337          {
338              Class record = null;
339              short sid    = 0;
340  
341              record = records[ i ];
342              try
343              {
344                  sid         = record.getField("sid").getShort(null);
345                  constructor = record.getConstructor(new Class[]
346                  {
347                      short.class, short.class, byte [].class
348                  });
349              }
350              catch (Exception illegalArgumentException)
351              {
352                  throw new RecordFormatException(
353                      "Unable to determine record types");
354              }
355              result.put(new Short(sid), constructor);
356          }
357          return result;
358      }
359  }
360