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