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.ddf;
019:
020: import org.apache.poi.util.LittleEndian;
021:
022: import java.io.PrintWriter;
023: import java.util.Collections;
024: import java.util.List;
025:
026: /**
027: * The base abstract record from which all escher records are defined. Subclasses will need
028: * to define methods for serialization/deserialization and for determining the record size.
029: *
030: * @author Glen Stampoultzis
031: */
032: abstract public class EscherRecord {
033: private short options;
034: private short recordId;
035:
036: /**
037: * Create a new instance
038: */
039: public EscherRecord() {
040: }
041:
042: /**
043: * Delegates to fillFields(byte[], int, EscherRecordFactory)
044: *
045: * @see #fillFields(byte[], int, org.apache.poi.ddf.EscherRecordFactory)
046: */
047: protected int fillFields(byte[] data, EscherRecordFactory f) {
048: return fillFields(data, 0, f);
049: }
050:
051: /**
052: * The contract of this method is to deserialize an escher record including
053: * it's children.
054: *
055: * @param data The byte array containing the serialized escher
056: * records.
057: * @param offset The offset into the byte array.
058: * @param recordFactory A factory for creating new escher records.
059: * @return The number of bytes written.
060: */
061: public abstract int fillFields(byte[] data, int offset,
062: EscherRecordFactory recordFactory);
063:
064: /**
065: * Reads the 8 byte header information and populates the <code>options</code>
066: * and <code>recordId</code> records.
067: *
068: * @param data the byte array to read from
069: * @param offset the offset to start reading from
070: * @return the number of bytes remaining in this record. This
071: * may include the children if this is a container.
072: */
073: protected int readHeader(byte[] data, int offset) {
074: EscherRecordHeader header = EscherRecordHeader.readHeader(data,
075: offset);
076: options = header.getOptions();
077: recordId = header.getRecordId();
078: return header.getRemainingBytes();
079: }
080:
081: /**
082: * Determine whether this is a container record by inspecting the option
083: * field.
084: * @return true is this is a container field.
085: */
086: public boolean isContainerRecord() {
087: return (options & (short) 0x000f) == (short) 0x000f;
088: }
089:
090: /**
091: * @return The options field for this record. All records have one.
092: */
093: public short getOptions() {
094: return options;
095: }
096:
097: /**
098: * Set the options this this record. Container records should have the
099: * last nibble set to 0xF.
100: */
101: public void setOptions(short options) {
102: this .options = options;
103: }
104:
105: /**
106: * Serializes to a new byte array. This is done by delegating to
107: * serialize(int, byte[]);
108: *
109: * @return the serialized record.
110: * @see #serialize(int, byte[])
111: */
112: public byte[] serialize() {
113: byte[] retval = new byte[getRecordSize()];
114:
115: serialize(0, retval);
116: return retval;
117: }
118:
119: /**
120: * Serializes to an existing byte array without serialization listener.
121: * This is done by delegating to serialize(int, byte[], EscherSerializationListener).
122: *
123: * @param offset the offset within the data byte array.
124: * @param data the data array to serialize to.
125: * @return The number of bytes written.
126: *
127: * @see #serialize(int, byte[], org.apache.poi.ddf.EscherSerializationListener)
128: */
129: public int serialize(int offset, byte[] data) {
130: return serialize(offset, data,
131: new NullEscherSerializationListener());
132: }
133:
134: /**
135: * Serializes the record to an existing byte array.
136: *
137: * @param offset the offset within the byte array
138: * @param data the data array to serialize to
139: * @param listener a listener for begin and end serialization events. This
140: * is useful because the serialization is
141: * hierarchical/recursive and sometimes you need to be able
142: * break into that.
143: * @return the number of bytes written.
144: */
145: public abstract int serialize(int offset, byte[] data,
146: EscherSerializationListener listener);
147:
148: /**
149: * Subclasses should effeciently return the number of bytes required to
150: * serialize the record.
151: *
152: * @return number of bytes
153: */
154: abstract public int getRecordSize();
155:
156: /**
157: * Return the current record id.
158: *
159: * @return The 16 bit record id.
160: */
161: public short getRecordId() {
162: return recordId;
163: }
164:
165: /**
166: * Sets the record id for this record.
167: */
168: public void setRecordId(short recordId) {
169: this .recordId = recordId;
170: }
171:
172: /**
173: * @return Returns the children of this record. By default this will
174: * be an empty list. EscherCotainerRecord is the only record
175: * that may contain children.
176: *
177: * @see EscherContainerRecord
178: */
179: public List getChildRecords() {
180: return Collections.EMPTY_LIST;
181: }
182:
183: /**
184: * Sets the child records for this record. By default this will throw
185: * an exception as only EscherContainerRecords may have children.
186: *
187: * @param childRecords Not used in base implementation.
188: */
189: public void setChildRecords(List childRecords) {
190: throw new IllegalArgumentException(
191: "This record does not support child records.");
192: }
193:
194: /**
195: * Escher records may need to be clonable in the future.
196: */
197: public Object clone() {
198: throw new RuntimeException("The class " + getClass().getName()
199: + " needs to define a clone method");
200: }
201:
202: /**
203: * Returns the indexed child record.
204: */
205: public EscherRecord getChild(int index) {
206: return (EscherRecord) getChildRecords().get(index);
207: }
208:
209: /**
210: * The display methods allows escher variables to print the record names
211: * according to their hierarchy.
212: *
213: * @param w The print writer to output to.
214: * @param indent The current indent level.
215: */
216: public void display(PrintWriter w, int indent) {
217: for (int i = 0; i < indent * 4; i++)
218: w.print(' ');
219: w.println(getRecordName());
220: }
221:
222: /**
223: * Subclasses should return the short name for this escher record.
224: */
225: public abstract String getRecordName();
226:
227: /**
228: * Returns the instance part of the option record.
229: *
230: * @return The instance part of the record
231: */
232: public short getInstance() {
233: return (short) (options >> 4);
234: }
235:
236: /**
237: * This class reads the standard escher header.
238: */
239: static class EscherRecordHeader {
240: private short options;
241: private short recordId;
242: private int remainingBytes;
243:
244: private EscherRecordHeader() {
245: }
246:
247: public static EscherRecordHeader readHeader(byte[] data,
248: int offset) {
249: EscherRecordHeader header = new EscherRecordHeader();
250: header.options = LittleEndian.getShort(data, offset);
251: header.recordId = LittleEndian.getShort(data, offset + 2);
252: header.remainingBytes = LittleEndian.getInt(data,
253: offset + 4);
254: return header;
255: }
256:
257: public short getOptions() {
258: return options;
259: }
260:
261: public short getRecordId() {
262: return recordId;
263: }
264:
265: public int getRemainingBytes() {
266: return remainingBytes;
267: }
268:
269: public String toString() {
270: return "EscherRecordHeader{" + "options=" + options
271: + ", recordId=" + recordId + ", remainingBytes="
272: + remainingBytes + "}";
273: }
274:
275: }
276:
277: }
|