You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

EscherArrayProperty.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.ddf;
  16. import java.util.Iterator;
  17. import java.util.Map;
  18. import java.util.NoSuchElementException;
  19. import java.util.Spliterator;
  20. import java.util.Spliterators;
  21. import java.util.function.Supplier;
  22. import java.util.stream.Collectors;
  23. import java.util.stream.StreamSupport;
  24. import org.apache.poi.util.GenericRecordUtil;
  25. import org.apache.poi.util.IOUtils;
  26. import org.apache.poi.util.Internal;
  27. import org.apache.poi.util.LittleEndian;
  28. import org.apache.poi.util.Removal;
  29. /**
  30. * Escher array properties are the most weird construction ever invented
  31. * with all sorts of special cases. I'm hopeful I've got them all.
  32. */
  33. public final class EscherArrayProperty extends EscherComplexProperty implements Iterable<byte[]> {
  34. // arbitrarily selected; may need to increase
  35. private static final int DEFAULT_MAX_RECORD_LENGTH = 100_000;
  36. private static int MAX_RECORD_LENGTH = DEFAULT_MAX_RECORD_LENGTH;
  37. /**
  38. * The size of the header that goes at the start of the array, before the data
  39. */
  40. private static final int FIXED_SIZE = 3 * 2;
  41. /**
  42. * Normally, the size recorded in the simple data (for the complex data) includes the size of the header.
  43. * There are a few cases when it doesn't though...
  44. */
  45. private boolean sizeIncludesHeaderSize = true;
  46. /**
  47. * When reading a property from data stream remember if the complex part is empty and set this flag.
  48. */
  49. private final boolean emptyComplexPart;
  50. /**
  51. * @param length the max record length allowed for EscherArrayProperty
  52. */
  53. public static void setMaxRecordLength(int length) {
  54. MAX_RECORD_LENGTH = length;
  55. }
  56. /**
  57. * @return the max record length allowed for EscherArrayProperty
  58. */
  59. public static int getMaxRecordLength() {
  60. return MAX_RECORD_LENGTH;
  61. }
  62. /**
  63. * Create an instance of an escher array property.
  64. * This constructor can be used to create emptyComplexParts with a complexSize = 0.
  65. * Preferably use {@link #EscherArrayProperty(EscherPropertyTypes, boolean, int)} with {@link #setComplexData(byte[])}.
  66. *
  67. * @param id The id consists of the property number, a flag indicating whether this is a blip id and a flag
  68. * indicating that this is a complex property.
  69. * @param complexSize the data size
  70. */
  71. @Internal
  72. public EscherArrayProperty(short id, int complexSize) {
  73. // this is called by EscherPropertyFactory which happens to call it with empty parts
  74. // if a part is initial empty, don't allow it to contain something again
  75. super(id, complexSize);
  76. emptyComplexPart = (complexSize == 0);
  77. }
  78. /**
  79. * Create an instance of an escher array property.
  80. * This constructor defaults to a 6 bytes header if the complexData is null or byte[0].
  81. *
  82. * @param propertyNumber the property number part of the property id
  83. * @param isBlipId {@code true}, if it references a blip
  84. * @param complexData the data
  85. *
  86. * @deprecated use {@link #EscherArrayProperty(EscherPropertyTypes, boolean, int)} and {@link #setComplexData(byte[])}
  87. */
  88. @Deprecated
  89. @Removal(version = "5.0.0")
  90. public EscherArrayProperty(short propertyNumber, boolean isBlipId, byte[] complexData) {
  91. // this is called by user code, if the complexData is empty/null, allocate a space for a valid header
  92. // be aware, that there are complex data areas with less than 6 bytes
  93. this((short)(propertyNumber | (isBlipId ? IS_BLIP : 0)), safeSize(complexData == null ? 0 : complexData.length));
  94. setComplexData(complexData);
  95. }
  96. /**
  97. * Create an instance of an escher array property.
  98. * This constructor defaults to a 6 bytes header if the complexSize is 0.
  99. *
  100. * @param type the property type of the property id
  101. * @param isBlipId {@code true}, if it references a blip
  102. * @param complexSize the data size
  103. */
  104. public EscherArrayProperty(EscherPropertyTypes type, boolean isBlipId, int complexSize) {
  105. this((short)(type.propNumber | (isBlipId ? IS_BLIP : 0)), safeSize(complexSize));
  106. }
  107. private static int safeSize(int complexSize) {
  108. // when called by user code, fix the size to be valid for the header
  109. return complexSize == 0 ? 6 : complexSize;
  110. }
  111. public int getNumberOfElementsInArray() {
  112. return (emptyComplexPart) ? 0 : LittleEndian.getUShort(getComplexData(), 0);
  113. }
  114. public void setNumberOfElementsInArray(int numberOfElements) {
  115. if (emptyComplexPart) {
  116. return;
  117. }
  118. rewriteArray(numberOfElements, false);
  119. LittleEndian.putShort(getComplexData(), 0, (short) numberOfElements);
  120. }
  121. private void rewriteArray(int numberOfElements, boolean copyToNewLen) {
  122. int expectedArraySize = numberOfElements * getActualSizeOfElements(getSizeOfElements()) + FIXED_SIZE;
  123. resizeComplexData(expectedArraySize, copyToNewLen ? expectedArraySize : getComplexData().length);
  124. }
  125. public int getNumberOfElementsInMemory() {
  126. return (emptyComplexPart) ? 0 : LittleEndian.getUShort(getComplexData(), 2);
  127. }
  128. public void setNumberOfElementsInMemory(int numberOfElements) {
  129. if (emptyComplexPart) {
  130. return;
  131. }
  132. rewriteArray(numberOfElements, true);
  133. LittleEndian.putShort(getComplexData(), 2, (short) numberOfElements);
  134. }
  135. public short getSizeOfElements() {
  136. return (emptyComplexPart) ? 0 : LittleEndian.getShort( getComplexData(), 4 );
  137. }
  138. public void setSizeOfElements(int sizeOfElements) {
  139. if (emptyComplexPart) {
  140. return;
  141. }
  142. LittleEndian.putShort( getComplexData(), 4, (short) sizeOfElements );
  143. int expectedArraySize = getNumberOfElementsInArray() * getActualSizeOfElements(getSizeOfElements()) + FIXED_SIZE;
  144. // Keep just the first 6 bytes. The rest is no good to us anyway.
  145. resizeComplexData(expectedArraySize, 6);
  146. }
  147. public byte[] getElement(int index) {
  148. int actualSize = getActualSizeOfElements(getSizeOfElements());
  149. return IOUtils.safelyClone(getComplexData(), FIXED_SIZE + index * actualSize, actualSize, MAX_RECORD_LENGTH);
  150. }
  151. public void setElement(int index, byte[] element) {
  152. if (emptyComplexPart) {
  153. return;
  154. }
  155. int actualSize = getActualSizeOfElements(getSizeOfElements());
  156. System.arraycopy( element, 0, getComplexData(), FIXED_SIZE + index * actualSize, actualSize);
  157. }
  158. /**
  159. * We have this method because the way in which arrays in escher works
  160. * is screwed for seemly arbitrary reasons. While most properties are
  161. * fairly consistent and have a predictable array size, escher arrays
  162. * have special cases.
  163. *
  164. * @param data The data array containing the escher array information
  165. * @param offset The offset into the array to start reading from.
  166. * @return the number of bytes used by this complex property.
  167. */
  168. public int setArrayData(byte[] data, int offset) {
  169. if (emptyComplexPart) {
  170. resizeComplexData(0);
  171. } else {
  172. short numElements = LittleEndian.getShort(data, offset);
  173. // LittleEndian.getShort(data, offset + 2); // numReserved
  174. short sizeOfElements = LittleEndian.getShort(data, offset + 4);
  175. // the code here seems to depend on complexData already being
  176. // sized correctly via the constructor
  177. int cdLen = getComplexData().length;
  178. int arraySize = getActualSizeOfElements(sizeOfElements) * numElements;
  179. if (arraySize == cdLen) {
  180. // The stored data size in the simple block excludes the header size
  181. resizeComplexData(arraySize + 6, 0);
  182. sizeIncludesHeaderSize = false;
  183. }
  184. setComplexData(data, offset);
  185. }
  186. return getComplexData().length;
  187. }
  188. /**
  189. * Serializes the simple part of this property. ie the first 6 bytes.
  190. *
  191. * Needs special code to handle the case when the size doesn't
  192. * include the size of the header block
  193. */
  194. @Override
  195. public int serializeSimplePart(byte[] data, int pos) {
  196. LittleEndian.putShort(data, pos, getId());
  197. int recordSize = getComplexData().length;
  198. if (!sizeIncludesHeaderSize) {
  199. recordSize -= 6;
  200. }
  201. LittleEndian.putInt(data, pos + 2, recordSize);
  202. return 6;
  203. }
  204. /**
  205. * Sometimes the element size is stored as a negative number. We
  206. * negate it and shift it to get the real value.
  207. */
  208. private static int getActualSizeOfElements(short sizeOfElements) {
  209. if (sizeOfElements < 0) {
  210. return (short) ( ( -sizeOfElements ) >> 2 );
  211. }
  212. return sizeOfElements;
  213. }
  214. @Override
  215. public Iterator<byte[]> iterator() {
  216. return new Iterator<byte[]>(){
  217. int idx;
  218. @Override
  219. public boolean hasNext() {
  220. return (idx < getNumberOfElementsInArray());
  221. }
  222. @Override
  223. public byte[] next() {
  224. if (!hasNext()) {
  225. throw new NoSuchElementException();
  226. }
  227. return getElement(idx++);
  228. }
  229. @Override
  230. public void remove() {
  231. throw new UnsupportedOperationException("not yet implemented");
  232. }
  233. };
  234. }
  235. /**
  236. * @since POI 5.2.0
  237. */
  238. @Override
  239. public Spliterator<byte[]> spliterator() {
  240. return Spliterators.spliterator(iterator(), getNumberOfElementsInArray(), 0);
  241. }
  242. @Override
  243. public Map<String, Supplier<?>> getGenericProperties() {
  244. return GenericRecordUtil.getGenericProperties(
  245. "base", super::getGenericProperties,
  246. "numElements", this::getNumberOfElementsInArray,
  247. "numElementsInMemory", this::getNumberOfElementsInMemory,
  248. "sizeOfElements", this::getSizeOfElements,
  249. "elements", () -> StreamSupport.stream(spliterator(), false).collect(Collectors.toList())
  250. );
  251. }
  252. }