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   package org.apache.poi.hssf.eventmodel;
56   
57   import java.io.InputStream;
58   import java.io.IOException;
59   
60   import java.util.*;
61   
62   import java.lang.reflect.Constructor;
63   
64   import org.apache.poi.hssf.record.BOFRecord;
65   import org.apache.poi.hssf.record.BackupRecord;
66   import org.apache.poi.hssf.record.BlankRecord;
67   import org.apache.poi.hssf.record.BookBoolRecord;
68   import org.apache.poi.hssf.record.BoolErrRecord;
69   import org.apache.poi.hssf.record.BottomMarginRecord;
70   import org.apache.poi.hssf.record.BoundSheetRecord;
71   import org.apache.poi.hssf.record.CalcCountRecord;
72   import org.apache.poi.hssf.record.CalcModeRecord;
73   import org.apache.poi.hssf.record.CodepageRecord;
74   import org.apache.poi.hssf.record.ColumnInfoRecord;
75   import org.apache.poi.hssf.record.ContinueRecord;
76   import org.apache.poi.hssf.record.CountryRecord;
77   import org.apache.poi.hssf.record.DBCellRecord;
78   import org.apache.poi.hssf.record.DSFRecord;
79   import org.apache.poi.hssf.record.DateWindow1904Record;
80   import org.apache.poi.hssf.record.DefaultColWidthRecord;
81   import org.apache.poi.hssf.record.DefaultRowHeightRecord;
82   import org.apache.poi.hssf.record.DeltaRecord;
83   import org.apache.poi.hssf.record.DimensionsRecord;
84   import org.apache.poi.hssf.record.EOFRecord;
85   import org.apache.poi.hssf.record.ExtSSTRecord;
86   import org.apache.poi.hssf.record.ExtendedFormatRecord;
87   import org.apache.poi.hssf.record.ExternSheetRecord;
88   import org.apache.poi.hssf.record.FnGroupCountRecord;
89   import org.apache.poi.hssf.record.FontRecord;
90   import org.apache.poi.hssf.record.FooterRecord;
91   import org.apache.poi.hssf.record.FormatRecord;
92   import org.apache.poi.hssf.record.FormulaRecord;
93   import org.apache.poi.hssf.record.GridsetRecord;
94   import org.apache.poi.hssf.record.GutsRecord;
95   import org.apache.poi.hssf.record.HCenterRecord;
96   import org.apache.poi.hssf.record.HeaderRecord;
97   import org.apache.poi.hssf.record.HideObjRecord;
98   import org.apache.poi.hssf.record.IndexRecord;
99   import org.apache.poi.hssf.record.InterfaceEndRecord;
100  import org.apache.poi.hssf.record.InterfaceHdrRecord;
101  import org.apache.poi.hssf.record.IterationRecord;
102  import org.apache.poi.hssf.record.LabelRecord;
103  import org.apache.poi.hssf.record.LabelSSTRecord;
104  import org.apache.poi.hssf.record.LeftMarginRecord;
105  import org.apache.poi.hssf.record.MMSRecord;
106  import org.apache.poi.hssf.record.MergeCellsRecord;
107  import org.apache.poi.hssf.record.MulBlankRecord;
108  import org.apache.poi.hssf.record.MulRKRecord;
109  import org.apache.poi.hssf.record.NameRecord;
110  import org.apache.poi.hssf.record.NumberRecord;
111  import org.apache.poi.hssf.record.PaletteRecord;
112  import org.apache.poi.hssf.record.PasswordRecord;
113  import org.apache.poi.hssf.record.PasswordRev4Record;
114  import org.apache.poi.hssf.record.PrecisionRecord;
115  import org.apache.poi.hssf.record.PrintGridlinesRecord;
116  import org.apache.poi.hssf.record.PrintHeadersRecord;
117  import org.apache.poi.hssf.record.PrintSetupRecord;
118  import org.apache.poi.hssf.record.ProtectRecord;
119  import org.apache.poi.hssf.record.ProtectionRev4Record;
120  import org.apache.poi.hssf.record.RKRecord;
121  import org.apache.poi.hssf.record.Record;
122  import org.apache.poi.hssf.record.RecordFormatException;
123  import org.apache.poi.hssf.record.RefModeRecord;
124  import org.apache.poi.hssf.record.RefreshAllRecord;
125  import org.apache.poi.hssf.record.RightMarginRecord;
126  import org.apache.poi.hssf.record.RowRecord;
127  import org.apache.poi.hssf.record.SSTRecord;
128  import org.apache.poi.hssf.record.SaveRecalcRecord;
129  import org.apache.poi.hssf.record.SelectionRecord;
130  import org.apache.poi.hssf.record.StringRecord;
131  import org.apache.poi.hssf.record.StyleRecord;
132  import org.apache.poi.hssf.record.TabIdRecord;
133  import org.apache.poi.hssf.record.TopMarginRecord;
134  import org.apache.poi.hssf.record.UnknownRecord;
135  import org.apache.poi.hssf.record.UseSelFSRecord;
136  import org.apache.poi.hssf.record.VCenterRecord;
137  import org.apache.poi.hssf.record.WSBoolRecord;
138  import org.apache.poi.hssf.record.WindowOneRecord;
139  import org.apache.poi.hssf.record.WindowProtectRecord;
140  import org.apache.poi.hssf.record.WindowTwoRecord;
141  import org.apache.poi.hssf.record.WriteAccessRecord;
142  import org.apache.poi.util.LittleEndian;
143  
144  
145  /**
146   * Event-based record factory.  As opposed to RecordFactory
147   * this refactored version throws record events as it comes
148   * accross the records.  I throws the "lazily" one record behind
149   * to ensure that ContinueRecords are processed first.
150   * 
151   * @author Andrew C. Oliver (acoliver@apache.org) - probably to blame for the bugs (so yank his chain on the list)
152   * @author Marc Johnson (mjohnson at apache dot org) - methods taken from RecordFactory
153   * @author Glen Stampoultzis (glens at apache.org) - methods taken from RecordFactory
154   */
155  public class EventRecordFactory
156  {
157      
158      /**
159       * contains the classes for all the records we want to parse.
160       */
161      private static final Class[] records;
162  
163      static {
164              records = new Class[]
165              {
166                  BOFRecord.class, InterfaceHdrRecord.class, MMSRecord.class,
167                  InterfaceEndRecord.class, WriteAccessRecord.class,
168                  CodepageRecord.class, DSFRecord.class, TabIdRecord.class,
169                  FnGroupCountRecord.class, WindowProtectRecord.class,
170                  ProtectRecord.class, PasswordRecord.class, ProtectionRev4Record.class,
171                  PasswordRev4Record.class, WindowOneRecord.class, BackupRecord.class,
172                  HideObjRecord.class, DateWindow1904Record.class,
173                  PrecisionRecord.class, RefreshAllRecord.class, BookBoolRecord.class,
174                  FontRecord.class, FormatRecord.class, ExtendedFormatRecord.class,
175                  StyleRecord.class, UseSelFSRecord.class, BoundSheetRecord.class,
176                  CountryRecord.class, SSTRecord.class, ExtSSTRecord.class,
177                  EOFRecord.class, IndexRecord.class, CalcModeRecord.class,
178                  CalcCountRecord.class, RefModeRecord.class, IterationRecord.class,
179                  DeltaRecord.class, SaveRecalcRecord.class, PrintHeadersRecord.class,
180                  PrintGridlinesRecord.class, GridsetRecord.class, GutsRecord.class,
181                  DefaultRowHeightRecord.class, WSBoolRecord.class, HeaderRecord.class,
182                  FooterRecord.class, HCenterRecord.class, VCenterRecord.class,
183                  PrintSetupRecord.class, DefaultColWidthRecord.class,
184                  DimensionsRecord.class, RowRecord.class, LabelSSTRecord.class,
185                  RKRecord.class, NumberRecord.class, DBCellRecord.class,
186                  WindowTwoRecord.class, SelectionRecord.class, ContinueRecord.class,
187                  LabelRecord.class, BlankRecord.class, ColumnInfoRecord.class,
188                  MulRKRecord.class, MulBlankRecord.class, MergeCellsRecord.class,
189                  BoolErrRecord.class, ExternSheetRecord.class, NameRecord.class,
190                  LeftMarginRecord.class, RightMarginRecord.class,
191                  TopMarginRecord.class, BottomMarginRecord.class,
192                  PaletteRecord.class, StringRecord.class
193              };
194         
195      }
196      
197      /**
198       * cache of the recordsToMap();
199       */
200      private static Map           recordsMap  = recordsToMap(records);
201  
202      /**
203       * cache of the return of getAllKnownSids so that we don't have to
204       * expensively get them every time.
205       */
206      private static short[] sidscache;
207  
208      /**
209       * List of the listners that are registred.  should all be ERFListener
210       */    
211      private List listeners;
212  
213      /**
214       * instance is abortable or not
215       */
216      private boolean abortable;
217      
218      /**
219       * Construct an abortable EventRecordFactory.  
220       * The same as calling new EventRecordFactory(true)
221       * @see #EventRecordFactory(boolean)
222       */
223      public EventRecordFactory() {
224          this(true);                  
225      }
226      
227      /**
228       * Create an EventRecordFactory
229       * @param abortable specifies whether the return from the listener 
230       * handler functions are obeyed.  False means they are ignored. True
231       * means the event loop exits on error.
232       */
233      public EventRecordFactory(boolean abortable) {
234          this.abortable = abortable;
235          listeners = new ArrayList(recordsMap.size());    
236          
237          if (sidscache == null) {
238           sidscache = getAllKnownRecordSIDs();   
239          }
240  
241      }
242      
243      /**
244       * Register a listener for records.  These can be for all records 
245       * or just a subset.
246       * 
247       * @param sids an array of Record.sid values identifying the records
248       * the listener will work with.  Alternatively if this is "null" then 
249       * all records are passed.
250       */
251      public void registerListener(ERFListener listener, short[] sids) {
252        if (sids == null)
253          sids = sidscache;
254        ERFListener wrapped = new ListenerWrapper(listener, sids, abortable);    
255        listeners.add(wrapped);
256      }
257      
258      /**
259       * used for unit tests to test the registration of record listeners.
260       * @return Iterator of ERFListeners
261       */
262      protected Iterator listeners() {
263          return listeners.iterator();
264      }
265  
266      /**
267       * sends the record event to all registered listeners.
268       * @param record the record to be thrown.
269       * @return boolean abort.  If exitability is turned on this aborts
270       * out of the event loop should any listener specify to do so.
271       */
272      private boolean throwRecordEvent(Record record)
273      {
274          boolean result = true;
275          Iterator i = listeners.iterator();
276          
277          while (i.hasNext()) {
278              result = ((ERFListener) i.next()).processRecord(record);  
279              if (abortable == true && result == false) {
280                  break;   
281              }
282          }
283          return result;
284      }
285  
286      /**
287       * Create an array of records from an input stream
288       *
289       * @param in the InputStream from which the records will be
290       *           obtained
291       *
292       * @return an array of Records created from the InputStream
293       *
294       * @exception RecordFormatException on error processing the
295       *            InputStream
296       */
297      public void processRecords(InputStream in)
298          throws RecordFormatException
299      {
300          Record    last_record = null;
301  
302          try
303          {
304              short rectype = 0;
305  
306              do
307              {
308                  rectype = LittleEndian.readShort(in);
309                  if (rectype != 0)
310                  {
311                      short  recsize = LittleEndian.readShort(in);
312                      byte[] data    = new byte[ ( int ) recsize ];
313  
314                      in.read(data);
315                      Record[] recs = createRecord(rectype, recsize,
316                                                   data);   // handle MulRK records
317  
318                      if (recs.length > 1)
319                      {
320                          for (int k = 0; k < recs.length; k++)
321                          {
322                              if ( last_record != null ) {
323                                  if (throwRecordEvent(last_record) == false && abortable == true) {
324                                   last_record = null;
325                                   break;   
326                                  }
327                              }
328                           //   records.add(
329                           //       recs[ k ]);               // these will be number records
330                              last_record =
331                                  recs[ k ];                // do to keep the algorythm homogenous...you can't
332                          }                                 // actually continue a number record anyhow.
333                      }
334                      else
335                      {
336                          Record record = recs[ 0 ];
337  
338                          if (record != null)
339                          {
340                              if (rectype == ContinueRecord.sid)
341                              {
342                                  if (last_record == null)
343                                  {
344                                      throw new RecordFormatException(
345                                          "First record is a ContinueRecord??");
346                                  }
347                                  last_record.processContinueRecord(data);
348                              }
349                              else
350                              {
351                                  if (last_record != null) {
352                                      if (throwRecordEvent(last_record) == false && abortable == true) {
353                                          last_record = null;
354                                          break;   
355                                      }
356                                  }
357                                  
358                                  last_record = record;
359                                  
360                                  //records.add(record);
361                              }
362                          }
363                      }
364                  }
365              }
366              while (rectype != 0);
367              
368              if (last_record != null) {
369                 throwRecordEvent(last_record);               
370              }
371          }
372          catch (IOException e)
373          {
374              throw new RecordFormatException("Error reading bytes");
375          }
376  
377          // Record[] retval = new Record[ records.size() ];
378          // retval = ( Record [] ) records.toArray(retval);
379       
380      }
381  
382      /**
383       * create a record, if there are MUL records than multiple records
384       * are returned digested into the non-mul form.
385       */
386      public static Record [] createRecord(short rectype, short size,
387                                           byte [] data)
388      {
389          Record   retval     = null;
390          Record[] realretval = null;
391  
392          try
393          {
394              Constructor constructor =
395                  ( Constructor ) recordsMap.get(new Short(rectype));
396  
397              if (constructor != null)
398              {
399                  retval = ( Record ) constructor.newInstance(new Object[]
400                  {
401                      new Short(rectype), new Short(size), data
402                  });
403              }
404              else
405              {
406                  retval = new UnknownRecord(rectype, size, data);
407              }
408          }
409          catch (Exception introspectionException)
410          {
411              introspectionException.printStackTrace();
412              throw new RecordFormatException(
413                  "Unable to construct record instance, the following exception occured: " + introspectionException.getMessage());
414          }
415          if (retval instanceof RKRecord)
416          {
417              RKRecord     rk  = ( RKRecord ) retval;
418              NumberRecord num = new NumberRecord();
419  
420              num.setColumn(rk.getColumn());
421              num.setRow(rk.getRow());
422              num.setXFIndex(rk.getXFIndex());
423              num.setValue(rk.getRKNumber());
424              retval = num;
425          }
426          else if (retval instanceof DBCellRecord)
427          {
428              retval = null;
429          }
430          else if (retval instanceof MulRKRecord)
431          {
432              MulRKRecord mrk = ( MulRKRecord ) retval;
433  
434              realretval = new Record[ mrk.getNumColumns() ];
435              for (int k = 0; k < mrk.getNumColumns(); k++)
436              {
437                  NumberRecord nr = new NumberRecord();
438  
439                  nr.setColumn(( short ) (k + mrk.getFirstColumn()));
440                  nr.setRow(mrk.getRow());
441                  nr.setXFIndex(mrk.getXFAt(k));
442                  nr.setValue(mrk.getRKNumberAt(k));
443                  realretval[ k ] = nr;
444              }
445          }
446          else if (retval instanceof MulBlankRecord)
447          {
448              MulBlankRecord mb = ( MulBlankRecord ) retval;
449  
450              realretval = new Record[ mb.getNumColumns() ];
451              for (int k = 0; k < mb.getNumColumns(); k++)
452              {
453                  BlankRecord br = new BlankRecord();
454  
455                  br.setColumn(( short ) (k + mb.getFirstColumn()));
456                  br.setRow(mb.getRow());
457                  br.setXFIndex(mb.getXFAt(k));
458                  realretval[ k ] = br;
459              }
460          }
461          if (realretval == null)
462          {
463              realretval      = new Record[ 1 ];
464              realretval[ 0 ] = retval;
465          }
466          return realretval;
467      }
468  
469      /**
470       * @return an array of all the SIDS for all known records
471       */
472      public static short [] getAllKnownRecordSIDs()
473      {
474          short[] results = new short[ recordsMap.size() ];
475          int     i       = 0;
476  
477          for (Iterator iterator = recordsMap.keySet().iterator();
478                  iterator.hasNext(); )
479          {
480              Short sid = ( Short ) iterator.next();
481  
482              results[ i++ ] = sid.shortValue();
483          }
484          return results;
485      }
486  
487      /**
488       * gets the record constructors and sticks them in the map by SID
489       * @return map of SIDs to short,short,byte[] constructors for Record classes
490       * most of org.apache.poi.hssf.record.*
491       */
492      private static Map recordsToMap(Class [] records)
493      {
494          Map         result = new HashMap();
495          Constructor constructor;
496  
497          for (int i = 0; i < records.length; i++)
498          {
499              Class record = null;
500              short sid    = 0;
501  
502              record = records[ i ];
503              try
504              {
505                  sid         = record.getField("sid").getShort(null);
506                  constructor = record.getConstructor(new Class[]
507                  {
508                      short.class, short.class, byte [].class
509                  });
510              }
511              catch (Exception illegalArgumentException)
512              {
513                  throw new RecordFormatException(
514                      "Unable to determine record types");
515              }
516              result.put(new Short(sid), constructor);
517          }
518          return result;
519      }
520  
521  }
522  
523  /**
524   * ListenerWrapper just wraps an ERFListener and adds support for throwing
525   * the event to multiple SIDs
526   */
527  class ListenerWrapper implements ERFListener {
528         private ERFListener listener;
529         private short[] sids;
530         private boolean abortable;
531  
532      ListenerWrapper(ERFListener listener, short[] sids, boolean abortable) {
533          this.listener = listener;
534          this.sids = sids;   
535          this.abortable = abortable;
536      }       
537      
538  
539      public boolean processRecord(Record rec)
540      {
541          boolean result = true;
542          for (int k = 0; k < sids.length; k++) {
543              if (sids[k] == rec.getSid()) {
544                  result = listener.processRecord(rec);
545              
546                  if (abortable == true && result == false) {
547                      break;   
548                  }
549              }
550          }
551          return result;
552      }   
553  }
554