1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716
|
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hpsf;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import org.apache.poi.hpsf.wellknown.PropertyIDMap;
import org.apache.poi.util.LittleEndian;
/**
* <p>Adds writing capability to the {@link Section} class.</p>
*
* <p>Please be aware that this class' functionality will be merged into the
* {@link Section} class at a later time, so the API will change.</p>
*
* @version $Id: MutableSection.java 489730 2006-12-22 19:18:16Z bayard $
* @since 2002-02-20
*/
public class MutableSection extends Section
{
/**
* <p>If the "dirty" flag is true, the section's size must be
* (re-)calculated before the section is written.</p>
*/
private boolean dirty = true;
/**
* <p>List to assemble the properties. Unfortunately a wrong
* decision has been taken when specifying the "properties" field
* as an Property[]. It should have been a {@link java.util.List}.</p>
*/
private List preprops;
/**
* <p>Contains the bytes making out the section. This byte array is
* established when the section's size is calculated and can be reused
* later. It is valid only if the "dirty" flag is false.</p>
*/
private byte[] sectionBytes;
/**
* <p>Creates an empty mutable section.</p>
*/
public MutableSection()
{
dirty = true;
formatID = null;
offset = -1;
preprops = new LinkedList();
}
/**
* <p>Constructs a <code>MutableSection</code> by doing a deep copy of an
* existing <code>Section</code>. All nested <code>Property</code>
* instances, will be their mutable counterparts in the new
* <code>MutableSection</code>.</p>
*
* @param s The section set to copy
*/
public MutableSection(final Section s)
{
setFormatID(s.getFormatID());
final Property[] pa = s.getProperties();
final MutableProperty[] mpa = new MutableProperty[pa.length];
for (int i = 0; i < pa.length; i++)
mpa[i] = new MutableProperty(pa[i]);
setProperties(mpa);
setDictionary(s.getDictionary());
}
/**
* <p>Sets the section's format ID.</p>
*
* @param formatID The section's format ID
*
* @see #setFormatID(byte[])
* @see Section#getFormatID
*/
public void setFormatID(final ClassID formatID)
{
this.formatID = formatID;
}
/**
* <p>Sets the section's format ID.</p>
*
* @param formatID The section's format ID as a byte array. It components
* are in big-endian format.
*
* @see #setFormatID(ClassID)
* @see Section#getFormatID
*/
public void setFormatID(final byte[] formatID)
{
ClassID fid = getFormatID();
if (fid == null)
{
fid = new ClassID();
setFormatID(fid);
}
fid.setBytes(formatID);
}
/**
* <p>Sets this section's properties. Any former values are overwritten.</p>
*
* @param properties This section's new properties.
*/
public void setProperties(final Property[] properties)
{
this.properties = properties;
preprops = new LinkedList();
for (int i = 0; i < properties.length; i++)
preprops.add(properties[i]);
dirty = true;
}
/**
* <p>Sets the string value of the property with the specified ID.</p>
*
* @param id The property's ID
* @param value The property's value. It will be written as a Unicode
* string.
*
* @see #setProperty(int, long, Object)
* @see #getProperty
*/
public void setProperty(final int id, final String value)
{
setProperty(id, Variant.VT_LPWSTR, value);
dirty = true;
}
/**
* <p>Sets the int value of the property with the specified ID.</p>
*
* @param id The property's ID
* @param value The property's value.
*
* @see #setProperty(int, long, Object)
* @see #getProperty
*/
public void setProperty(final int id, final int value)
{
setProperty(id, Variant.VT_I4, new Integer(value));
dirty = true;
}
/**
* <p>Sets the long value of the property with the specified ID.</p>
*
* @param id The property's ID
* @param value The property's value.
*
* @see #setProperty(int, long, Object)
* @see #getProperty
*/
public void setProperty(final int id, final long value)
{
setProperty(id, Variant.VT_I8, new Long(value));
dirty = true;
}
/**
* <p>Sets the boolean value of the property with the specified ID.</p>
*
* @param id The property's ID
* @param value The property's value.
*
* @see #setProperty(int, long, Object)
* @see #getProperty
*/
public void setProperty(final int id, final boolean value)
{
setProperty(id, Variant.VT_BOOL, new Boolean(value));
dirty = true;
}
/**
* <p>Sets the value and the variant type of the property with the
* specified ID. If a property with this ID is not yet present in
* the section, it will be added. An already present property with
* the specified ID will be overwritten. A default mapping will be
* used to choose the property's type.</p>
*
* @param id The property's ID.
* @param variantType The property's variant type.
* @param value The property's value.
*
* @see #setProperty(int, String)
* @see #getProperty
* @see Variant
*/
public void setProperty(final int id, final long variantType,
final Object value)
{
final MutableProperty p = new MutableProperty();
p.setID(id);
p.setType(variantType);
p.setValue(value);
setProperty(p);
dirty = true;
}
/**
* <p>Sets a property.</p>
*
* @param p The property to be set.
*
* @see #setProperty(int, long, Object)
* @see #getProperty
* @see Variant
*/
public void setProperty(final Property p)
{
final long id = p.getID();
removeProperty(id);
preprops.add(p);
dirty = true;
}
/**
* <p>Removes a property.</p>
*
* @param id The ID of the property to be removed
*/
public void removeProperty(final long id)
{
for (final Iterator i = preprops.iterator(); i.hasNext();)
if (((Property) i.next()).getID() == id)
{
i.remove();
break;
}
dirty = true;
}
/**
* <p>Sets the value of the boolean property with the specified
* ID.</p>
*
* @param id The property's ID
* @param value The property's value
*
* @see #setProperty(int, long, Object)
* @see #getProperty
* @see Variant
*/
protected void setPropertyBooleanValue(final int id, final boolean value)
{
setProperty(id, Variant.VT_BOOL, new Boolean(value));
}
/**
* <p>Returns the section's size.</p>
*
* @return the section's size.
*/
public int getSize()
{
if (dirty)
{
try
{
size = calcSize();
dirty = false;
}
catch (HPSFRuntimeException ex)
{
throw ex;
}
catch (Exception ex)
{
throw new HPSFRuntimeException(ex);
}
}
return size;
}
/**
* <p>Calculates the section's size. It is the sum of the lengths of the
* section's header (8), the properties list (16 times the number of
* properties) and the properties themselves.</p>
*
* @return the section's length in bytes.
* @throws WritingNotSupportedException
* @throws IOException
*/
private int calcSize() throws WritingNotSupportedException, IOException
{
final ByteArrayOutputStream out = new ByteArrayOutputStream();
write(out);
out.close();
/* Pad to multiple of 4 bytes so that even the Windows shell (explorer)
* shows custom properties. */
sectionBytes = Util.pad4(out.toByteArray());
return sectionBytes.length;
}
/**
* <p>Writes this section into an output stream.</p>
*
* <p>Internally this is done by writing into three byte array output
* streams: one for the properties, one for the property list and one for
* the section as such. The two former are appended to the latter when they
* have received all their data.</p>
*
* @param out The stream to write into.
*
* @return The number of bytes written, i.e. the section's size.
* @exception IOException if an I/O error occurs
* @exception WritingNotSupportedException if HPSF does not yet support
* writing a property's variant type.
*/
public int write(final OutputStream out)
throws WritingNotSupportedException, IOException
{
/* Check whether we have already generated the bytes making out the
* section. */
if (!dirty && sectionBytes != null)
{
out.write(sectionBytes);
return sectionBytes.length;
}
/* The properties are written to this stream. */
final ByteArrayOutputStream propertyStream =
new ByteArrayOutputStream();
/* The property list is established here. After each property that has
* been written to "propertyStream", a property list entry is written to
* "propertyListStream". */
final ByteArrayOutputStream propertyListStream =
new ByteArrayOutputStream();
/* Maintain the current position in the list. */
int position = 0;
/* Increase the position variable by the size of the property list so
* that it points behind the property list and to the beginning of the
* properties themselves. */
position += 2 * LittleEndian.INT_SIZE +
getPropertyCount() * 2 * LittleEndian.INT_SIZE;
/* Writing the section's dictionary it tricky. If there is a dictionary
* (property 0) the codepage property (property 1) must be set, too. */
int codepage = -1;
if (getProperty(PropertyIDMap.PID_DICTIONARY) != null)
{
final Object p1 = getProperty(PropertyIDMap.PID_CODEPAGE);
if (p1 != null)
{
if (!(p1 instanceof Integer))
throw new IllegalPropertySetDataException
("The codepage property (ID = 1) must be an " +
"Integer object.");
}
else
/* Warning: The codepage property is not set although a
* dictionary is present. In order to cope with this problem we
* add the codepage property and set it to Unicode. */
setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
new Integer(Constants.CP_UNICODE));
codepage = getCodepage();
}
/* Sort the property list by their property IDs: */
Collections.sort(preprops, new Comparator()
{
public int compare(final Object o1, final Object o2)
{
final Property p1 = (Property) o1;
final Property p2 = (Property) o2;
if (p1.getID() < p2.getID())
return -1;
else if (p1.getID() == p2.getID())
return 0;
else
return 1;
}
});
/* Write the properties and the property list into their respective
* streams: */
for (final ListIterator i = preprops.listIterator(); i.hasNext();)
{
final MutableProperty p = (MutableProperty) i.next();
final long id = p.getID();
/* Write the property list entry. */
TypeWriter.writeUIntToStream(propertyListStream, p.getID());
TypeWriter.writeUIntToStream(propertyListStream, position);
/* If the property ID is not equal 0 we write the property and all
* is fine. However, if it equals 0 we have to write the section's
* dictionary which has an implicit type only and an explicit
* value. */
if (id != 0)
/* Write the property and update the position to the next
* property. */
position += p.write(propertyStream, getCodepage());
else
{
if (codepage == -1)
throw new IllegalPropertySetDataException
("Codepage (property 1) is undefined.");
position += writeDictionary(propertyStream, dictionary,
codepage);
}
}
propertyStream.close();
propertyListStream.close();
/* Write the section: */
byte[] pb1 = propertyListStream.toByteArray();
byte[] pb2 = propertyStream.toByteArray();
/* Write the section's length: */
TypeWriter.writeToStream(out, LittleEndian.INT_SIZE * 2 +
pb1.length + pb2.length);
/* Write the section's number of properties: */
TypeWriter.writeToStream(out, getPropertyCount());
/* Write the property list: */
out.write(pb1);
/* Write the properties: */
out.write(pb2);
int streamLength = LittleEndian.INT_SIZE * 2 + pb1.length + pb2.length;
return streamLength;
}
/**
* <p>Writes the section's dictionary.</p>
*
* @param out The output stream to write to.
* @param dictionary The dictionary.
* @param codepage The codepage to be used to write the dictionary items.
* @return The number of bytes written
* @exception IOException if an I/O exception occurs.
*/
private static int writeDictionary(final OutputStream out,
final Map dictionary, final int codepage)
throws IOException
{
int length = TypeWriter.writeUIntToStream(out, dictionary.size());
for (final Iterator i = dictionary.keySet().iterator(); i.hasNext();)
{
final Long key = (Long) i.next();
final String value = (String) dictionary.get(key);
if (codepage == Constants.CP_UNICODE)
{
/* Write the dictionary item in Unicode. */
int sLength = value.length() + 1;
if (sLength % 2 == 1)
sLength++;
length += TypeWriter.writeUIntToStream(out, key.longValue());
length += TypeWriter.writeUIntToStream(out, sLength);
final byte[] ca =
value.getBytes(VariantSupport.codepageToEncoding(codepage));
for (int j = 2; j < ca.length; j += 2)
{
out.write(ca[j+1]);
out.write(ca[j]);
length += 2;
}
sLength -= value.length();
while (sLength > 0)
{
out.write(0x00);
out.write(0x00);
length += 2;
sLength--;
}
}
else
{
/* Write the dictionary item in another codepage than
* Unicode. */
length += TypeWriter.writeUIntToStream(out, key.longValue());
length += TypeWriter.writeUIntToStream(out, value.length() + 1);
final byte[] ba =
value.getBytes(VariantSupport.codepageToEncoding(codepage));
for (int j = 0; j < ba.length; j++)
{
out.write(ba[j]);
length++;
}
out.write(0x00);
length++;
}
}
return length;
}
/**
* <p>Overwrites the super class' method to cope with a redundancy:
* the property count is maintained in a separate member variable, but
* shouldn't.</p>
*
* @return The number of properties in this section
*/
public int getPropertyCount()
{
return preprops.size();
}
/**
* <p>Gets this section's properties.</p>
*
* @return this section's properties.
*/
public Property[] getProperties()
{
properties = (Property[]) preprops.toArray(new Property[0]);
return properties;
}
/**
* <p>Gets a property.</p>
*
* @param id The ID of the property to get
* @return The property or <code>null</code> if there is no such property
*/
public Object getProperty(final long id)
{
/* Calling getProperties() ensures that properties and preprops are in
* sync.</p> */
getProperties();
return super.getProperty(id);
}
/**
* <p>Sets the section's dictionary. All keys in the dictionary must be
* {@link java.lang.Long} instances, all values must be
* {@link java.lang.String}s. This method overwrites the properties with IDs
* 0 and 1 since they are reserved for the dictionary and the dictionary's
* codepage. Setting these properties explicitly might have surprising
* effects. An application should never do this but always use this
* method.</p>
*
* @param dictionary The dictionary
*
* @exception IllegalPropertySetDataException if the dictionary's key and
* value types are not correct.
*
* @see Section#getDictionary()
*/
public void setDictionary(final Map dictionary)
throws IllegalPropertySetDataException
{
if (dictionary != null)
{
for (final Iterator i = dictionary.keySet().iterator();
i.hasNext();)
if (!(i.next() instanceof Long))
throw new IllegalPropertySetDataException
("Dictionary keys must be of type Long.");
for (final Iterator i = dictionary.values().iterator();
i.hasNext();)
if (!(i.next() instanceof String))
throw new IllegalPropertySetDataException
("Dictionary values must be of type String.");
this.dictionary = dictionary;
/* Set the dictionary property (ID 0). Please note that the second
* parameter in the method call below is unused because dictionaries
* don't have a type. */
setProperty(PropertyIDMap.PID_DICTIONARY, -1, dictionary);
/* If the codepage property (ID 1) for the strings (keys and
* values) used in the dictionary is not yet defined, set it to
* Unicode. */
final Integer codepage =
(Integer) getProperty(PropertyIDMap.PID_CODEPAGE);
if (codepage == null)
setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
new Integer(Constants.CP_UNICODE));
}
else
/* Setting the dictionary to null means to remove property 0.
* However, it does not mean to remove property 1 (codepage). */
removeProperty(PropertyIDMap.PID_DICTIONARY);
}
/**
* <p>Sets a property.</p>
*
* @param id The property ID.
* @param value The property's value. The value's class must be one of those
* supported by HPSF.
*/
public void setProperty(final int id, final Object value)
{
if (value instanceof String)
setProperty(id, (String) value);
else if (value instanceof Long)
setProperty(id, ((Long) value).longValue());
else if (value instanceof Integer)
setProperty(id, ((Integer) value).intValue());
else if (value instanceof Short)
setProperty(id, ((Short) value).intValue());
else if (value instanceof Boolean)
setProperty(id, ((Boolean) value).booleanValue());
else if (value instanceof Date)
setProperty(id, Variant.VT_FILETIME, value);
else
throw new HPSFRuntimeException(
"HPSF does not support properties of type " +
value.getClass().getName() + ".");
}
/**
* <p>Removes all properties from the section including 0 (dictionary) and
* 1 (codepage).</p>
*/
public void clear()
{
final Property[] properties = getProperties();
for (int i = 0; i < properties.length; i++)
{
final Property p = properties[i];
removeProperty(p.getID());
}
}
/**
* <p>Sets the codepage.</p>
*
* @param codepage the codepage
*/
public void setCodepage(final int codepage)
{
setProperty(PropertyIDMap.PID_CODEPAGE, Variant.VT_I2,
new Integer(codepage));
}
}
|