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.ArrayList;
     13 import java.util.Arrays;
     14 import java.util.Collections;
     15 import java.util.Iterator;
     16 import java.util.List;
     17 import java.util.ListIterator;
     18 
     19 import com.adobe.xmp.XMPConst;
     20 import com.adobe.xmp.XMPError;
     21 import com.adobe.xmp.XMPException;
     22 import com.adobe.xmp.options.PropertyOptions;
     23 
     24 
     25 /**
     26  * A node in the internally XMP tree, which can be a schema node, a property node, an array node,
     27  * an array item, a struct node or a qualifier node (without '?').
     28  *
     29  * Possible improvements:
     30  *
     31  * 1. The kind Node of node might be better represented by a class-hierarchy of different nodes.
     32  * 2. The array type should be an enum
     33  * 3. isImplicitNode should be removed completely and replaced by return values of fi.
     34  * 4. hasLanguage, hasType should be automatically maintained by XMPNode
     35  *
     36  * @since 21.02.2006
     37  */
     38 class XMPNode implements Comparable
     39 {
     40 	/** name of the node, contains different information depending of the node kind */
     41 	private String name;
     42 	/** value of the node, contains different information depending of the node kind */
     43 	private String value;
     44 	/** link to the parent node */
     45 	private XMPNode parent;
     46 	/** list of child nodes, lazy initialized */
     47 	private List children = null;
     48 	/** list of qualifier of the node, lazy initialized */
     49 	private List qualifier = null;
     50 	/** options describing the kind of the node */
     51 	private PropertyOptions options = null;
     52 
     53 	// internal processing options
     54 
     55 	/** flag if the node is implicitly created */
     56 	private boolean implicit;
     57 	/** flag if the node has aliases */
     58 	private boolean hasAliases;
     59 	/** flag if the node is an alias */
     60 	private boolean alias;
     61 	/** flag if the node has an "rdf:value" child node. */
     62 	private boolean hasValueChild;
     63 
     64 
     65 
     66 	/**
     67 	 * Creates an <code>XMPNode</code> with initial values.
     68 	 *
     69 	 * @param name the name of the node
     70 	 * @param value the value of the node
     71 	 * @param options the options of the node
     72 	 */
     73 	public XMPNode(String name, String value, PropertyOptions options)
     74 	{
     75 		this.name = name;
     76 		this.value = value;
     77 		this.options = options;
     78 	}
     79 
     80 
     81 	/**
     82 	 * Constructor for the node without value.
     83 	 *
     84 	 * @param name the name of the node
     85 	 * @param options the options of the node
     86 	 */
     87 	public XMPNode(String name, PropertyOptions options)
     88 	{
     89 		this(name, null, options);
     90 	}
     91 
     92 
     93 	/**
     94 	 * Resets the node.
     95 	 */
     96 	public void clear()
     97 	{
     98 		options = null;
     99 		name = null;
    100 		value = null;
    101 		children = null;
    102 		qualifier = null;
    103 	}
    104 
    105 
    106 	/**
    107 	 * @return Returns the parent node.
    108 	 */
    109 	public XMPNode getParent()
    110 	{
    111 		return parent;
    112 	}
    113 
    114 
    115 	/**
    116 	 * @param index an index [1..size]
    117 	 * @return Returns the child with the requested index.
    118 	 */
    119 	public XMPNode getChild(int index)
    120 	{
    121 		return (XMPNode) getChildren().get(index - 1);
    122 	}
    123 
    124 
    125 	/**
    126 	 * Adds a node as child to this node.
    127 	 * @param node an XMPNode
    128 	 * @throws XMPException
    129 	 */
    130 	public void addChild(XMPNode node) throws XMPException
    131 	{
    132 		// check for duplicate properties
    133 		assertChildNotExisting(node.getName());
    134 		node.setParent(this);
    135 		getChildren().add(node);
    136 	}
    137 
    138 
    139 	/**
    140 	 * Adds a node as child to this node.
    141 	 * @param index the index of the node <em>before</em> which the new one is inserted.
    142 	 * <em>Note:</em> The node children are indexed from [1..size]!
    143 	 * An index of size + 1 appends a node.
    144 	 * @param node an XMPNode
    145 	 * @throws XMPException
    146 	 */
    147 	public void addChild(int index, XMPNode node) throws XMPException
    148 	{
    149 		assertChildNotExisting(node.getName());
    150 		node.setParent(this);
    151 		getChildren().add(index - 1, node);
    152 	}
    153 
    154 
    155 	/**
    156 	 * Replaces a node with another one.
    157 	 * @param index the index of the node that will be replaced.
    158 	 * <em>Note:</em> The node children are indexed from [1..size]!
    159 	 * @param node the replacement XMPNode
    160 	 */
    161 	public void replaceChild(int index, XMPNode node)
    162 	{
    163 		node.setParent(this);
    164 		getChildren().set(index - 1, node);
    165 	}
    166 
    167 
    168 	/**
    169 	 * Removes a child at the requested index.
    170 	 * @param itemIndex the index to remove [1..size]
    171 	 */
    172 	public void removeChild(int itemIndex)
    173 	{
    174 		getChildren().remove(itemIndex - 1);
    175 		cleanupChildren();
    176 	}
    177 
    178 
    179 	/**
    180 	 * Removes a child node.
    181 	 * If its a schema node and doesn't have any children anymore, its deleted.
    182 	 *
    183 	 * @param node the child node to delete.
    184 	 */
    185 	public void removeChild(XMPNode node)
    186 	{
    187 		getChildren().remove(node);
    188 		cleanupChildren();
    189 	}
    190 
    191 
    192 	/**
    193 	 * Removes the children list if this node has no children anymore;
    194 	 * checks if the provided node is a schema node and doesn't have any children anymore,
    195 	 * its deleted.
    196 	 */
    197 	protected void cleanupChildren()
    198 	{
    199 		if (children.isEmpty())
    200 		{
    201 			children = null;
    202 		}
    203 	}
    204 
    205 
    206 	/**
    207 	 * Removes all children from the node.
    208 	 */
    209 	public void removeChildren()
    210 	{
    211 		children = null;
    212 	}
    213 
    214 
    215 	/**
    216 	 * @return Returns the number of children without neccessarily creating a list.
    217 	 */
    218 	public int getChildrenLength()
    219 	{
    220 		return children != null ?
    221 			children.size() :
    222 			0;
    223 	}
    224 
    225 
    226 	/**
    227 	 * @param expr child node name to look for
    228 	 * @return Returns an <code>XMPNode</code> if node has been found, <code>null</code> otherwise.
    229 	 */
    230 	public XMPNode findChildByName(String expr)
    231 	{
    232 		return find(getChildren(), expr);
    233 	}
    234 
    235 
    236 	/**
    237 	 * @param index an index [1..size]
    238 	 * @return Returns the qualifier with the requested index.
    239 	 */
    240 	public XMPNode getQualifier(int index)
    241 	{
    242 		return (XMPNode) getQualifier().get(index - 1);
    243 	}
    244 
    245 
    246 	/**
    247 	 * @return Returns the number of qualifier without neccessarily creating a list.
    248 	 */
    249 	public int getQualifierLength()
    250 	{
    251 		return qualifier != null ?
    252 			qualifier.size() :
    253 			0;
    254 	}
    255 
    256 
    257 	/**
    258 	 * Appends a qualifier to the qualifier list and sets respective options.
    259 	 * @param qualNode a qualifier node.
    260 	 * @throws XMPException
    261 	 */
    262 	public void addQualifier(XMPNode qualNode) throws XMPException
    263 	{
    264 		assertQualifierNotExisting(qualNode.getName());
    265 		qualNode.setParent(this);
    266 		qualNode.getOptions().setQualifier(true);
    267 		getOptions().setHasQualifiers(true);
    268 
    269 		// contraints
    270 		if (qualNode.isLanguageNode())
    271 		{
    272 			// "xml:lang" is always first and the option "hasLanguage" is set
    273 			options.setHasLanguage(true);
    274 			getQualifier().add(0, qualNode);
    275 		}
    276 		else if (qualNode.isTypeNode())
    277 		{
    278 			// "rdf:type" must be first or second after "xml:lang" and the option "hasType" is set
    279 			options.setHasType(true);
    280 			getQualifier().add(
    281 				!options.getHasLanguage() ? 0 : 1,
    282 				qualNode);
    283 		}
    284 		else
    285 		{
    286 			// other qualifiers are appended
    287 			getQualifier().add(qualNode);
    288 		}
    289 	}
    290 
    291 
    292 	/**
    293 	 * Removes one qualifier node and fixes the options.
    294 	 * @param qualNode qualifier to remove
    295 	 */
    296 	public void removeQualifier(XMPNode qualNode)
    297 	{
    298 		PropertyOptions opts = getOptions();
    299 		if (qualNode.isLanguageNode())
    300 		{
    301 			// if "xml:lang" is removed, remove hasLanguage-flag too
    302 			opts.setHasLanguage(false);
    303 		}
    304 		else if (qualNode.isTypeNode())
    305 		{
    306 			// if "rdf:type" is removed, remove hasType-flag too
    307 			opts.setHasType(false);
    308 		}
    309 
    310 		getQualifier().remove(qualNode);
    311 		if (qualifier.isEmpty())
    312 		{
    313 			opts.setHasQualifiers(false);
    314 			qualifier = null;
    315 		}
    316 
    317 	}
    318 
    319 
    320 	/**
    321 	 * Removes all qualifiers from the node and sets the options appropriate.
    322 	 */
    323 	public void removeQualifiers()
    324 	{
    325 		PropertyOptions opts = getOptions();
    326 		// clear qualifier related options
    327 		opts.setHasQualifiers(false);
    328 		opts.setHasLanguage(false);
    329 		opts.setHasType(false);
    330 		qualifier = null;
    331 	}
    332 
    333 
    334 	/**
    335 	 * @param expr qualifier node name to look for
    336 	 * @return Returns a qualifier <code>XMPNode</code> if node has been found,
    337 	 * <code>null</code> otherwise.
    338 	 */
    339 	public XMPNode findQualifierByName(String expr)
    340 	{
    341 		return find(qualifier, expr);
    342 	}
    343 
    344 
    345 	/**
    346 	 * @return Returns whether the node has children.
    347 	 */
    348 	public boolean hasChildren()
    349 	{
    350 		return children != null  &&  children.size() > 0;
    351 	}
    352 
    353 
    354 	/**
    355 	 * @return Returns an iterator for the children.
    356 	 * <em>Note:</em> take care to use it.remove(), as the flag are not adjusted in that case.
    357 	 */
    358 	public Iterator iterateChildren()
    359 	{
    360 		if (children != null)
    361 		{
    362 			return getChildren().iterator();
    363 		}
    364 		else
    365 		{
    366 			return Collections.EMPTY_LIST.listIterator();
    367 		}
    368 	}
    369 
    370 
    371 	/**
    372 	 * @return Returns whether the node has qualifier attached.
    373 	 */
    374 	public boolean hasQualifier()
    375 	{
    376 		return qualifier != null  &&  qualifier.size() > 0;
    377 	}
    378 
    379 
    380 	/**
    381 	 * @return Returns an iterator for the qualifier.
    382 	 * <em>Note:</em> take care to use it.remove(), as the flag are not adjusted in that case.
    383 	 */
    384 	public Iterator iterateQualifier()
    385 	{
    386 		if (qualifier != null)
    387 		{
    388 			final Iterator it = getQualifier().iterator();
    389 
    390 			return new Iterator()
    391 			{
    392 				public boolean hasNext()
    393 				{
    394 					return it.hasNext();
    395 				}
    396 
    397 				public Object next()
    398 				{
    399 					return it.next();
    400 				}
    401 
    402 				public void remove()
    403 				{
    404 					throw new UnsupportedOperationException(
    405 							"remove() is not allowed due to the internal contraints");
    406 				}
    407 
    408 			};
    409 		}
    410 		else
    411 		{
    412 			return Collections.EMPTY_LIST.iterator();
    413 		}
    414 	}
    415 
    416 
    417 	/**
    418 	 * Performs a <b>deep clone</b> of the node and the complete subtree.
    419 	 *
    420 	 * @see java.lang.Object#clone()
    421 	 */
    422 	public Object clone()
    423 	{
    424 		PropertyOptions newOptions;
    425 		try
    426 		{
    427 			newOptions = new PropertyOptions(getOptions().getOptions());
    428 		}
    429 		catch (XMPException e)
    430 		{
    431 			// cannot happen
    432 			newOptions = new PropertyOptions();
    433 		}
    434 
    435 		XMPNode newNode = new XMPNode(name, value, newOptions);
    436 		cloneSubtree(newNode);
    437 
    438 		return newNode;
    439 	}
    440 
    441 
    442 	/**
    443 	 * Performs a <b>deep clone</b> of the complete subtree (children and
    444 	 * qualifier )into and add it to the destination node.
    445 	 *
    446 	 * @param destination the node to add the cloned subtree
    447 	 */
    448 	public void cloneSubtree(XMPNode destination)
    449 	{
    450 		try
    451 		{
    452 			for (Iterator it = iterateChildren(); it.hasNext();)
    453 			{
    454 				XMPNode child = (XMPNode) it.next();
    455 				destination.addChild((XMPNode) child.clone());
    456 			}
    457 
    458 			for (Iterator it = iterateQualifier(); it.hasNext();)
    459 			{
    460 				XMPNode qualifier = (XMPNode) it.next();
    461 				destination.addQualifier((XMPNode) qualifier.clone());
    462 			}
    463 		}
    464 		catch (XMPException e)
    465 		{
    466 			// cannot happen (duplicate childs/quals do not exist in this node)
    467 			assert false;
    468 		}
    469 
    470 	}
    471 
    472 
    473 	/**
    474 	 * Renders this node and the tree unter this node in a human readable form.
    475 	 * @param recursive Flag is qualifier and child nodes shall be rendered too
    476 	 * @return Returns a multiline string containing the dump.
    477 	 */
    478 	public String dumpNode(boolean recursive)
    479 	{
    480 		StringBuffer result = new StringBuffer(512);
    481 		this.dumpNode(result, recursive, 0, 0);
    482 		return result.toString();
    483 	}
    484 
    485 
    486 	/**
    487 	 * @see Comparable#compareTo(Object)
    488 	 */
    489 	public int compareTo(Object xmpNode)
    490 	{
    491 		if (getOptions().isSchemaNode())
    492 		{
    493 			return this.value.compareTo(((XMPNode) xmpNode).getValue());
    494 		}
    495 		else
    496 		{
    497 			return this.name.compareTo(((XMPNode) xmpNode).getName());
    498 		}
    499 	}
    500 
    501 
    502 	/**
    503 	 * @return Returns the name.
    504 	 */
    505 	public String getName()
    506 	{
    507 		return name;
    508 	}
    509 
    510 
    511 	/**
    512 	 * @param name The name to set.
    513 	 */
    514 	public void setName(String name)
    515 	{
    516 		this.name = name;
    517 	}
    518 
    519 
    520 	/**
    521 	 * @return Returns the value.
    522 	 */
    523 	public String getValue()
    524 	{
    525 		return value;
    526 	}
    527 
    528 
    529 	/**
    530 	 * @param value The value to set.
    531 	 */
    532 	public void setValue(String value)
    533 	{
    534 		this.value = value;
    535 	}
    536 
    537 
    538 	/**
    539 	 * @return Returns the options.
    540 	 */
    541 	public PropertyOptions getOptions()
    542 	{
    543 		if (options == null)
    544 		{
    545 			options = new PropertyOptions();
    546 		}
    547 		return options;
    548 	}
    549 
    550 
    551 	/**
    552 	 * Updates the options of the node.
    553 	 * @param options the options to set.
    554 	 */
    555 	public void setOptions(PropertyOptions options)
    556 	{
    557 		this.options = options;
    558 	}
    559 
    560 
    561 	/**
    562 	 * @return Returns the implicit flag
    563 	 */
    564 	public boolean isImplicit()
    565 	{
    566 		return implicit;
    567 	}
    568 
    569 
    570 	/**
    571 	 * @param implicit Sets the implicit node flag
    572 	 */
    573 	public void setImplicit(boolean implicit)
    574 	{
    575 		this.implicit = implicit;
    576 	}
    577 
    578 
    579 	/**
    580 	 * @return Returns if the node contains aliases (applies only to schema nodes)
    581 	 */
    582 	public boolean getHasAliases()
    583 	{
    584 		return hasAliases;
    585 	}
    586 
    587 
    588 	/**
    589 	 * @param hasAliases sets the flag that the node contains aliases
    590 	 */
    591 	public void setHasAliases(boolean hasAliases)
    592 	{
    593 		this.hasAliases = hasAliases;
    594 	}
    595 
    596 
    597 	/**
    598 	 * @return Returns if the node contains aliases (applies only to schema nodes)
    599 	 */
    600 	public boolean isAlias()
    601 	{
    602 		return alias;
    603 	}
    604 
    605 
    606 	/**
    607 	 * @param alias sets the flag that the node is an alias
    608 	 */
    609 	public void setAlias(boolean alias)
    610 	{
    611 		this.alias = alias;
    612 	}
    613 
    614 
    615 	/**
    616 	 * @return the hasValueChild
    617 	 */
    618 	public boolean getHasValueChild()
    619 	{
    620 		return hasValueChild;
    621 	}
    622 
    623 
    624 	/**
    625 	 * @param hasValueChild the hasValueChild to set
    626 	 */
    627 	public void setHasValueChild(boolean hasValueChild)
    628 	{
    629 		this.hasValueChild = hasValueChild;
    630 	}
    631 
    632 
    633 
    634 	/**
    635 	 * Sorts the complete datamodel according to the following rules:
    636 	 * <ul>
    637 	 * 		<li>Nodes at one level are sorted by name, that is prefix + local name
    638 	 * 		<li>Starting at the root node the children and qualifier are sorted recursively,
    639 	 * 			which the following exceptions.
    640 	 * 		<li>Sorting will not be used for arrays.
    641 	 * 		<li>Within qualifier "xml:lang" and/or "rdf:type" stay at the top in that order,
    642 	 * 			all others are sorted.
    643 	 * </ul>
    644 	 */
    645 	public void sort()
    646 	{
    647 		// sort qualifier
    648 		if (hasQualifier())
    649 		{
    650 			XMPNode[] quals = (XMPNode[]) getQualifier()
    651 				.toArray(new XMPNode[getQualifierLength()]);
    652 			int sortFrom = 0;
    653 			while (
    654 					quals.length > sortFrom  &&
    655 					(XMPConst.XML_LANG.equals(quals[sortFrom].getName())  ||
    656 					 "rdf:type".equals(quals[sortFrom].getName()))
    657 				  )
    658 			{
    659 				quals[sortFrom].sort();
    660 				sortFrom++;
    661 			}
    662 
    663 			Arrays.sort(quals, sortFrom, quals.length);
    664 			ListIterator it = qualifier.listIterator();
    665 			for (int j = 0; j < quals.length; j++)
    666 			{
    667 				it.next();
    668 				it.set(quals[j]);
    669 				quals[j].sort();
    670 			}
    671 		}
    672 
    673 		// sort children
    674 		if (hasChildren())
    675 		{
    676 			if (!getOptions().isArray())
    677 			{
    678 				Collections.sort(children);
    679 			}
    680 			for (Iterator it = iterateChildren(); it.hasNext();)
    681 			{
    682 				((XMPNode) it.next()).sort();
    683 
    684 			}
    685 		}
    686 	}
    687 
    688 
    689 
    690 	//------------------------------------------------------------------------------ private methods
    691 
    692 
    693 	/**
    694 	 * Dumps this node and its qualifier and children recursively.
    695 	 * <em>Note:</em> It creats empty options on every node.
    696 	 *
    697 	 * @param result the buffer to append the dump.
    698 	 * @param recursive Flag is qualifier and child nodes shall be rendered too
    699 	 * @param indent the current indent level.
    700 	 * @param index the index within the parent node (important for arrays)
    701 	 */
    702 	private void dumpNode(StringBuffer result, boolean recursive, int indent, int index)
    703 	{
    704 		// write indent
    705 		for (int i = 0; i < indent; i++)
    706 		{
    707 			result.append('\t');
    708 		}
    709 
    710 		// render Node
    711 		if (parent != null)
    712 		{
    713 			if (getOptions().isQualifier())
    714 			{
    715 				result.append('?');
    716 				result.append(name);
    717 			}
    718 			else if (getParent().getOptions().isArray())
    719 			{
    720 				result.append('[');
    721 				result.append(index);
    722 				result.append(']');
    723 			}
    724 			else
    725 			{
    726 				result.append(name);
    727 			}
    728 		}
    729 		else
    730 		{
    731 			// applies only to the root node
    732 			result.append("ROOT NODE");
    733 			if (name != null  &&  name.length() > 0)
    734 			{
    735 				// the "about" attribute
    736 				result.append(" (");
    737 				result.append(name);
    738 				result.append(')');
    739 			}
    740 		}
    741 
    742 		if (value != null  &&  value.length() > 0)
    743 		{
    744 			result.append(" = \"");
    745 			result.append(value);
    746 			result.append('"');
    747 		}
    748 
    749 		// render options if at least one is set
    750 		if (getOptions().containsOneOf(0xffffffff))
    751 		{
    752 			result.append("\t(");
    753 			result.append(getOptions().toString());
    754 			result.append(" : ");
    755 			result.append(getOptions().getOptionsString());
    756 			result.append(')');
    757 		}
    758 
    759 		result.append('\n');
    760 
    761 		// render qualifier
    762 		if (recursive  &&  hasQualifier())
    763 		{
    764 			XMPNode[] quals = (XMPNode[]) getQualifier()
    765 				.toArray(new XMPNode[getQualifierLength()]);
    766 			int i = 0;
    767 			while (quals.length > i  &&
    768 					(XMPConst.XML_LANG.equals(quals[i].getName())  ||
    769 					 "rdf:type".equals(quals[i].getName()))
    770 				  )
    771 			{
    772 				i++;
    773 			}
    774 			Arrays.sort(quals, i, quals.length);
    775 			for (i = 0; i < quals.length; i++)
    776 			{
    777 				XMPNode qualifier = quals[i];
    778 				qualifier.dumpNode(result, recursive, indent + 2, i + 1);
    779 			}
    780 		}
    781 
    782 		// render children
    783 		if (recursive  &&  hasChildren())
    784 		{
    785 			XMPNode[] children = (XMPNode[]) getChildren()
    786 				.toArray(new XMPNode[getChildrenLength()]);
    787 			if (!getOptions().isArray())
    788 			{
    789 				Arrays.sort(children);
    790 			}
    791 			for (int i = 0; i < children.length; i++)
    792 			{
    793 				XMPNode child = children[i];
    794 				child.dumpNode(result, recursive, indent + 1, i + 1);
    795 			}
    796 		}
    797 	}
    798 
    799 
    800 	/**
    801 	 * @return Returns whether this node is a language qualifier.
    802 	 */
    803 	private boolean isLanguageNode()
    804 	{
    805 		return XMPConst.XML_LANG.equals(name);
    806 	}
    807 
    808 
    809 	/**
    810 	 * @return Returns whether this node is a type qualifier.
    811 	 */
    812 	private boolean isTypeNode()
    813 	{
    814 		return "rdf:type".equals(name);
    815 	}
    816 
    817 
    818 	/**
    819 	 * <em>Note:</em> This method should always be called when accessing 'children' to be sure
    820 	 * that its initialized.
    821 	 * @return Returns list of children that is lazy initialized.
    822 	 */
    823 	private List getChildren()
    824 	{
    825 		if (children == null)
    826 		{
    827 			children = new ArrayList(0);
    828 		}
    829 		return children;
    830 	}
    831 
    832 
    833 	/**
    834 	 * @return Returns a read-only copy of child nodes list.
    835 	 */
    836 	public List getUnmodifiableChildren()
    837 	{
    838 		return Collections.unmodifiableList(new ArrayList(getChildren()));
    839 	}
    840 
    841 
    842 	/**
    843 	 * @return Returns list of qualifier that is lazy initialized.
    844 	 */
    845 	private List getQualifier()
    846 	{
    847 		if (qualifier == null)
    848 		{
    849 			qualifier = new ArrayList(0);
    850 		}
    851 		return qualifier;
    852 	}
    853 
    854 
    855 	/**
    856 	 * Sets the parent node, this is solely done by <code>addChild(...)</code>
    857 	 * and <code>addQualifier()</code>.
    858 	 *
    859 	 * @param parent
    860 	 *            Sets the parent node.
    861 	 */
    862 	protected void setParent(XMPNode parent)
    863 	{
    864 		this.parent = parent;
    865 	}
    866 
    867 
    868 	/**
    869 	 * Internal find.
    870 	 * @param list the list to search in
    871 	 * @param expr the search expression
    872 	 * @return Returns the found node or <code>nulls</code>.
    873 	 */
    874 	private XMPNode find(List list, String expr)
    875 	{
    876 
    877 		if (list != null)
    878 		{
    879 			for (Iterator it = list.iterator(); it.hasNext();)
    880 			{
    881 				XMPNode child = (XMPNode) it.next();
    882 				if (child.getName().equals(expr))
    883 				{
    884 					return child;
    885 				}
    886 			}
    887 		}
    888 		return null;
    889 	}
    890 
    891 
    892 	/**
    893 	 * Checks that a node name is not existing on the same level, except for array items.
    894 	 * @param childName the node name to check
    895 	 * @throws XMPException Thrown if a node with the same name is existing.
    896 	 */
    897 	private void assertChildNotExisting(String childName) throws XMPException
    898 	{
    899 		if (!XMPConst.ARRAY_ITEM_NAME.equals(childName)  &&
    900 			findChildByName(childName) != null)
    901 		{
    902 			throw new XMPException("Duplicate property or field node '" + childName + "'",
    903 					XMPError.BADXMP);
    904 		}
    905 	}
    906 
    907 
    908 	/**
    909 	 * Checks that a qualifier name is not existing on the same level.
    910 	 * @param qualifierName the new qualifier name
    911 	 * @throws XMPException Thrown if a node with the same name is existing.
    912 	 */
    913 	private void assertQualifierNotExisting(String qualifierName) throws XMPException
    914 	{
    915 		if (!XMPConst.ARRAY_ITEM_NAME.equals(qualifierName)  &&
    916 			findQualifierByName(qualifierName) != null)
    917 		{
    918 			throw new XMPException("Duplicate '" + qualifierName + "' qualifier", XMPError.BADXMP);
    919 		}
    920 	}
    921 }