Home | History | Annotate | Download | only in impl
      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