| /* ==================================================================== |
| 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.hssf.record; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.apache.poi.ddf.DefaultEscherRecordFactory; |
| import org.apache.poi.ddf.EscherContainerRecord; |
| import org.apache.poi.ddf.EscherRecord; |
| import org.apache.poi.ddf.EscherRecordFactory; |
| import org.apache.poi.ddf.NullEscherSerializationListener; |
| import org.apache.poi.hssf.util.LazilyConcatenatedByteArray; |
| import org.apache.poi.util.LittleEndian; |
| |
| /** |
| * The escher container record is used to hold escher records. It is abstract and |
| * must be subclassed for maximum benefit. |
| * <p> |
| * Child records are deserialized on-demand unless the {@code poi.deserialize.escher} System Property is defined. |
| */ |
| public abstract class AbstractEscherHolderRecord extends Record { |
| private static boolean DESERIALIZE; |
| static { |
| try { |
| DESERIALIZE = (System.getProperty("poi.deserialize.escher") != null); |
| } catch (SecurityException e) { |
| DESERIALIZE = false; |
| } |
| } |
| |
| private final List<EscherRecord> escherRecords = new ArrayList<>(); |
| private final LazilyConcatenatedByteArray rawDataContainer = new LazilyConcatenatedByteArray(); |
| |
| public AbstractEscherHolderRecord() {} |
| |
| public AbstractEscherHolderRecord(AbstractEscherHolderRecord other) { |
| other.escherRecords.stream().map(EscherRecord::copy).forEach(escherRecords::add); |
| rawDataContainer.concatenate(other.rawDataContainer); |
| } |
| |
| public AbstractEscherHolderRecord(RecordInputStream in) { |
| if (!DESERIALIZE) { |
| rawDataContainer.concatenate(in.readRemainder()); |
| } else { |
| byte[] data = in.readAllContinuedRemainder(); |
| convertToEscherRecords( 0, data.length, data ); |
| } |
| } |
| |
| protected void convertRawBytesToEscherRecords() { |
| if (!DESERIALIZE) { |
| byte[] rawData = getRawData(); |
| convertToEscherRecords(0, rawData.length, rawData); |
| } |
| } |
| private void convertToEscherRecords( int offset, int size, byte[] data ) |
| { |
| escherRecords.clear(); |
| EscherRecordFactory recordFactory = new DefaultEscherRecordFactory(); |
| int pos = offset; |
| while ( pos < offset + size ) |
| { |
| EscherRecord r = recordFactory.createRecord(data, pos); |
| int bytesRead = r.fillFields(data, pos, recordFactory ); |
| escherRecords.add(r); |
| pos += bytesRead; |
| } |
| } |
| |
| protected abstract String getRecordName(); |
| |
| @Override |
| public int serialize(int offset, byte[] data) { |
| byte[] rawData = getRawData(); |
| |
| LittleEndian.putShort(data, offset, getSid()); |
| offset += 2; |
| LittleEndian.putShort(data, offset, (short) (getRecordSize() - 4)); |
| offset += 2; |
| |
| if (escherRecords.isEmpty() && rawData != null) { |
| System.arraycopy(rawData, 0, data, offset, rawData.length); |
| return rawData.length + 4; |
| } |
| |
| NullEscherSerializationListener listener = new NullEscherSerializationListener(); |
| for (EscherRecord r : escherRecords) { |
| offset += r.serialize(offset, data, listener); |
| } |
| return getRecordSize(); |
| } |
| |
| @Override |
| public int getRecordSize() { |
| byte[] rawData = getRawData(); |
| if (escherRecords.size() == 0 && rawData != null) { |
| // XXX: It should be possible to derive this without concatenating the array, too. |
| return rawData.length; |
| } |
| int size = 0; |
| for (EscherRecord r : escherRecords) { |
| size += r.getRecordSize(); |
| } |
| return size; |
| } |
| |
| |
| |
| @Override |
| public abstract short getSid(); |
| |
| @Override |
| public abstract AbstractEscherHolderRecord copy(); |
| |
| public void addEscherRecord(int index, EscherRecord element) |
| { |
| escherRecords.add( index, element ); |
| } |
| |
| public boolean addEscherRecord(EscherRecord element) |
| { |
| return escherRecords.add( element ); |
| } |
| |
| public List<EscherRecord> getEscherRecords() |
| { |
| return escherRecords; |
| } |
| |
| public void clearEscherRecords() |
| { |
| escherRecords.clear(); |
| } |
| |
| /** |
| * If we have a EscherContainerRecord as one of our |
| * children (and most top level escher holders do), |
| * then return that. |
| * |
| * @return the EscherContainerRecord or {@code null} if no child is a container record |
| */ |
| public EscherContainerRecord getEscherContainer() { |
| for (EscherRecord er : escherRecords) { |
| if(er instanceof EscherContainerRecord) { |
| return (EscherContainerRecord)er; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Descends into all our children, returning the |
| * first EscherRecord with the given id, or null |
| * if none found |
| * |
| * @param id the record to look for |
| * |
| * @return the record or {@code null} if it can't be found |
| */ |
| public EscherRecord findFirstWithId(short id) { |
| return findFirstWithId(id, getEscherRecords()); |
| } |
| |
| private EscherRecord findFirstWithId(short id, List<EscherRecord> records) { |
| // Check at our level |
| for (EscherRecord r : records) { |
| if(r.getRecordId() == id) { |
| return r; |
| } |
| } |
| |
| // Then check our children in turn |
| for (EscherRecord r : records) { |
| if(r.isContainerRecord()) { |
| EscherRecord found = findFirstWithId(id, r.getChildRecords()); |
| if(found != null) { |
| return found; |
| } |
| } |
| } |
| |
| // Not found in this lot |
| return null; |
| } |
| |
| |
| public EscherRecord getEscherRecord(int index) |
| { |
| return escherRecords.get(index); |
| } |
| |
| /** |
| * Big drawing group records are split but it's easier to deal with them |
| * as a whole group so we need to join them together. |
| * |
| * @param record the record data to concatenate to the end |
| */ |
| public void join( AbstractEscherHolderRecord record ) |
| { |
| rawDataContainer.concatenate(record.getRawData()); |
| } |
| |
| public void processContinueRecord( byte[] record ) |
| { |
| rawDataContainer.concatenate(record); |
| } |
| |
| public byte[] getRawData() |
| { |
| return rawDataContainer.toArray(); |
| } |
| |
| public void setRawData( byte[] rawData ) |
| { |
| rawDataContainer.clear(); |
| rawDataContainer.concatenate(rawData); |
| } |
| |
| /** |
| * Convert raw data to escher records. |
| */ |
| public void decode() |
| { |
| if (escherRecords.isEmpty()) { |
| byte[] rawData = getRawData(); |
| convertToEscherRecords(0, rawData.length, rawData); |
| } |
| } |
| |
| @Override |
| public List<EscherRecord> getGenericChildren() { |
| return escherRecords; |
| } |
| } |