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.

RecordContainer.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  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.hslf.record;
  16. import org.apache.poi.util.ArrayUtil;
  17. import org.apache.poi.util.LittleEndian;
  18. import org.apache.poi.hslf.util.MutableByteArrayOutputStream;
  19. import java.io.IOException;
  20. import java.io.OutputStream;
  21. import java.io.ByteArrayOutputStream;
  22. import java.util.ArrayList;
  23. /**
  24. * Abstract class which all container records will extend. Providers
  25. * helpful methods for writing child records out to disk
  26. *
  27. * @author Nick Burch
  28. */
  29. public abstract class RecordContainer extends Record
  30. {
  31. protected Record[] _children;
  32. private Boolean changingChildRecordsLock = Boolean.TRUE;
  33. /**
  34. * Return any children
  35. */
  36. public Record[] getChildRecords() { return _children; }
  37. /**
  38. * We're not an atom
  39. */
  40. public boolean isAnAtom() { return false; }
  41. /* ===============================================================
  42. * Internal Move Helpers
  43. * ===============================================================
  44. */
  45. /**
  46. * Finds the location of the given child record
  47. */
  48. private int findChildLocation(Record child) {
  49. // Synchronized as we don't want things changing
  50. // as we're doing our search
  51. synchronized(changingChildRecordsLock) {
  52. for(int i=0; i<_children.length; i++) {
  53. if(_children[i].equals(child)) {
  54. return i;
  55. }
  56. }
  57. }
  58. return -1;
  59. }
  60. /**
  61. * Adds a child record, at the very end.
  62. * @param newChild The child record to add
  63. */
  64. private void appendChild(Record newChild) {
  65. synchronized(changingChildRecordsLock) {
  66. // Copy over, and pop the child in at the end
  67. Record[] nc = new Record[(_children.length + 1)];
  68. System.arraycopy(_children, 0, nc, 0, _children.length);
  69. // Switch the arrays
  70. nc[_children.length] = newChild;
  71. _children = nc;
  72. }
  73. }
  74. /**
  75. * Adds the given new Child Record at the given location,
  76. * shuffling everything from there on down by one
  77. * @param newChild
  78. * @param position
  79. */
  80. private void addChildAt(Record newChild, int position) {
  81. synchronized(changingChildRecordsLock) {
  82. // Firstly, have the child added in at the end
  83. appendChild(newChild);
  84. // Now, have them moved to the right place
  85. moveChildRecords( (_children.length-1), position, 1 );
  86. }
  87. }
  88. /**
  89. * Moves <i>number</i> child records from <i>oldLoc</i>
  90. * to <i>newLoc</i>. Caller must have the changingChildRecordsLock
  91. * @param oldLoc the current location of the records to move
  92. * @param newLoc the new location for the records
  93. * @param number the number of records to move
  94. */
  95. private void moveChildRecords(int oldLoc, int newLoc, int number) {
  96. if(oldLoc == newLoc) { return; }
  97. if(number == 0) { return; }
  98. // Check that we're not asked to move too many
  99. if(oldLoc+number > _children.length) {
  100. throw new IllegalArgumentException("Asked to move more records than there are!");
  101. }
  102. // Do the move
  103. ArrayUtil.arrayMoveWithin(_children, oldLoc, newLoc, number);
  104. }
  105. /**
  106. * Finds the first child record of the given type,
  107. * or null if none of the child records are of the
  108. * given type. Does not descend.
  109. */
  110. public Record findFirstOfType(long type) {
  111. for(int i=0; i<_children.length; i++) {
  112. if(_children[i].getRecordType() == type) {
  113. return _children[i];
  114. }
  115. }
  116. return null;
  117. }
  118. /**
  119. * Remove a child record from this record container
  120. *
  121. * @param ch the child to remove
  122. * @return the removed record
  123. */
  124. public Record removeChild(Record ch) {
  125. Record rm = null;
  126. ArrayList<Record> lst = new ArrayList<Record>();
  127. for(Record r : _children) {
  128. if(r != ch) lst.add(r);
  129. else rm = r;
  130. }
  131. _children = lst.toArray(new Record[lst.size()]);
  132. return rm;
  133. }
  134. /* ===============================================================
  135. * External Move Methods
  136. * ===============================================================
  137. */
  138. /**
  139. * Add a new child record onto a record's list of children.
  140. */
  141. public void appendChildRecord(Record newChild) {
  142. synchronized(changingChildRecordsLock) {
  143. appendChild(newChild);
  144. }
  145. }
  146. /**
  147. * Adds the given Child Record after the supplied record
  148. * @param newChild
  149. * @param after
  150. */
  151. public void addChildAfter(Record newChild, Record after) {
  152. synchronized(changingChildRecordsLock) {
  153. // Decide where we're going to put it
  154. int loc = findChildLocation(after);
  155. if(loc == -1) {
  156. throw new IllegalArgumentException("Asked to add a new child after another record, but that record wasn't one of our children!");
  157. }
  158. // Add one place after the supplied record
  159. addChildAt(newChild, loc+1);
  160. }
  161. }
  162. /**
  163. * Adds the given Child Record before the supplied record
  164. * @param newChild
  165. * @param before
  166. */
  167. public void addChildBefore(Record newChild, Record before) {
  168. synchronized(changingChildRecordsLock) {
  169. // Decide where we're going to put it
  170. int loc = findChildLocation(before);
  171. if(loc == -1) {
  172. throw new IllegalArgumentException("Asked to add a new child before another record, but that record wasn't one of our children!");
  173. }
  174. // Add at the place of the supplied record
  175. addChildAt(newChild, loc);
  176. }
  177. }
  178. /**
  179. * Moves the given Child Record to before the supplied record
  180. */
  181. public void moveChildBefore(Record child, Record before) {
  182. moveChildrenBefore(child, 1, before);
  183. }
  184. /**
  185. * Moves the given Child Records to before the supplied record
  186. */
  187. public void moveChildrenBefore(Record firstChild, int number, Record before) {
  188. if(number < 1) { return; }
  189. synchronized(changingChildRecordsLock) {
  190. // Decide where we're going to put them
  191. int newLoc = findChildLocation(before);
  192. if(newLoc == -1) {
  193. throw new IllegalArgumentException("Asked to move children before another record, but that record wasn't one of our children!");
  194. }
  195. // Figure out where they are now
  196. int oldLoc = findChildLocation(firstChild);
  197. if(oldLoc == -1) {
  198. throw new IllegalArgumentException("Asked to move a record that wasn't a child!");
  199. }
  200. // Actually move
  201. moveChildRecords(oldLoc, newLoc, number);
  202. }
  203. }
  204. /**
  205. * Moves the given Child Records to after the supplied record
  206. */
  207. public void moveChildrenAfter(Record firstChild, int number, Record after) {
  208. if(number < 1) { return; }
  209. synchronized(changingChildRecordsLock) {
  210. // Decide where we're going to put them
  211. int newLoc = findChildLocation(after);
  212. if(newLoc == -1) {
  213. throw new IllegalArgumentException("Asked to move children before another record, but that record wasn't one of our children!");
  214. }
  215. // We actually want after this though
  216. newLoc++;
  217. // Figure out where they are now
  218. int oldLoc = findChildLocation(firstChild);
  219. if(oldLoc == -1) {
  220. throw new IllegalArgumentException("Asked to move a record that wasn't a child!");
  221. }
  222. // Actually move
  223. moveChildRecords(oldLoc, newLoc, number);
  224. }
  225. }
  226. /**
  227. * Set child records.
  228. *
  229. * @param records the new child records
  230. */
  231. public void setChildRecord(Record[] records) {
  232. this._children = records;
  233. }
  234. /* ===============================================================
  235. * External Serialisation Methods
  236. * ===============================================================
  237. */
  238. /**
  239. * Write out our header, and our children.
  240. * @param headerA the first byte of the header
  241. * @param headerB the second byte of the header
  242. * @param type the record type
  243. * @param children our child records
  244. * @param out the stream to write to
  245. */
  246. public void writeOut(byte headerA, byte headerB, long type, Record[] children, OutputStream out) throws IOException {
  247. // If we have a mutable output stream, take advantage of that
  248. if(out instanceof MutableByteArrayOutputStream) {
  249. MutableByteArrayOutputStream mout =
  250. (MutableByteArrayOutputStream)out;
  251. // Grab current size
  252. int oldSize = mout.getBytesWritten();
  253. // Write out our header, less the size
  254. mout.write(new byte[] {headerA,headerB});
  255. byte[] typeB = new byte[2];
  256. LittleEndian.putShort(typeB, 0, (short)type);
  257. mout.write(typeB);
  258. mout.write(new byte[4]);
  259. // Write out the children
  260. for(int i=0; i<children.length; i++) {
  261. children[i].writeOut(mout);
  262. }
  263. // Update our header with the size
  264. // Don't forget to knock 8 more off, since we don't include the
  265. // header in the size
  266. int length = mout.getBytesWritten() - oldSize - 8;
  267. byte[] size = new byte[4];
  268. LittleEndian.putInt(size,0,length);
  269. mout.overwrite(size, oldSize+4);
  270. } else {
  271. // Going to have to do it a slower way, because we have
  272. // to update the length come the end
  273. // Create a ByteArrayOutputStream to hold everything in
  274. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  275. // Write out our header, less the size
  276. baos.write(new byte[] {headerA,headerB});
  277. byte[] typeB = new byte[2];
  278. LittleEndian.putShort(typeB,0,(short)type);
  279. baos.write(typeB);
  280. baos.write(new byte[] {0,0,0,0});
  281. // Write out our children
  282. for(int i=0; i<children.length; i++) {
  283. children[i].writeOut(baos);
  284. }
  285. // Grab the bytes back
  286. byte[] toWrite = baos.toByteArray();
  287. // Update our header with the size
  288. // Don't forget to knock 8 more off, since we don't include the
  289. // header in the size
  290. LittleEndian.putInt(toWrite,4,(toWrite.length-8));
  291. // Write out the bytes
  292. out.write(toWrite);
  293. }
  294. }
  295. /**
  296. * Find the records that are parent-aware, and tell them who their parent is
  297. */
  298. public static void handleParentAwareRecords(RecordContainer br) {
  299. // Loop over child records, looking for interesting ones
  300. for (Record record : br.getChildRecords()) {
  301. // Tell parent aware records of their parent
  302. if (record instanceof ParentAwareRecord) {
  303. ((ParentAwareRecord) record).setParentRecord(br);
  304. }
  305. // Walk on down for the case of container records
  306. if (record instanceof RecordContainer) {
  307. handleParentAwareRecords((RecordContainer)record);
  308. }
  309. }
  310. }
  311. }