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.poifs.property;
019:
020: import java.io.*;
021:
022: import java.util.*;
023:
024: import org.apache.poi.hpsf.ClassID;
025:
026: import org.apache.poi.poifs.common.POIFSConstants;
027: import org.apache.poi.poifs.dev.POIFSViewable;
028: import org.apache.poi.util.ByteField;
029: import org.apache.poi.util.IntegerField;
030: import org.apache.poi.util.LittleEndianConsts;
031: import org.apache.poi.util.ShortField;
032:
033: /**
034: * This abstract base class is the ancestor of all classes
035: * implementing POIFS Property behavior.
036: *
037: * @author Marc Johnson (mjohnson at apache dot org)
038: */
039:
040: public abstract class Property implements Child, POIFSViewable {
041: static final private byte _default_fill = (byte) 0x00;
042: static final private int _name_size_offset = 0x40;
043: static final private int _max_name_length = (_name_size_offset / LittleEndianConsts.SHORT_SIZE) - 1;
044: static final protected int _NO_INDEX = -1;
045:
046: // useful offsets
047: static final private int _node_color_offset = 0x43;
048: static final private int _previous_property_offset = 0x44;
049: static final private int _next_property_offset = 0x48;
050: static final private int _child_property_offset = 0x4C;
051: static final private int _storage_clsid_offset = 0x50;
052: static final private int _user_flags_offset = 0x60;
053: static final private int _seconds_1_offset = 0x64;
054: static final private int _days_1_offset = 0x68;
055: static final private int _seconds_2_offset = 0x6C;
056: static final private int _days_2_offset = 0x70;
057: static final private int _start_block_offset = 0x74;
058: static final private int _size_offset = 0x78;
059:
060: // node colors
061: static final protected byte _NODE_BLACK = 1;
062: static final protected byte _NODE_RED = 0;
063:
064: // documents must be at least this size to be stored in big blocks
065: static final private int _big_block_minimum_bytes = 4096;
066: private String _name;
067: private ShortField _name_size;
068: private ByteField _property_type;
069: private ByteField _node_color;
070: private IntegerField _previous_property;
071: private IntegerField _next_property;
072: private IntegerField _child_property;
073: private ClassID _storage_clsid;
074: private IntegerField _user_flags;
075: private IntegerField _seconds_1;
076: private IntegerField _days_1;
077: private IntegerField _seconds_2;
078: private IntegerField _days_2;
079: private IntegerField _start_block;
080: private IntegerField _size;
081: private byte[] _raw_data;
082: private int _index;
083: private Child _next_child;
084: private Child _previous_child;
085:
086: /**
087: * Default constructor
088: */
089:
090: protected Property() {
091: _raw_data = new byte[POIFSConstants.PROPERTY_SIZE];
092: Arrays.fill(_raw_data, _default_fill);
093: _name_size = new ShortField(_name_size_offset);
094: _property_type = new ByteField(
095: PropertyConstants.PROPERTY_TYPE_OFFSET);
096: _node_color = new ByteField(_node_color_offset);
097: _previous_property = new IntegerField(
098: _previous_property_offset, _NO_INDEX, _raw_data);
099: _next_property = new IntegerField(_next_property_offset,
100: _NO_INDEX, _raw_data);
101: _child_property = new IntegerField(_child_property_offset,
102: _NO_INDEX, _raw_data);
103: _storage_clsid = new ClassID(_raw_data, _storage_clsid_offset);
104: _user_flags = new IntegerField(_user_flags_offset, 0, _raw_data);
105: _seconds_1 = new IntegerField(_seconds_1_offset, 0, _raw_data);
106: _days_1 = new IntegerField(_days_1_offset, 0, _raw_data);
107: _seconds_2 = new IntegerField(_seconds_2_offset, 0, _raw_data);
108: _days_2 = new IntegerField(_days_2_offset, 0, _raw_data);
109: _start_block = new IntegerField(_start_block_offset);
110: _size = new IntegerField(_size_offset, 0, _raw_data);
111: _index = _NO_INDEX;
112: setName("");
113: setNextChild(null);
114: setPreviousChild(null);
115: }
116:
117: /**
118: * Constructor from byte data
119: *
120: * @param index index number
121: * @param array byte data
122: * @param offset offset into byte data
123: */
124:
125: protected Property(final int index, final byte[] array,
126: final int offset) {
127: _raw_data = new byte[POIFSConstants.PROPERTY_SIZE];
128: System.arraycopy(array, offset, _raw_data, 0,
129: POIFSConstants.PROPERTY_SIZE);
130: _name_size = new ShortField(_name_size_offset, _raw_data);
131: _property_type = new ByteField(
132: PropertyConstants.PROPERTY_TYPE_OFFSET, _raw_data);
133: _node_color = new ByteField(_node_color_offset, _raw_data);
134: _previous_property = new IntegerField(
135: _previous_property_offset, _raw_data);
136: _next_property = new IntegerField(_next_property_offset,
137: _raw_data);
138: _child_property = new IntegerField(_child_property_offset,
139: _raw_data);
140: _storage_clsid = new ClassID(_raw_data, _storage_clsid_offset);
141: _user_flags = new IntegerField(_user_flags_offset, 0, _raw_data);
142: _seconds_1 = new IntegerField(_seconds_1_offset, _raw_data);
143: _days_1 = new IntegerField(_days_1_offset, _raw_data);
144: _seconds_2 = new IntegerField(_seconds_2_offset, _raw_data);
145: _days_2 = new IntegerField(_days_2_offset, _raw_data);
146: _start_block = new IntegerField(_start_block_offset, _raw_data);
147: _size = new IntegerField(_size_offset, _raw_data);
148: _index = index;
149: int name_length = (_name_size.get() / LittleEndianConsts.SHORT_SIZE) - 1;
150:
151: if (name_length < 1) {
152: _name = "";
153: } else {
154: char[] char_array = new char[name_length];
155: int name_offset = 0;
156:
157: for (int j = 0; j < name_length; j++) {
158: char_array[j] = (char) new ShortField(name_offset,
159: _raw_data).get();
160: name_offset += LittleEndianConsts.SHORT_SIZE;
161: }
162: _name = new String(char_array, 0, name_length);
163: }
164: _next_child = null;
165: _previous_child = null;
166: }
167:
168: /**
169: * Write the raw data to an OutputStream.
170: *
171: * @param stream the OutputStream to which the data should be
172: * written.
173: *
174: * @exception IOException on problems writing to the specified
175: * stream.
176: */
177:
178: public void writeData(final OutputStream stream) throws IOException {
179: stream.write(_raw_data);
180: }
181:
182: /**
183: * Set the start block for the document referred to by this
184: * Property.
185: *
186: * @param startBlock the start block index
187: */
188:
189: public void setStartBlock(final int startBlock) {
190: _start_block.set(startBlock, _raw_data);
191: }
192:
193: /**
194: * @return the start block
195: */
196:
197: public int getStartBlock() {
198: return _start_block.get();
199: }
200:
201: /**
202: * find out the document size
203: *
204: * @return size in bytes
205: */
206:
207: public int getSize() {
208: return _size.get();
209: }
210:
211: /**
212: * Based on the currently defined size, should this property use
213: * small blocks?
214: *
215: * @return true if the size is less than _big_block_minimum_bytes
216: */
217:
218: public boolean shouldUseSmallBlocks() {
219: return Property.isSmall(_size.get());
220: }
221:
222: /**
223: * does the length indicate a small document?
224: *
225: * @param length length in bytes
226: *
227: * @return true if the length is less than
228: * _big_block_minimum_bytes
229: */
230:
231: public static boolean isSmall(final int length) {
232: return length < _big_block_minimum_bytes;
233: }
234:
235: /**
236: * Get the name of this property
237: *
238: * @return property name as String
239: */
240:
241: public String getName() {
242: return _name;
243: }
244:
245: /**
246: * @return true if a directory type Property
247: */
248:
249: abstract public boolean isDirectory();
250:
251: /**
252: * Sets the storage clsid, which is the Class ID of a COM object which
253: * reads and writes this stream
254: * @return storage Class ID for this property stream
255: */
256: public ClassID getStorageClsid() {
257: return _storage_clsid;
258: }
259:
260: /**
261: * Set the name; silently truncates the name if it's too long.
262: *
263: * @param name the new name
264: */
265: protected final void setName(final String name) {
266: char[] char_array = name.toCharArray();
267: int limit = Math.min(char_array.length, _max_name_length);
268:
269: _name = new String(char_array, 0, limit);
270: short offset = 0;
271: int j = 0;
272:
273: for (; j < limit; j++) {
274: new ShortField(offset, (short) char_array[j], _raw_data);
275: offset += LittleEndianConsts.SHORT_SIZE;
276: }
277: for (; j < _max_name_length + 1; j++) {
278: new ShortField(offset, (short) 0, _raw_data);
279: offset += LittleEndianConsts.SHORT_SIZE;
280: }
281:
282: // double the count, and include the null at the end
283: _name_size.set(
284: (short) ((limit + 1) * LittleEndianConsts.SHORT_SIZE),
285: _raw_data);
286: }
287:
288: /**
289: * Sets the storage class ID for this property stream. This is the Class ID
290: * of the COM object which can read and write this property stream
291: * @param clsidStorage Storage Class ID
292: */
293: public void setStorageClsid(ClassID clsidStorage) {
294: _storage_clsid = clsidStorage;
295: if (clsidStorage == null) {
296: Arrays.fill(_raw_data, _storage_clsid_offset,
297: _storage_clsid_offset + ClassID.LENGTH, (byte) 0);
298: } else {
299: clsidStorage.write(_raw_data, _storage_clsid_offset);
300: }
301: }
302:
303: /**
304: * Set the property type. Makes no attempt to validate the value.
305: *
306: * @param propertyType the property type (root, file, directory)
307: */
308:
309: protected void setPropertyType(final byte propertyType) {
310: _property_type.set(propertyType, _raw_data);
311: }
312:
313: /**
314: * Set the node color.
315: *
316: * @param nodeColor the node color (red or black)
317: */
318:
319: protected void setNodeColor(final byte nodeColor) {
320: _node_color.set(nodeColor, _raw_data);
321: }
322:
323: /**
324: * Set the child property.
325: *
326: * @param child the child property's index in the Property Table
327: */
328:
329: protected void setChildProperty(final int child) {
330: _child_property.set(child, _raw_data);
331: }
332:
333: /**
334: * Get the child property (its index in the Property Table)
335: *
336: * @return child property index
337: */
338:
339: protected int getChildIndex() {
340: return _child_property.get();
341: }
342:
343: /**
344: * Set the size of the document associated with this Property
345: *
346: * @param size the size of the document, in bytes
347: */
348:
349: protected void setSize(final int size) {
350: _size.set(size, _raw_data);
351: }
352:
353: /**
354: * Set the index for this Property
355: *
356: * @param index this Property's index within its containing
357: * Property Table
358: */
359:
360: protected void setIndex(final int index) {
361: _index = index;
362: }
363:
364: /**
365: * get the index for this Property
366: *
367: * @return the index of this Property within its Property Table
368: */
369:
370: protected int getIndex() {
371: return _index;
372: }
373:
374: /**
375: * Perform whatever activities need to be performed prior to
376: * writing
377: */
378:
379: abstract protected void preWrite();
380:
381: /**
382: * get the next sibling
383: *
384: * @return index of next sibling
385: */
386:
387: int getNextChildIndex() {
388: return _next_property.get();
389: }
390:
391: /**
392: * get the previous sibling
393: *
394: * @return index of previous sibling
395: */
396:
397: int getPreviousChildIndex() {
398: return _previous_property.get();
399: }
400:
401: /**
402: * determine whether the specified index is valid
403: *
404: * @param index value to be checked
405: *
406: * @return true if the index is valid
407: */
408:
409: static boolean isValidIndex(int index) {
410: return index != _NO_INDEX;
411: }
412:
413: /* ********** START implementation of Child ********** */
414:
415: /**
416: * Get the next Child, if any
417: *
418: * @return the next Child; may return null
419: */
420:
421: public Child getNextChild() {
422: return _next_child;
423: }
424:
425: /**
426: * Get the previous Child, if any
427: *
428: * @return the previous Child; may return null
429: */
430:
431: public Child getPreviousChild() {
432: return _previous_child;
433: }
434:
435: /**
436: * Set the next Child
437: *
438: * @param child the new 'next' child; may be null, which has the
439: * effect of saying there is no 'next' child
440: */
441:
442: public void setNextChild(final Child child) {
443: _next_child = child;
444: _next_property.set((child == null) ? _NO_INDEX
445: : ((Property) child).getIndex(), _raw_data);
446: }
447:
448: /**
449: * Set the previous Child
450: *
451: * @param child the new 'previous' child; may be null, which has
452: * the effect of saying there is no 'previous' child
453: */
454:
455: public void setPreviousChild(final Child child) {
456: _previous_child = child;
457: _previous_property.set((child == null) ? _NO_INDEX
458: : ((Property) child).getIndex(), _raw_data);
459: }
460:
461: /* ********** END implementation of Child ********** */
462: /* ********** START begin implementation of POIFSViewable ********** */
463:
464: /**
465: * Get an array of objects, some of which may implement
466: * POIFSViewable
467: *
468: * @return an array of Object; may not be null, but may be empty
469: */
470:
471: public Object[] getViewableArray() {
472: Object[] results = new Object[5];
473:
474: results[0] = "Name = \"" + getName() + "\"";
475: results[1] = "Property Type = " + _property_type.get();
476: results[2] = "Node Color = " + _node_color.get();
477: long time = _days_1.get();
478:
479: time <<= 32;
480: time += ((long) _seconds_1.get()) & 0x0000FFFFL;
481: results[3] = "Time 1 = " + time;
482: time = _days_2.get();
483: time <<= 32;
484: time += ((long) _seconds_2.get()) & 0x0000FFFFL;
485: results[4] = "Time 2 = " + time;
486: return results;
487: }
488:
489: /**
490: * Get an Iterator of objects, some of which may implement
491: * POIFSViewable
492: *
493: * @return an Iterator; may not be null, but may have an empty
494: * back end store
495: */
496:
497: public Iterator getViewableIterator() {
498: return Collections.EMPTY_LIST.iterator();
499: }
500:
501: /**
502: * Give viewers a hint as to whether to call getViewableArray or
503: * getViewableIterator
504: *
505: * @return true if a viewer should call getViewableArray, false if
506: * a viewer should call getViewableIterator
507: */
508:
509: public boolean preferArray() {
510: return true;
511: }
512:
513: /**
514: * Provides a short description of the object, to be used when a
515: * POIFSViewable object has not provided its contents.
516: *
517: * @return short description
518: */
519:
520: public String getShortDescription() {
521: StringBuffer buffer = new StringBuffer();
522:
523: buffer.append("Property: \"").append(getName()).append("\"");
524: return buffer.toString();
525: }
526:
527: /* ********** END begin implementation of POIFSViewable ********** */
528: } // end public abstract class Property
|