001: /* ====================================================================
002: Licensed to the Apache Software Foundation (ASF) under one or more
003: contributor license agreements. See the NOTICE file distributed with
004: this work for additional information regarding copyright ownership.
005: The ASF licenses this file to You under the Apache License, Version 2.0
006: (the "License"); you may not use this file except in compliance with
007: the License. You may obtain a copy of the License at
008:
009: http://www.apache.org/licenses/LICENSE-2.0
010:
011: Unless required by applicable law or agreed to in writing, software
012: distributed under the License is distributed on an "AS IS" BASIS,
013: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: See the License for the specific language governing permissions and
015: limitations under the License.
016: ==================================================================== */
017:
018: package org.apache.poi.hslf.record;
019:
020: import org.apache.poi.util.ArrayUtil;
021: import org.apache.poi.util.LittleEndian;
022: import org.apache.poi.hslf.util.MutableByteArrayOutputStream;
023:
024: import java.io.IOException;
025: import java.io.OutputStream;
026: import java.io.ByteArrayOutputStream;
027:
028: /**
029: * Abstract class which all container records will extend. Providers
030: * helpful methods for writing child records out to disk
031: *
032: * @author Nick Burch
033: */
034:
035: public abstract class RecordContainer extends Record {
036: protected Record[] _children;
037: private Boolean changingChildRecordsLock = new Boolean(true);
038:
039: /**
040: * Return any children
041: */
042: public Record[] getChildRecords() {
043: return _children;
044: }
045:
046: /**
047: * We're not an atom
048: */
049: public boolean isAnAtom() {
050: return false;
051: }
052:
053: /* ===============================================================
054: * Internal Move Helpers
055: * ===============================================================
056: */
057:
058: /**
059: * Finds the location of the given child record
060: */
061: private int findChildLocation(Record child) {
062: // Synchronized as we don't want things changing
063: // as we're doing our search
064: synchronized (changingChildRecordsLock) {
065: for (int i = 0; i < _children.length; i++) {
066: if (_children[i].equals(child)) {
067: return i;
068: }
069: }
070: }
071: return -1;
072: }
073:
074: /**
075: * Adds a child record, at the very end.
076: * @param newChild The child record to add
077: */
078: private void appendChild(Record newChild) {
079: synchronized (changingChildRecordsLock) {
080: // Copy over, and pop the child in at the end
081: Record[] nc = new Record[(_children.length + 1)];
082: System.arraycopy(_children, 0, nc, 0, _children.length);
083: // Switch the arrays
084: nc[_children.length] = newChild;
085: _children = nc;
086: }
087: }
088:
089: /**
090: * Adds the given new Child Record at the given location,
091: * shuffling everything from there on down by one
092: * @param newChild
093: * @param position
094: */
095: private void addChildAt(Record newChild, int position) {
096: synchronized (changingChildRecordsLock) {
097: // Firstly, have the child added in at the end
098: appendChild(newChild);
099:
100: // Now, have them moved to the right place
101: moveChildRecords((_children.length - 1), position, 1);
102: }
103: }
104:
105: /**
106: * Moves <i>number</i> child records from <i>oldLoc</i>
107: * to <i>newLoc</i>. Caller must have the changingChildRecordsLock
108: * @param oldLoc the current location of the records to move
109: * @param newLoc the new location for the records
110: * @param number the number of records to move
111: */
112: private void moveChildRecords(int oldLoc, int newLoc, int number) {
113: if (oldLoc == newLoc) {
114: return;
115: }
116: if (number == 0) {
117: return;
118: }
119:
120: // Check that we're not asked to move too many
121: if (oldLoc + number > _children.length) {
122: throw new IllegalArgumentException(
123: "Asked to move more records than there are!");
124: }
125:
126: // Do the move
127: ArrayUtil.arrayMoveWithin(_children, oldLoc, newLoc, number);
128: }
129:
130: /* ===============================================================
131: * External Move Methods
132: * ===============================================================
133: */
134:
135: /**
136: * Add a new child record onto a record's list of children.
137: */
138: public void appendChildRecord(Record newChild) {
139: synchronized (changingChildRecordsLock) {
140: appendChild(newChild);
141: }
142: }
143:
144: /**
145: * Adds the given Child Record after the supplied record
146: * @param newChild
147: * @param after
148: */
149: public void addChildAfter(Record newChild, Record after) {
150: synchronized (changingChildRecordsLock) {
151: // Decide where we're going to put it
152: int loc = findChildLocation(after);
153: if (loc == -1) {
154: throw new IllegalArgumentException(
155: "Asked to add a new child after another record, but that record wasn't one of our children!");
156: }
157:
158: // Add one place after the supplied record
159: addChildAt(newChild, loc + 1);
160: }
161: }
162:
163: /**
164: * Adds the given Child Record before the supplied record
165: * @param newChild
166: * @param before
167: */
168: public void addChildBefore(Record newChild, Record before) {
169: synchronized (changingChildRecordsLock) {
170: // Decide where we're going to put it
171: int loc = findChildLocation(before);
172: if (loc == -1) {
173: throw new IllegalArgumentException(
174: "Asked to add a new child before another record, but that record wasn't one of our children!");
175: }
176:
177: // Add at the place of the supplied record
178: addChildAt(newChild, loc);
179: }
180: }
181:
182: /**
183: * Moves the given Child Record to before the supplied record
184: */
185: public void moveChildBefore(Record child, Record before) {
186: moveChildrenBefore(child, 1, before);
187: }
188:
189: /**
190: * Moves the given Child Records to before the supplied record
191: */
192: public void moveChildrenBefore(Record firstChild, int number,
193: Record before) {
194: if (number < 1) {
195: return;
196: }
197:
198: synchronized (changingChildRecordsLock) {
199: // Decide where we're going to put them
200: int newLoc = findChildLocation(before);
201: if (newLoc == -1) {
202: throw new IllegalArgumentException(
203: "Asked to move children before another record, but that record wasn't one of our children!");
204: }
205:
206: // Figure out where they are now
207: int oldLoc = findChildLocation(firstChild);
208: if (oldLoc == -1) {
209: throw new IllegalArgumentException(
210: "Asked to move a record that wasn't a child!");
211: }
212:
213: // Actually move
214: moveChildRecords(oldLoc, newLoc, number);
215: }
216: }
217:
218: /**
219: * Moves the given Child Records to after the supplied record
220: */
221: public void moveChildrenAfter(Record firstChild, int number,
222: Record after) {
223: if (number < 1) {
224: return;
225: }
226:
227: synchronized (changingChildRecordsLock) {
228: // Decide where we're going to put them
229: int newLoc = findChildLocation(after);
230: if (newLoc == -1) {
231: throw new IllegalArgumentException(
232: "Asked to move children before another record, but that record wasn't one of our children!");
233: }
234: // We actually want after this though
235: newLoc++;
236:
237: // Figure out where they are now
238: int oldLoc = findChildLocation(firstChild);
239: if (oldLoc == -1) {
240: throw new IllegalArgumentException(
241: "Asked to move a record that wasn't a child!");
242: }
243:
244: // Actually move
245: moveChildRecords(oldLoc, newLoc, number);
246: }
247: }
248:
249: /* ===============================================================
250: * External Serialisation Methods
251: * ===============================================================
252: */
253:
254: /**
255: * Write out our header, and our children.
256: * @param headerA the first byte of the header
257: * @param headerB the second byte of the header
258: * @param type the record type
259: * @param children our child records
260: * @param out the stream to write to
261: */
262: public void writeOut(byte headerA, byte headerB, long type,
263: Record[] children, OutputStream out) throws IOException {
264: // If we have a mutable output stream, take advantage of that
265: if (out instanceof MutableByteArrayOutputStream) {
266: MutableByteArrayOutputStream mout = (MutableByteArrayOutputStream) out;
267:
268: // Grab current size
269: int oldSize = mout.getBytesWritten();
270:
271: // Write out our header, less the size
272: mout.write(new byte[] { headerA, headerB });
273: byte[] typeB = new byte[2];
274: LittleEndian.putShort(typeB, (short) type);
275: mout.write(typeB);
276: mout.write(new byte[4]);
277:
278: // Write out the children
279: for (int i = 0; i < children.length; i++) {
280: children[i].writeOut(mout);
281: }
282:
283: // Update our header with the size
284: // Don't forget to knock 8 more off, since we don't include the
285: // header in the size
286: int length = mout.getBytesWritten() - oldSize - 8;
287: byte[] size = new byte[4];
288: LittleEndian.putInt(size, 0, length);
289: mout.overwrite(size, oldSize + 4);
290: } else {
291: // Going to have to do it a slower way, because we have
292: // to update the length come the end
293:
294: // Create a ByteArrayOutputStream to hold everything in
295: ByteArrayOutputStream baos = new ByteArrayOutputStream();
296:
297: // Write out our header, less the size
298: baos.write(new byte[] { headerA, headerB });
299: byte[] typeB = new byte[2];
300: LittleEndian.putShort(typeB, (short) type);
301: baos.write(typeB);
302: baos.write(new byte[] { 0, 0, 0, 0 });
303:
304: // Write out our children
305: for (int i = 0; i < children.length; i++) {
306: children[i].writeOut(baos);
307: }
308:
309: // Grab the bytes back
310: byte[] toWrite = baos.toByteArray();
311:
312: // Update our header with the size
313: // Don't forget to knock 8 more off, since we don't include the
314: // header in the size
315: LittleEndian.putInt(toWrite, 4, (toWrite.length - 8));
316:
317: // Write out the bytes
318: out.write(toWrite);
319: }
320: }
321: }
|