#64036 - Replace reflection calls in factories for Java 9+ - Escher factories

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1873187 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2020-01-26 19:50:40 +00:00
parent 563c29f8cf
commit 8202a34d69
10 changed files with 248 additions and 687 deletions

View File

@ -17,11 +17,12 @@
package org.apache.poi.ddf; package org.apache.poi.ddf;
import java.lang.reflect.Constructor; import java.util.function.Supplier;
import java.util.HashMap;
import java.util.Map;
import org.apache.poi.util.BitField;
import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.Removal;
/** /**
* Generates escher records when provided the byte array containing those records. * Generates escher records when provided the byte array containing those records.
@ -29,14 +30,7 @@ import org.apache.poi.util.LittleEndian;
* @see EscherRecordFactory * @see EscherRecordFactory
*/ */
public class DefaultEscherRecordFactory implements EscherRecordFactory { public class DefaultEscherRecordFactory implements EscherRecordFactory {
private static Class<?>[] escherRecordClasses = { EscherBSERecord.class, private static final BitField IS_CONTAINER = BitFieldFactory.getInstance(0xF);
EscherOptRecord.class, EscherTertiaryOptRecord.class,
EscherClientAnchorRecord.class, EscherDgRecord.class,
EscherSpgrRecord.class, EscherSpRecord.class,
EscherClientDataRecord.class, EscherDggRecord.class,
EscherSplitMenuColorsRecord.class, EscherChildAnchorRecord.class,
EscherTextboxRecord.class };
private static Map<Short, Constructor<? extends EscherRecord>> recordsMap = recordsToMap( escherRecordClasses );
/** /**
* Creates an instance of the escher record factory * Creates an instance of the escher record factory
@ -51,86 +45,41 @@ public class DefaultEscherRecordFactory implements EscherRecordFactory {
short recordId = LittleEndian.getShort( data, offset + 2 ); short recordId = LittleEndian.getShort( data, offset + 2 );
// int remainingBytes = LittleEndian.getInt( data, offset + 4 ); // int remainingBytes = LittleEndian.getInt( data, offset + 4 );
// Options of 0x000F means container record final EscherRecord escherRecord = getConstructor(options, recordId).get();
// However, EscherTextboxRecord are containers of records for the
// host application, not of other Escher records, so treat them
// differently
if (isContainer(options, recordId)) {
EscherContainerRecord r = new EscherContainerRecord();
r.setRecordId( recordId );
r.setOptions( options );
return r;
}
if (recordId >= EscherBlipRecord.RECORD_ID_START
&& recordId <= EscherBlipRecord.RECORD_ID_END) {
EscherBlipRecord r;
if (recordId == EscherBitmapBlip.RECORD_ID_DIB ||
recordId == EscherBitmapBlip.RECORD_ID_JPEG ||
recordId == EscherBitmapBlip.RECORD_ID_PNG)
{
r = new EscherBitmapBlip();
}
else if (recordId == EscherMetafileBlip.RECORD_ID_EMF ||
recordId == EscherMetafileBlip.RECORD_ID_WMF ||
recordId == EscherMetafileBlip.RECORD_ID_PICT)
{
r = new EscherMetafileBlip();
} else {
r = new EscherBlipRecord();
}
r.setRecordId( recordId );
r.setOptions( options );
return r;
}
Constructor<? extends EscherRecord> recordConstructor = recordsMap.get(Short.valueOf(recordId));
final EscherRecord escherRecord;
if (recordConstructor == null) {
return new UnknownEscherRecord();
}
try {
escherRecord = recordConstructor.newInstance();
} catch (Exception e) {
return new UnknownEscherRecord();
}
escherRecord.setRecordId(recordId); escherRecord.setRecordId(recordId);
escherRecord.setOptions(options); escherRecord.setOptions(options);
return escherRecord; return escherRecord;
} }
/** protected Supplier<? extends EscherRecord> getConstructor(short options, short recordId) {
* Converts from a list of classes into a map that contains the record id as the key and EscherRecordTypes recordTypes = EscherRecordTypes.forTypeID(recordId);
* the Constructor in the value part of the map. It does this by using reflection to look up
* the RECORD_ID field then using reflection again to find a reference to the constructor.
*
* @param recClasses The records to convert
* @return The map containing the id/constructor pairs.
*/
protected static Map<Short, Constructor<? extends EscherRecord>> recordsToMap(Class<?>[] recClasses) {
Map<Short, Constructor<? extends EscherRecord>> result = new HashMap<>();
final Class<?>[] EMPTY_CLASS_ARRAY = new Class[0];
for (Class<?> recClass : recClasses) { // Options of 0x000F means container record
@SuppressWarnings("unchecked") // However, EscherTextboxRecord are containers of records for the host application,
Class<? extends EscherRecord> recCls = (Class<? extends EscherRecord>) recClass; // not of other Escher records, but those are returned by the above anyway
short sid; if (recordTypes == EscherRecordTypes.UNKNOWN && IS_CONTAINER.isAllSet(options)) {
try { return EscherContainerRecord::new;
sid = recCls.getField("RECORD_ID").getShort(null);
} catch (IllegalArgumentException | NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
Constructor<? extends EscherRecord> constructor;
try {
constructor = recCls.getConstructor(EMPTY_CLASS_ARRAY);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
result.put(Short.valueOf(sid), constructor);
} }
return result;
if (recordTypes.constructor != null) {
return recordTypes.constructor;
}
// handle unknown blip records
if (EscherBlipRecord.RECORD_ID_START <= recordId && recordId <= EscherBlipRecord.RECORD_ID_END) {
return EscherBlipRecord::new;
}
// catch all
return UnknownEscherRecord::new;
} }
/**
* @deprecated this method is not used anymore to identify container records
*/
@Deprecated
@Removal(version = "5.0.0")
public static boolean isContainer(short options, short recordId){ public static boolean isContainer(short options, short recordId){
if(recordId >= EscherContainerRecord.DGG_CONTAINER && recordId if(recordId >= EscherContainerRecord.DGG_CONTAINER && recordId
<= EscherContainerRecord.SOLVER_CONTAINER){ <= EscherContainerRecord.SOLVER_CONTAINER){

View File

@ -30,11 +30,13 @@ import org.apache.poi.util.LittleEndian;
* shape within a container. * shape within a container.
*/ */
public class EscherClientDataRecord extends EscherRecord { public class EscherClientDataRecord extends EscherRecord {
//arbitrarily selected; may need to increase
private static final int MAX_RECORD_LENGTH = 100_000;
public static final short RECORD_ID = EscherRecordTypes.CLIENT_DATA.typeID; public static final short RECORD_ID = EscherRecordTypes.CLIENT_DATA.typeID;
//arbitrarily selected; may need to increase
private static final int MAX_RECORD_LENGTH = 100_000;
private static final byte[] EMPTY = {};
private byte[] remainingData; private byte[] remainingData;
public EscherClientDataRecord() {} public EscherClientDataRecord() {}
@ -48,7 +50,7 @@ public class EscherClientDataRecord extends EscherRecord {
public int fillFields(byte[] data, int offset, EscherRecordFactory recordFactory) { public int fillFields(byte[] data, int offset, EscherRecordFactory recordFactory) {
int bytesRemaining = readHeader( data, offset ); int bytesRemaining = readHeader( data, offset );
int pos = offset + 8; int pos = offset + 8;
remainingData = IOUtils.safelyAllocate(bytesRemaining, MAX_RECORD_LENGTH); remainingData = (bytesRemaining == 0) ? EMPTY : IOUtils.safelyAllocate(bytesRemaining, MAX_RECORD_LENGTH);
System.arraycopy( data, pos, remainingData, 0, bytesRemaining ); System.arraycopy( data, pos, remainingData, 0, bytesRemaining );
return 8 + bytesRemaining; return 8 + bytesRemaining;
} }
@ -58,7 +60,7 @@ public class EscherClientDataRecord extends EscherRecord {
listener.beforeRecordSerialize( offset, getRecordId(), this ); listener.beforeRecordSerialize( offset, getRecordId(), this );
if (remainingData == null) { if (remainingData == null) {
remainingData = new byte[0]; remainingData = EMPTY;
} }
LittleEndian.putShort( data, offset, getOptions() ); LittleEndian.putShort( data, offset, getOptions() );
LittleEndian.putShort( data, offset + 2, getRecordId() ); LittleEndian.putShort( data, offset + 2, getRecordId() );

View File

@ -1,322 +0,0 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.ddf;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Supplier;
import java.util.zip.InflaterInputStream;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
public final class EscherPictBlip extends EscherBlipRecord {
private static final POILogger log = POILogFactory.getLogger(EscherPictBlip.class);
//arbitrarily selected; may need to increase
private static final int MAX_RECORD_LENGTH = 100_000;
public static final short RECORD_ID_EMF = EscherRecordTypes.BLIP_EMF.typeID;
public static final short RECORD_ID_WMF = EscherRecordTypes.BLIP_WMF.typeID;
public static final short RECORD_ID_PICT = EscherRecordTypes.BLIP_PICT.typeID;
private static final int HEADER_SIZE = 8;
private final byte[] field_1_UID = new byte[16];
private int field_2_cb;
private int field_3_rcBounds_x1;
private int field_3_rcBounds_y1;
private int field_3_rcBounds_x2;
private int field_3_rcBounds_y2;
private int field_4_ptSize_w;
private int field_4_ptSize_h;
private int field_5_cbSave;
private byte field_6_fCompression;
private byte field_7_fFilter;
private byte[] raw_pictureData;
public EscherPictBlip() {}
public EscherPictBlip(EscherPictBlip other) {
super(other);
System.arraycopy(other.field_1_UID, 0, field_1_UID, 0, field_1_UID.length);
field_2_cb = other.field_2_cb;
field_3_rcBounds_x1 = other.field_3_rcBounds_x1;
field_3_rcBounds_y1 = other.field_3_rcBounds_y1;
field_3_rcBounds_x2 = other.field_3_rcBounds_x2;
field_3_rcBounds_y2 = other.field_3_rcBounds_y2;
field_4_ptSize_w = other.field_4_ptSize_w;
field_4_ptSize_h = other.field_4_ptSize_h;
field_5_cbSave = other.field_5_cbSave;
field_6_fCompression = other.field_6_fCompression;
field_7_fFilter = other.field_7_fFilter;
raw_pictureData = (other.raw_pictureData == null) ? null : other.raw_pictureData.clone();
}
@Override
public int fillFields(byte[] data, int offset, EscherRecordFactory recordFactory) {
int bytesAfterHeader = readHeader(data, offset);
int pos = offset + HEADER_SIZE;
System.arraycopy( data, pos, field_1_UID, 0, 16 ); pos += 16;
field_2_cb = LittleEndian.getInt( data, pos ); pos += 4;
field_3_rcBounds_x1 = LittleEndian.getInt( data, pos ); pos += 4;
field_3_rcBounds_y1 = LittleEndian.getInt( data, pos ); pos += 4;
field_3_rcBounds_x2 = LittleEndian.getInt( data, pos ); pos += 4;
field_3_rcBounds_y2 = LittleEndian.getInt( data, pos ); pos += 4;
field_4_ptSize_w = LittleEndian.getInt( data, pos ); pos += 4;
field_4_ptSize_h = LittleEndian.getInt( data, pos ); pos += 4;
field_5_cbSave = LittleEndian.getInt( data, pos ); pos += 4;
field_6_fCompression = data[pos]; pos++;
field_7_fFilter = data[pos]; pos++;
raw_pictureData = IOUtils.safelyAllocate(field_5_cbSave, MAX_RECORD_LENGTH);
System.arraycopy( data, pos, raw_pictureData, 0, field_5_cbSave );
// 0 means DEFLATE compression
// 0xFE means no compression
if (field_6_fCompression == 0)
{
super.setPictureData(inflatePictureData(raw_pictureData));
}
else
{
super.setPictureData(raw_pictureData);
}
return bytesAfterHeader + HEADER_SIZE;
}
@Override
public int serialize(int offset, byte[] data, EscherSerializationListener listener) {
listener.beforeRecordSerialize(offset, getRecordId(), this);
int pos = offset;
LittleEndian.putShort( data, pos, getOptions() ); pos += 2;
LittleEndian.putShort( data, pos, getRecordId() ); pos += 2;
LittleEndian.putInt( data, 0, getRecordSize() - HEADER_SIZE ); pos += 4;
System.arraycopy( field_1_UID, 0, data, pos, 16 ); pos += 16;
LittleEndian.putInt( data, pos, field_2_cb ); pos += 4;
LittleEndian.putInt( data, pos, field_3_rcBounds_x1 ); pos += 4;
LittleEndian.putInt( data, pos, field_3_rcBounds_y1 ); pos += 4;
LittleEndian.putInt( data, pos, field_3_rcBounds_x2 ); pos += 4;
LittleEndian.putInt( data, pos, field_3_rcBounds_y2 ); pos += 4;
LittleEndian.putInt( data, pos, field_4_ptSize_w ); pos += 4;
LittleEndian.putInt( data, pos, field_4_ptSize_h ); pos += 4;
LittleEndian.putInt( data, pos, field_5_cbSave ); pos += 4;
data[pos] = field_6_fCompression; pos++;
data[pos] = field_7_fFilter; pos++;
System.arraycopy( raw_pictureData, 0, data, pos, raw_pictureData.length );
listener.afterRecordSerialize(offset + getRecordSize(), getRecordId(), getRecordSize(), this);
return HEADER_SIZE + 16 + 1 + raw_pictureData.length;
}
/**
* Decompresses the provided data, returning the inflated result.
*
* @param data the deflated picture data.
* @return the inflated picture data.
*/
private static byte[] inflatePictureData(byte[] data) {
try {
InflaterInputStream in = new InflaterInputStream(new ByteArrayInputStream(data));
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buf = new byte[4096];
int readBytes;
while ((readBytes = in.read(buf)) > 0) {
out.write(buf, 0, readBytes);
}
return out.toByteArray();
} catch (IOException e) {
log.log(POILogger.INFO, "Possibly corrupt compression or non-compressed data", e);
return data;
}
}
@Override
public int getRecordSize() {
return 8 + 50 + raw_pictureData.length;
}
/**
* Gets the first MD4, that specifies the unique identifier of the
* uncompressed blip data
*
* @return the first MD4
*/
public byte[] getUID() {
return field_1_UID;
}
/**
* Sets the first MD4, that specifies the unique identifier of the
* uncompressed blip data
*
* @param uid the first MD4
*/
public void setUID(byte[] uid) {
if (uid == null || uid.length != 16) {
throw new IllegalArgumentException("uid must be byte[16]");
}
System.arraycopy(uid, 0, field_1_UID, 0, field_1_UID.length);
}
/**
* Gets the uncompressed size (in bytes)
*
* @return the uncompressed size
*/
public int getUncompressedSize() {
return field_2_cb;
}
/**
* Sets the uncompressed size (in bytes)
*
* @param uncompressedSize the uncompressed size
*/
public void setUncompressedSize(int uncompressedSize) {
field_2_cb = uncompressedSize;
}
/**
* Get the clipping region of the pict file
*
* @return the clipping region
*/
public Rectangle getBounds() {
return new Rectangle(field_3_rcBounds_x1,
field_3_rcBounds_y1,
field_3_rcBounds_x2 - field_3_rcBounds_x1,
field_3_rcBounds_y2 - field_3_rcBounds_y1);
}
/**
* Sets the clipping region
*
* @param bounds the clipping region
*/
public void setBounds(Rectangle bounds) {
field_3_rcBounds_x1 = bounds.x;
field_3_rcBounds_y1 = bounds.y;
field_3_rcBounds_x2 = bounds.x + bounds.width;
field_3_rcBounds_y2 = bounds.y + bounds.height;
}
/**
* Gets the dimensions of the metafile
*
* @return the dimensions of the metafile
*/
public Dimension getSizeEMU() {
return new Dimension(field_4_ptSize_w, field_4_ptSize_h);
}
/**
* Gets the dimensions of the metafile
*
* @param sizeEMU the dimensions of the metafile
*/
public void setSizeEMU(Dimension sizeEMU) {
field_4_ptSize_w = sizeEMU.width;
field_4_ptSize_h = sizeEMU.height;
}
/**
* Gets the compressed size of the metafile (in bytes)
*
* @return the compressed size
*/
public int getCompressedSize() {
return field_5_cbSave;
}
/**
* Sets the compressed size of the metafile (in bytes)
*
* @param compressedSize the compressed size
*/
public void setCompressedSize(int compressedSize) {
field_5_cbSave = compressedSize;
}
/**
* Gets the compression of the metafile
*
* @return true, if the metafile is compressed
*/
public boolean isCompressed() {
return (field_6_fCompression == 0);
}
/**
* Sets the compression of the metafile
*
* @param compressed the compression state, true if it's compressed
*/
public void setCompressed(boolean compressed) {
field_6_fCompression = compressed ? 0 : (byte)0xFE;
}
/**
* Gets the filter byte - this is usually 0xFE
*
* @return the filter byte
*/
public byte getFilter() {
return field_7_fFilter;
}
/**
* Sets the filter byte - this is usually 0xFE
*
* @param filter the filter byte
*/
public void setFilter(byte filter) {
field_7_fFilter = filter;
}
@Override
public Map<String, Supplier<?>> getGenericProperties() {
final Map<String, Supplier<?>> m = new LinkedHashMap<>(super.getGenericProperties());
m.put("uid", this::getUID);
m.put("uncompressedSize", this::getUncompressedSize);
m.put("bounds", this::getBounds);
m.put("sizeInEMU", this::getSizeEMU);
m.put("compressedSize", this::getCompressedSize);
m.put("isCompressed", this::isCompressed);
m.put("filter", this::getFilter);
return Collections.unmodifiableMap(m);
}
@Override
public EscherPictBlip copy() {
return new EscherPictBlip(this);
}
}

View File

@ -19,62 +19,65 @@ package org.apache.poi.ddf;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
public enum EscherRecordTypes { public enum EscherRecordTypes {
// records greater then 0xF000 belong to with Microsoft Office Drawing format also known as Escher // records greater then 0xF000 belong to Microsoft Office Drawing format also known as Escher
DGG_CONTAINER(0xF000, "DggContainer", null), DGG_CONTAINER(0xF000, "DggContainer", null, EscherContainerRecord::new),
BSTORE_CONTAINER(0xf001, "BStoreContainer", null), BSTORE_CONTAINER(0xf001, "BStoreContainer", null, EscherContainerRecord::new),
DG_CONTAINER(0xf002, "DgContainer", null), DG_CONTAINER(0xf002, "DgContainer", null, EscherContainerRecord::new),
SPGR_CONTAINER(0xf003, "SpgrContainer", null), SPGR_CONTAINER(0xf003, "SpgrContainer", null, EscherContainerRecord::new),
SP_CONTAINER(0xf004, "SpContainer", null), SP_CONTAINER(0xf004, "SpContainer", null, EscherContainerRecord::new),
SOLVER_CONTAINER(0xf005, "SolverContainer", null), SOLVER_CONTAINER(0xf005, "SolverContainer", null, EscherContainerRecord::new),
DGG(0xf006, "Dgg", "MsofbtDgg"), DGG(0xf006, "Dgg", "MsofbtDgg", EscherDggRecord::new),
BSE(0xf007, "BSE", "MsofbtBSE"), BSE(0xf007, "BSE", "MsofbtBSE", EscherBSERecord::new),
DG(0xf008, "Dg", "MsofbtDg"), DG(0xf008, "Dg", "MsofbtDg", EscherDgRecord::new),
SPGR(0xf009, "Spgr", "MsofbtSpgr"), SPGR(0xf009, "Spgr", "MsofbtSpgr", EscherSpgrRecord::new),
SP(0xf00a, "Sp", "MsofbtSp"), SP(0xf00a, "Sp", "MsofbtSp", EscherSpRecord::new),
OPT(0xf00b, "Opt", "msofbtOPT"), OPT(0xf00b, "Opt", "msofbtOPT", EscherOptRecord::new),
TEXTBOX(0xf00c, null, null), TEXTBOX(0xf00c, null, null, EscherTextboxRecord::new),
CLIENT_TEXTBOX(0xf00d, "ClientTextbox", "msofbtClientTextbox"), CLIENT_TEXTBOX(0xf00d, "ClientTextbox", "msofbtClientTextbox", EscherTextboxRecord::new),
ANCHOR(0xf00e, null, null), ANCHOR(0xf00e, null, null, null),
CHILD_ANCHOR(0xf00f, "ChildAnchor", "MsofbtChildAnchor"), CHILD_ANCHOR(0xf00f, "ChildAnchor", "MsofbtChildAnchor", EscherChildAnchorRecord::new),
CLIENT_ANCHOR(0xf010, "ClientAnchor", "MsofbtClientAnchor"), CLIENT_ANCHOR(0xf010, "ClientAnchor", "MsofbtClientAnchor", EscherClientAnchorRecord::new),
CLIENT_DATA(0xf011, "ClientData", "MsofbtClientData"), CLIENT_DATA(0xf011, "ClientData", "MsofbtClientData", EscherClientDataRecord::new),
CONNECTOR_RULE(0xf012, null, null), CONNECTOR_RULE(0xf012, null, null, null),
ALIGN_RULE(0xf013, null, null), ALIGN_RULE(0xf013, null, null, null),
ARC_RULE(0xf014, null, null), ARC_RULE(0xf014, null, null, null),
CLIENT_RULE(0xf015, null, null), CLIENT_RULE(0xf015, null, null, null),
CLSID(0xf016, null, null), CLSID(0xf016, null, null, null),
CALLOUT_RULE(0xf017, null, null), CALLOUT_RULE(0xf017, null, null, null),
BLIP_START(0xf018, "Blip", "msofbtBlip"), BLIP_START(0xf018, "Blip", "msofbtBlip", null),
BLIP_EMF(0xf018 + 2, "BlipEmf", null), BLIP_EMF(0xf018 + 2, "BlipEmf", null, EscherMetafileBlip::new),
BLIP_WMF(0xf018 + 3, "BlipWmf", null), BLIP_WMF(0xf018 + 3, "BlipWmf", null, EscherMetafileBlip::new),
BLIP_PICT(0xf018 + 4, "BlipPict", null), BLIP_PICT(0xf018 + 4, "BlipPict", null, EscherMetafileBlip::new),
BLIP_JPEG(0xf018 + 5, "BlipJpeg", null), BLIP_JPEG(0xf018 + 5, "BlipJpeg", null, EscherBitmapBlip::new),
BLIP_PNG(0xf018 + 6, "BlipPng", null), BLIP_PNG(0xf018 + 6, "BlipPng", null, EscherBitmapBlip::new),
BLIP_DIB(0xf018 + 7, "BlipDib", null), BLIP_DIB(0xf018 + 7, "BlipDib", null, EscherBitmapBlip::new),
BLIP_END(0xf117, "Blip", "msofbtBlip"), BLIP_END(0xf117, "Blip", "msofbtBlip", null),
REGROUP_ITEMS(0xf118, null, null), REGROUP_ITEMS(0xf118, null, null, null),
SELECTION(0xf119, null, null), SELECTION(0xf119, null, null, null),
COLOR_MRU(0xf11a, null, null), COLOR_MRU(0xf11a, null, null, null),
DELETED_PSPL(0xf11d, null, null), DELETED_PSPL(0xf11d, null, null, null),
SPLIT_MENU_COLORS(0xf11e, "SplitMenuColors", "MsofbtSplitMenuColors"), SPLIT_MENU_COLORS(0xf11e, "SplitMenuColors", "MsofbtSplitMenuColors", EscherSplitMenuColorsRecord::new),
OLE_OBJECT(0xf11f, null, null), OLE_OBJECT(0xf11f, null, null, null),
COLOR_SCHEME(0xf120, null, null), COLOR_SCHEME(0xf120, null, null, null),
// same as EscherTertiaryOptRecord.RECORD_ID // same as EscherTertiaryOptRecord.RECORD_ID
USER_DEFINED(0xf122, "TertiaryOpt", null), USER_DEFINED(0xf122, "TertiaryOpt", null, EscherTertiaryOptRecord::new),
UNKNOWN(0xffff, "unknown", "unknown"); UNKNOWN(0xffff, "unknown", "unknown", UnknownEscherRecord::new);
public final short typeID; public final short typeID;
public final String recordName; public final String recordName;
public final String description; public final String description;
public final Supplier<? extends EscherRecord> constructor;
EscherRecordTypes(int typeID, String recordName, String description) { EscherRecordTypes(int typeID, String recordName, String description, Supplier<? extends EscherRecord> constructor) {
this.typeID = (short) typeID; this.typeID = (short) typeID;
this.recordName = recordName; this.recordName = recordName;
this.description = description; this.description = description;
this.constructor = constructor;
} }
private Short getTypeId() { private Short getTypeId() {

View File

@ -31,7 +31,7 @@ public final class ContinueRecord extends StandardRecord {
private byte[] _data; private byte[] _data;
public ContinueRecord(byte[] data) { public ContinueRecord(byte[] data) {
_data = data; _data = data.clone();
} }
public ContinueRecord(ContinueRecord other) { public ContinueRecord(ContinueRecord other) {

View File

@ -43,6 +43,10 @@ public final class DrawingRecord extends StandardRecord {
recordData = in.readRemainder(); recordData = in.readRemainder();
} }
public DrawingRecord(byte[] data) {
recordData = data.clone();
}
/** /**
* @deprecated POI 3.9 * @deprecated POI 3.9
*/ */

View File

@ -17,11 +17,15 @@
package org.apache.poi.hssf.record; package org.apache.poi.hssf.record;
import static org.apache.poi.hssf.record.RecordInputStream.MAX_RECORD_DATA_SIZE;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -31,14 +35,11 @@ import org.apache.poi.ddf.EscherClientDataRecord;
import org.apache.poi.ddf.EscherContainerRecord; import org.apache.poi.ddf.EscherContainerRecord;
import org.apache.poi.ddf.EscherDgRecord; import org.apache.poi.ddf.EscherDgRecord;
import org.apache.poi.ddf.EscherRecord; import org.apache.poi.ddf.EscherRecord;
import org.apache.poi.ddf.EscherRecordFactory;
import org.apache.poi.ddf.EscherSerializationListener; 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.util.IOUtils; import org.apache.poi.util.IOUtils;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.util.RecordFormatException; import org.apache.poi.util.RecordFormatException;
/** /**
@ -84,8 +85,8 @@ import org.apache.poi.util.RecordFormatException;
*/ */
public final class EscherAggregate extends AbstractEscherHolderRecord { public final class EscherAggregate extends AbstractEscherHolderRecord {
public static final short sid = 9876; // not a real sid - dummy value // not a real sid - dummy value
private static final POILogger log = POILogFactory.getLogger(EscherAggregate.class); public static final short sid = 9876;
//arbitrarily selected; may need to increase //arbitrarily selected; may need to increase
private static final int MAX_RECORD_LENGTH = 100_000_000; private static final int MAX_RECORD_LENGTH = 100_000_000;
@ -364,17 +365,6 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
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) {
return sid == DrawingRecord.sid ||
sid == ContinueRecord.sid ||
sid == ObjRecord.sid ||
sid == TextObjectRecord.sid;
}
/** /**
* Collapses the drawing records into an aggregate. * Collapses the drawing records into an aggregate.
* read Drawing, Obj, TxtObj, Note and Continue records into single byte array, * read Drawing, Obj, TxtObj, Note and Continue records into single byte array,
@ -384,84 +374,85 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
* @param locFirstDrawingRecord - location of the first DrawingRecord inside sheet * @param locFirstDrawingRecord - location of the first DrawingRecord inside sheet
* @return new EscherAggregate create from all aggregated records which belong to drawing layer * @return new EscherAggregate create from all aggregated records which belong to drawing layer
*/ */
public static EscherAggregate createAggregate(List<RecordBase> records, int locFirstDrawingRecord) { public static EscherAggregate createAggregate(final List<RecordBase> records, final int locFirstDrawingRecord) {
// 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.
final List<EscherRecord> shapeRecords = new ArrayList<>();
EscherRecordFactory recordFactory = new DefaultEscherRecordFactory() {
public EscherRecord createRecord(byte[] data, int offset) {
EscherRecord r = super.createRecord(data, offset);
if (r.getRecordId() == EscherClientDataRecord.RECORD_ID || r.getRecordId() == EscherTextboxRecord.RECORD_ID) {
shapeRecords.add(r);
}
return r;
}
};
// Create one big buffer
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
EscherAggregate agg = new EscherAggregate(false); EscherAggregate agg = new EscherAggregate(false);
int loc = locFirstDrawingRecord;
while (loc + 1 < records.size() ShapeCollector recordFactory = new ShapeCollector();
&& (isDrawingLayerRecord(sid(records, loc)))) { List<Record> objectRecords = new ArrayList<>();
try {
if (!(sid(records, loc) == DrawingRecord.sid || sid(records, loc) == ContinueRecord.sid)) { int nextIdx = locFirstDrawingRecord;
loc++; for (RecordBase rb : records.subList(locFirstDrawingRecord, records.size())) {
nextIdx++;
switch (sid(rb)) {
case DrawingRecord.sid:
recordFactory.addBytes(((DrawingRecord)rb).getRecordData());
continue; continue;
} case ContinueRecord.sid:
if (sid(records, loc) == DrawingRecord.sid) { recordFactory.addBytes(((ContinueRecord)rb).getData());
buffer.write(((DrawingRecord) records.get(loc)).getRecordData()); continue;
} else { case ObjRecord.sid:
buffer.write(((ContinueRecord) records.get(loc)).getData()); case TextObjectRecord.sid:
} objectRecords.add((org.apache.poi.hssf.record.Record)rb);
} catch (IOException e) { continue;
throw new RuntimeException("Couldn't get data from drawing/continue records", e); case NoteRecord.sid:
// any NoteRecords that follow the drawing block must be aggregated and saved in the tailRec collection
NoteRecord r = (NoteRecord)rb;
agg.tailRec.put(r.getShapeId(), r);
continue;
default:
nextIdx--;
break;
} }
loc++; break;
}
// replace drawing block with the created EscherAggregate
records.set(locFirstDrawingRecord, agg);
if (locFirstDrawingRecord+1 <= nextIdx) {
records.subList(locFirstDrawingRecord + 1, nextIdx).clear();
} }
// Decode the shapes // Decode the shapes
// agg.escherRecords = new ArrayList(); Iterator<EscherRecord> shapeIter = recordFactory.parse(agg).iterator();
int pos = 0;
while (pos < buffer.size()) {
EscherRecord r = recordFactory.createRecord(buffer.toByteArray(), pos);
int bytesRead = r.fillFields(buffer.toByteArray(), pos, recordFactory);
agg.addEscherRecord(r);
pos += bytesRead;
}
// Associate the object records with the shapes // Associate the object records with the shapes
loc = locFirstDrawingRecord + 1; objectRecords.forEach(or -> agg.shapeToObj.put(shapeIter.next(), or));
int shapeIndex = 0;
while (loc < records.size()
&& (isDrawingLayerRecord(sid(records, loc)))) {
if (!isObjectRecord(records, loc)) {
loc++;
continue;
}
Record objRecord = (org.apache.poi.hssf.record.Record) records.get(loc);
agg.shapeToObj.put(shapeRecords.get(shapeIndex++), objRecord);
loc++;
}
// any NoteRecords that follow the drawing block must be aggregated and and saved in the tailRec collection
while (loc < records.size()) {
if (sid(records, loc) == NoteRecord.sid) {
NoteRecord r = (NoteRecord) records.get(loc);
agg.tailRec.put(r.getShapeId(), r);
} else {
break;
}
loc++;
}
int locLastDrawingRecord = loc;
// replace drawing block with the created EscherAggregate
records.subList(locFirstDrawingRecord, locLastDrawingRecord).clear();
records.add(locFirstDrawingRecord, agg);
return agg; return agg;
} }
private static class ShapeCollector extends DefaultEscherRecordFactory {
final List<EscherRecord> objShapes = new ArrayList<>();
final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
void addBytes(byte[] data) {
try {
buffer.write(data);
} catch (IOException e) {
throw new RuntimeException("Couldn't get data from drawing/continue records", e);
}
}
public EscherRecord createRecord(byte[] data, int offset) {
EscherRecord r = super.createRecord(data, offset);
short rid = r.getRecordId();
if (rid == EscherClientDataRecord.RECORD_ID || rid == EscherTextboxRecord.RECORD_ID) {
objShapes.add(r);
}
return r;
}
List<EscherRecord> parse(EscherAggregate agg) {
byte[] buf = buffer.toByteArray();
for (int pos = 0, bytesRead; pos < buf.length; pos += bytesRead) {
EscherRecord r = createRecord(buf, pos);
bytesRead = r.fillFields(buf, pos, this);
agg.addEscherRecord(r);
}
return objShapes;
}
}
/** /**
* Serializes this aggregate to a byte array. Since this is an aggregate * Serializes this aggregate to a byte array. Since this is an aggregate
* record it will effectively serialize the aggregated records. * record it will effectively serialize the aggregated records.
@ -470,7 +461,7 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
* @param data The byte array to serialize to. * @param data The byte array to serialize to.
* @return The number of bytes serialized. * @return The number of bytes serialized.
*/ */
public int serialize(int offset, byte[] data) { public int serialize(final int offset, final byte[] data) {
// Determine buffer size // Determine buffer size
List <EscherRecord>records = getEscherRecords(); List <EscherRecord>records = getEscherRecords();
int size = getEscherRecordSize(records); int size = getEscherRecordSize(records);
@ -501,18 +492,14 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
// the first one because it's the patriach). // the first one because it's the patriach).
pos = offset; pos = offset;
int writtenEscherBytes = 0; int writtenEscherBytes = 0;
int i; boolean isFirst = true;
for (i = 1; i < shapes.size(); i++) { int endOffset = 0;
int endOffset = spEndingOffsets.get(i) - 1; for (int i = 1; i < shapes.size(); i++) {
int startOffset; int startOffset = endOffset;
if (i == 1) endOffset = spEndingOffsets.get(i);
startOffset = 0;
else
startOffset = spEndingOffsets.get(i - 1);
byte[] drawingData = new byte[endOffset - startOffset + 1]; byte[] drawingData = Arrays.copyOfRange(buffer, startOffset, endOffset);
System.arraycopy(buffer, startOffset, drawingData, 0, drawingData.length); pos += writeDataIntoDrawingRecord(drawingData, writtenEscherBytes, pos, data, isFirst);
pos += writeDataIntoDrawingRecord(drawingData, writtenEscherBytes, pos, data, i);
writtenEscherBytes += drawingData.length; writtenEscherBytes += drawingData.length;
@ -520,24 +507,22 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
Record obj = shapeToObj.get(shapes.get(i)); Record obj = shapeToObj.get(shapes.get(i));
pos += obj.serialize(pos, data); pos += obj.serialize(pos, data);
if (i == shapes.size() - 1 && endOffset < buffer.length - 1) { isFirst = false;
drawingData = new byte[buffer.length - endOffset - 1];
System.arraycopy(buffer, endOffset + 1, drawingData, 0, drawingData.length);
pos += writeDataIntoDrawingRecord(drawingData, writtenEscherBytes, pos, data, i);
}
} }
if ((pos - offset) < buffer.length - 1) {
byte[] drawingData = new byte[buffer.length - (pos - offset)]; if (endOffset < buffer.length - 1) {
System.arraycopy(buffer, (pos - offset), drawingData, 0, drawingData.length); byte[] drawingData = Arrays.copyOfRange(buffer, endOffset, buffer.length);
pos += writeDataIntoDrawingRecord(drawingData, writtenEscherBytes, pos, data, i); pos += writeDataIntoDrawingRecord(drawingData, writtenEscherBytes, pos, data, isFirst);
} }
for (NoteRecord noteRecord : tailRec.values()) { for (NoteRecord noteRecord : tailRec.values()) {
pos += noteRecord.serialize(pos, data); pos += noteRecord.serialize(pos, data);
} }
int bytesWritten = pos - offset; int bytesWritten = pos - offset;
if (bytesWritten != getRecordSize()) if (bytesWritten != getRecordSize()) {
throw new RecordFormatException(bytesWritten + " bytes written but getRecordSize() reports " + getRecordSize()); throw new RecordFormatException(bytesWritten + " bytes written but getRecordSize() reports " + getRecordSize());
}
return bytesWritten; return bytesWritten;
} }
@ -547,34 +532,19 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
* drawing or continue record) * drawing or continue record)
* @param pos current position of data array * @param pos current position of data array
* @param data - array of bytes where drawing records must be serialized * @param data - array of bytes where drawing records must be serialized
* @param i - number of shape, saved into data array * @param isFirst - is it the first shape, saved into data array
* @return offset of data array after serialization * @return offset of data array after serialization
*/ */
private int writeDataIntoDrawingRecord(byte[] drawingData, int writtenEscherBytes, int pos, byte[] data, int i) { private int writeDataIntoDrawingRecord(final byte[] drawingData, final int writtenEscherBytes, final int pos, final byte[] data, final boolean isFirst) {
int temp = 0; int temp = 0;
//First record in drawing layer MUST be DrawingRecord //First record in drawing layer MUST be DrawingRecord
if (writtenEscherBytes + drawingData.length > RecordInputStream.MAX_RECORD_DATA_SIZE && i != 1) { boolean useDrawingRecord = isFirst || (writtenEscherBytes + drawingData.length) <= MAX_RECORD_DATA_SIZE;
for (int j = 0; j < drawingData.length; j += RecordInputStream.MAX_RECORD_DATA_SIZE) {
byte[] buf = new byte[Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE, drawingData.length - j)]; for (int j = 0; j < drawingData.length; j += MAX_RECORD_DATA_SIZE) {
System.arraycopy(drawingData, j, buf, 0, Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE, drawingData.length - j)); byte[] buf = Arrays.copyOfRange(drawingData, j, Math.min(j+MAX_RECORD_DATA_SIZE, drawingData.length));
ContinueRecord drawing = new ContinueRecord(buf); Record drawing = (useDrawingRecord) ? new DrawingRecord(buf) : new ContinueRecord(buf);
temp += drawing.serialize(pos + temp, data); temp += drawing.serialize(pos + temp, data);
} useDrawingRecord = false;
} else {
for (int j = 0; j < drawingData.length; j += RecordInputStream.MAX_RECORD_DATA_SIZE) {
if (j == 0) {
DrawingRecord drawing = new DrawingRecord();
byte[] buf = new byte[Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE, drawingData.length - j)];
System.arraycopy(drawingData, j, buf, 0, Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE, drawingData.length - j));
drawing.setData(buf);
temp += drawing.serialize(pos + temp, data);
} else {
byte[] buf = new byte[Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE, drawingData.length - j)];
System.arraycopy(drawingData, j, buf, 0, Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE, drawingData.length - j));
ContinueRecord drawing = new ContinueRecord(buf);
temp += drawing.serialize(pos + temp, data);
}
}
} }
return temp; return temp;
} }
@ -624,14 +594,15 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
if (i == spEndingOffsets.size() - 1 && spEndingOffsets.get(i) < pos) { if (i == spEndingOffsets.size() - 1 && spEndingOffsets.get(i) < pos) {
continueRecordsHeadersSize += 4; continueRecordsHeadersSize += 4;
} }
if (spEndingOffsets.get(i) - spEndingOffsets.get(i - 1) <= RecordInputStream.MAX_RECORD_DATA_SIZE) { if (spEndingOffsets.get(i) - spEndingOffsets.get(i - 1) <= MAX_RECORD_DATA_SIZE) {
continue; continue;
} }
continueRecordsHeadersSize += ((spEndingOffsets.get(i) - spEndingOffsets.get(i - 1)) / RecordInputStream.MAX_RECORD_DATA_SIZE) * 4; continueRecordsHeadersSize += ((spEndingOffsets.get(i) - spEndingOffsets.get(i - 1)) / MAX_RECORD_DATA_SIZE) * 4;
} }
int drawingRecordSize = rawEscherSize + (shapeToObj.size()) * 4; int drawingRecordSize = rawEscherSize + (shapeToObj.size()) * 4;
if (rawEscherSize != 0 && spEndingOffsets.size() == 1/**EMPTY**/) { if (rawEscherSize != 0 && spEndingOffsets.size() == 1) {
// EMPTY
continueRecordsHeadersSize += 4; continueRecordsHeadersSize += 4;
} }
int objRecordSize = 0; int objRecordSize = 0;
@ -671,16 +642,6 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
// =============== Private methods ======================== // =============== Private methods ========================
/**
*
* @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;
}
/** /**
* create base tree with such structure: * create base tree with such structure:
* EscherDgContainer * EscherDgContainer
@ -741,7 +702,9 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
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)); if (dg != null) {
dg.setOptions((short) (dgId << 4));
}
} }
/** /**
@ -757,26 +720,26 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
*/ */
public void setMainSpRecordId(int shapeId) { public void setMainSpRecordId(int shapeId) {
EscherContainerRecord dgContainer = getEscherContainer(); EscherContainerRecord dgContainer = getEscherContainer();
EscherContainerRecord spgrConatiner = dgContainer.getChildById(EscherContainerRecord.SPGR_CONTAINER); EscherContainerRecord spgrContainer = dgContainer.getChildById(EscherContainerRecord.SPGR_CONTAINER);
EscherContainerRecord spContainer = (EscherContainerRecord) spgrConatiner.getChild(0); if (spgrContainer != null) {
EscherSpRecord sp = spContainer.getChildById(EscherSpRecord.RECORD_ID); EscherContainerRecord spContainer = (EscherContainerRecord) spgrContainer.getChild(0);
sp.setShapeId(shapeId); EscherSpRecord sp = spContainer.getChildById(EscherSpRecord.RECORD_ID);
if (sp != null) {
sp.setShapeId(shapeId);
}
}
} }
/** /**
* @param records list of records to look into * @param record the record to look into
* @param loc - location of the record which sid must be returned * @return sid of the record
* @return sid of the record with selected location
*/ */
private static short sid(List<RecordBase> records, int loc) { private static short sid(RecordBase record) {
RecordBase record = records.get(loc); // Aggregates don't have a sid
if (record instanceof Record) { // We could step into them, but for these needs we don't care
return ((org.apache.poi.hssf.record.Record)record).getSid(); return (record instanceof org.apache.poi.hssf.record.Record)
} else { ? ((org.apache.poi.hssf.record.Record)record).getSid()
// Aggregates don't have a sid : -1;
// We could step into them, but for these needs we don't care
return -1;
}
} }
/** /**

View File

@ -58,11 +58,8 @@ public class HSSFShapeFactory {
HSSFShapeGroup group = new HSSFShapeGroup(container, obj); HSSFShapeGroup group = new HSSFShapeGroup(container, obj);
List<EscherContainerRecord> children = container.getChildContainers(); List<EscherContainerRecord> children = container.getChildContainers();
// skip the first child record, it is group descriptor // skip the first child record, it is group descriptor
for (int i = 0; i < children.size(); i++) { if (children.size() > 1) {
EscherContainerRecord spContainer = children.get(i); children.subList(1, children.size()).forEach(c -> createShapeTree(c, agg, group, root));
if (i != 0) {
createShapeTree(spContainer, agg, group, root);
}
} }
out.addShape(group); out.addShape(group);
} else if (container.getRecordId() == EscherContainerRecord.SP_CONTAINER) { } else if (container.getRecordId() == EscherContainerRecord.SP_CONTAINER) {

View File

@ -17,11 +17,11 @@
package org.apache.poi.hslf.record; package org.apache.poi.hslf.record;
import java.lang.reflect.Constructor; import java.util.function.Supplier;
import java.util.Map;
import org.apache.poi.ddf.*; import org.apache.poi.ddf.DefaultEscherRecordFactory;
import org.apache.poi.util.LittleEndian; import org.apache.poi.ddf.EscherRecord;
import org.apache.poi.ddf.EscherRecordFactory;
/** /**
* Generates escher records when provided the byte array containing those records. * Generates escher records when provided the byte array containing those records.
@ -29,39 +29,20 @@ import org.apache.poi.util.LittleEndian;
* @see EscherRecordFactory * @see EscherRecordFactory
*/ */
public class HSLFEscherRecordFactory extends DefaultEscherRecordFactory { public class HSLFEscherRecordFactory extends DefaultEscherRecordFactory {
private static Class<?>[] escherRecordClasses = { EscherPlaceholder.class, HSLFEscherClientDataRecord.class };
private static Map<Short, Constructor<? extends EscherRecord>> recordsMap = recordsToMap( escherRecordClasses );
/** /**
* Creates an instance of the escher record factory * Creates an instance of the escher record factory
*/ */
public HSLFEscherRecordFactory() { public HSLFEscherRecordFactory() {
// no instance initialisation // no instance initialisation
} }
@Override
public EscherRecord createRecord(byte[] data, int offset) {
short options = LittleEndian.getShort( data, offset );
short recordId = LittleEndian.getShort( data, offset + 2 );
// int remainingBytes = LittleEndian.getInt( data, offset + 4 );
Constructor<? extends EscherRecord> recordConstructor = recordsMap.get(Short.valueOf(recordId)); @Override
if (recordConstructor == null) { protected Supplier<? extends EscherRecord> getConstructor(short options, short recordId) {
return super.createRecord(data, offset); if (recordId == EscherPlaceholder.RECORD_ID) {
return EscherPlaceholder::new;
} else if (recordId == HSLFEscherClientDataRecord.RECORD_ID) {
return HSLFEscherClientDataRecord::new;
} }
EscherRecord escherRecord = null; return super.getConstructor(options, recordId);
try {
escherRecord = recordConstructor.newInstance(new Object[] {});
} catch (Exception e) {
return super.createRecord(data, offset);
}
escherRecord.setRecordId(recordId);
escherRecord.setOptions(options);
if (escherRecord instanceof EscherContainerRecord) {
escherRecord.fillFields(data, offset, this);
}
return escherRecord;
} }
} }

View File

@ -726,31 +726,22 @@ public class TestDrawingAggregate {
List<org.apache.poi.hssf.record.Record> dgRecords = RecordFactory.createRecords(new ByteArrayInputStream(dgBytes)); List<org.apache.poi.hssf.record.Record> dgRecords = RecordFactory.createRecords(new ByteArrayInputStream(dgBytes));
assertEquals(20, dgRecords.size()); assertEquals(20, dgRecords.size());
short[] expectedSids = { int[] expectedSids = {
DrawingRecord.sid, DrawingRecord.sid, ObjRecord.sid,
ObjRecord.sid, DrawingRecord.sid, TextObjectRecord.sid,
DrawingRecord.sid, DrawingRecord.sid, ObjRecord.sid,
TextObjectRecord.sid, DrawingRecord.sid, TextObjectRecord.sid,
DrawingRecord.sid, DrawingRecord.sid, ObjRecord.sid,
ObjRecord.sid, DrawingRecord.sid, TextObjectRecord.sid,
DrawingRecord.sid, DrawingRecord.sid, ObjRecord.sid,
TextObjectRecord.sid, DrawingRecord.sid, TextObjectRecord.sid,
DrawingRecord.sid, ContinueRecord.sid, ObjRecord.sid,
ObjRecord.sid, ContinueRecord.sid, TextObjectRecord.sid
DrawingRecord.sid,
TextObjectRecord.sid,
DrawingRecord.sid,
ObjRecord.sid,
DrawingRecord.sid,
TextObjectRecord.sid,
ContinueRecord.sid,
ObjRecord.sid,
ContinueRecord.sid,
TextObjectRecord.sid
}; };
for (int i = 0; i < expectedSids.length; i++) {
assertEquals("unexpected record.sid and index[" + i + "]", expectedSids[i], dgRecords.get(i).getSid()); int[] actualSids = dgRecords.stream().mapToInt(Record::getSid).toArray();
} assertArrayEquals("unexpected record.sid", expectedSids, actualSids);
DrawingManager2 drawingManager = new DrawingManager2(new EscherDggRecord()); DrawingManager2 drawingManager = new DrawingManager2(new EscherDggRecord());
// create a dummy sheet consisting of our test data // create a dummy sheet consisting of our test data
@ -904,26 +895,19 @@ public class TestDrawingAggregate {
List<org.apache.poi.hssf.record.Record> dgRecords = RecordFactory.createRecords(new ByteArrayInputStream(dgBytes)); List<org.apache.poi.hssf.record.Record> dgRecords = RecordFactory.createRecords(new ByteArrayInputStream(dgBytes));
assertEquals(14, dgRecords.size()); assertEquals(14, dgRecords.size());
short[] expectedSids = { int[] expectedSids = {
DrawingRecord.sid, DrawingRecord.sid, ObjRecord.sid,
ObjRecord.sid, DrawingRecord.sid, ObjRecord.sid,
DrawingRecord.sid, DrawingRecord.sid, ObjRecord.sid,
ObjRecord.sid, DrawingRecord.sid, ObjRecord.sid,
DrawingRecord.sid, ContinueRecord.sid, ObjRecord.sid,
ObjRecord.sid, ContinueRecord.sid, ObjRecord.sid,
DrawingRecord.sid, ContinueRecord.sid, ObjRecord.sid
ObjRecord.sid,
ContinueRecord.sid,
ObjRecord.sid,
ContinueRecord.sid,
ObjRecord.sid,
ContinueRecord.sid,
ObjRecord.sid
}; };
for (int i = 0; i < expectedSids.length; i++) { int[] actualSids = dgRecords.stream().mapToInt(Record::getSid).toArray();
assertEquals("unexpected record.sid and index[" + i + "]", expectedSids[i], dgRecords.get(i).getSid()); assertArrayEquals("unexpected record.sid", expectedSids, actualSids);
}
DrawingManager2 drawingManager = new DrawingManager2(new EscherDggRecord()); DrawingManager2 drawingManager = new DrawingManager2(new EscherDggRecord());
// create a dummy sheet consisting of our test data // create a dummy sheet consisting of our test data