Inspired by bug #44958 - Record level support for Data Tables. (No formula parser...
[poi.git] / src / java / org / apache / poi / hssf / eventmodel / EventRecordFactory.java
blob8c4abb76ad8d6e9415c78b3ac224799e784c9a49
1 /* ====================================================================
2 Licensed to the Apache Software Foundation (ASF) under one or more
3 contributor license agreements. See the NOTICE file distributed with
4 this work for additional information regarding copyright ownership.
5 The ASF licenses this file to You under the Apache License, Version 2.0
6 (the "License"); you may not use this file except in compliance with
7 the License. You may obtain a copy of the License at
9 http://www.apache.org/licenses/LICENSE-2.0
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16 ==================================================================== */
18 package org.apache.poi.hssf.eventmodel;
20 import java.io.InputStream;
21 import java.lang.reflect.Constructor;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Map;
28 import org.apache.poi.hssf.record.BOFRecord;
29 import org.apache.poi.hssf.record.BackupRecord;
30 import org.apache.poi.hssf.record.BlankRecord;
31 import org.apache.poi.hssf.record.BookBoolRecord;
32 import org.apache.poi.hssf.record.BoolErrRecord;
33 import org.apache.poi.hssf.record.BottomMarginRecord;
34 import org.apache.poi.hssf.record.BoundSheetRecord;
35 import org.apache.poi.hssf.record.CalcCountRecord;
36 import org.apache.poi.hssf.record.CalcModeRecord;
37 import org.apache.poi.hssf.record.CodepageRecord;
38 import org.apache.poi.hssf.record.ColumnInfoRecord;
39 import org.apache.poi.hssf.record.ContinueRecord;
40 import org.apache.poi.hssf.record.CountryRecord;
41 import org.apache.poi.hssf.record.DBCellRecord;
42 import org.apache.poi.hssf.record.DSFRecord;
43 import org.apache.poi.hssf.record.DateWindow1904Record;
44 import org.apache.poi.hssf.record.DefaultColWidthRecord;
45 import org.apache.poi.hssf.record.DefaultRowHeightRecord;
46 import org.apache.poi.hssf.record.DeltaRecord;
47 import org.apache.poi.hssf.record.DimensionsRecord;
48 import org.apache.poi.hssf.record.EOFRecord;
49 import org.apache.poi.hssf.record.ExtSSTRecord;
50 import org.apache.poi.hssf.record.ExtendedFormatRecord;
51 import org.apache.poi.hssf.record.ExternSheetRecord;
52 import org.apache.poi.hssf.record.FnGroupCountRecord;
53 import org.apache.poi.hssf.record.FontRecord;
54 import org.apache.poi.hssf.record.FooterRecord;
55 import org.apache.poi.hssf.record.FormatRecord;
56 import org.apache.poi.hssf.record.GridsetRecord;
57 import org.apache.poi.hssf.record.GutsRecord;
58 import org.apache.poi.hssf.record.HCenterRecord;
59 import org.apache.poi.hssf.record.HeaderRecord;
60 import org.apache.poi.hssf.record.HideObjRecord;
61 import org.apache.poi.hssf.record.IndexRecord;
62 import org.apache.poi.hssf.record.InterfaceEndRecord;
63 import org.apache.poi.hssf.record.InterfaceHdrRecord;
64 import org.apache.poi.hssf.record.IterationRecord;
65 import org.apache.poi.hssf.record.LabelRecord;
66 import org.apache.poi.hssf.record.LabelSSTRecord;
67 import org.apache.poi.hssf.record.LeftMarginRecord;
68 import org.apache.poi.hssf.record.MMSRecord;
69 import org.apache.poi.hssf.record.MergeCellsRecord;
70 import org.apache.poi.hssf.record.MulBlankRecord;
71 import org.apache.poi.hssf.record.MulRKRecord;
72 import org.apache.poi.hssf.record.NameRecord;
73 import org.apache.poi.hssf.record.NumberRecord;
74 import org.apache.poi.hssf.record.PaneRecord;
75 import org.apache.poi.hssf.record.PaletteRecord;
76 import org.apache.poi.hssf.record.PasswordRecord;
77 import org.apache.poi.hssf.record.PasswordRev4Record;
78 import org.apache.poi.hssf.record.PrecisionRecord;
79 import org.apache.poi.hssf.record.PrintGridlinesRecord;
80 import org.apache.poi.hssf.record.PrintHeadersRecord;
81 import org.apache.poi.hssf.record.PrintSetupRecord;
82 import org.apache.poi.hssf.record.ProtectRecord;
83 import org.apache.poi.hssf.record.ProtectionRev4Record;
84 import org.apache.poi.hssf.record.RKRecord;
85 import org.apache.poi.hssf.record.Record;
86 import org.apache.poi.hssf.record.RecordFormatException;
87 import org.apache.poi.hssf.record.RecordInputStream;
88 import org.apache.poi.hssf.record.RefModeRecord;
89 import org.apache.poi.hssf.record.RefreshAllRecord;
90 import org.apache.poi.hssf.record.RightMarginRecord;
91 import org.apache.poi.hssf.record.RowRecord;
92 import org.apache.poi.hssf.record.SSTRecord;
93 import org.apache.poi.hssf.record.SaveRecalcRecord;
94 import org.apache.poi.hssf.record.SelectionRecord;
95 import org.apache.poi.hssf.record.SharedFormulaRecord;
96 import org.apache.poi.hssf.record.StringRecord;
97 import org.apache.poi.hssf.record.StyleRecord;
98 import org.apache.poi.hssf.record.TabIdRecord;
99 import org.apache.poi.hssf.record.TableRecord;
100 import org.apache.poi.hssf.record.TopMarginRecord;
101 import org.apache.poi.hssf.record.UnknownRecord;
102 import org.apache.poi.hssf.record.UseSelFSRecord;
103 import org.apache.poi.hssf.record.VCenterRecord;
104 import org.apache.poi.hssf.record.WSBoolRecord;
105 import org.apache.poi.hssf.record.WindowOneRecord;
106 import org.apache.poi.hssf.record.WindowProtectRecord;
107 import org.apache.poi.hssf.record.WindowTwoRecord;
108 import org.apache.poi.hssf.record.WriteAccessRecord;
109 import org.apache.poi.hssf.record.WriteProtectRecord;
110 import org.apache.poi.hssf.record.FilePassRecord;
111 import org.apache.poi.hssf.record.NoteRecord;
115 * Event-based record factory. As opposed to RecordFactory
116 * this refactored version throws record events as it comes
117 * accross the records. I throws the "lazily" one record behind
118 * to ensure that ContinueRecords are processed first.
120 * @author Andrew C. Oliver (acoliver@apache.org) - probably to blame for the bugs (so yank his chain on the list)
121 * @author Marc Johnson (mjohnson at apache dot org) - methods taken from RecordFactory
122 * @author Glen Stampoultzis (glens at apache.org) - methods taken from RecordFactory
123 * @author Csaba Nagy (ncsaba at yahoo dot com)
125 public class EventRecordFactory
129 * contains the classes for all the records we want to parse.
131 private static final Class[] records;
133 static {
134 records = new Class[]
136 BOFRecord.class, InterfaceHdrRecord.class, MMSRecord.class,
137 InterfaceEndRecord.class, WriteAccessRecord.class,
138 CodepageRecord.class, DSFRecord.class, TabIdRecord.class,
139 FnGroupCountRecord.class, WindowProtectRecord.class,
140 ProtectRecord.class, PasswordRecord.class, ProtectionRev4Record.class,
141 PasswordRev4Record.class, WindowOneRecord.class, BackupRecord.class,
142 HideObjRecord.class, DateWindow1904Record.class,
143 PrecisionRecord.class, RefreshAllRecord.class, BookBoolRecord.class,
144 FontRecord.class, FormatRecord.class, ExtendedFormatRecord.class,
145 StyleRecord.class, UseSelFSRecord.class, BoundSheetRecord.class,
146 CountryRecord.class, SSTRecord.class, ExtSSTRecord.class,
147 EOFRecord.class, IndexRecord.class, CalcModeRecord.class,
148 CalcCountRecord.class, RefModeRecord.class, IterationRecord.class,
149 DeltaRecord.class, SaveRecalcRecord.class, PrintHeadersRecord.class,
150 PrintGridlinesRecord.class, GridsetRecord.class, GutsRecord.class,
151 DefaultRowHeightRecord.class, WSBoolRecord.class, HeaderRecord.class,
152 FooterRecord.class, HCenterRecord.class, VCenterRecord.class,
153 PrintSetupRecord.class, DefaultColWidthRecord.class,
154 DimensionsRecord.class, RowRecord.class, LabelSSTRecord.class,
155 RKRecord.class, NumberRecord.class, DBCellRecord.class,
156 WindowTwoRecord.class, SelectionRecord.class, ContinueRecord.class,
157 LabelRecord.class, BlankRecord.class, ColumnInfoRecord.class,
158 MulRKRecord.class, MulBlankRecord.class, MergeCellsRecord.class,
159 BoolErrRecord.class, ExternSheetRecord.class, NameRecord.class,
160 LeftMarginRecord.class, RightMarginRecord.class,
161 TopMarginRecord.class, BottomMarginRecord.class,
162 PaletteRecord.class, StringRecord.class, SharedFormulaRecord.class,
163 WriteProtectRecord.class, FilePassRecord.class, PaneRecord.class,
164 NoteRecord.class, TableRecord.class
170 * cache of the recordsToMap();
172 private static Map recordsMap = recordsToMap(records);
175 * cache of the return of getAllKnownSids so that we don't have to
176 * expensively get them every time.
178 private static short[] sidscache;
181 * List of the listners that are registred. should all be ERFListener
183 private List listeners;
186 * instance is abortable or not
188 private boolean abortable;
191 * Construct an abortable EventRecordFactory.
192 * The same as calling new EventRecordFactory(true)
193 * @see #EventRecordFactory(boolean)
195 public EventRecordFactory() {
196 this(true);
200 * Create an EventRecordFactory
201 * @param abortable specifies whether the return from the listener
202 * handler functions are obeyed. False means they are ignored. True
203 * means the event loop exits on error.
205 public EventRecordFactory(boolean abortable) {
206 this.abortable = abortable;
207 listeners = new ArrayList(recordsMap.size());
209 if (sidscache == null) {
210 sidscache = getAllKnownRecordSIDs();
216 * Register a listener for records. These can be for all records
217 * or just a subset.
219 * @param sids an array of Record.sid values identifying the records
220 * the listener will work with. Alternatively if this is "null" then
221 * all records are passed.
223 public void registerListener(ERFListener listener, short[] sids) {
224 if (sids == null)
225 sids = sidscache;
226 ERFListener wrapped = new ListenerWrapper(listener, sids, abortable);
227 listeners.add(wrapped);
231 * used for unit tests to test the registration of record listeners.
232 * @return Iterator of ERFListeners
234 protected Iterator listeners() {
235 return listeners.iterator();
239 * sends the record event to all registered listeners.
240 * @param record the record to be thrown.
241 * @return boolean abort. If exitability is turned on this aborts
242 * out of the event loop should any listener specify to do so.
244 private boolean throwRecordEvent(Record record)
246 boolean result = true;
247 Iterator i = listeners.iterator();
249 while (i.hasNext()) {
250 result = ((ERFListener) i.next()).processRecord(record);
251 if (abortable == true && result == false) {
252 break;
255 return result;
259 * Create an array of records from an input stream
261 * @param in the InputStream from which the records will be
262 * obtained
264 * @exception RecordFormatException on error processing the
265 * InputStream
267 public void processRecords(InputStream in)
268 throws RecordFormatException
270 Record last_record = null;
272 RecordInputStream recStream = new RecordInputStream(in);
274 while (recStream.hasNextRecord()) {
275 recStream.nextRecord();
276 Record[] recs = createRecord(recStream); // handle MulRK records
277 if (recs.length > 1)
279 for (int k = 0; k < recs.length; k++)
281 if ( last_record != null ) {
282 if (throwRecordEvent(last_record) == false && abortable == true) {
283 last_record = null;
284 break;
287 last_record =
288 recs[ k ]; // do to keep the algorythm homogenous...you can't
289 } // actually continue a number record anyhow.
291 else
293 Record record = recs[ 0 ];
295 if (record != null)
297 if (last_record != null) {
298 if (throwRecordEvent(last_record) == false && abortable == true) {
299 last_record = null;
300 break;
304 last_record = record;
310 if (last_record != null) {
311 throwRecordEvent(last_record);
316 * create a record, if there are MUL records than multiple records
317 * are returned digested into the non-mul form.
319 public static Record [] createRecord(RecordInputStream in)
321 Record retval = null;
322 Record[] realretval = null;
326 Constructor constructor =
327 ( Constructor ) recordsMap.get(new Short(in.getSid()));
329 if (constructor != null)
331 retval = ( Record ) constructor.newInstance(new Object[]
336 else
338 retval = new UnknownRecord(in);
341 catch (Exception introspectionException)
343 throw new RecordFormatException("Unable to construct record instance" , introspectionException);
345 if (retval instanceof RKRecord)
347 RKRecord rk = ( RKRecord ) retval;
348 NumberRecord num = new NumberRecord();
350 num.setColumn(rk.getColumn());
351 num.setRow(rk.getRow());
352 num.setXFIndex(rk.getXFIndex());
353 num.setValue(rk.getRKNumber());
354 retval = num;
356 else if (retval instanceof DBCellRecord)
358 retval = null;
360 else if (retval instanceof MulRKRecord)
362 MulRKRecord mrk = ( MulRKRecord ) retval;
364 realretval = new Record[ mrk.getNumColumns() ];
365 for (int k = 0; k < mrk.getNumColumns(); k++)
367 NumberRecord nr = new NumberRecord();
369 nr.setColumn(( short ) (k + mrk.getFirstColumn()));
370 nr.setRow(mrk.getRow());
371 nr.setXFIndex(mrk.getXFAt(k));
372 nr.setValue(mrk.getRKNumberAt(k));
373 realretval[ k ] = nr;
376 else if (retval instanceof MulBlankRecord)
378 MulBlankRecord mb = ( MulBlankRecord ) retval;
380 realretval = new Record[ mb.getNumColumns() ];
381 for (int k = 0; k < mb.getNumColumns(); k++)
383 BlankRecord br = new BlankRecord();
385 br.setColumn(( short ) (k + mb.getFirstColumn()));
386 br.setRow(mb.getRow());
387 br.setXFIndex(mb.getXFAt(k));
388 realretval[ k ] = br;
391 if (realretval == null)
393 realretval = new Record[ 1 ];
394 realretval[ 0 ] = retval;
396 return realretval;
400 * @return an array of all the SIDS for all known records
402 public static short [] getAllKnownRecordSIDs()
404 short[] results = new short[ recordsMap.size() ];
405 int i = 0;
407 for (Iterator iterator = recordsMap.keySet().iterator();
408 iterator.hasNext(); )
410 Short sid = ( Short ) iterator.next();
412 results[ i++ ] = sid.shortValue();
414 return results;
418 * gets the record constructors and sticks them in the map by SID
419 * @return map of SIDs to short,short,byte[] constructors for Record classes
420 * most of org.apache.poi.hssf.record.*
422 private static Map recordsToMap(Class [] records)
424 Map result = new HashMap();
425 Constructor constructor;
427 for (int i = 0; i < records.length; i++)
429 Class record = null;
430 short sid = 0;
432 record = records[ i ];
435 sid = record.getField("sid").getShort(null);
436 constructor = record.getConstructor(new Class[]
438 RecordInputStream.class
441 catch (Exception illegalArgumentException)
443 throw new RecordFormatException(
444 "Unable to determine record types");
446 result.put(new Short(sid), constructor);
448 return result;
454 * ListenerWrapper just wraps an ERFListener and adds support for throwing
455 * the event to multiple SIDs
457 class ListenerWrapper implements ERFListener {
458 private ERFListener listener;
459 private short[] sids;
460 private boolean abortable;
462 ListenerWrapper(ERFListener listener, short[] sids, boolean abortable) {
463 this.listener = listener;
464 this.sids = sids;
465 this.abortable = abortable;
469 public boolean processRecord(Record rec)
471 boolean result = true;
472 for (int k = 0; k < sids.length; k++) {
473 if (sids[k] == rec.getSid()) {
474 result = listener.processRecord(rec);
476 if (abortable == true && result == false) {
477 break;
481 return result;