123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- /* ====================================================================
- 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.util.Iterator;
- import java.util.Map;
- import java.util.NoSuchElementException;
- import java.util.Spliterator;
- import java.util.Spliterators;
- import java.util.function.Supplier;
- import java.util.stream.Collectors;
- import java.util.stream.StreamSupport;
-
- import org.apache.poi.util.GenericRecordUtil;
- import org.apache.poi.util.IOUtils;
- import org.apache.poi.util.Internal;
- import org.apache.poi.util.LittleEndian;
- import org.apache.poi.util.Removal;
-
- /**
- * Escher array properties are the most weird construction ever invented
- * with all sorts of special cases. I'm hopeful I've got them all.
- */
- public final class EscherArrayProperty extends EscherComplexProperty implements Iterable<byte[]> {
-
- // arbitrarily selected; may need to increase
- private static final int DEFAULT_MAX_RECORD_LENGTH = 100_000;
- private static int MAX_RECORD_LENGTH = DEFAULT_MAX_RECORD_LENGTH;
-
- /**
- * The size of the header that goes at the start of the array, before the data
- */
- private static final int FIXED_SIZE = 3 * 2;
- /**
- * Normally, the size recorded in the simple data (for the complex data) includes the size of the header.
- * There are a few cases when it doesn't though...
- */
- private boolean sizeIncludesHeaderSize = true;
-
- /**
- * When reading a property from data stream remember if the complex part is empty and set this flag.
- */
- private final boolean emptyComplexPart;
-
- /**
- * @param length the max record length allowed for EscherArrayProperty
- */
- public static void setMaxRecordLength(int length) {
- MAX_RECORD_LENGTH = length;
- }
-
- /**
- * @return the max record length allowed for EscherArrayProperty
- */
- public static int getMaxRecordLength() {
- return MAX_RECORD_LENGTH;
- }
-
- /**
- * Create an instance of an escher array property.
- * This constructor can be used to create emptyComplexParts with a complexSize = 0.
- * Preferably use {@link #EscherArrayProperty(EscherPropertyTypes, boolean, int)} with {@link #setComplexData(byte[])}.
- *
- * @param id The id consists of the property number, a flag indicating whether this is a blip id and a flag
- * indicating that this is a complex property.
- * @param complexSize the data size
- */
- @Internal
- public EscherArrayProperty(short id, int complexSize) {
- // this is called by EscherPropertyFactory which happens to call it with empty parts
- // if a part is initial empty, don't allow it to contain something again
- super(id, complexSize);
- emptyComplexPart = (complexSize == 0);
- }
-
- /**
- * Create an instance of an escher array property.
- * This constructor defaults to a 6 bytes header if the complexData is null or byte[0].
- *
- * @param propertyNumber the property number part of the property id
- * @param isBlipId {@code true}, if it references a blip
- * @param complexData the data
- *
- * @deprecated use {@link #EscherArrayProperty(EscherPropertyTypes, boolean, int)} and {@link #setComplexData(byte[])}
- */
- @Deprecated
- @Removal(version = "5.0.0")
- public EscherArrayProperty(short propertyNumber, boolean isBlipId, byte[] complexData) {
- // this is called by user code, if the complexData is empty/null, allocate a space for a valid header
- // be aware, that there are complex data areas with less than 6 bytes
- this((short)(propertyNumber | (isBlipId ? IS_BLIP : 0)), safeSize(complexData == null ? 0 : complexData.length));
- setComplexData(complexData);
- }
-
- /**
- * Create an instance of an escher array property.
- * This constructor defaults to a 6 bytes header if the complexSize is 0.
- *
- * @param type the property type of the property id
- * @param isBlipId {@code true}, if it references a blip
- * @param complexSize the data size
- */
- public EscherArrayProperty(EscherPropertyTypes type, boolean isBlipId, int complexSize) {
- this((short)(type.propNumber | (isBlipId ? IS_BLIP : 0)), safeSize(complexSize));
- }
-
- private static int safeSize(int complexSize) {
- // when called by user code, fix the size to be valid for the header
- return complexSize == 0 ? 6 : complexSize;
- }
-
- public int getNumberOfElementsInArray() {
- return (emptyComplexPart) ? 0 : LittleEndian.getUShort(getComplexData(), 0);
- }
-
- public void setNumberOfElementsInArray(int numberOfElements) {
- if (emptyComplexPart) {
- return;
- }
- rewriteArray(numberOfElements, false);
- LittleEndian.putShort(getComplexData(), 0, (short) numberOfElements);
- }
-
- private void rewriteArray(int numberOfElements, boolean copyToNewLen) {
- int expectedArraySize = numberOfElements * getActualSizeOfElements(getSizeOfElements()) + FIXED_SIZE;
- resizeComplexData(expectedArraySize, copyToNewLen ? expectedArraySize : getComplexData().length);
- }
-
- public int getNumberOfElementsInMemory() {
- return (emptyComplexPart) ? 0 : LittleEndian.getUShort(getComplexData(), 2);
- }
-
- public void setNumberOfElementsInMemory(int numberOfElements) {
- if (emptyComplexPart) {
- return;
- }
- rewriteArray(numberOfElements, true);
- LittleEndian.putShort(getComplexData(), 2, (short) numberOfElements);
- }
-
- public short getSizeOfElements() {
- return (emptyComplexPart) ? 0 : LittleEndian.getShort( getComplexData(), 4 );
- }
-
- public void setSizeOfElements(int sizeOfElements) {
- if (emptyComplexPart) {
- return;
- }
- LittleEndian.putShort( getComplexData(), 4, (short) sizeOfElements );
-
- int expectedArraySize = getNumberOfElementsInArray() * getActualSizeOfElements(getSizeOfElements()) + FIXED_SIZE;
- // Keep just the first 6 bytes. The rest is no good to us anyway.
- resizeComplexData(expectedArraySize, 6);
- }
-
- public byte[] getElement(int index) {
- int actualSize = getActualSizeOfElements(getSizeOfElements());
- return IOUtils.safelyClone(getComplexData(), FIXED_SIZE + index * actualSize, actualSize, MAX_RECORD_LENGTH);
- }
-
- public void setElement(int index, byte[] element) {
- if (emptyComplexPart) {
- return;
- }
- int actualSize = getActualSizeOfElements(getSizeOfElements());
- System.arraycopy( element, 0, getComplexData(), FIXED_SIZE + index * actualSize, actualSize);
- }
-
- /**
- * We have this method because the way in which arrays in escher works
- * is screwed for seemly arbitrary reasons. While most properties are
- * fairly consistent and have a predictable array size, escher arrays
- * have special cases.
- *
- * @param data The data array containing the escher array information
- * @param offset The offset into the array to start reading from.
- * @return the number of bytes used by this complex property.
- */
- public int setArrayData(byte[] data, int offset) {
- if (emptyComplexPart) {
- resizeComplexData(0);
- } else {
- short numElements = LittleEndian.getShort(data, offset);
- // LittleEndian.getShort(data, offset + 2); // numReserved
- short sizeOfElements = LittleEndian.getShort(data, offset + 4);
-
- // the code here seems to depend on complexData already being
- // sized correctly via the constructor
- int cdLen = getComplexData().length;
- int arraySize = getActualSizeOfElements(sizeOfElements) * numElements;
- if (arraySize == cdLen) {
- // The stored data size in the simple block excludes the header size
- resizeComplexData(arraySize + 6, 0);
- sizeIncludesHeaderSize = false;
- }
- setComplexData(data, offset);
- }
- return getComplexData().length;
- }
-
- /**
- * Serializes the simple part of this property. ie the first 6 bytes.
- *
- * Needs special code to handle the case when the size doesn't
- * include the size of the header block
- */
- @Override
- public int serializeSimplePart(byte[] data, int pos) {
- LittleEndian.putShort(data, pos, getId());
- int recordSize = getComplexData().length;
- if (!sizeIncludesHeaderSize) {
- recordSize -= 6;
- }
- LittleEndian.putInt(data, pos + 2, recordSize);
- return 6;
- }
-
- /**
- * Sometimes the element size is stored as a negative number. We
- * negate it and shift it to get the real value.
- */
- private static int getActualSizeOfElements(short sizeOfElements) {
- if (sizeOfElements < 0) {
- return (short) ( ( -sizeOfElements ) >> 2 );
- }
- return sizeOfElements;
- }
-
- @Override
- public Iterator<byte[]> iterator() {
- return new Iterator<byte[]>(){
- int idx;
- @Override
- public boolean hasNext() {
- return (idx < getNumberOfElementsInArray());
- }
-
- @Override
- public byte[] next() {
- if (!hasNext()) {
- throw new NoSuchElementException();
- }
- return getElement(idx++);
- }
-
- @Override
- public void remove() {
- throw new UnsupportedOperationException("not yet implemented");
- }
- };
- }
-
- /**
- * @since POI 5.2.0
- */
- @Override
- public Spliterator<byte[]> spliterator() {
- return Spliterators.spliterator(iterator(), getNumberOfElementsInArray(), 0);
- }
-
- @Override
- public Map<String, Supplier<?>> getGenericProperties() {
- return GenericRecordUtil.getGenericProperties(
- "base", super::getGenericProperties,
- "numElements", this::getNumberOfElementsInArray,
- "numElementsInMemory", this::getNumberOfElementsInMemory,
- "sizeOfElements", this::getSizeOfElements,
- "elements", () -> StreamSupport.stream(spliterator(), false).collect(Collectors.toList())
- );
- }
- }
|