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.hpsf;
019:
020: import java.io.ByteArrayOutputStream;
021: import java.io.IOException;
022: import java.io.OutputStream;
023: import java.util.Collections;
024: import java.util.Comparator;
025: import java.util.Date;
026: import java.util.Iterator;
027: import java.util.LinkedList;
028: import java.util.List;
029: import java.util.ListIterator;
030: import java.util.Map;
031:
032: import org.apache.poi.hpsf.wellknown.PropertyIDMap;
033: import org.apache.poi.util.LittleEndian;
034:
035: /**
036: * <p>Adds writing capability to the {@link Section} class.</p>
037: *
038: * <p>Please be aware that this class' functionality will be merged into the
039: * {@link Section} class at a later time, so the API will change.</p>
040: *
041: * @version $Id: MutableSection.java 489730 2006-12-22 19:18:16Z bayard $
042: * @since 2002-02-20
043: */
044: public class MutableSection extends Section {
045: /**
046: * <p>If the "dirty" flag is true, the section's size must be
047: * (re-)calculated before the section is written.</p>
048: */
049: private boolean dirty = true;
050:
051: /**
052: * <p>List to assemble the properties. Unfortunately a wrong
053: * decision has been taken when specifying the "properties" field
054: * as an Property[]. It should have been a {@link java.util.List}.</p>
055: */
056: private List preprops;
057:
058: /**
059: * <p>Contains the bytes making out the section. This byte array is
060: * established when the section's size is calculated and can be reused
061: * later. It is valid only if the "dirty" flag is false.</p>
062: */
063: private byte[] sectionBytes;
064:
065: /**
066: * <p>Creates an empty mutable section.</p>
067: */
068: public MutableSection() {
069: dirty = true;
070: formatID = null;
071: offset = -1;
072: preprops = new LinkedList();
073: }
074:
075: /**
076: * <p>Constructs a <code>MutableSection</code> by doing a deep copy of an
077: * existing <code>Section</code>. All nested <code>Property</code>
078: * instances, will be their mutable counterparts in the new
079: * <code>MutableSection</code>.</p>
080: *
081: * @param s The section set to copy
082: */
083: public MutableSection(final Section s) {
084: setFormatID(s.getFormatID());
085: final Property[] pa = s.getProperties();
086: final MutableProperty[] mpa = new MutableProperty[pa.length];
087: for (int i = 0; i < pa.length; i++)
088: mpa[i] = new MutableProperty(pa[i]);
089: setProperties(mpa);
090: setDictionary(s.getDictionary());
091: }
092:
093: /**
094: * <p>Sets the section's format ID.</p>
095: *
096: * @param formatID The section's format ID
097: *
098: * @see #setFormatID(byte[])
099: * @see Section#getFormatID
100: */
101: public void setFormatID(final ClassID formatID) {
102: this .formatID = formatID;
103: }
104:
105: /**
106: * <p>Sets the section's format ID.</p>
107: *
108: * @param formatID The section's format ID as a byte array. It components
109: * are in big-endian format.
110: *
111: * @see #setFormatID(ClassID)
112: * @see Section#getFormatID
113: */
114: public void setFormatID(final byte[] formatID) {
115: ClassID fid = getFormatID();
116: if (fid == null) {
117: fid = new ClassID();
118: setFormatID(fid);
119: }
120: fid.setBytes(formatID);
121: }
122:
123: /**
124: * <p>Sets this section's properties. Any former values are overwritten.</p>
125: *
126: * @param properties This section's new properties.
127: */
128: public void setProperties(final Property[] properties) {
129: this .properties = properties;
130: preprops = new LinkedList();
131: for (int i = 0; i < properties.length; i++)
132: preprops.add(properties[i]);
133: dirty = true;
134: }
135:
136: /**
137: * <p>Sets the string value of the property with the specified ID.</p>
138: *
139: * @param id The property's ID
140: * @param value The property's value. It will be written as a Unicode
141: * string.
142: *
143: * @see #setProperty(int, long, Object)
144: * @see #getProperty
145: */
146: public void setProperty(final int id, final String value) {
147: setProperty(id, Variant.VT_LPWSTR, value);
148: dirty = true;
149: }
150:
151: /**
152: * <p>Sets the int value of the property with the specified ID.</p>
153: *
154: * @param id The property's ID
155: * @param value The property's value.
156: *
157: * @see #setProperty(int, long, Object)
158: * @see #getProperty
159: */
160: public void setProperty(final int id, final int value) {
161: setProperty(id, Variant.VT_I4, new Integer(value));
162: dirty = true;
163: }
164:
165: /**
166: * <p>Sets the long value of the property with the specified ID.</p>
167: *
168: * @param id The property's ID
169: * @param value The property's value.
170: *
171: * @see #setProperty(int, long, Object)
172: * @see #getProperty
173: */
174: public void setProperty(final int id, final long value) {
175: setProperty(id, Variant.VT_I8, new Long(value));
176: dirty = true;
177: }
178:
179: /**
180: * <p>Sets the boolean value of the property with the specified ID.</p>
181: *
182: * @param id The property's ID
183: * @param value The property's value.
184: *
185: * @see #setProperty(int, long, Object)
186: * @see #getProperty
187: */
188: public void setProperty(final int id, final boolean value) {
189: setProperty(id, Variant.VT_BOOL, new Boolean(value));
190: dirty = true;
191: }
192:
193: /**
194: * <p>Sets the value and the variant type of the property with the
195: * specified ID. If a property with this ID is not yet present in
196: * the section, it will be added. An already present property with
197: * the specified ID will be overwritten. A default mapping will be
198: * used to choose the property's type.</p>
199: *
200: * @param id The property's ID.
201: * @param variantType The property's variant type.
202: * @param value The property's value.
203: *
204: * @see #setProperty(int, String)
205: * @see #getProperty
206: * @see Variant
207: */
208: public void setProperty(final int id, final long variantType,
209: final Object value) {
210: final MutableProperty p = new MutableProperty();
211: p.setID(id);
212: p.setType(variantType);
213: p.setValue(value);
214: setProperty(p);
215: dirty = true;
216: }
217:
218: /**
219: * <p>Sets a property.</p>
220: *
221: * @param p The property to be set.
222: *
223: * @see #setProperty(int, long, Object)
224: * @see #getProperty
225: * @see Variant
226: */
227: public void setProperty(final Property p) {
228: final long id = p.getID();
229: removeProperty(id);
230: preprops.add(p);
231: dirty = true;
232: }
233:
234: /**
235: * <p>Removes a property.</p>
236: *
237: * @param id The ID of the property to be removed
238: */
239: public void removeProperty(final long id) {
240: for (final Iterator i = preprops.iterator(); i.hasNext();)
241: if (((Property) i.next()).getID() == id) {
242: i.remove();
243: break;
244: }
245: dirty = true;
246: }
247:
248: /**
249: * <p>Sets the value of the boolean property with the specified
250: * ID.</p>
251: *
252: * @param id The property's ID
253: * @param value The property's value
254: *
255: * @see #setProperty(int, long, Object)
256: * @see #getProperty
257: * @see Variant
258: */
259: protected void setPropertyBooleanValue(final int id,
260: final boolean value) {
261: setProperty(id, Variant.VT_BOOL, new Boolean(value));
262: }
263:
264: /**
265: * <p>Returns the section's size.</p>
266: *
267: * @return the section's size.
268: */
269: public int getSize() {
270: if (dirty) {
271: try {
272: size = calcSize();
273: dirty = false;
274: } catch (HPSFRuntimeException ex) {
275: throw ex;
276: } catch (Exception ex) {
277: throw new HPSFRuntimeException(ex);
278: }
279: }
280: return size;
281: }
282:
283: /**
284: * <p>Calculates the section's size. It is the sum of the lengths of the
285: * section's header (8), the properties list (16 times the number of
286: * properties) and the properties themselves.</p>
287: *
288: * @return the section's length in bytes.
289: * @throws WritingNotSupportedException
290: * @throws IOException
291: */
292: private int calcSize() throws WritingNotSupportedException,
293: IOException {
294: final ByteArrayOutputStream out = new ByteArrayOutputStream();
295: write(out);
296: out.close();
297: /* Pad to multiple of 4 bytes so that even the Windows shell (explorer)
298: * shows custom properties. */
299: sectionBytes = Util.pad4(out.toByteArray());
300: return sectionBytes.length;
301: }
302:
303: /**
304: * <p>Writes this section into an output stream.</p>
305: *
306: * <p>Internally this is done by writing into three byte array output
307: * streams: one for the properties, one for the property list and one for
308: * the section as such. The two former are appended to the latter when they
309: * have received all their data.</p>
310: *
311: * @param out The stream to write into.
312: *
313: * @return The number of bytes written, i.e. the section's size.
314: * @exception IOException if an I/O error occurs
315: * @exception WritingNotSupportedException if HPSF does not yet support
316: * writing a property's variant type.
317: */
318: public int write(final OutputStream out)
319: throws WritingNotSupportedException, IOException {
320: /* Check whether we have already generated the bytes making out the
321: * section. */
322: if (!dirty && sectionBytes != null) {
323: out.write(sectionBytes);
324: return sectionBytes.length;
325: }
326:
327: /* The properties are written to this stream. */
328: final ByteArrayOutputStream propertyStream = new ByteArrayOutputStream();
329:
330: /* The property list is established here. After each property that has
331: * been written to "propertyStream", a property list entry is written to
332: * "propertyListStream". */
333: final ByteArrayOutputStream propertyListStream = new ByteArrayOutputStream();
334:
335: /* Maintain the current position in the list. */
336: int position = 0;
337:
338: /* Increase the position variable by the size of the property list so
339: * that it points behind the property list and to the beginning of the
340: * properties themselves. */
341: position += 2 * LittleEndian.INT_SIZE + getPropertyCount() * 2
342: * LittleEndian.INT_SIZE;
343:
344: /* Writing the section's dictionary it tricky. If there is a dictionary
345: * (property 0) the codepage property (property 1) must be set, too. */
346: int codepage = -1;
347: if (getProperty(PropertyIDMap.PID_DICTIONARY) != null) {
348: final Object p1 = getProperty(PropertyIDMap.PID_CODEPAGE);
349: if (p1 != null) {
350: if (!(p1 instanceof Integer))
351: throw new IllegalPropertySetDataException(
352: "The codepage property (ID = 1) must be an "
353: + "Integer object.");
354: } else
355: /* Warning: The codepage property is not set although a
356: * dictionary is present. In order to cope with this problem we
357: * add the codepage property and set it to Unicode. */
358: setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
359: new Integer(Constants.CP_UNICODE));
360: codepage = getCodepage();
361: }
362:
363: /* Sort the property list by their property IDs: */
364: Collections.sort(preprops, new Comparator() {
365: public int compare(final Object o1, final Object o2) {
366: final Property p1 = (Property) o1;
367: final Property p2 = (Property) o2;
368: if (p1.getID() < p2.getID())
369: return -1;
370: else if (p1.getID() == p2.getID())
371: return 0;
372: else
373: return 1;
374: }
375: });
376:
377: /* Write the properties and the property list into their respective
378: * streams: */
379: for (final ListIterator i = preprops.listIterator(); i
380: .hasNext();) {
381: final MutableProperty p = (MutableProperty) i.next();
382: final long id = p.getID();
383:
384: /* Write the property list entry. */
385: TypeWriter.writeUIntToStream(propertyListStream, p.getID());
386: TypeWriter.writeUIntToStream(propertyListStream, position);
387:
388: /* If the property ID is not equal 0 we write the property and all
389: * is fine. However, if it equals 0 we have to write the section's
390: * dictionary which has an implicit type only and an explicit
391: * value. */
392: if (id != 0)
393: /* Write the property and update the position to the next
394: * property. */
395: position += p.write(propertyStream, getCodepage());
396: else {
397: if (codepage == -1)
398: throw new IllegalPropertySetDataException(
399: "Codepage (property 1) is undefined.");
400: position += writeDictionary(propertyStream, dictionary,
401: codepage);
402: }
403: }
404: propertyStream.close();
405: propertyListStream.close();
406:
407: /* Write the section: */
408: byte[] pb1 = propertyListStream.toByteArray();
409: byte[] pb2 = propertyStream.toByteArray();
410:
411: /* Write the section's length: */
412: TypeWriter.writeToStream(out, LittleEndian.INT_SIZE * 2
413: + pb1.length + pb2.length);
414:
415: /* Write the section's number of properties: */
416: TypeWriter.writeToStream(out, getPropertyCount());
417:
418: /* Write the property list: */
419: out.write(pb1);
420:
421: /* Write the properties: */
422: out.write(pb2);
423:
424: int streamLength = LittleEndian.INT_SIZE * 2 + pb1.length
425: + pb2.length;
426: return streamLength;
427: }
428:
429: /**
430: * <p>Writes the section's dictionary.</p>
431: *
432: * @param out The output stream to write to.
433: * @param dictionary The dictionary.
434: * @param codepage The codepage to be used to write the dictionary items.
435: * @return The number of bytes written
436: * @exception IOException if an I/O exception occurs.
437: */
438: private static int writeDictionary(final OutputStream out,
439: final Map dictionary, final int codepage)
440: throws IOException {
441: int length = TypeWriter.writeUIntToStream(out, dictionary
442: .size());
443: for (final Iterator i = dictionary.keySet().iterator(); i
444: .hasNext();) {
445: final Long key = (Long) i.next();
446: final String value = (String) dictionary.get(key);
447:
448: if (codepage == Constants.CP_UNICODE) {
449: /* Write the dictionary item in Unicode. */
450: int sLength = value.length() + 1;
451: if (sLength % 2 == 1)
452: sLength++;
453: length += TypeWriter.writeUIntToStream(out, key
454: .longValue());
455: length += TypeWriter.writeUIntToStream(out, sLength);
456: final byte[] ca = value.getBytes(VariantSupport
457: .codepageToEncoding(codepage));
458: for (int j = 2; j < ca.length; j += 2) {
459: out.write(ca[j + 1]);
460: out.write(ca[j]);
461: length += 2;
462: }
463: sLength -= value.length();
464: while (sLength > 0) {
465: out.write(0x00);
466: out.write(0x00);
467: length += 2;
468: sLength--;
469: }
470: } else {
471: /* Write the dictionary item in another codepage than
472: * Unicode. */
473: length += TypeWriter.writeUIntToStream(out, key
474: .longValue());
475: length += TypeWriter.writeUIntToStream(out, value
476: .length() + 1);
477: final byte[] ba = value.getBytes(VariantSupport
478: .codepageToEncoding(codepage));
479: for (int j = 0; j < ba.length; j++) {
480: out.write(ba[j]);
481: length++;
482: }
483: out.write(0x00);
484: length++;
485: }
486: }
487: return length;
488: }
489:
490: /**
491: * <p>Overwrites the super class' method to cope with a redundancy:
492: * the property count is maintained in a separate member variable, but
493: * shouldn't.</p>
494: *
495: * @return The number of properties in this section
496: */
497: public int getPropertyCount() {
498: return preprops.size();
499: }
500:
501: /**
502: * <p>Gets this section's properties.</p>
503: *
504: * @return this section's properties.
505: */
506: public Property[] getProperties() {
507: properties = (Property[]) preprops.toArray(new Property[0]);
508: return properties;
509: }
510:
511: /**
512: * <p>Gets a property.</p>
513: *
514: * @param id The ID of the property to get
515: * @return The property or <code>null</code> if there is no such property
516: */
517: public Object getProperty(final long id) {
518: /* Calling getProperties() ensures that properties and preprops are in
519: * sync.</p> */
520: getProperties();
521: return super .getProperty(id);
522: }
523:
524: /**
525: * <p>Sets the section's dictionary. All keys in the dictionary must be
526: * {@link java.lang.Long} instances, all values must be
527: * {@link java.lang.String}s. This method overwrites the properties with IDs
528: * 0 and 1 since they are reserved for the dictionary and the dictionary's
529: * codepage. Setting these properties explicitly might have surprising
530: * effects. An application should never do this but always use this
531: * method.</p>
532: *
533: * @param dictionary The dictionary
534: *
535: * @exception IllegalPropertySetDataException if the dictionary's key and
536: * value types are not correct.
537: *
538: * @see Section#getDictionary()
539: */
540: public void setDictionary(final Map dictionary)
541: throws IllegalPropertySetDataException {
542: if (dictionary != null) {
543: for (final Iterator i = dictionary.keySet().iterator(); i
544: .hasNext();)
545: if (!(i.next() instanceof Long))
546: throw new IllegalPropertySetDataException(
547: "Dictionary keys must be of type Long.");
548: for (final Iterator i = dictionary.values().iterator(); i
549: .hasNext();)
550: if (!(i.next() instanceof String))
551: throw new IllegalPropertySetDataException(
552: "Dictionary values must be of type String.");
553: this .dictionary = dictionary;
554:
555: /* Set the dictionary property (ID 0). Please note that the second
556: * parameter in the method call below is unused because dictionaries
557: * don't have a type. */
558: setProperty(PropertyIDMap.PID_DICTIONARY, -1, dictionary);
559:
560: /* If the codepage property (ID 1) for the strings (keys and
561: * values) used in the dictionary is not yet defined, set it to
562: * Unicode. */
563: final Integer codepage = (Integer) getProperty(PropertyIDMap.PID_CODEPAGE);
564: if (codepage == null)
565: setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
566: new Integer(Constants.CP_UNICODE));
567: } else
568: /* Setting the dictionary to null means to remove property 0.
569: * However, it does not mean to remove property 1 (codepage). */
570: removeProperty(PropertyIDMap.PID_DICTIONARY);
571: }
572:
573: /**
574: * <p>Sets a property.</p>
575: *
576: * @param id The property ID.
577: * @param value The property's value. The value's class must be one of those
578: * supported by HPSF.
579: */
580: public void setProperty(final int id, final Object value) {
581: if (value instanceof String)
582: setProperty(id, (String) value);
583: else if (value instanceof Long)
584: setProperty(id, ((Long) value).longValue());
585: else if (value instanceof Integer)
586: setProperty(id, ((Integer) value).intValue());
587: else if (value instanceof Short)
588: setProperty(id, ((Short) value).intValue());
589: else if (value instanceof Boolean)
590: setProperty(id, ((Boolean) value).booleanValue());
591: else if (value instanceof Date)
592: setProperty(id, Variant.VT_FILETIME, value);
593: else
594: throw new HPSFRuntimeException(
595: "HPSF does not support properties of type "
596: + value.getClass().getName() + ".");
597: }
598:
599: /**
600: * <p>Removes all properties from the section including 0 (dictionary) and
601: * 1 (codepage).</p>
602: */
603: public void clear() {
604: final Property[] properties = getProperties();
605: for (int i = 0; i < properties.length; i++) {
606: final Property p = properties[i];
607: removeProperty(p.getID());
608: }
609: }
610:
611: /**
612: * <p>Sets the codepage.</p>
613: *
614: * @param codepage the codepage
615: */
616: public void setCodepage(final int codepage) {
617: setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
618: new Integer(codepage));
619: }
620:
621: }
|