1 // ================================================================================================= 2 // ADOBE SYSTEMS INCORPORATED 3 // Copyright 2006 Adobe Systems Incorporated 4 // All Rights Reserved 5 // 6 // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms 7 // of the Adobe license agreement accompanying it. 8 // ================================================================================================= 9 10 package com.adobe.xmp.impl; 11 12 import java.util.Calendar; 13 import java.util.Iterator; 14 15 import com.adobe.xmp.XMPConst; 16 import com.adobe.xmp.XMPDateTime; 17 import com.adobe.xmp.XMPError; 18 import com.adobe.xmp.XMPException; 19 import com.adobe.xmp.XMPIterator; 20 import com.adobe.xmp.XMPMeta; 21 import com.adobe.xmp.XMPPathFactory; 22 import com.adobe.xmp.XMPUtils; 23 import com.adobe.xmp.impl.xpath.XMPPath; 24 import com.adobe.xmp.impl.xpath.XMPPathParser; 25 import com.adobe.xmp.options.IteratorOptions; 26 import com.adobe.xmp.options.ParseOptions; 27 import com.adobe.xmp.options.PropertyOptions; 28 import com.adobe.xmp.properties.XMPProperty; 29 30 31 /** 32 * Implementation for {@link XMPMeta}. 33 * 34 * @since 17.02.2006 35 */ 36 public class XMPMetaImpl implements XMPMeta, XMPConst 37 { 38 /** Property values are Strings by default */ 39 private static final int VALUE_STRING = 0; 40 /** */ 41 private static final int VALUE_BOOLEAN = 1; 42 /** */ 43 private static final int VALUE_INTEGER = 2; 44 /** */ 45 private static final int VALUE_LONG = 3; 46 /** */ 47 private static final int VALUE_DOUBLE = 4; 48 /** */ 49 private static final int VALUE_DATE = 5; 50 /** */ 51 private static final int VALUE_CALENDAR = 6; 52 /** */ 53 private static final int VALUE_BASE64 = 7; 54 55 /** root of the metadata tree */ 56 private XMPNode tree; 57 /** the xpacket processing instructions content */ 58 private String packetHeader = null; 59 60 61 /** 62 * Constructor for an empty metadata object. 63 */ 64 public XMPMetaImpl() 65 { 66 // create root node 67 tree = new XMPNode(null, null, null); 68 } 69 70 71 /** 72 * Constructor for a cloned metadata tree. 73 * 74 * @param tree 75 * an prefilled metadata tree which fulfills all 76 * <code>XMPNode</code> contracts. 77 */ 78 public XMPMetaImpl(XMPNode tree) 79 { 80 this.tree = tree; 81 } 82 83 84 /** 85 * @see XMPMeta#appendArrayItem(String, String, PropertyOptions, String, 86 * PropertyOptions) 87 */ 88 public void appendArrayItem(String schemaNS, String arrayName, PropertyOptions arrayOptions, 89 String itemValue, PropertyOptions itemOptions) throws XMPException 90 { 91 ParameterAsserts.assertSchemaNS(schemaNS); 92 ParameterAsserts.assertArrayName(arrayName); 93 94 if (arrayOptions == null) 95 { 96 arrayOptions = new PropertyOptions(); 97 } 98 if (!arrayOptions.isOnlyArrayOptions()) 99 { 100 throw new XMPException("Only array form flags allowed for arrayOptions", 101 XMPError.BADOPTIONS); 102 } 103 104 // Check if array options are set correctly. 105 arrayOptions = XMPNodeUtils.verifySetOptions(arrayOptions, null); 106 107 108 // Locate or create the array. If it already exists, make sure the array 109 // form from the options 110 // parameter is compatible with the current state. 111 XMPPath arrayPath = XMPPathParser.expandXPath(schemaNS, arrayName); 112 113 114 // Just lookup, don't try to create. 115 XMPNode arrayNode = XMPNodeUtils.findNode(tree, arrayPath, false, null); 116 117 if (arrayNode != null) 118 { 119 // The array exists, make sure the form is compatible. Zero 120 // arrayForm means take what exists. 121 if (!arrayNode.getOptions().isArray()) 122 { 123 throw new XMPException("The named property is not an array", XMPError.BADXPATH); 124 } 125 // if (arrayOptions != null && !arrayOptions.equalArrayTypes(arrayNode.getOptions())) 126 // { 127 // throw new XMPException("Mismatch of existing and specified array form", BADOPTIONS); 128 // } 129 } 130 else 131 { 132 // The array does not exist, try to create it. 133 if (arrayOptions.isArray()) 134 { 135 arrayNode = XMPNodeUtils.findNode(tree, arrayPath, true, arrayOptions); 136 if (arrayNode == null) 137 { 138 throw new XMPException("Failure creating array node", XMPError.BADXPATH); 139 } 140 } 141 else 142 { 143 // array options missing 144 throw new XMPException("Explicit arrayOptions required to create new array", 145 XMPError.BADOPTIONS); 146 } 147 } 148 149 doSetArrayItem(arrayNode, ARRAY_LAST_ITEM, itemValue, itemOptions, true); 150 } 151 152 153 /** 154 * @see XMPMeta#appendArrayItem(String, String, String) 155 */ 156 public void appendArrayItem(String schemaNS, String arrayName, String itemValue) 157 throws XMPException 158 { 159 appendArrayItem(schemaNS, arrayName, null, itemValue, null); 160 } 161 162 163 /** 164 * @throws XMPException 165 * @see XMPMeta#countArrayItems(String, String) 166 */ 167 public int countArrayItems(String schemaNS, String arrayName) throws XMPException 168 { 169 ParameterAsserts.assertSchemaNS(schemaNS); 170 ParameterAsserts.assertArrayName(arrayName); 171 172 XMPPath arrayPath = XMPPathParser.expandXPath(schemaNS, arrayName); 173 XMPNode arrayNode = XMPNodeUtils.findNode(tree, arrayPath, false, null); 174 175 if (arrayNode == null) 176 { 177 return 0; 178 } 179 180 if (arrayNode.getOptions().isArray()) 181 { 182 return arrayNode.getChildrenLength(); 183 } 184 else 185 { 186 throw new XMPException("The named property is not an array", XMPError.BADXPATH); 187 } 188 } 189 190 191 /** 192 * @see XMPMeta#deleteArrayItem(String, String, int) 193 */ 194 public void deleteArrayItem(String schemaNS, String arrayName, int itemIndex) 195 { 196 try 197 { 198 ParameterAsserts.assertSchemaNS(schemaNS); 199 ParameterAsserts.assertArrayName(arrayName); 200 201 String itemPath = XMPPathFactory.composeArrayItemPath(arrayName, itemIndex); 202 deleteProperty(schemaNS, itemPath); 203 } 204 catch (XMPException e) 205 { 206 // EMPTY, exceptions are ignored within delete 207 } 208 } 209 210 211 /** 212 * @see XMPMeta#deleteProperty(String, String) 213 */ 214 public void deleteProperty(String schemaNS, String propName) 215 { 216 try 217 { 218 ParameterAsserts.assertSchemaNS(schemaNS); 219 ParameterAsserts.assertPropName(propName); 220 221 XMPPath expPath = XMPPathParser.expandXPath(schemaNS, propName); 222 223 XMPNode propNode = XMPNodeUtils.findNode(tree, expPath, false, null); 224 if (propNode != null) 225 { 226 XMPNodeUtils.deleteNode(propNode); 227 } 228 } 229 catch (XMPException e) 230 { 231 // EMPTY, exceptions are ignored within delete 232 } 233 } 234 235 236 /** 237 * @see XMPMeta#deleteQualifier(String, String, String, String) 238 */ 239 public void deleteQualifier(String schemaNS, String propName, String qualNS, String qualName) 240 { 241 try 242 { 243 // Note: qualNS and qualName are checked inside composeQualfierPath 244 ParameterAsserts.assertSchemaNS(schemaNS); 245 ParameterAsserts.assertPropName(propName); 246 247 String qualPath = propName + XMPPathFactory.composeQualifierPath(qualNS, qualName); 248 deleteProperty(schemaNS, qualPath); 249 } 250 catch (XMPException e) 251 { 252 // EMPTY, exceptions within delete are ignored 253 } 254 } 255 256 257 /** 258 * @see XMPMeta#deleteStructField(String, String, String, String) 259 */ 260 public void deleteStructField(String schemaNS, String structName, String fieldNS, 261 String fieldName) 262 { 263 try 264 { 265 // fieldNS and fieldName are checked inside composeStructFieldPath 266 ParameterAsserts.assertSchemaNS(schemaNS); 267 ParameterAsserts.assertStructName(structName); 268 269 String fieldPath = structName 270 + XMPPathFactory.composeStructFieldPath(fieldNS, fieldName); 271 deleteProperty(schemaNS, fieldPath); 272 } 273 catch (XMPException e) 274 { 275 // EMPTY, exceptions within delete are ignored 276 } 277 } 278 279 280 /** 281 * @see XMPMeta#doesPropertyExist(String, String) 282 */ 283 public boolean doesPropertyExist(String schemaNS, String propName) 284 { 285 try 286 { 287 ParameterAsserts.assertSchemaNS(schemaNS); 288 ParameterAsserts.assertPropName(propName); 289 290 XMPPath expPath = XMPPathParser.expandXPath(schemaNS, propName); 291 final XMPNode propNode = XMPNodeUtils.findNode(tree, expPath, false, null); 292 return propNode != null; 293 } 294 catch (XMPException e) 295 { 296 return false; 297 } 298 } 299 300 301 /** 302 * @see XMPMeta#doesArrayItemExist(String, String, int) 303 */ 304 public boolean doesArrayItemExist(String schemaNS, String arrayName, int itemIndex) 305 { 306 try 307 { 308 ParameterAsserts.assertSchemaNS(schemaNS); 309 ParameterAsserts.assertArrayName(arrayName); 310 311 String path = XMPPathFactory.composeArrayItemPath(arrayName, itemIndex); 312 return doesPropertyExist(schemaNS, path); 313 } 314 catch (XMPException e) 315 { 316 return false; 317 } 318 } 319 320 321 /** 322 * @see XMPMeta#doesStructFieldExist(String, String, String, String) 323 */ 324 public boolean doesStructFieldExist(String schemaNS, String structName, String fieldNS, 325 String fieldName) 326 { 327 try 328 { 329 // fieldNS and fieldName are checked inside composeStructFieldPath() 330 ParameterAsserts.assertSchemaNS(schemaNS); 331 ParameterAsserts.assertStructName(structName); 332 333 String path = XMPPathFactory.composeStructFieldPath(fieldNS, fieldName); 334 return doesPropertyExist(schemaNS, structName + path); 335 } 336 catch (XMPException e) 337 { 338 return false; 339 } 340 } 341 342 343 /** 344 * @see XMPMeta#doesQualifierExist(String, String, String, String) 345 */ 346 public boolean doesQualifierExist(String schemaNS, String propName, String qualNS, 347 String qualName) 348 { 349 try 350 { 351 // qualNS and qualName are checked inside composeQualifierPath() 352 ParameterAsserts.assertSchemaNS(schemaNS); 353 ParameterAsserts.assertPropName(propName); 354 355 String path = XMPPathFactory.composeQualifierPath(qualNS, qualName); 356 return doesPropertyExist(schemaNS, propName + path); 357 } 358 catch (XMPException e) 359 { 360 return false; 361 } 362 } 363 364 365 /** 366 * @see XMPMeta#getArrayItem(String, String, int) 367 */ 368 public XMPProperty getArrayItem(String schemaNS, String arrayName, int itemIndex) 369 throws XMPException 370 { 371 ParameterAsserts.assertSchemaNS(schemaNS); 372 ParameterAsserts.assertArrayName(arrayName); 373 374 String itemPath = XMPPathFactory.composeArrayItemPath(arrayName, itemIndex); 375 return getProperty(schemaNS, itemPath); 376 } 377 378 379 /** 380 * @throws XMPException 381 * @see XMPMeta#getLocalizedText(String, String, String, String) 382 */ 383 public XMPProperty getLocalizedText(String schemaNS, String altTextName, String genericLang, 384 String specificLang) throws XMPException 385 { 386 ParameterAsserts.assertSchemaNS(schemaNS); 387 ParameterAsserts.assertArrayName(altTextName); 388 ParameterAsserts.assertSpecificLang(specificLang); 389 390 genericLang = genericLang != null ? Utils.normalizeLangValue(genericLang) : null; 391 specificLang = Utils.normalizeLangValue(specificLang); 392 393 XMPPath arrayPath = XMPPathParser.expandXPath(schemaNS, altTextName); 394 XMPNode arrayNode = XMPNodeUtils.findNode(tree, arrayPath, false, null); 395 if (arrayNode == null) 396 { 397 return null; 398 } 399 400 Object[] result = XMPNodeUtils.chooseLocalizedText(arrayNode, genericLang, specificLang); 401 int match = ((Integer) result[0]).intValue(); 402 final XMPNode itemNode = (XMPNode) result[1]; 403 404 if (match != XMPNodeUtils.CLT_NO_VALUES) 405 { 406 return new XMPProperty() 407 { 408 public Object getValue() 409 { 410 return itemNode.getValue(); 411 } 412 413 414 public PropertyOptions getOptions() 415 { 416 return itemNode.getOptions(); 417 } 418 419 420 public String getLanguage() 421 { 422 return itemNode.getQualifier(1).getValue(); 423 } 424 425 426 public String toString() 427 { 428 return itemNode.getValue().toString(); 429 } 430 }; 431 } 432 else 433 { 434 return null; 435 } 436 } 437 438 439 /** 440 * @see XMPMeta#setLocalizedText(String, String, String, String, String, 441 * PropertyOptions) 442 */ 443 public void setLocalizedText(String schemaNS, String altTextName, String genericLang, 444 String specificLang, String itemValue, PropertyOptions options) throws XMPException 445 { 446 ParameterAsserts.assertSchemaNS(schemaNS); 447 ParameterAsserts.assertArrayName(altTextName); 448 ParameterAsserts.assertSpecificLang(specificLang); 449 450 genericLang = genericLang != null ? Utils.normalizeLangValue(genericLang) : null; 451 specificLang = Utils.normalizeLangValue(specificLang); 452 453 XMPPath arrayPath = XMPPathParser.expandXPath(schemaNS, altTextName); 454 455 // Find the array node and set the options if it was just created. 456 XMPNode arrayNode = XMPNodeUtils.findNode(tree, arrayPath, true, new PropertyOptions( 457 PropertyOptions.ARRAY | PropertyOptions.ARRAY_ORDERED 458 | PropertyOptions.ARRAY_ALTERNATE | PropertyOptions.ARRAY_ALT_TEXT)); 459 460 if (arrayNode == null) 461 { 462 throw new XMPException("Failed to find or create array node", XMPError.BADXPATH); 463 } 464 else if (!arrayNode.getOptions().isArrayAltText()) 465 { 466 if (!arrayNode.hasChildren() && arrayNode.getOptions().isArrayAlternate()) 467 { 468 arrayNode.getOptions().setArrayAltText(true); 469 } 470 else 471 { 472 throw new XMPException( 473 "Specified property is no alt-text array", XMPError.BADXPATH); 474 } 475 } 476 477 // Make sure the x-default item, if any, is first. 478 boolean haveXDefault = false; 479 XMPNode xdItem = null; 480 481 for (Iterator it = arrayNode.iterateChildren(); it.hasNext();) 482 { 483 XMPNode currItem = (XMPNode) it.next(); 484 if (!currItem.hasQualifier() 485 || !XMPConst.XML_LANG.equals(currItem.getQualifier(1).getName())) 486 { 487 throw new XMPException("Language qualifier must be first", XMPError.BADXPATH); 488 } 489 else if (XMPConst.X_DEFAULT.equals(currItem.getQualifier(1).getValue())) 490 { 491 xdItem = currItem; 492 haveXDefault = true; 493 break; 494 } 495 } 496 497 // Moves x-default to the beginning of the array 498 if (xdItem != null && arrayNode.getChildrenLength() > 1) 499 { 500 arrayNode.removeChild(xdItem); 501 arrayNode.addChild(1, xdItem); 502 } 503 504 // Find the appropriate item. 505 // chooseLocalizedText will make sure the array is a language 506 // alternative. 507 Object[] result = XMPNodeUtils.chooseLocalizedText(arrayNode, genericLang, specificLang); 508 int match = ((Integer) result[0]).intValue(); 509 XMPNode itemNode = (XMPNode) result[1]; 510 511 boolean specificXDefault = XMPConst.X_DEFAULT.equals(specificLang); 512 513 switch (match) 514 { 515 case XMPNodeUtils.CLT_NO_VALUES: 516 517 // Create the array items for the specificLang and x-default, with 518 // x-default first. 519 XMPNodeUtils.appendLangItem(arrayNode, XMPConst.X_DEFAULT, itemValue); 520 haveXDefault = true; 521 if (!specificXDefault) 522 { 523 XMPNodeUtils.appendLangItem(arrayNode, specificLang, itemValue); 524 } 525 break; 526 527 case XMPNodeUtils.CLT_SPECIFIC_MATCH: 528 529 if (!specificXDefault) 530 { 531 // Update the specific item, update x-default if it matches the 532 // old value. 533 if (haveXDefault && xdItem != itemNode && xdItem != null 534 && xdItem.getValue().equals(itemNode.getValue())) 535 { 536 xdItem.setValue(itemValue); 537 } 538 // ! Do this after the x-default check! 539 itemNode.setValue(itemValue); 540 } 541 else 542 { 543 // Update all items whose values match the old x-default value. 544 assert haveXDefault && xdItem == itemNode; 545 for (Iterator it = arrayNode.iterateChildren(); it.hasNext();) 546 { 547 XMPNode currItem = (XMPNode) it.next(); 548 if (currItem == xdItem 549 || !currItem.getValue().equals( 550 xdItem != null ? xdItem.getValue() : null)) 551 { 552 continue; 553 } 554 currItem.setValue(itemValue); 555 } 556 // And finally do the x-default item. 557 if (xdItem != null) 558 { 559 xdItem.setValue(itemValue); 560 } 561 } 562 break; 563 564 case XMPNodeUtils.CLT_SINGLE_GENERIC: 565 566 // Update the generic item, update x-default if it matches the old 567 // value. 568 if (haveXDefault && xdItem != itemNode && xdItem != null 569 && xdItem.getValue().equals(itemNode.getValue())) 570 { 571 xdItem.setValue(itemValue); 572 } 573 itemNode.setValue(itemValue); // ! Do this after 574 // the x-default 575 // check! 576 break; 577 578 case XMPNodeUtils.CLT_MULTIPLE_GENERIC: 579 580 // Create the specific language, ignore x-default. 581 XMPNodeUtils.appendLangItem(arrayNode, specificLang, itemValue); 582 if (specificXDefault) 583 { 584 haveXDefault = true; 585 } 586 break; 587 588 case XMPNodeUtils.CLT_XDEFAULT: 589 590 // Create the specific language, update x-default if it was the only 591 // item. 592 if (xdItem != null && arrayNode.getChildrenLength() == 1) 593 { 594 xdItem.setValue(itemValue); 595 } 596 XMPNodeUtils.appendLangItem(arrayNode, specificLang, itemValue); 597 break; 598 599 case XMPNodeUtils.CLT_FIRST_ITEM: 600 601 // Create the specific language, don't add an x-default item. 602 XMPNodeUtils.appendLangItem(arrayNode, specificLang, itemValue); 603 if (specificXDefault) 604 { 605 haveXDefault = true; 606 } 607 break; 608 609 default: 610 // does not happen under normal circumstances 611 throw new XMPException("Unexpected result from ChooseLocalizedText", 612 XMPError.INTERNALFAILURE); 613 614 } 615 616 // Add an x-default at the front if needed. 617 if (!haveXDefault && arrayNode.getChildrenLength() == 1) 618 { 619 XMPNodeUtils.appendLangItem(arrayNode, XMPConst.X_DEFAULT, itemValue); 620 } 621 } 622 623 624 /** 625 * @see XMPMeta#setLocalizedText(String, String, String, String, String) 626 */ 627 public void setLocalizedText(String schemaNS, String altTextName, String genericLang, 628 String specificLang, String itemValue) throws XMPException 629 { 630 setLocalizedText(schemaNS, altTextName, genericLang, specificLang, itemValue, null); 631 } 632 633 634 /** 635 * @throws XMPException 636 * @see XMPMeta#getProperty(String, String) 637 */ 638 public XMPProperty getProperty(String schemaNS, String propName) throws XMPException 639 { 640 return getProperty(schemaNS, propName, VALUE_STRING); 641 } 642 643 644 /** 645 * Returns a property, but the result value can be requested. It can be one 646 * of {@link XMPMetaImpl#VALUE_STRING}, {@link XMPMetaImpl#VALUE_BOOLEAN}, 647 * {@link XMPMetaImpl#VALUE_INTEGER}, {@link XMPMetaImpl#VALUE_LONG}, 648 * {@link XMPMetaImpl#VALUE_DOUBLE}, {@link XMPMetaImpl#VALUE_DATE}, 649 * {@link XMPMetaImpl#VALUE_CALENDAR}, {@link XMPMetaImpl#VALUE_BASE64}. 650 * 651 * @see XMPMeta#getProperty(String, String) 652 * @param schemaNS 653 * a schema namespace 654 * @param propName 655 * a property name or path 656 * @param valueType 657 * the type of the value, see VALUE_... 658 * @return Returns an <code>XMPProperty</code> 659 * @throws XMPException 660 * Collects any exception that occurs. 661 */ 662 protected XMPProperty getProperty(String schemaNS, String propName, int valueType) 663 throws XMPException 664 { 665 ParameterAsserts.assertSchemaNS(schemaNS); 666 ParameterAsserts.assertPropName(propName); 667 668 final XMPPath expPath = XMPPathParser.expandXPath(schemaNS, propName); 669 final XMPNode propNode = XMPNodeUtils.findNode(tree, expPath, false, null); 670 671 if (propNode != null) 672 { 673 if (valueType != VALUE_STRING && propNode.getOptions().isCompositeProperty()) 674 { 675 throw new XMPException("Property must be simple when a value type is requested", 676 XMPError.BADXPATH); 677 } 678 679 final Object value = evaluateNodeValue(valueType, propNode); 680 681 return new XMPProperty() 682 { 683 public Object getValue() 684 { 685 return value; 686 } 687 688 689 public PropertyOptions getOptions() 690 { 691 return propNode.getOptions(); 692 } 693 694 695 public String getLanguage() 696 { 697 return null; 698 } 699 700 701 public String toString() 702 { 703 return value.toString(); 704 } 705 }; 706 } 707 else 708 { 709 return null; 710 } 711 } 712 713 714 /** 715 * Returns a property, but the result value can be requested. 716 * 717 * @see XMPMeta#getProperty(String, String) 718 * @param schemaNS 719 * a schema namespace 720 * @param propName 721 * a property name or path 722 * @param valueType 723 * the type of the value, see VALUE_... 724 * @return Returns the node value as an object according to the 725 * <code>valueType</code>. 726 * @throws XMPException 727 * Collects any exception that occurs. 728 */ 729 protected Object getPropertyObject(String schemaNS, String propName, int valueType) 730 throws XMPException 731 { 732 ParameterAsserts.assertSchemaNS(schemaNS); 733 ParameterAsserts.assertPropName(propName); 734 735 final XMPPath expPath = XMPPathParser.expandXPath(schemaNS, propName); 736 final XMPNode propNode = XMPNodeUtils.findNode(tree, expPath, false, null); 737 738 if (propNode != null) 739 { 740 if (valueType != VALUE_STRING && propNode.getOptions().isCompositeProperty()) 741 { 742 throw new XMPException("Property must be simple when a value type is requested", 743 XMPError.BADXPATH); 744 } 745 746 return evaluateNodeValue(valueType, propNode); 747 } 748 else 749 { 750 return null; 751 } 752 } 753 754 755 /** 756 * @see XMPMeta#getPropertyBoolean(String, String) 757 */ 758 public Boolean getPropertyBoolean(String schemaNS, String propName) throws XMPException 759 { 760 return (Boolean) getPropertyObject(schemaNS, propName, VALUE_BOOLEAN); 761 } 762 763 764 /** 765 * @throws XMPException 766 * @see XMPMeta#setPropertyBoolean(String, String, boolean, PropertyOptions) 767 */ 768 public void setPropertyBoolean(String schemaNS, String propName, boolean propValue, 769 PropertyOptions options) throws XMPException 770 { 771 setProperty(schemaNS, propName, propValue ? TRUESTR : FALSESTR, options); 772 } 773 774 775 /** 776 * @see XMPMeta#setPropertyBoolean(String, String, boolean) 777 */ 778 public void setPropertyBoolean(String schemaNS, String propName, boolean propValue) 779 throws XMPException 780 { 781 setProperty(schemaNS, propName, propValue ? TRUESTR : FALSESTR, null); 782 } 783 784 785 /** 786 * @see XMPMeta#getPropertyInteger(String, String) 787 */ 788 public Integer getPropertyInteger(String schemaNS, String propName) throws XMPException 789 { 790 return (Integer) getPropertyObject(schemaNS, propName, VALUE_INTEGER); 791 } 792 793 794 /** 795 * @see XMPMeta#setPropertyInteger(String, String, int, PropertyOptions) 796 */ 797 public void setPropertyInteger(String schemaNS, String propName, int propValue, 798 PropertyOptions options) throws XMPException 799 { 800 setProperty(schemaNS, propName, new Integer(propValue), options); 801 } 802 803 804 /** 805 * @see XMPMeta#setPropertyInteger(String, String, int) 806 */ 807 public void setPropertyInteger(String schemaNS, String propName, int propValue) 808 throws XMPException 809 { 810 setProperty(schemaNS, propName, new Integer(propValue), null); 811 } 812 813 814 /** 815 * @see XMPMeta#getPropertyLong(String, String) 816 */ 817 public Long getPropertyLong(String schemaNS, String propName) throws XMPException 818 { 819 return (Long) getPropertyObject(schemaNS, propName, VALUE_LONG); 820 } 821 822 823 /** 824 * @see XMPMeta#setPropertyLong(String, String, long, PropertyOptions) 825 */ 826 public void setPropertyLong(String schemaNS, String propName, long propValue, 827 PropertyOptions options) throws XMPException 828 { 829 setProperty(schemaNS, propName, new Long(propValue), options); 830 } 831 832 833 /** 834 * @see XMPMeta#setPropertyLong(String, String, long) 835 */ 836 public void setPropertyLong(String schemaNS, String propName, long propValue) 837 throws XMPException 838 { 839 setProperty(schemaNS, propName, new Long(propValue), null); 840 } 841 842 843 /** 844 * @see XMPMeta#getPropertyDouble(String, String) 845 */ 846 public Double getPropertyDouble(String schemaNS, String propName) throws XMPException 847 { 848 return (Double) getPropertyObject(schemaNS, propName, VALUE_DOUBLE); 849 } 850 851 852 /** 853 * @see XMPMeta#setPropertyDouble(String, String, double, PropertyOptions) 854 */ 855 public void setPropertyDouble(String schemaNS, String propName, double propValue, 856 PropertyOptions options) throws XMPException 857 { 858 setProperty(schemaNS, propName, new Double(propValue), options); 859 } 860 861 862 /** 863 * @see XMPMeta#setPropertyDouble(String, String, double) 864 */ 865 public void setPropertyDouble(String schemaNS, String propName, double propValue) 866 throws XMPException 867 { 868 setProperty(schemaNS, propName, new Double(propValue), null); 869 } 870 871 872 /** 873 * @see XMPMeta#getPropertyDate(String, String) 874 */ 875 public XMPDateTime getPropertyDate(String schemaNS, String propName) throws XMPException 876 { 877 return (XMPDateTime) getPropertyObject(schemaNS, propName, VALUE_DATE); 878 } 879 880 881 /** 882 * @see XMPMeta#setPropertyDate(String, String, XMPDateTime, 883 * PropertyOptions) 884 */ 885 public void setPropertyDate(String schemaNS, String propName, XMPDateTime propValue, 886 PropertyOptions options) throws XMPException 887 { 888 setProperty(schemaNS, propName, propValue, options); 889 } 890 891 892 /** 893 * @see XMPMeta#setPropertyDate(String, String, XMPDateTime) 894 */ 895 public void setPropertyDate(String schemaNS, String propName, XMPDateTime propValue) 896 throws XMPException 897 { 898 setProperty(schemaNS, propName, propValue, null); 899 } 900 901 902 /** 903 * @see XMPMeta#getPropertyCalendar(String, String) 904 */ 905 public Calendar getPropertyCalendar(String schemaNS, String propName) throws XMPException 906 { 907 return (Calendar) getPropertyObject(schemaNS, propName, VALUE_CALENDAR); 908 } 909 910 911 /** 912 * @see XMPMeta#setPropertyCalendar(String, String, Calendar, 913 * PropertyOptions) 914 */ 915 public void setPropertyCalendar(String schemaNS, String propName, Calendar propValue, 916 PropertyOptions options) throws XMPException 917 { 918 setProperty(schemaNS, propName, propValue, options); 919 } 920 921 922 /** 923 * @see XMPMeta#setPropertyCalendar(String, String, Calendar) 924 */ 925 public void setPropertyCalendar(String schemaNS, String propName, Calendar propValue) 926 throws XMPException 927 { 928 setProperty(schemaNS, propName, propValue, null); 929 } 930 931 932 /** 933 * @see XMPMeta#getPropertyBase64(String, String) 934 */ 935 public byte[] getPropertyBase64(String schemaNS, String propName) throws XMPException 936 { 937 return (byte[]) getPropertyObject(schemaNS, propName, VALUE_BASE64); 938 } 939 940 941 /** 942 * @see XMPMeta#getPropertyString(String, String) 943 */ 944 public String getPropertyString(String schemaNS, String propName) throws XMPException 945 { 946 return (String) getPropertyObject(schemaNS, propName, VALUE_STRING); 947 } 948 949 950 /** 951 * @see XMPMeta#setPropertyBase64(String, String, byte[], PropertyOptions) 952 */ 953 public void setPropertyBase64(String schemaNS, String propName, byte[] propValue, 954 PropertyOptions options) throws XMPException 955 { 956 setProperty(schemaNS, propName, propValue, options); 957 } 958 959 960 /** 961 * @see XMPMeta#setPropertyBase64(String, String, byte[]) 962 */ 963 public void setPropertyBase64(String schemaNS, String propName, byte[] propValue) 964 throws XMPException 965 { 966 setProperty(schemaNS, propName, propValue, null); 967 } 968 969 970 /** 971 * @throws XMPException 972 * @see XMPMeta#getQualifier(String, String, String, String) 973 */ 974 public XMPProperty getQualifier(String schemaNS, String propName, String qualNS, 975 String qualName) throws XMPException 976 { 977 // qualNS and qualName are checked inside composeQualfierPath 978 ParameterAsserts.assertSchemaNS(schemaNS); 979 ParameterAsserts.assertPropName(propName); 980 981 String qualPath = propName + XMPPathFactory.composeQualifierPath(qualNS, qualName); 982 return getProperty(schemaNS, qualPath); 983 } 984 985 986 /** 987 * @see XMPMeta#getStructField(String, String, String, String) 988 */ 989 public XMPProperty getStructField(String schemaNS, String structName, String fieldNS, 990 String fieldName) throws XMPException 991 { 992 // fieldNS and fieldName are checked inside composeStructFieldPath 993 ParameterAsserts.assertSchemaNS(schemaNS); 994 ParameterAsserts.assertStructName(structName); 995 996 String fieldPath = structName + XMPPathFactory.composeStructFieldPath(fieldNS, fieldName); 997 return getProperty(schemaNS, fieldPath); 998 } 999 1000 1001 /** 1002 * @throws XMPException 1003 * @see XMPMeta#iterator() 1004 */ 1005 public XMPIterator iterator() throws XMPException 1006 { 1007 return iterator(null, null, null); 1008 } 1009 1010 1011 /** 1012 * @see XMPMeta#iterator(IteratorOptions) 1013 */ 1014 public XMPIterator iterator(IteratorOptions options) throws XMPException 1015 { 1016 return iterator(null, null, options); 1017 } 1018 1019 1020 /** 1021 * @see XMPMeta#iterator(String, String, IteratorOptions) 1022 */ 1023 public XMPIterator iterator(String schemaNS, String propName, IteratorOptions options) 1024 throws XMPException 1025 { 1026 return new XMPIteratorImpl(this, schemaNS, propName, options); 1027 } 1028 1029 1030 /** 1031 * @throws XMPException 1032 * @see XMPMeta#setArrayItem(String, String, int, String, PropertyOptions) 1033 */ 1034 public void setArrayItem(String schemaNS, String arrayName, int itemIndex, String itemValue, 1035 PropertyOptions options) throws XMPException 1036 { 1037 ParameterAsserts.assertSchemaNS(schemaNS); 1038 ParameterAsserts.assertArrayName(arrayName); 1039 1040 // Just lookup, don't try to create. 1041 XMPPath arrayPath = XMPPathParser.expandXPath(schemaNS, arrayName); 1042 XMPNode arrayNode = XMPNodeUtils.findNode(tree, arrayPath, false, null); 1043 1044 if (arrayNode != null) 1045 { 1046 doSetArrayItem(arrayNode, itemIndex, itemValue, options, false); 1047 } 1048 else 1049 { 1050 throw new XMPException("Specified array does not exist", XMPError.BADXPATH); 1051 } 1052 } 1053 1054 1055 /** 1056 * @see XMPMeta#setArrayItem(String, String, int, String) 1057 */ 1058 public void setArrayItem(String schemaNS, String arrayName, int itemIndex, String itemValue) 1059 throws XMPException 1060 { 1061 setArrayItem(schemaNS, arrayName, itemIndex, itemValue, null); 1062 } 1063 1064 1065 /** 1066 * @throws XMPException 1067 * @see XMPMeta#insertArrayItem(String, String, int, String, 1068 * PropertyOptions) 1069 */ 1070 public void insertArrayItem(String schemaNS, String arrayName, int itemIndex, String itemValue, 1071 PropertyOptions options) throws XMPException 1072 { 1073 ParameterAsserts.assertSchemaNS(schemaNS); 1074 ParameterAsserts.assertArrayName(arrayName); 1075 1076 // Just lookup, don't try to create. 1077 XMPPath arrayPath = XMPPathParser.expandXPath(schemaNS, arrayName); 1078 XMPNode arrayNode = XMPNodeUtils.findNode(tree, arrayPath, false, null); 1079 1080 if (arrayNode != null) 1081 { 1082 doSetArrayItem(arrayNode, itemIndex, itemValue, options, true); 1083 } 1084 else 1085 { 1086 throw new XMPException("Specified array does not exist", XMPError.BADXPATH); 1087 } 1088 } 1089 1090 1091 /** 1092 * @see XMPMeta#insertArrayItem(String, String, int, String) 1093 */ 1094 public void insertArrayItem(String schemaNS, String arrayName, int itemIndex, String itemValue) 1095 throws XMPException 1096 { 1097 insertArrayItem(schemaNS, arrayName, itemIndex, itemValue, null); 1098 } 1099 1100 1101 /** 1102 * @throws XMPException 1103 * @see XMPMeta#setProperty(String, String, Object, PropertyOptions) 1104 */ 1105 public void setProperty(String schemaNS, String propName, Object propValue, 1106 PropertyOptions options) throws XMPException 1107 { 1108 ParameterAsserts.assertSchemaNS(schemaNS); 1109 ParameterAsserts.assertPropName(propName); 1110 1111 options = XMPNodeUtils.verifySetOptions(options, propValue); 1112 1113 XMPPath expPath = XMPPathParser.expandXPath(schemaNS, propName); 1114 1115 XMPNode propNode = XMPNodeUtils.findNode(tree, expPath, true, options); 1116 if (propNode != null) 1117 { 1118 setNode(propNode, propValue, options, false); 1119 } 1120 else 1121 { 1122 throw new XMPException("Specified property does not exist", XMPError.BADXPATH); 1123 } 1124 } 1125 1126 1127 /** 1128 * @see XMPMeta#setProperty(String, String, Object) 1129 */ 1130 public void setProperty(String schemaNS, String propName, Object propValue) throws XMPException 1131 { 1132 setProperty(schemaNS, propName, propValue, null); 1133 } 1134 1135 1136 /** 1137 * @throws XMPException 1138 * @see XMPMeta#setQualifier(String, String, String, String, String, 1139 * PropertyOptions) 1140 */ 1141 public void setQualifier(String schemaNS, String propName, String qualNS, String qualName, 1142 String qualValue, PropertyOptions options) throws XMPException 1143 { 1144 ParameterAsserts.assertSchemaNS(schemaNS); 1145 ParameterAsserts.assertPropName(propName); 1146 1147 if (!doesPropertyExist(schemaNS, propName)) 1148 { 1149 throw new XMPException("Specified property does not exist!", XMPError.BADXPATH); 1150 } 1151 1152 String qualPath = propName + XMPPathFactory.composeQualifierPath(qualNS, qualName); 1153 setProperty(schemaNS, qualPath, qualValue, options); 1154 } 1155 1156 1157 /** 1158 * @see XMPMeta#setQualifier(String, String, String, String, String) 1159 */ 1160 public void setQualifier(String schemaNS, String propName, String qualNS, String qualName, 1161 String qualValue) throws XMPException 1162 { 1163 setQualifier(schemaNS, propName, qualNS, qualName, qualValue, null); 1164 1165 } 1166 1167 1168 /** 1169 * @see XMPMeta#setStructField(String, String, String, String, String, 1170 * PropertyOptions) 1171 */ 1172 public void setStructField(String schemaNS, String structName, String fieldNS, 1173 String fieldName, String fieldValue, PropertyOptions options) throws XMPException 1174 { 1175 ParameterAsserts.assertSchemaNS(schemaNS); 1176 ParameterAsserts.assertStructName(structName); 1177 1178 String fieldPath = structName + XMPPathFactory.composeStructFieldPath(fieldNS, fieldName); 1179 setProperty(schemaNS, fieldPath, fieldValue, options); 1180 } 1181 1182 1183 /** 1184 * @see XMPMeta#setStructField(String, String, String, String, String) 1185 */ 1186 public void setStructField(String schemaNS, String structName, String fieldNS, 1187 String fieldName, String fieldValue) throws XMPException 1188 { 1189 setStructField(schemaNS, structName, fieldNS, fieldName, fieldValue, null); 1190 } 1191 1192 1193 /** 1194 * @see XMPMeta#getObjectName() 1195 */ 1196 public String getObjectName() 1197 { 1198 return tree.getName() != null ? tree.getName() : ""; 1199 } 1200 1201 1202 /** 1203 * @see XMPMeta#setObjectName(String) 1204 */ 1205 public void setObjectName(String name) 1206 { 1207 tree.setName(name); 1208 } 1209 1210 1211 /** 1212 * @see XMPMeta#getPacketHeader() 1213 */ 1214 public String getPacketHeader() 1215 { 1216 return packetHeader; 1217 } 1218 1219 1220 /** 1221 * Sets the packetHeader attributes, only used by the parser. 1222 * @param packetHeader the processing instruction content 1223 */ 1224 public void setPacketHeader(String packetHeader) 1225 { 1226 this.packetHeader = packetHeader; 1227 } 1228 1229 1230 /** 1231 * Performs a deep clone of the XMPMeta-object 1232 * 1233 * @see java.lang.Object#clone() 1234 */ 1235 public Object clone() 1236 { 1237 XMPNode clonedTree = (XMPNode) tree.clone(); 1238 return new XMPMetaImpl(clonedTree); 1239 } 1240 1241 1242 /** 1243 * @see XMPMeta#dumpObject() 1244 */ 1245 public String dumpObject() 1246 { 1247 // renders tree recursively 1248 return getRoot().dumpNode(true); 1249 } 1250 1251 1252 /** 1253 * @see XMPMeta#sort() 1254 */ 1255 public void sort() 1256 { 1257 this.tree.sort(); 1258 } 1259 1260 1261 /** 1262 * @see XMPMeta#normalize(ParseOptions) 1263 */ 1264 public void normalize(ParseOptions options) throws XMPException 1265 { 1266 if (options == null) 1267 { 1268 options = new ParseOptions(); 1269 } 1270 XMPNormalizer.process(this, options); 1271 } 1272 1273 1274 /** 1275 * @return Returns the root node of the XMP tree. 1276 */ 1277 public XMPNode getRoot() 1278 { 1279 return tree; 1280 } 1281 1282 1283 1284 // ------------------------------------------------------------------------------------- 1285 // private 1286 1287 1288 /** 1289 * Locate or create the item node and set the value. Note the index 1290 * parameter is one-based! The index can be in the range [1..size + 1] or 1291 * "last()", normalize it and check the insert flags. The order of the 1292 * normalization checks is important. If the array is empty we end up with 1293 * an index and location to set item size + 1. 1294 * 1295 * @param arrayNode an array node 1296 * @param itemIndex the index where to insert the item 1297 * @param itemValue the item value 1298 * @param itemOptions the options for the new item 1299 * @param insert insert oder overwrite at index position? 1300 * @throws XMPException 1301 */ 1302 private void doSetArrayItem(XMPNode arrayNode, int itemIndex, String itemValue, 1303 PropertyOptions itemOptions, boolean insert) throws XMPException 1304 { 1305 XMPNode itemNode = new XMPNode(ARRAY_ITEM_NAME, null); 1306 itemOptions = XMPNodeUtils.verifySetOptions(itemOptions, itemValue); 1307 1308 // in insert mode the index after the last is allowed, 1309 // even ARRAY_LAST_ITEM points to the index *after* the last. 1310 int maxIndex = insert ? arrayNode.getChildrenLength() + 1 : arrayNode.getChildrenLength(); 1311 if (itemIndex == ARRAY_LAST_ITEM) 1312 { 1313 itemIndex = maxIndex; 1314 } 1315 1316 if (1 <= itemIndex && itemIndex <= maxIndex) 1317 { 1318 if (!insert) 1319 { 1320 arrayNode.removeChild(itemIndex); 1321 } 1322 arrayNode.addChild(itemIndex, itemNode); 1323 setNode(itemNode, itemValue, itemOptions, false); 1324 } 1325 else 1326 { 1327 throw new XMPException("Array index out of bounds", XMPError.BADINDEX); 1328 } 1329 } 1330 1331 1332 /** 1333 * The internals for setProperty() and related calls, used after the node is 1334 * found or created. 1335 * 1336 * @param node 1337 * the newly created node 1338 * @param value 1339 * the node value, can be <code>null</code> 1340 * @param newOptions 1341 * options for the new node, must not be <code>null</code>. 1342 * @param deleteExisting flag if the existing value is to be overwritten 1343 * @throws XMPException thrown if options and value do not correspond 1344 */ 1345 void setNode(XMPNode node, Object value, PropertyOptions newOptions, boolean deleteExisting) 1346 throws XMPException 1347 { 1348 if (deleteExisting) 1349 { 1350 node.clear(); 1351 } 1352 1353 // its checked by setOptions(), if the merged result is a valid options set 1354 node.getOptions().mergeWith(newOptions); 1355 1356 if (!node.getOptions().isCompositeProperty()) 1357 { 1358 // This is setting the value of a leaf node. 1359 XMPNodeUtils.setNodeValue(node, value); 1360 } 1361 else 1362 { 1363 if (value != null && value.toString().length() > 0) 1364 { 1365 throw new XMPException("Composite nodes can't have values", XMPError.BADXPATH); 1366 } 1367 1368 node.removeChildren(); 1369 } 1370 1371 } 1372 1373 1374 /** 1375 * Evaluates a raw node value to the given value type, apply special 1376 * conversions for defined types in XMP. 1377 * 1378 * @param valueType 1379 * an int indicating the value type 1380 * @param propNode 1381 * the node containing the value 1382 * @return Returns a literal value for the node. 1383 * @throws XMPException 1384 */ 1385 private Object evaluateNodeValue(int valueType, final XMPNode propNode) throws XMPException 1386 { 1387 final Object value; 1388 String rawValue = propNode.getValue(); 1389 switch (valueType) 1390 { 1391 case VALUE_BOOLEAN: 1392 value = new Boolean(XMPUtils.convertToBoolean(rawValue)); 1393 break; 1394 case VALUE_INTEGER: 1395 value = new Integer(XMPUtils.convertToInteger(rawValue)); 1396 break; 1397 case VALUE_LONG: 1398 value = new Long(XMPUtils.convertToLong(rawValue)); 1399 break; 1400 case VALUE_DOUBLE: 1401 value = new Double(XMPUtils.convertToDouble(rawValue)); 1402 break; 1403 case VALUE_DATE: 1404 value = XMPUtils.convertToDate(rawValue); 1405 break; 1406 case VALUE_CALENDAR: 1407 XMPDateTime dt = XMPUtils.convertToDate(rawValue); 1408 value = dt.getCalendar(); 1409 break; 1410 case VALUE_BASE64: 1411 value = XMPUtils.decodeBase64(rawValue); 1412 break; 1413 case VALUE_STRING: 1414 default: 1415 // leaf values return empty string instead of null 1416 // for the other cases the converter methods provides a "null" 1417 // value. 1418 // a default value can only occur if this method is made public. 1419 value = rawValue != null || propNode.getOptions().isCompositeProperty() ? rawValue : ""; 1420 break; 1421 } 1422 return value; 1423 } 1424 } 1425