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