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
This commit is contained in:
Josh Micich 2008-08-04 21:08:11 +00:00
parent ee01022c01
commit 04d5f9c316
9 changed files with 393 additions and 761 deletions

View File

@ -37,6 +37,7 @@
<!-- Don't forget to update status.xml too! -->
<release version="3.1.1-alpha1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="fix">44894 - refactor duplicate logic from EventRecordFactory to RecordFactory</action>
<action dev="POI-DEVELOPERS" type="add">Support for Headers / Footers in HSLF</action>
<action dev="POI-DEVELOPERS" type="fix">44953 - Extensive fixes for data validation</action>
<action dev="POI-DEVELOPERS" type="fix">45519 - Fixed to keep datavalidation records together</action>

View File

@ -34,6 +34,7 @@
<!-- Don't forget to update changes.xml too! -->
<changes>
<release version="3.1.1-alpha1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="fix">44894 - refactor duplicate logic from EventRecordFactory to RecordFactory</action>
<action dev="POI-DEVELOPERS" type="add">Support for Headers / Footers in HSLF</action>
<action dev="POI-DEVELOPERS" type="fix">44953 - Extensive fixes for data validation</action>
<action dev="POI-DEVELOPERS" type="fix">45519 - Fixed to keep datavalidation records together</action>

View File

@ -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);
private final ERFListener _listener;
private final short[] _sids;
/**
* 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;
/**
* 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 <code>false</code> 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.
}
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;
}
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;
}
}
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 (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");
}
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 (record != null) {
if (last_record != null) {
if (!processRecord(last_record)) {
return;
}
}
last_record = record;
}
}
}
return result;
}
}
if (last_record != null) {
processRecord(last_record);
}
}
}

View File

@ -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);
}

View File

@ -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<P>
* Description: Takes a stream and outputs an array of Record objects.<P>
*
* @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;
private static final Class[] CONSTRUCTOR_ARGS = { RecordInputStream.class, };
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);
/**
* changes the default capacity (10000) to handle larger files
/**
* contains the classes for all the records we want to parse.<br/>
* Note - this most but not *every* subclass of Record.
*/
public static void setCapacity(int capacity)
{
NUM_RECORDS = capacity;
}
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,
};
/**
* 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
* cache of the recordsToMap();
*/
private static Map recordsMap = recordsToMap(records);
public static List createRecords(InputStream in)
throws RecordFormatException
{
ArrayList records = new ArrayList(NUM_RECORDS);
private static short[] _allKnownRecordSIDs;
/**
* 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()));
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);
}
if (constructor == null) {
return new Record[] { new UnknownRecord(in), };
}
catch (Exception introspectionException)
{
throw new RecordFormatException("Unable to construct record instance",introspectionException);
}
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;
for (int i = 0; i < records.length; i++) {
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");
}
if(!uniqueRecClasses.add(recClass)) {
throw new RuntimeException("duplicate record class (" + cn + ")");
}
short sid;
record = records[ i ];
try
{
sid = record.getField("sid").getShort(null);
constructor = record.getConstructor(new Class[]
{
RecordInputStream.class
});
}
catch (Exception illegalArgumentException)
{
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;
}
}

View File

@ -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);

View File

@ -18,80 +18,24 @@
package org.apache.poi.hssf.eventmodel;
import java.io.ByteArrayInputStream;
import java.util.Iterator;
import org.apache.poi.hssf.record.BOFRecord;
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.TestcaseRecordInputStream;
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.RecordFactory;
import org.apache.poi.hssf.record.TestcaseRecordInputStream;
import org.apache.poi.hssf.record.UnknownRecord;
/**
* enclosing_type describe the purpose here
*
* @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<rec1.length; k++) {
if (rec1[k] != rec2[k]) {
retval = false;
break;
}
}
} else {
retval = false;
if (rec1.length != rec2.length) {
return false;
}
return retval;
for (int k=0; k<rec1.length; k++) {
if (rec1[k] != rec2[k]) {
return false;
}
}
return true;
}
@ -202,10 +138,8 @@ public class TestEventRecordFactory extends TestCase
* FAILURE: The wrong records are created or contain the wrong values <P>
*
*/
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]);
}
}

View File

@ -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);

View File

@ -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"));