complete refactoring in EscherAggregate

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1372065 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Evgeniy Berlog 2012-08-12 10:18:43 +00:00
parent b200e0b4c2
commit a2b21484ae
5 changed files with 102 additions and 110 deletions

View File

@ -1496,7 +1496,7 @@ public final class InternalSheet {
return -1; return -1;
} }
EscherAggregate aggregate = new EscherAggregate(); EscherAggregate aggregate = new EscherAggregate(true);
loc = findFirstRecordLocBySid(EscherAggregate.sid); loc = findFirstRecordLocBySid(EscherAggregate.sid);
if (loc == -1) { if (loc == -1) {
loc = findFirstRecordLocBySid( WindowTwoRecord.sid ); loc = findFirstRecordLocBySid( WindowTwoRecord.sid );
@ -1508,7 +1508,7 @@ public final class InternalSheet {
} }
List<RecordBase> records = getRecords(); List<RecordBase> records = getRecords();
EscherAggregate.createAggregate( records, loc, drawingManager ); EscherAggregate.createAggregate(records, loc);
return loc; return loc;
} }

View File

@ -31,8 +31,6 @@ import org.apache.poi.ddf.EscherSerializationListener;
import org.apache.poi.ddf.EscherSpRecord; import org.apache.poi.ddf.EscherSpRecord;
import org.apache.poi.ddf.EscherSpgrRecord; import org.apache.poi.ddf.EscherSpgrRecord;
import org.apache.poi.ddf.EscherTextboxRecord; import org.apache.poi.ddf.EscherTextboxRecord;
import org.apache.poi.hssf.model.DrawingManager2;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogger;
@ -290,12 +288,6 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
public static final short ST_TEXTBOX = (short) 202; public static final short ST_TEXTBOX = (short) 202;
public static final short ST_NIL = (short) 0x0FFF; public static final short ST_NIL = (short) 0x0FFF;
/**
* if we want to get the same byte array if we open existing file and serialize it we should save
* note records in right order. This list contains ids of NoteRecords in such order as we read from existing file
*/
private List<Integer> _tailIds = new ArrayList<Integer>();
/** /**
* Maps shape container objects to their {@link TextObjectRecord} or {@link ObjRecord} * Maps shape container objects to their {@link TextObjectRecord} or {@link ObjRecord}
*/ */
@ -304,13 +296,17 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
/** /**
* list of "tail" records that need to be serialized after all drawing group records * list of "tail" records that need to be serialized after all drawing group records
*/ */
private Map<Integer, NoteRecord> tailRec = new HashMap<Integer, NoteRecord>(); private Map<Integer, NoteRecord> tailRec = new LinkedHashMap<Integer, NoteRecord>();
public EscherAggregate() { /**
buildBaseTree(); * create new EscherAggregate
} * @param createDefaultTree if true creates base tree of the escher records, see EscherAggregate.buildBaseTree()
* else return empty escher aggregate
public EscherAggregate(DrawingManager2 drawingManager) { */
public EscherAggregate(boolean createDefaultTree) {
if (createDefaultTree){
buildBaseTree();
}
} }
/** /**
@ -327,13 +323,12 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
public String toString() { public String toString() {
String nl = System.getProperty("line.separtor"); String nl = System.getProperty("line.separtor");
StringBuffer result = new StringBuffer(); StringBuilder result = new StringBuilder();
result.append('[').append(getRecordName()).append(']' + nl); result.append('[').append(getRecordName()).append(']').append(nl);
for (Iterator iterator = getEscherRecords().iterator(); iterator.hasNext(); ) { for (EscherRecord escherRecord : getEscherRecords()) {
EscherRecord escherRecord = (EscherRecord) iterator.next();
result.append(escherRecord.toString()); result.append(escherRecord.toString());
} }
result.append("[/").append(getRecordName()).append(']' + nl); result.append("[/").append(getRecordName()).append(']').append(nl);
return result.toString(); return result.toString();
} }
@ -341,18 +336,23 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
/** /**
* Calculates the xml representation of this record. This is * Calculates the xml representation of this record. This is
* simply a dump of all the records. * simply a dump of all the records.
* @param tab - string which must be added before each line (used by default '\t')
* @return xml representation of the all aggregated records
*/ */
public String toXml(String tab) { public String toXml(String tab) {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append(tab).append("<").append(getRecordName()).append(">\n"); builder.append(tab).append("<").append(getRecordName()).append(">\n");
for (Iterator iterator = getEscherRecords().iterator(); iterator.hasNext(); ) { for (EscherRecord escherRecord : getEscherRecords()) {
EscherRecord escherRecord = (EscherRecord) iterator.next();
builder.append(escherRecord.toXml(tab + "\t")); builder.append(escherRecord.toXml(tab + "\t"));
} }
builder.append(tab).append("</").append(getRecordName()).append(">\n"); builder.append(tab).append("</").append(getRecordName()).append(">\n");
return builder.toString(); return builder.toString();
} }
/**
* @param sid - record sid we want to check if it belongs to drawing layer
* @return true if record is instance of DrawingRecord or ContinueRecord or ObjRecord or TextObjRecord
*/
private static boolean isDrawingLayerRecord(final short sid) { private static boolean isDrawingLayerRecord(final short sid) {
return sid == DrawingRecord.sid || return sid == DrawingRecord.sid ||
sid == ContinueRecord.sid || sid == ContinueRecord.sid ||
@ -365,8 +365,11 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
* read Drawing, Obj, TxtObj, Note and Continue records into single byte array, * read Drawing, Obj, TxtObj, Note and Continue records into single byte array,
* create Escher tree from byte array, create map <EscherRecord, Record> * create Escher tree from byte array, create map <EscherRecord, Record>
* *
* @param records - list of all records inside sheet
* @param locFirstDrawingRecord - location of the first DrawingRecord inside sheet
* @return new EscherAggregate create from all aggregated records which belong to drawing layer
*/ */
public static EscherAggregate createAggregate(List records, int locFirstDrawingRecord, DrawingManager2 drawingManager) { public static EscherAggregate createAggregate(List<RecordBase> records, int locFirstDrawingRecord) {
// Keep track of any shape records created so we can match them back to the object id's. // Keep track of any shape records created so we can match them back to the object id's.
// Textbox objects are also treated as shape objects. // Textbox objects are also treated as shape objects.
final List<EscherRecord> shapeRecords = new ArrayList<EscherRecord>(); final List<EscherRecord> shapeRecords = new ArrayList<EscherRecord>();
@ -382,8 +385,7 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
// Create one big buffer // Create one big buffer
ByteArrayOutputStream buffer = new ByteArrayOutputStream(); ByteArrayOutputStream buffer = new ByteArrayOutputStream();
EscherAggregate agg = new EscherAggregate(false);
EscherAggregate agg = new EscherAggregate(drawingManager);
int loc = locFirstDrawingRecord; int loc = locFirstDrawingRecord;
while (loc + 1 < records.size() while (loc + 1 < records.size()
&& (isDrawingLayerRecord(sid(records, loc)))) { && (isDrawingLayerRecord(sid(records, loc)))) {
@ -428,12 +430,10 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
} }
// any NoteRecords that follow the drawing block must be aggregated and and saved in the tailRec collection // any NoteRecords that follow the drawing block must be aggregated and and saved in the tailRec collection
// TODO remove this logic. 'tail' records should be inserted in the main record stream
while (loc < records.size()) { while (loc < records.size()) {
if (sid(records, loc) == NoteRecord.sid) { if (sid(records, loc) == NoteRecord.sid) {
NoteRecord r = (NoteRecord) records.get(loc); NoteRecord r = (NoteRecord) records.get(loc);
agg.tailRec.put(r.getShapeId(), r); agg.tailRec.put(r.getShapeId(), r);
agg._tailIds.add(agg._tailIds.size(), r.getShapeId());
} else { } else {
break; break;
} }
@ -457,16 +457,16 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
*/ */
public int serialize(int offset, byte[] data) { public int serialize(int offset, byte[] data) {
// Determine buffer size // Determine buffer size
List records = getEscherRecords(); List <EscherRecord>records = getEscherRecords();
int size = getEscherRecordSize(records); int size = getEscherRecordSize(records);
byte[] buffer = new byte[size]; byte[] buffer = new byte[size];
// Serialize escher records into one big data structure and keep note of ending offsets. // Serialize escher records into one big data structure and keep note of ending offsets.
final List spEndingOffsets = new ArrayList(); final List <Integer>spEndingOffsets = new ArrayList<Integer>();
final List shapes = new ArrayList(); final List <EscherRecord> shapes = new ArrayList<EscherRecord>();
int pos = 0; int pos = 0;
for (Iterator iterator = records.iterator(); iterator.hasNext(); ) { for (Object record : records) {
EscherRecord e = (EscherRecord) iterator.next(); EscherRecord e = (EscherRecord) record;
pos += e.serialize(pos, buffer, new EscherSerializationListener() { pos += e.serialize(pos, buffer, new EscherSerializationListener() {
public void beforeRecordSerialize(int offset, short recordId, EscherRecord record) { public void beforeRecordSerialize(int offset, short recordId, EscherRecord record) {
} }
@ -479,9 +479,8 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
} }
}); });
} }
// todo: fix this
shapes.add(0, null); shapes.add(0, null);
spEndingOffsets.add(0, null); spEndingOffsets.add(0, 0);
// Split escher records into separate MSODRAWING and OBJ, TXO records. (We don't break on // Split escher records into separate MSODRAWING and OBJ, TXO records. (We don't break on
// the first one because it's the patriach). // the first one because it's the patriach).
@ -489,12 +488,12 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
int writtenEscherBytes = 0; int writtenEscherBytes = 0;
int i; int i;
for (i = 1; i < shapes.size(); i++) { for (i = 1; i < shapes.size(); i++) {
int endOffset = (Integer) spEndingOffsets.get(i) - 1; int endOffset = spEndingOffsets.get(i) - 1;
int startOffset; int startOffset;
if (i == 1) if (i == 1)
startOffset = 0; startOffset = 0;
else else
startOffset = (Integer) spEndingOffsets.get(i - 1); startOffset = spEndingOffsets.get(i - 1);
byte[] drawingData = new byte[endOffset - startOffset + 1]; byte[] drawingData = new byte[endOffset - startOffset + 1];
System.arraycopy(buffer, startOffset, drawingData, 0, drawingData.length); System.arraycopy(buffer, startOffset, drawingData, 0, drawingData.length);
@ -518,19 +517,8 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
pos += writeDataIntoDrawingRecord(drawingData, writtenEscherBytes, pos, data, i); pos += writeDataIntoDrawingRecord(drawingData, writtenEscherBytes, pos, data, i);
} }
// write records that need to be serialized after all drawing group records for (i = 0; i < tailRec.size(); i++) {
Map<Integer, NoteRecord> tailCopy = new HashMap<Integer, NoteRecord>(tailRec); Record rec = (Record) tailRec.values().toArray()[i];
// at first we should save records in correct order which were already in the file during EscherAggregate.createAggregate()
for (Integer id : _tailIds) {
NoteRecord note = tailCopy.get(id);
if (null != note) {
pos += note.serialize(pos, data);
tailCopy.remove(id);
}
}
// Add all other notes which were created after createAggregate()
for (i = 0; i < tailCopy.size(); i++) {
Record rec = (Record) tailCopy.values().toArray()[i];
pos += rec.serialize(pos, data); pos += rec.serialize(pos, data);
} }
int bytesWritten = pos - offset; int bytesWritten = pos - offset;
@ -583,10 +571,11 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
* @param records List of escher records * @param records List of escher records
* @return the number of bytes * @return the number of bytes
*/ */
private int getEscherRecordSize(List records) { private int getEscherRecordSize(List<EscherRecord> records) {
int size = 0; int size = 0;
for (Iterator iterator = records.iterator(); iterator.hasNext(); ) for (EscherRecord record : records){
size += ((EscherRecord) iterator.next()).getRecordSize(); size += record.getRecordSize();
}
return size; return size;
} }
@ -632,49 +621,64 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
continueRecordsHeadersSize += 4; continueRecordsHeadersSize += 4;
} }
int objRecordSize = 0; int objRecordSize = 0;
for (Iterator iterator = shapeToObj.values().iterator(); iterator.hasNext(); ) { for (Record r : shapeToObj.values()) {
Record r = (Record) iterator.next();
objRecordSize += r.getRecordSize(); objRecordSize += r.getRecordSize();
} }
int tailRecordSize = 0; int tailRecordSize = 0;
for (Iterator iterator = tailRec.values().iterator(); iterator.hasNext(); ) { for (NoteRecord noteRecord : tailRec.values()) {
Record r = (Record) iterator.next(); tailRecordSize += noteRecord.getRecordSize();
tailRecordSize += r.getRecordSize();
} }
return drawingRecordSize + objRecordSize + tailRecordSize + continueRecordsHeadersSize; return drawingRecordSize + objRecordSize + tailRecordSize + continueRecordsHeadersSize;
} }
/** /**
* Associates an escher record to an OBJ record or a TXO record. * Associates an escher record to an OBJ record or a TXO record.
* @param r - ClientData or Textbox record
* @param objRecord - Obj or TextObj record
*/ */
public Object associateShapeToObjRecord(EscherRecord r, Record objRecord) { public void associateShapeToObjRecord(EscherRecord r, Record objRecord) {
return shapeToObj.put(r, objRecord); shapeToObj.put(r, objRecord);
} }
/**
* Remove echerRecord and associated to it Obj or TextObj record
* @param rec - clientData or textbox record to be removed
*/
public void removeShapeToObjRecord(EscherRecord rec) { public void removeShapeToObjRecord(EscherRecord rec) {
shapeToObj.remove(rec); shapeToObj.remove(rec);
} }
/**
* @return "ESCHERAGGREGATE"
*/
protected String getRecordName() { protected String getRecordName() {
return "ESCHERAGGREGATE"; return "ESCHERAGGREGATE";
} }
// =============== Private methods ======================== // =============== Private methods ========================
private static boolean isObjectRecord(List records, int loc) { /**
*
* @param records list of the record to look inside
* @param loc location of the checked record
* @return true if record is instance of ObjRecord or TextObjectRecord
*/
private static boolean isObjectRecord(List <RecordBase>records, int loc) {
return sid(records, loc) == ObjRecord.sid || sid(records, loc) == TextObjectRecord.sid; return sid(records, loc) == ObjRecord.sid || sid(records, loc) == TextObjectRecord.sid;
} }
private EscherRecord findClientData(EscherContainerRecord spContainer) { /**
for (Iterator<EscherRecord> iterator = spContainer.getChildIterator(); iterator.hasNext(); ) { * create base tree with such structure:
EscherRecord r = iterator.next(); * EscherDgContainer
if (r.getRecordId() == EscherClientDataRecord.RECORD_ID) { * -EscherSpgrContainer
return r; * --EscherSpContainer
} * ---EscherSpRecord
} * ---EscherSpgrRecord
throw new IllegalArgumentException("Can not find client data record"); * ---EscherSpRecord
} * -EscherDgRecord
*
* id of DgRecord and SpRecord are empty and must be set later by HSSFPatriarch
*/
private void buildBaseTree() { private void buildBaseTree() {
EscherContainerRecord dgContainer = new EscherContainerRecord(); EscherContainerRecord dgContainer = new EscherContainerRecord();
EscherContainerRecord spgrContainer = new EscherContainerRecord(); EscherContainerRecord spgrContainer = new EscherContainerRecord();
@ -713,12 +717,30 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
addEscherRecord(dgContainer); addEscherRecord(dgContainer);
} }
/**
* EscherDgContainer
* -EscherSpgrContainer
* -EscherDgRecord - set id for this record
* set id for DgRecord of DgContainer
* @param dgId - id which must be set
*/
public void setDgId(short dgId) { public void setDgId(short dgId) {
EscherContainerRecord dgContainer = getEscherContainer(); EscherContainerRecord dgContainer = getEscherContainer();
EscherDgRecord dg = dgContainer.getChildById(EscherDgRecord.RECORD_ID); EscherDgRecord dg = dgContainer.getChildById(EscherDgRecord.RECORD_ID);
dg.setOptions((short) (dgId << 4)); dg.setOptions((short) (dgId << 4));
} }
/**
* EscherDgContainer
* -EscherSpgrContainer
* --EscherSpContainer
* ---EscherSpRecord -set id for this record
* ---***
* --***
* -EscherDgRecord
* set id for the sp record of the first spContainer in main spgrConatiner
* @param shapeId - id which must be set
*/
public void setMainSpRecordId(int shapeId) { public void setMainSpRecordId(int shapeId) {
EscherContainerRecord dgContainer = getEscherContainer(); EscherContainerRecord dgContainer = getEscherContainer();
EscherContainerRecord spgrConatiner = (EscherContainerRecord) dgContainer.getChildById(EscherContainerRecord.SPGR_CONTAINER); EscherContainerRecord spgrConatiner = (EscherContainerRecord) dgContainer.getChildById(EscherContainerRecord.SPGR_CONTAINER);
@ -727,27 +749,13 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
sp.setShapeId(shapeId); sp.setShapeId(shapeId);
} }
private static short sid(List records, int loc) {
return ((Record) records.get(loc)).getSid();
}
// Duplicated from org.apache.poi.hslf.model.Shape
/** /**
* Helper method to return escher child by record ID * @param records list of records to look into
* * @param loc - location of the record which sid must be returned
* @return escher record or <code>null</code> if not found. * @return sid of the record with selected location
*/ */
private static EscherRecord getEscherChild(EscherContainerRecord owner, private static short sid(List <RecordBase>records, int loc) {
int recordId) { return ((Record) records.get(loc)).getSid();
for (Iterator iterator = owner.getChildRecords().iterator(); iterator
.hasNext(); ) {
EscherRecord escherRecord = (EscherRecord) iterator.next();
if (escherRecord.getRecordId() == recordId)
return escherRecord;
}
return null;
} }
/** /**

View File

@ -75,7 +75,7 @@ public final class HSSFPatriarch implements HSSFShapeContainer, Drawing {
* @return new patriarch with copies of all shapes from the existing patriarch * @return new patriarch with copies of all shapes from the existing patriarch
*/ */
static HSSFPatriarch createPatriarch(HSSFPatriarch patriarch, HSSFSheet sheet){ static HSSFPatriarch createPatriarch(HSSFPatriarch patriarch, HSSFSheet sheet){
HSSFPatriarch newPatriarch = new HSSFPatriarch(sheet, new EscherAggregate()); HSSFPatriarch newPatriarch = new HSSFPatriarch(sheet, new EscherAggregate(true));
newPatriarch.afterCreate(); newPatriarch.afterCreate();
for (HSSFShape shape: patriarch.getChildren()){ for (HSSFShape shape: patriarch.getChildren()){
HSSFShape newShape; HSSFShape newShape;

View File

@ -72,14 +72,13 @@ public final class TestEscherAggregate extends TestCase {
ObjRecord r2 = new ObjRecord(); ObjRecord r2 = new ObjRecord();
List<Record> records = new ArrayList<Record>(); List<RecordBase> records = new ArrayList<RecordBase>();
records.add( d1 ); records.add( d1 );
records.add( r1 ); records.add( r1 );
records.add( d2 ); records.add( d2 );
records.add( r2 ); records.add( r2 );
DrawingManager2 drawingManager = new DrawingManager2(new EscherDggRecord() ); EscherAggregate aggregate = EscherAggregate.createAggregate(records, 0);
EscherAggregate aggregate = EscherAggregate.createAggregate( records, 0, drawingManager );
assertEquals( 1, aggregate.getEscherRecords().size() ); assertEquals( 1, aggregate.getEscherRecords().size() );
assertEquals( (short) 0xF002, aggregate.getEscherRecord( 0 ).getRecordId() ); assertEquals( (short) 0xF002, aggregate.getEscherRecord( 0 ).getRecordId() );
@ -120,7 +119,7 @@ public final class TestEscherAggregate extends TestCase {
spContainer2.addChildRecord( d2 ); spContainer2.addChildRecord( d2 );
spContainer3.addChildRecord( d3 ); spContainer3.addChildRecord( d3 );
EscherAggregate aggregate = new EscherAggregate(null); EscherAggregate aggregate = new EscherAggregate(false);
aggregate.addEscherRecord( container1 ); aggregate.addEscherRecord( container1 );
aggregate.associateShapeToObjRecord( d2, new ObjRecord() ); aggregate.associateShapeToObjRecord( d2, new ObjRecord() );
aggregate.associateShapeToObjRecord( d3, new ObjRecord() ); aggregate.associateShapeToObjRecord( d3, new ObjRecord() );

View File

@ -88,21 +88,6 @@ public class HSSFTestHelper {
return shape.getOptRecord(); return shape.getOptRecord();
} }
public static void convertHSSFGroup(HSSFShapeGroup shape, EscherContainerRecord escherParent, Map shapeToObj){
Class clazz = EscherAggregate.class;
try {
Method method = clazz.getDeclaredMethod("convertGroup", HSSFShapeGroup.class, EscherContainerRecord.class, Map.class);
method.setAccessible(true);
method.invoke(new EscherAggregate(new MockDrawingManager()), shape, escherParent, shapeToObj);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public static void setShapeId(HSSFShape shape, int id){ public static void setShapeId(HSSFShape shape, int id){
shape.setShapeId(id); shape.setShapeId(id);
} }