From 34ea10b29d689276307f9a094932f7a5aff5a55a Mon Sep 17 00:00:00 2001 From: Josh Micich Date: Mon, 4 Aug 2008 21:08:11 +0000 Subject: [PATCH] Patch 44894 - refactoring duplicate logic from EventRecordFactory to RecordFactory git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@682508 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/changes.xml | 1 + src/documentation/content/xdocs/status.xml | 1 + .../hssf/eventmodel/EventRecordFactory.java | 476 ++-------------- .../poi/hssf/eventmodel/ModelFactory.java | 3 +- .../apache/poi/hssf/record/RecordFactory.java | 514 ++++++++++-------- .../poi/hssf/usermodel/HSSFWorkbook.java | 3 - .../eventmodel/TestEventRecordFactory.java | 136 ++--- .../org/apache/poi/hssf/model/TestSheet.java | 4 +- .../hssf/usermodel/TestDataValidation.java | 4 +- 9 files changed, 387 insertions(+), 755 deletions(-) diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index b4515d39c6..cd1dfd0856 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -37,6 +37,7 @@ + 44894 - refactor duplicate logic from EventRecordFactory to RecordFactory Support for Headers / Footers in HSLF 44953 - Extensive fixes for data validation 45519 - Fixed to keep datavalidation records together diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 7dbddb6f8a..b09ea50a02 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 44894 - refactor duplicate logic from EventRecordFactory to RecordFactory Support for Headers / Footers in HSLF 44953 - Extensive fixes for data validation 45519 - Fixed to keep datavalidation records together diff --git a/src/java/org/apache/poi/hssf/eventmodel/EventRecordFactory.java b/src/java/org/apache/poi/hssf/eventmodel/EventRecordFactory.java index 8c4abb76ad..5cdd813700 100644 --- a/src/java/org/apache/poi/hssf/eventmodel/EventRecordFactory.java +++ b/src/java/org/apache/poi/hssf/eventmodel/EventRecordFactory.java @@ -18,241 +18,60 @@ package org.apache.poi.hssf.eventmodel; import java.io.InputStream; -import java.lang.reflect.Constructor; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import org.apache.poi.hssf.record.BOFRecord; -import org.apache.poi.hssf.record.BackupRecord; -import org.apache.poi.hssf.record.BlankRecord; -import org.apache.poi.hssf.record.BookBoolRecord; -import org.apache.poi.hssf.record.BoolErrRecord; -import org.apache.poi.hssf.record.BottomMarginRecord; -import org.apache.poi.hssf.record.BoundSheetRecord; -import org.apache.poi.hssf.record.CalcCountRecord; -import org.apache.poi.hssf.record.CalcModeRecord; -import org.apache.poi.hssf.record.CodepageRecord; -import org.apache.poi.hssf.record.ColumnInfoRecord; -import org.apache.poi.hssf.record.ContinueRecord; -import org.apache.poi.hssf.record.CountryRecord; -import org.apache.poi.hssf.record.DBCellRecord; -import org.apache.poi.hssf.record.DSFRecord; -import org.apache.poi.hssf.record.DateWindow1904Record; -import org.apache.poi.hssf.record.DefaultColWidthRecord; -import org.apache.poi.hssf.record.DefaultRowHeightRecord; -import org.apache.poi.hssf.record.DeltaRecord; -import org.apache.poi.hssf.record.DimensionsRecord; -import org.apache.poi.hssf.record.EOFRecord; -import org.apache.poi.hssf.record.ExtSSTRecord; -import org.apache.poi.hssf.record.ExtendedFormatRecord; -import org.apache.poi.hssf.record.ExternSheetRecord; -import org.apache.poi.hssf.record.FnGroupCountRecord; -import org.apache.poi.hssf.record.FontRecord; -import org.apache.poi.hssf.record.FooterRecord; -import org.apache.poi.hssf.record.FormatRecord; -import org.apache.poi.hssf.record.GridsetRecord; -import org.apache.poi.hssf.record.GutsRecord; -import org.apache.poi.hssf.record.HCenterRecord; -import org.apache.poi.hssf.record.HeaderRecord; -import org.apache.poi.hssf.record.HideObjRecord; -import org.apache.poi.hssf.record.IndexRecord; -import org.apache.poi.hssf.record.InterfaceEndRecord; -import org.apache.poi.hssf.record.InterfaceHdrRecord; -import org.apache.poi.hssf.record.IterationRecord; -import org.apache.poi.hssf.record.LabelRecord; -import org.apache.poi.hssf.record.LabelSSTRecord; -import org.apache.poi.hssf.record.LeftMarginRecord; -import org.apache.poi.hssf.record.MMSRecord; -import org.apache.poi.hssf.record.MergeCellsRecord; -import org.apache.poi.hssf.record.MulBlankRecord; -import org.apache.poi.hssf.record.MulRKRecord; -import org.apache.poi.hssf.record.NameRecord; -import org.apache.poi.hssf.record.NumberRecord; -import org.apache.poi.hssf.record.PaneRecord; -import org.apache.poi.hssf.record.PaletteRecord; -import org.apache.poi.hssf.record.PasswordRecord; -import org.apache.poi.hssf.record.PasswordRev4Record; -import org.apache.poi.hssf.record.PrecisionRecord; -import org.apache.poi.hssf.record.PrintGridlinesRecord; -import org.apache.poi.hssf.record.PrintHeadersRecord; -import org.apache.poi.hssf.record.PrintSetupRecord; -import org.apache.poi.hssf.record.ProtectRecord; -import org.apache.poi.hssf.record.ProtectionRev4Record; -import org.apache.poi.hssf.record.RKRecord; -import org.apache.poi.hssf.record.Record; -import org.apache.poi.hssf.record.RecordFormatException; -import org.apache.poi.hssf.record.RecordInputStream; -import org.apache.poi.hssf.record.RefModeRecord; -import org.apache.poi.hssf.record.RefreshAllRecord; -import org.apache.poi.hssf.record.RightMarginRecord; -import org.apache.poi.hssf.record.RowRecord; -import org.apache.poi.hssf.record.SSTRecord; -import org.apache.poi.hssf.record.SaveRecalcRecord; -import org.apache.poi.hssf.record.SelectionRecord; -import org.apache.poi.hssf.record.SharedFormulaRecord; -import org.apache.poi.hssf.record.StringRecord; -import org.apache.poi.hssf.record.StyleRecord; -import org.apache.poi.hssf.record.TabIdRecord; -import org.apache.poi.hssf.record.TableRecord; -import org.apache.poi.hssf.record.TopMarginRecord; -import org.apache.poi.hssf.record.UnknownRecord; -import org.apache.poi.hssf.record.UseSelFSRecord; -import org.apache.poi.hssf.record.VCenterRecord; -import org.apache.poi.hssf.record.WSBoolRecord; -import org.apache.poi.hssf.record.WindowOneRecord; -import org.apache.poi.hssf.record.WindowProtectRecord; -import org.apache.poi.hssf.record.WindowTwoRecord; -import org.apache.poi.hssf.record.WriteAccessRecord; -import org.apache.poi.hssf.record.WriteProtectRecord; -import org.apache.poi.hssf.record.FilePassRecord; -import org.apache.poi.hssf.record.NoteRecord; +import java.util.Arrays; +import org.apache.poi.hssf.record.*; /** * Event-based record factory. As opposed to RecordFactory - * this refactored version throws record events as it comes - * accross the records. I throws the "lazily" one record behind - * to ensure that ContinueRecords are processed first. + * this version sends {@link ERFListener#processRecord(Record) } messages to + * the supplied listener. Record notifications are sent one record behind + * to ensure that {@link ContinueRecord}s are processed first. * * @author Andrew C. Oliver (acoliver@apache.org) - probably to blame for the bugs (so yank his chain on the list) * @author Marc Johnson (mjohnson at apache dot org) - methods taken from RecordFactory * @author Glen Stampoultzis (glens at apache.org) - methods taken from RecordFactory * @author Csaba Nagy (ncsaba at yahoo dot com) */ -public class EventRecordFactory -{ - - /** - * contains the classes for all the records we want to parse. - */ - private static final Class[] records; +public final class EventRecordFactory { - static { - records = new Class[] - { - BOFRecord.class, InterfaceHdrRecord.class, MMSRecord.class, - InterfaceEndRecord.class, WriteAccessRecord.class, - CodepageRecord.class, DSFRecord.class, TabIdRecord.class, - FnGroupCountRecord.class, WindowProtectRecord.class, - ProtectRecord.class, PasswordRecord.class, ProtectionRev4Record.class, - PasswordRev4Record.class, WindowOneRecord.class, BackupRecord.class, - HideObjRecord.class, DateWindow1904Record.class, - PrecisionRecord.class, RefreshAllRecord.class, BookBoolRecord.class, - FontRecord.class, FormatRecord.class, ExtendedFormatRecord.class, - StyleRecord.class, UseSelFSRecord.class, BoundSheetRecord.class, - CountryRecord.class, SSTRecord.class, ExtSSTRecord.class, - EOFRecord.class, IndexRecord.class, CalcModeRecord.class, - CalcCountRecord.class, RefModeRecord.class, IterationRecord.class, - DeltaRecord.class, SaveRecalcRecord.class, PrintHeadersRecord.class, - PrintGridlinesRecord.class, GridsetRecord.class, GutsRecord.class, - DefaultRowHeightRecord.class, WSBoolRecord.class, HeaderRecord.class, - FooterRecord.class, HCenterRecord.class, VCenterRecord.class, - PrintSetupRecord.class, DefaultColWidthRecord.class, - DimensionsRecord.class, RowRecord.class, LabelSSTRecord.class, - RKRecord.class, NumberRecord.class, DBCellRecord.class, - WindowTwoRecord.class, SelectionRecord.class, ContinueRecord.class, - LabelRecord.class, BlankRecord.class, ColumnInfoRecord.class, - MulRKRecord.class, MulBlankRecord.class, MergeCellsRecord.class, - BoolErrRecord.class, ExternSheetRecord.class, NameRecord.class, - LeftMarginRecord.class, RightMarginRecord.class, - TopMarginRecord.class, BottomMarginRecord.class, - PaletteRecord.class, StringRecord.class, SharedFormulaRecord.class, - WriteProtectRecord.class, FilePassRecord.class, PaneRecord.class, - NoteRecord.class, TableRecord.class - }; - - } - - /** - * cache of the recordsToMap(); - */ - private static Map recordsMap = recordsToMap(records); - - /** - * cache of the return of getAllKnownSids so that we don't have to - * expensively get them every time. - */ - private static short[] sidscache; - - /** - * List of the listners that are registred. should all be ERFListener - */ - private List listeners; + private final ERFListener _listener; + private final short[] _sids; /** - * instance is abortable or not - */ - private boolean abortable; - - /** - * Construct an abortable EventRecordFactory. - * The same as calling new EventRecordFactory(true) - * @see #EventRecordFactory(boolean) - */ - public EventRecordFactory() { - this(true); - } - - /** - * Create an EventRecordFactory - * @param abortable specifies whether the return from the listener - * handler functions are obeyed. False means they are ignored. True - * means the event loop exits on error. - */ - public EventRecordFactory(boolean abortable) { - this.abortable = abortable; - listeners = new ArrayList(recordsMap.size()); - - if (sidscache == null) { - sidscache = getAllKnownRecordSIDs(); - } - - } - - /** - * Register a listener for records. These can be for all records - * or just a subset. - * + * * @param sids an array of Record.sid values identifying the records * the listener will work with. Alternatively if this is "null" then - * all records are passed. + * all records are passed. For all 'known' record types use {@link RecordFactory#getAllKnownRecordSIDs()} */ - public void registerListener(ERFListener listener, short[] sids) { - if (sids == null) - sids = sidscache; - ERFListener wrapped = new ListenerWrapper(listener, sids, abortable); - listeners.add(wrapped); - } - - /** - * used for unit tests to test the registration of record listeners. - * @return Iterator of ERFListeners - */ - protected Iterator listeners() { - return listeners.iterator(); + public EventRecordFactory(ERFListener listener, short[] sids) { + _listener = listener; + if (sids == null) { + _sids = null; + } else { + _sids = (short[]) sids.clone(); + Arrays.sort(_sids); // for faster binary search + } } + private boolean isSidIncluded(short sid) { + if (_sids == null) { + return true; + } + return Arrays.binarySearch(_sids, sid) >= 0; + } + /** * sends the record event to all registered listeners. * @param record the record to be thrown. - * @return boolean abort. If exitability is turned on this aborts - * out of the event loop should any listener specify to do so. + * @return false to abort. This aborts + * out of the event loop should the listener return false */ - private boolean throwRecordEvent(Record record) - { - boolean result = true; - Iterator i = listeners.iterator(); - - while (i.hasNext()) { - result = ((ERFListener) i.next()).processRecord(record); - if (abortable == true && result == false) { - break; - } - } - return result; + private boolean processRecord(Record record) { + if (!isSidIncluded(record.getSid())) { + return true; + } + return _listener.processRecord(record); } /** @@ -264,220 +83,39 @@ public class EventRecordFactory * @exception RecordFormatException on error processing the * InputStream */ - public void processRecords(InputStream in) - throws RecordFormatException - { - Record last_record = null; + public void processRecords(InputStream in) throws RecordFormatException { + Record last_record = null; RecordInputStream recStream = new RecordInputStream(in); while (recStream.hasNextRecord()) { - recStream.nextRecord(); - Record[] recs = createRecord(recStream); // handle MulRK records - if (recs.length > 1) - { - for (int k = 0; k < recs.length; k++) - { - if ( last_record != null ) { - if (throwRecordEvent(last_record) == false && abortable == true) { - last_record = null; - break; - } - } - last_record = - recs[ k ]; // do to keep the algorythm homogenous...you can't - } // actually continue a number record anyhow. + recStream.nextRecord(); + Record[] recs = RecordFactory.createRecord(recStream); // handle MulRK records + if (recs.length > 1) { + for (int k = 0; k < recs.length; k++) { + if ( last_record != null ) { + if (!processRecord(last_record)) { + return; + } } - else - { - Record record = recs[ 0 ]; - - if (record != null) - { - if (last_record != null) { - if (throwRecordEvent(last_record) == false && abortable == true) { - last_record = null; - break; - } - } - - last_record = record; - } + last_record = recs[ k ]; // do to keep the algorithm homogeneous...you can't + } // actually continue a number record anyhow. + } else { + Record record = recs[ 0 ]; + + if (record != null) { + if (last_record != null) { + if (!processRecord(last_record)) { + return; } } - - - if (last_record != null) { - throwRecordEvent(last_record); - } - } - - /** - * create a record, if there are MUL records than multiple records - * are returned digested into the non-mul form. - */ - public static Record [] createRecord(RecordInputStream in) - { - Record retval = null; - Record[] realretval = null; - - try - { - Constructor constructor = - ( Constructor ) recordsMap.get(new Short(in.getSid())); - - if (constructor != null) - { - retval = ( Record ) constructor.newInstance(new Object[] - { - in - }); - } - else - { - retval = new UnknownRecord(in); - } - } - catch (Exception introspectionException) - { - throw new RecordFormatException("Unable to construct record instance" , introspectionException); - } - if (retval instanceof RKRecord) - { - RKRecord rk = ( RKRecord ) retval; - NumberRecord num = new NumberRecord(); - - num.setColumn(rk.getColumn()); - num.setRow(rk.getRow()); - num.setXFIndex(rk.getXFIndex()); - num.setValue(rk.getRKNumber()); - retval = num; - } - else if (retval instanceof DBCellRecord) - { - retval = null; - } - else if (retval instanceof MulRKRecord) - { - MulRKRecord mrk = ( MulRKRecord ) retval; - - realretval = new Record[ mrk.getNumColumns() ]; - for (int k = 0; k < mrk.getNumColumns(); k++) - { - NumberRecord nr = new NumberRecord(); - - nr.setColumn(( short ) (k + mrk.getFirstColumn())); - nr.setRow(mrk.getRow()); - nr.setXFIndex(mrk.getXFAt(k)); - nr.setValue(mrk.getRKNumberAt(k)); - realretval[ k ] = nr; - } - } - else if (retval instanceof MulBlankRecord) - { - MulBlankRecord mb = ( MulBlankRecord ) retval; - - realretval = new Record[ mb.getNumColumns() ]; - for (int k = 0; k < mb.getNumColumns(); k++) - { - BlankRecord br = new BlankRecord(); - - br.setColumn(( short ) (k + mb.getFirstColumn())); - br.setRow(mb.getRow()); - br.setXFIndex(mb.getXFAt(k)); - realretval[ k ] = br; - } - } - if (realretval == null) - { - realretval = new Record[ 1 ]; - realretval[ 0 ] = retval; - } - return realretval; - } - - /** - * @return an array of all the SIDS for all known records - */ - public static short [] getAllKnownRecordSIDs() - { - short[] results = new short[ recordsMap.size() ]; - int i = 0; - - for (Iterator iterator = recordsMap.keySet().iterator(); - iterator.hasNext(); ) - { - Short sid = ( Short ) iterator.next(); - - results[ i++ ] = sid.shortValue(); - } - return results; - } - - /** - * gets the record constructors and sticks them in the map by SID - * @return map of SIDs to short,short,byte[] constructors for Record classes - * most of org.apache.poi.hssf.record.* - */ - private static Map recordsToMap(Class [] records) - { - Map result = new HashMap(); - Constructor constructor; - - for (int i = 0; i < records.length; i++) - { - Class record = null; - short sid = 0; - - record = records[ i ]; - try - { - sid = record.getField("sid").getShort(null); - constructor = record.getConstructor(new Class[] - { - RecordInputStream.class - }); - } - catch (Exception illegalArgumentException) - { - throw new RecordFormatException( - "Unable to determine record types"); + last_record = record; + } } - result.put(new Short(sid), constructor); } - return result; - } - -} - -/** - * ListenerWrapper just wraps an ERFListener and adds support for throwing - * the event to multiple SIDs - */ -class ListenerWrapper implements ERFListener { - private ERFListener listener; - private short[] sids; - private boolean abortable; - - ListenerWrapper(ERFListener listener, short[] sids, boolean abortable) { - this.listener = listener; - this.sids = sids; - this.abortable = abortable; - } - - - public boolean processRecord(Record rec) - { - boolean result = true; - for (int k = 0; k < sids.length; k++) { - if (sids[k] == rec.getSid()) { - result = listener.processRecord(rec); - if (abortable == true && result == false) { - break; - } - } + if (last_record != null) { + processRecord(last_record); } - return result; - } -} + } +} \ No newline at end of file diff --git a/src/java/org/apache/poi/hssf/eventmodel/ModelFactory.java b/src/java/org/apache/poi/hssf/eventmodel/ModelFactory.java index ade4532042..d75ca6c2e6 100644 --- a/src/java/org/apache/poi/hssf/eventmodel/ModelFactory.java +++ b/src/java/org/apache/poi/hssf/eventmodel/ModelFactory.java @@ -65,8 +65,7 @@ public class ModelFactory implements ERFListener * Start processing the Workbook stream into Model events. */ public void run(InputStream stream) { - EventRecordFactory factory = new EventRecordFactory(true); - factory.registerListener(this,null); + EventRecordFactory factory = new EventRecordFactory(this,null); lastEOF = true; factory.processRecords(stream); } diff --git a/src/java/org/apache/poi/hssf/record/RecordFactory.java b/src/java/org/apache/poi/hssf/record/RecordFactory.java index aaee94991d..20c8b27e02 100644 --- a/src/java/org/apache/poi/hssf/record/RecordFactory.java +++ b/src/java/org/apache/poi/hssf/record/RecordFactory.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -15,201 +14,186 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - package org.apache.poi.hssf.record; import java.io.InputStream; import java.lang.reflect.Constructor; -import java.util.*; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; /** * Title: Record Factory

* Description: Takes a stream and outputs an array of Record objects.

* - * @deprecated use {@link org.apache.poi.hssf.eventmodel.EventRecordFactory} instead * @see org.apache.poi.hssf.eventmodel.EventRecordFactory * @author Andrew C. Oliver (acoliver at apache dot org) * @author Marc Johnson (mjohnson at apache dot org) * @author Glen Stampoultzis (glens at apache.org) * @author Csaba Nagy (ncsaba at yahoo dot com) */ +public final class RecordFactory { + private static final int NUM_RECORDS = 512; -public class RecordFactory -{ - private static int NUM_RECORDS = 10000; - private static final Class[] records; - - static { - records = new Class[] - { - BOFRecord.class, InterfaceHdrRecord.class, MMSRecord.class, - InterfaceEndRecord.class, WriteAccessRecord.class, - CodepageRecord.class, DSFRecord.class, TabIdRecord.class, - FnGroupCountRecord.class, WindowProtectRecord.class, - ProtectRecord.class, PasswordRecord.class, ProtectionRev4Record.class, - PasswordRev4Record.class, WindowOneRecord.class, BackupRecord.class, - HideObjRecord.class, DateWindow1904Record.class, - PrecisionRecord.class, RefreshAllRecord.class, BookBoolRecord.class, - FontRecord.class, FormatRecord.class, ExtendedFormatRecord.class, - StyleRecord.class, UseSelFSRecord.class, BoundSheetRecord.class, - CountryRecord.class, SSTRecord.class, ExtSSTRecord.class, - EOFRecord.class, IndexRecord.class, CalcModeRecord.class, - CalcCountRecord.class, RefModeRecord.class, IterationRecord.class, - DeltaRecord.class, SaveRecalcRecord.class, PrintHeadersRecord.class, - PrintGridlinesRecord.class, GridsetRecord.class, GutsRecord.class, - DefaultRowHeightRecord.class, WSBoolRecord.class, HeaderRecord.class, - FooterRecord.class, HCenterRecord.class, VCenterRecord.class, - PrintSetupRecord.class, DefaultColWidthRecord.class, - DimensionsRecord.class, RowRecord.class, LabelSSTRecord.class, - RKRecord.class, NumberRecord.class, DBCellRecord.class, - WindowTwoRecord.class, SelectionRecord.class, ContinueRecord.class, - LabelRecord.class, BlankRecord.class, ColumnInfoRecord.class, - MulRKRecord.class, MulBlankRecord.class, MergeCellsRecord.class, - FormulaRecord.class, BoolErrRecord.class, ExternSheetRecord.class, - NameRecord.class, LeftMarginRecord.class, RightMarginRecord.class, - TopMarginRecord.class, BottomMarginRecord.class, - DrawingRecord.class, DrawingGroupRecord.class, DrawingSelectionRecord.class, - ObjRecord.class, TextObjectRecord.class, - PaletteRecord.class, StringRecord.class, RecalcIdRecord.class, SharedFormulaRecord.class, - HorizontalPageBreakRecord.class, VerticalPageBreakRecord.class, - WriteProtectRecord.class, FilePassRecord.class, PaneRecord.class, - NoteRecord.class, ObjectProtectRecord.class, ScenarioProtectRecord.class, - FileSharingRecord.class, ChartTitleFormatRecord.class, - DVRecord.class, DVALRecord.class, UncalcedRecord.class, - ChartRecord.class, LegendRecord.class, ChartTitleFormatRecord.class, - SeriesRecord.class, SeriesTextRecord.class, - HyperlinkRecord.class, - ExternalNameRecord.class, // TODO - same changes in non-@deprecated version of this class - SupBookRecord.class, - CRNCountRecord.class, - CRNRecord.class, - CFHeaderRecord.class, - CFRuleRecord.class, - TableRecord.class - }; - } - private static Map recordsMap = recordsToMap(records); + private static final Class[] CONSTRUCTOR_ARGS = { RecordInputStream.class, }; + /** + * contains the classes for all the records we want to parse.
+ * Note - this most but not *every* subclass of Record. + */ + private static final Class[] records = { + BackupRecord.class, + BlankRecord.class, + BOFRecord.class, + BookBoolRecord.class, + BoolErrRecord.class, + BottomMarginRecord.class, + BoundSheetRecord.class, + CalcCountRecord.class, + CalcModeRecord.class, + CFHeaderRecord.class, + CFRuleRecord.class, + ChartRecord.class, + ChartTitleFormatRecord.class, + CodepageRecord.class, + ColumnInfoRecord.class, + ContinueRecord.class, + CountryRecord.class, + CRNCountRecord.class, + CRNRecord.class, + DateWindow1904Record.class, + DBCellRecord.class, + DefaultColWidthRecord.class, + DefaultRowHeightRecord.class, + DeltaRecord.class, + DimensionsRecord.class, + DrawingGroupRecord.class, + DrawingRecord.class, + DrawingSelectionRecord.class, + DSFRecord.class, + DVALRecord.class, + DVRecord.class, + EOFRecord.class, + ExtendedFormatRecord.class, + ExternalNameRecord.class, + ExternSheetRecord.class, + ExtSSTRecord.class, + FilePassRecord.class, + FileSharingRecord.class, + FnGroupCountRecord.class, + FontRecord.class, + FooterRecord.class, + FormatRecord.class, + FormulaRecord.class, + GridsetRecord.class, + GutsRecord.class, + HCenterRecord.class, + HeaderRecord.class, + HideObjRecord.class, + HorizontalPageBreakRecord.class, + HyperlinkRecord.class, + IndexRecord.class, + InterfaceEndRecord.class, + InterfaceHdrRecord.class, + IterationRecord.class, + LabelRecord.class, + LabelSSTRecord.class, + LeftMarginRecord.class, + LegendRecord.class, + MergeCellsRecord.class, + MMSRecord.class, + MulBlankRecord.class, + MulRKRecord.class, + NameRecord.class, + NoteRecord.class, + NumberRecord.class, + ObjectProtectRecord.class, + ObjRecord.class, + PaletteRecord.class, + PaneRecord.class, + PasswordRecord.class, + PasswordRev4Record.class, + PrecisionRecord.class, + PrintGridlinesRecord.class, + PrintHeadersRecord.class, + PrintSetupRecord.class, + ProtectionRev4Record.class, + ProtectRecord.class, + RecalcIdRecord.class, + RefModeRecord.class, + RefreshAllRecord.class, + RightMarginRecord.class, + RKRecord.class, + RowRecord.class, + SaveRecalcRecord.class, + ScenarioProtectRecord.class, + SelectionRecord.class, + SeriesRecord.class, + SeriesTextRecord.class, + SharedFormulaRecord.class, + SSTRecord.class, + StringRecord.class, + StyleRecord.class, + SupBookRecord.class, + TabIdRecord.class, + TableRecord.class, + TextObjectRecord.class, + TopMarginRecord.class, + UncalcedRecord.class, + UseSelFSRecord.class, + VCenterRecord.class, + VerticalPageBreakRecord.class, + WindowOneRecord.class, + WindowProtectRecord.class, + WindowTwoRecord.class, + WriteAccessRecord.class, + WriteProtectRecord.class, + WSBoolRecord.class, + }; + /** - * changes the default capacity (10000) to handle larger files + * cache of the recordsToMap(); */ + private static Map recordsMap = recordsToMap(records); - public static void setCapacity(int capacity) - { - NUM_RECORDS = capacity; - } - + private static short[] _allKnownRecordSIDs; + /** - * Create an array of records from an input stream - * - * @param in the InputStream from which the records will be - * obtained - * - * @return an array of Records created from the InputStream - * - * @exception RecordFormatException on error processing the - * InputStream + * create a record, if there are MUL records than multiple records + * are returned digested into the non-mul form. */ + public static Record [] createRecord(RecordInputStream in) { + Constructor constructor = (Constructor) recordsMap.get(new Short(in.getSid())); - public static List createRecords(InputStream in) - throws RecordFormatException - { - ArrayList records = new ArrayList(NUM_RECORDS); - - RecordInputStream recStream = new RecordInputStream(in); - DrawingRecord lastDrawingRecord = new DrawingRecord( ); - Record lastRecord = null; - while (recStream.hasNextRecord()) { - recStream.nextRecord(); - if (recStream.getSid() != 0) - { - Record[] recs = createRecord(recStream); // handle MulRK records - - if (recs.length > 1) - { - for (int k = 0; k < recs.length; k++) - { - records.add( - recs[ k ]); // these will be number records - } - } - else - { - Record record = recs[ 0 ]; - - if (record != null) - { - if (record.getSid() == DrawingGroupRecord.sid - && lastRecord instanceof DrawingGroupRecord) - { - DrawingGroupRecord lastDGRecord = (DrawingGroupRecord) lastRecord; - lastDGRecord.join((AbstractEscherHolderRecord) record); - } - else if (record.getSid() == ContinueRecord.sid && - ((lastRecord instanceof ObjRecord) || (lastRecord instanceof TextObjectRecord))) { - // Drawing records have a very strange continue behaviour. - //There can actually be OBJ records mixed between the continues. - lastDrawingRecord.processContinueRecord( ((ContinueRecord)record).getData() ); - //we must rememeber the position of the continue record. - //in the serialization procedure the original structure of records must be preserved - records.add(record); - } else if (record.getSid() == ContinueRecord.sid && - (lastRecord instanceof DrawingGroupRecord)) { - ((DrawingGroupRecord)lastRecord).processContinueRecord(((ContinueRecord)record).getData()); - } else if (record.getSid() == ContinueRecord.sid && - (lastRecord instanceof StringRecord)) { - ((StringRecord)lastRecord).processContinueRecord(((ContinueRecord)record).getData()); - } else if (record.getSid() == ContinueRecord.sid) { - if (lastRecord instanceof UnknownRecord) { - //Gracefully handle records that we dont know about, - //that happen to be continued - records.add(record); - } else - throw new RecordFormatException("Unhandled Continue Record"); - } - else { - lastRecord = record; - if (record instanceof DrawingRecord) - lastDrawingRecord = (DrawingRecord) record; - records.add(record); - } - } - } - } - } - - return records; - } - - public static Record [] createRecord(RecordInputStream in) - { - Record retval; - Record[] realretval = null; - - try - { - Constructor constructor = - ( Constructor ) recordsMap.get(new Short(in.getSid())); - - if (constructor != null) - { - retval = ( Record ) constructor.newInstance(new Object[] - { - in - }); - } - else - { - retval = new UnknownRecord(in); - } - } - catch (Exception introspectionException) - { - throw new RecordFormatException("Unable to construct record instance",introspectionException); + if (constructor == null) { + return new Record[] { new UnknownRecord(in), }; } - if (retval instanceof RKRecord) - { + + Record retval; + + try { + retval = ( Record ) constructor.newInstance(new Object[] { in }); + } catch (InvocationTargetException e) { + throw new RecordFormatException("Unable to construct record instance" , e.getTargetException()); + } catch (IllegalArgumentException e) { + throw new RuntimeException(e); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + + if (retval instanceof RKRecord) { + // RK record is a slightly smaller alternative to NumberRecord + // POI likes NumberRecord better RKRecord rk = ( RKRecord ) retval; NumberRecord num = new NumberRecord(); @@ -217,92 +201,174 @@ public class RecordFactory num.setRow(rk.getRow()); num.setXFIndex(rk.getXFIndex()); num.setValue(rk.getRKNumber()); - retval = num; + return new Record[] { num, }; } - else if (retval instanceof DBCellRecord) - { - retval = null; + if (retval instanceof DBCellRecord) { + // Not needed by POI. Regenerated from scratch by POI when spreadsheet is written + return new Record[] { null, }; } - else if (retval instanceof MulRKRecord) - { + // expand multiple records where necessary + if (retval instanceof MulRKRecord) { MulRKRecord mrk = ( MulRKRecord ) retval; - realretval = new Record[ mrk.getNumColumns() ]; - for (int k = 0; k < mrk.getNumColumns(); k++) - { + Record[] mulRecs = new Record[ mrk.getNumColumns() ]; + for (int k = 0; k < mrk.getNumColumns(); k++) { NumberRecord nr = new NumberRecord(); nr.setColumn(( short ) (k + mrk.getFirstColumn())); nr.setRow(mrk.getRow()); nr.setXFIndex(mrk.getXFAt(k)); nr.setValue(mrk.getRKNumberAt(k)); - realretval[ k ] = nr; + mulRecs[ k ] = nr; } + return mulRecs; } - else if (retval instanceof MulBlankRecord) - { + if (retval instanceof MulBlankRecord) { MulBlankRecord mb = ( MulBlankRecord ) retval; - realretval = new Record[ mb.getNumColumns() ]; - for (int k = 0; k < mb.getNumColumns(); k++) - { + Record[] mulRecs = new Record[ mb.getNumColumns() ]; + for (int k = 0; k < mb.getNumColumns(); k++) { BlankRecord br = new BlankRecord(); br.setColumn(( short ) (k + mb.getFirstColumn())); br.setRow(mb.getRow()); br.setXFIndex(mb.getXFAt(k)); - realretval[ k ] = br; + mulRecs[ k ] = br; } + return mulRecs; } - if (realretval == null) - { - realretval = new Record[ 1 ]; - realretval[ 0 ] = retval; - } - return realretval; + return new Record[] { retval, }; } - public static short [] getAllKnownRecordSIDs() - { - short[] results = new short[ recordsMap.size() ]; - int i = 0; + /** + * @return an array of all the SIDS for all known records + */ + public static short[] getAllKnownRecordSIDs() { + if (_allKnownRecordSIDs == null) { + short[] results = new short[ recordsMap.size() ]; + int i = 0; - for (Iterator iterator = recordsMap.keySet().iterator(); - iterator.hasNext(); ) - { - Short sid = ( Short ) iterator.next(); + for (Iterator iterator = recordsMap.keySet().iterator(); iterator.hasNext(); ) { + Short sid = (Short) iterator.next(); - results[ i++ ] = sid.shortValue(); - } - return results; + results[i++] = sid.shortValue(); + } + Arrays.sort(results); + _allKnownRecordSIDs = results; + } + + return (short[]) _allKnownRecordSIDs.clone(); } - private static Map recordsToMap(Class [] records) - { - Map result = new HashMap(); - Constructor constructor; + /** + * gets the record constructors and sticks them in the map by SID + * @return map of SIDs to short,short,byte[] constructors for Record classes + * most of org.apache.poi.hssf.record.* + */ + private static Map recordsToMap(Class [] records) { + Map result = new HashMap(); + Set uniqueRecClasses = new HashSet(records.length * 3 / 2); - for (int i = 0; i < records.length; i++) - { - Class record; - short sid; + for (int i = 0; i < records.length; i++) { - record = records[ i ]; - try - { - sid = record.getField("sid").getShort(null); - constructor = record.getConstructor(new Class[] - { - RecordInputStream.class - }); + Class recClass = records[ i ]; + String cn = recClass.getName(); + System.out.println(cn.substring(cn.lastIndexOf('.')+1) + ".class,"); + if(!Record.class.isAssignableFrom(recClass)) { + throw new RuntimeException("Invalid record sub-class (" + cn + ")"); + } + if(Modifier.isAbstract(recClass.getModifiers())) { + throw new RuntimeException("Invalid record class (" + cn + ") - must not be abstract"); } - catch (Exception illegalArgumentException) - { + if(!uniqueRecClasses.add(recClass)) { + throw new RuntimeException("duplicate record class (" + cn + ")"); + } + + short sid; + Constructor constructor; + try { + sid = recClass.getField("sid").getShort(null); + constructor = recClass.getConstructor(CONSTRUCTOR_ARGS); + } catch (Exception illegalArgumentException) { throw new RecordFormatException( - "Unable to determine record types", illegalArgumentException); + "Unable to determine record types"); } - result.put(new Short(sid), constructor); + Short key = new Short(sid); + if (result.containsKey(key)) { + Class prev = (Class)result.get(key); + throw new RuntimeException("duplicate record sid 0x" + Integer.toHexString(sid).toUpperCase() + + " for classes (" + cn + ") and (" + prev.getName() + ")"); + } + result.put(key, constructor); } return result; } + + /** + * Create an array of records from an input stream + * + * @param in the InputStream from which the records will be obtained + * + * @return an array of Records created from the InputStream + * + * @exception RecordFormatException on error processing the InputStream + */ + public static List createRecords(InputStream in) throws RecordFormatException { + + List records = new ArrayList(NUM_RECORDS); + + RecordInputStream recStream = new RecordInputStream(in); + DrawingRecord lastDrawingRecord = new DrawingRecord( ); + Record lastRecord = null; + while (recStream.hasNextRecord()) { + recStream.nextRecord(); + if (recStream.getSid() != 0) { + Record[] recs = createRecord(recStream); // handle MulRK records + + if (recs.length > 1) { + for (int k = 0; k < recs.length; k++) { + records.add(recs[ k ]); // these will be number records + } + } else { + Record record = recs[ 0 ]; + + if (record != null) { + if (record.getSid() == DrawingGroupRecord.sid + && lastRecord instanceof DrawingGroupRecord) { + DrawingGroupRecord lastDGRecord = (DrawingGroupRecord) lastRecord; + lastDGRecord.join((AbstractEscherHolderRecord) record); + } else if (record.getSid() == ContinueRecord.sid && + ((lastRecord instanceof ObjRecord) || (lastRecord instanceof TextObjectRecord))) { + // Drawing records have a very strange continue behaviour. + //There can actually be OBJ records mixed between the continues. + lastDrawingRecord.processContinueRecord( ((ContinueRecord)record).getData() ); + //we must remember the position of the continue record. + //in the serialization procedure the original structure of records must be preserved + records.add(record); + } else if (record.getSid() == ContinueRecord.sid && + (lastRecord instanceof DrawingGroupRecord)) { + ((DrawingGroupRecord)lastRecord).processContinueRecord(((ContinueRecord)record).getData()); + } else if (record.getSid() == ContinueRecord.sid && + (lastRecord instanceof StringRecord)) { + ((StringRecord)lastRecord).processContinueRecord(((ContinueRecord)record).getData()); + } else if (record.getSid() == ContinueRecord.sid) { + if (lastRecord instanceof UnknownRecord) { + //Gracefully handle records that we dont know about, + //that happen to be continued + records.add(record); + } else + throw new RecordFormatException("Unhandled Continue Record"); + } else { + lastRecord = record; + if (record instanceof DrawingRecord) { + lastDrawingRecord = (DrawingRecord) record; + } + records.add(record); + } + } + } + } + } + return records; + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java index 84d7817fc4..13f46255c6 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java @@ -22,7 +22,6 @@ import org.apache.poi.ddf.EscherBSERecord; import org.apache.poi.ddf.EscherBitmapBlip; import org.apache.poi.ddf.EscherRecord; import org.apache.poi.ddf.EscherBlipRecord; -import org.apache.poi.hssf.eventmodel.EventRecordFactory; import org.apache.poi.hssf.model.Sheet; import org.apache.poi.hssf.model.Workbook; import org.apache.poi.hssf.record.*; @@ -248,8 +247,6 @@ public class HSSFWorkbook extends POIDocument // it happens to be spelled. InputStream stream = directory.createDocumentInputStream(workbookName); - EventRecordFactory factory = new EventRecordFactory(); - List records = RecordFactory.createRecords(stream); workbook = Workbook.createWorkbook(records); diff --git a/src/testcases/org/apache/poi/hssf/eventmodel/TestEventRecordFactory.java b/src/testcases/org/apache/poi/hssf/eventmodel/TestEventRecordFactory.java index 0449795b5d..c4a5ec84b3 100644 --- a/src/testcases/org/apache/poi/hssf/eventmodel/TestEventRecordFactory.java +++ b/src/testcases/org/apache/poi/hssf/eventmodel/TestEventRecordFactory.java @@ -18,16 +18,16 @@ package org.apache.poi.hssf.eventmodel; import java.io.ByteArrayInputStream; -import java.util.Iterator; + +import junit.framework.TestCase; import org.apache.poi.hssf.record.BOFRecord; +import org.apache.poi.hssf.record.ContinueRecord; import org.apache.poi.hssf.record.EOFRecord; import org.apache.poi.hssf.record.Record; -import org.apache.poi.hssf.record.UnknownRecord; -import org.apache.poi.hssf.record.ContinueRecord; +import org.apache.poi.hssf.record.RecordFactory; import org.apache.poi.hssf.record.TestcaseRecordInputStream; - -import junit.framework.TestCase; +import org.apache.poi.hssf.record.UnknownRecord; /** * enclosing_type describe the purpose here @@ -35,63 +35,7 @@ import junit.framework.TestCase; * @author Andrew C. Oliver acoliver@apache.org * @author Csaba Nagy (ncsaba at yahoo dot com) */ -public class TestEventRecordFactory extends TestCase -{ - boolean wascalled; - - private EventRecordFactory factory; - /** - * Constructor for TestEventRecordFactory. - * @param arg0 - */ - public TestEventRecordFactory(String arg0) - { - super(arg0); - } - - public static void main(String[] args) - { - junit.textui.TestRunner.run(TestEventRecordFactory.class); - } - - protected void setUp() throws Exception - { - super.setUp(); - factory = new EventRecordFactory(); - } - - protected void tearDown() throws Exception - { - super.tearDown(); - } - - /** - * tests that a listener can be registered and that once - * registered can be returned as expected. - */ - public void testRegisterListener() - { - factory.registerListener(new ERFListener() { - public boolean processRecord(Record rec) { - return true; - } - },null); - - Iterator i = factory.listeners(); - assertTrue("iterator must have one",i.hasNext()); - - factory.registerListener(new ERFListener() { - public boolean processRecord(Record rec) { - return true; - } - },null); - - i = factory.listeners(); - - i.next(); - assertTrue("iterator must have two",i.hasNext()); - factory = new EventRecordFactory(); - } +public final class TestEventRecordFactory extends TestCase { /** * tests that the records can be processed and properly return @@ -99,17 +43,17 @@ public class TestEventRecordFactory extends TestCase */ public void testProcessRecords() { - byte[] bytes = null; - int offset = 0; - //boolean wascalled = false; - factory.registerListener(new ERFListener() { + final boolean[] wascalled = { false, }; // hack to pass boolean by ref into inner class + + ERFListener listener = new ERFListener() { public boolean processRecord(Record rec) { - wascalled = true; + wascalled[0] = true; assertTrue("must be BOFRecord got SID="+rec.getSid(), (rec.getSid() == BOFRecord.sid)); return true; } - }, new short[] {BOFRecord.sid}); + }; + EventRecordFactory factory = new EventRecordFactory(listener, new short[] {BOFRecord.sid}); BOFRecord bof = new BOFRecord(); bof.setBuild((short)0); @@ -120,23 +64,20 @@ public class TestEventRecordFactory extends TestCase bof.setHistoryBitMask(BOFRecord.HISTORY_MASK); EOFRecord eof = new EOFRecord(); - bytes = new byte[bof.getRecordSize() + eof.getRecordSize()]; + byte[] bytes = new byte[bof.getRecordSize() + eof.getRecordSize()]; + int offset = 0; offset = bof.serialize(offset,bytes); offset = eof.serialize(offset,bytes); factory.processRecords(new ByteArrayInputStream(bytes)); - assertTrue("The record listener must be called",wascalled); + assertTrue("The record listener must be called", wascalled[0]); } /** * tests that the create record function returns a properly * constructed record in the simple case. */ - public void testCreateRecord() - { - byte[] bytes = null; - byte[] nbytes = null; - Record[] records = null; + public void testCreateRecord() { BOFRecord bof = new BOFRecord(); bof.setBuild((short)0); bof.setBuildYear((short)1999); @@ -145,11 +86,11 @@ public class TestEventRecordFactory extends TestCase bof.setVersion((short)0x06); bof.setHistoryBitMask(BOFRecord.HISTORY_MASK); - bytes = bof.serialize(); - nbytes = new byte[bytes.length - 4]; + byte[] bytes = bof.serialize(); + byte[] nbytes = new byte[bytes.length - 4]; System.arraycopy(bytes,4,nbytes,0,nbytes.length); - records = factory.createRecord(new TestcaseRecordInputStream(bof.getSid(),(short)nbytes.length,nbytes)); + Record[] records = RecordFactory.createRecord(new TestcaseRecordInputStream(bof.getSid(),(short)nbytes.length,nbytes)); assertTrue("record.length must be 1, was ="+records.length,records.length == 1); assertTrue("record is the same", compareRec(bof,records[0])); @@ -162,24 +103,19 @@ public class TestEventRecordFactory extends TestCase * @param second the second record to compare * @return boolean whether or not the record where equal */ - private boolean compareRec(Record first, Record second) - { - boolean retval = true; + private static boolean compareRec(Record first, Record second) { byte[] rec1 = first.serialize(); byte[] rec2 = second.serialize(); - if (rec1.length == rec2.length) { - for (int k=0; k * */ - public void testContinuedUnknownRecord() - { - final byte[] data = new byte[] - { + public void testContinuedUnknownRecord() { + final byte[] data = { 0, -1, 0, 0, // an unknown record with 0 length 0x3C , 0, 3, 0, 1, 2, 3, // a continuation record with 3 bytes of data 0x3C , 0, 1, 0, 4 // one more continuation record with 1 byte of data @@ -213,8 +147,7 @@ public class TestEventRecordFactory extends TestCase final int[] recCnt = { 0 }; final int[] offset = { 0 }; - factory.registerListener( - new ERFListener() { + ERFListener listener = new ERFListener() { private String[] expectedRecordTypes = { UnknownRecord.class.getName(), ContinueRecord.class.getName(), @@ -238,14 +171,11 @@ public class TestEventRecordFactory extends TestCase assertEquals(message + " data byte " + i, data[offset[0]++], recData[i]); } } - }, - new short[] {-256, 0x3C} - ); + }; + EventRecordFactory factory = new EventRecordFactory(listener, new short[] {-256, 0x3C}); factory.processRecords(new ByteArrayInputStream(data)); assertEquals("nr. of processed records", 3, recCnt[0]); assertEquals("nr. of processed bytes", data.length, offset[0]); } - - } diff --git a/src/testcases/org/apache/poi/hssf/model/TestSheet.java b/src/testcases/org/apache/poi/hssf/model/TestSheet.java index 71881accec..4589ee5b13 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestSheet.java +++ b/src/testcases/org/apache/poi/hssf/model/TestSheet.java @@ -416,9 +416,9 @@ public final class TestSheet extends TestCase { int size = sheet.getSize(); byte[] data = new byte[size]; sheet.serialize(0, data); - EventRecordFactory erf = new EventRecordFactory(); + MyIndexRecordListener myIndexListener = new MyIndexRecordListener(); - erf.registerListener(myIndexListener, new short[] { IndexRecord.sid, }); + EventRecordFactory erf = new EventRecordFactory(myIndexListener, new short[] { IndexRecord.sid, }); erf.processRecords(new ByteArrayInputStream(data)); IndexRecord indexRecord = myIndexListener.getIndexRecord(); int dbCellRecordPos = indexRecord.getDbcellAt(0); diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestDataValidation.java b/src/testcases/org/apache/poi/hssf/usermodel/TestDataValidation.java index 20933819b4..ba1a78c4a0 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestDataValidation.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestDataValidation.java @@ -594,9 +594,9 @@ public final class TestDataValidation extends TestCase { byte[] wbData = baos.toByteArray(); if (false) { // TODO (Jul 2008) fix EventRecordFactory to process unknown records, (and DV records for that matter) - EventRecordFactory erf = new EventRecordFactory(); + ERFListener erfListener = null; // new MyERFListener(); - erf.registerListener(erfListener, null); + EventRecordFactory erf = new EventRecordFactory(erfListener, null); try { POIFSFileSystem fs = new POIFSFileSystem(new ByteArrayInputStream(baos.toByteArray())); erf.processRecords(fs.createDocumentInputStream("Workbook")); -- 2.39.5