content: // =================================================================================================
  // ADOBE SYSTEMS INCORPORATED
  // Copyright 2006 Adobe Systems Incorporated
  // All Rights Reserved
  //
  // NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
  // of the Adobe license agreement accompanying it.
  // =================================================================================================
  
  package com.adobe.internal.xmp.impl;
  
  import java.util.Collections;
  import java.util.Iterator;
  import java.util.NoSuchElementException;
  
  import com.adobe.internal.xmp.XMPError;
  import com.adobe.internal.xmp.XMPException;
  import com.adobe.internal.xmp.XMPIterator;
  import com.adobe.internal.xmp.XMPMetaFactory;
  import com.adobe.internal.xmp.impl.xpath.XMPPath;
  import com.adobe.internal.xmp.impl.xpath.XMPPathParser;
  import com.adobe.internal.xmp.options.IteratorOptions;
  import com.adobe.internal.xmp.options.PropertyOptions;
  import com.adobe.internal.xmp.properties.XMPPropertyInfo;
  
  
  /**
   * The <code>XMPIterator</code> implementation.
   * Iterates the XMP Tree according to a set of options.
   * During the iteration the XMPMeta-object must not be changed.
   * Calls to <code>skipSubtree()</code> / <code>skipSiblings()</code> will affect the iteration.
   * FfF: Introduce a lock mechanism to prohibit changes in the XMPTree during the iteration;
   * FfF: will throw a ConcurrentModificationException.
   *
   * @author  Stefan Makswit
   * @version $Revision$
   * @since   29.06.2006
   */
  public class XMPIteratorImpl implements XMPIterator
  {
  	/** stores the iterator options */
  	private IteratorOptions options;
  	/** the base namespace of the property path, will be changed during the iteration */
  	private String baseNS = null;
  	/** flag to indicate that skipSiblings() has been called. */
  	protected boolean skipSiblings = false;
  	/** flag to indicate that skipSiblings() has been called. */
  	protected boolean skipSubtree = false;
  	/** the node iterator doing the work */
  	private Iterator nodeIterator = null;
  
  
  	/**
  	 * Constructor with optionsl initial values. If <code>propName</code> is provided,
  	 * <code>schemaNS</code> has also be provided.
  	 * @param xmp the iterated metadata object.
  	 * @param schemaNS the iteration is reduced to this schema (optional)
  	 * @param propPath the iteration is redurce to this property within the <code>schemaNS</code>
  	 * @param options advanced iteration options, see {@link IteratorOptions}
  	 * @throws XMPException If the node defined by the paramters is not existing.
  	 */
  	public XMPIteratorImpl(XMPMetaImpl xmp, String schemaNS, String propPath,
  			IteratorOptions options) throws XMPException
  	{
  		// make sure that options is defined at least with defaults
  		this.options = options != null ? options : new IteratorOptions();
  
  		// the start node of the iteration depending on the schema and property filter
  		XMPNode startNode = null;
  		String initialPath = null;
  		boolean baseSchema = schemaNS != null  &&  schemaNS.length() > 0;
  		boolean baseProperty = propPath != null  &&  propPath.length() > 0;
  
  		if (!baseSchema  &&  !baseProperty)
  		{
  			// complete tree will be iterated
  			startNode = xmp.getRoot();
  		}
  		else if (baseSchema  &&  baseProperty)
  		{
  			// Schema and property node provided
  
  			// FfF: Shall the indices "last()" and selectors be converted to real indices?
  			// FfF: Need to wait for customer input.
  			// FfF: in case: findNode would have to change the path accordingly
  			XMPPath path = XMPPathParser.expandXPath(schemaNS, propPath);
  
  			// base path is the prop path without the property leaf
  			XMPPath basePath = new XMPPath();
  			for (int i = 0; i < path.size() - 1; i++)
  			{
  				basePath.add(path.getSegment(i));
  			}
  
  			startNode = XMPNodeUtils.findNode(xmp.getRoot(), path, false, null);
  			baseNS = schemaNS;
  			initialPath = basePath.toString();
  		}
  		else if (baseSchema  &&  !baseProperty)
  		{
  			// Only Schema provided
  			startNode = XMPNodeUtils.findSchemaNode(xmp.getRoot(), schemaNS, false);
  		}
  		else // !baseSchema  &&  baseProperty
  		{
  			// No schema but property provided -> error
  			throw new XMPException("Schema namespace URI is required", XMPError.BADSCHEMA);
  		}
  
  
  		// create iterator
  		if (startNode != null)
  		{
  			if (!this.options.isJustChildren())
  			{
  				nodeIterator = new NodeIterator(startNode, initialPath, 1);
  			}
  			else
  			{
  				nodeIterator = new NodeIteratorChildren(startNode, initialPath);
  			}
  		}
  		else
  		{
  			// create null iterator
  			nodeIterator = Collections.EMPTY_LIST.iterator();
  		}
  	}
  
  
  	/**
  	 * @see XMPIterator#skipSubtree()
  	 */
  	public void skipSubtree()
  	{
  		this.skipSubtree = true;
  	}
  
  
  	/**
  	 * @see XMPIterator#skipSiblings()
  	 */
  	public void skipSiblings()
  	{
  		skipSubtree();
  		this.skipSiblings = true;
  	}
  
  
  	/**
  	 * @see java.util.Iterator#hasNext()
  	 */
  	public boolean hasNext()
  	{
  		return nodeIterator.hasNext();
  	}
  
  
  	/**
  	 * @see java.util.Iterator#next()
  	 */
  	public Object next()
  	{
  		return nodeIterator.next();
  	}
  
  
  	/**
  	 * @see java.util.Iterator#remove()
  	 */
  	public void remove()
  	{
  		throw new UnsupportedOperationException("The XMPIterator does not support remove().");
  	}
  
  
  	/**
  	 * @return Exposes the options for inner class.
  	 */
  	protected IteratorOptions getOptions()
  	{
  		return options;
  	}
  
  
  	/**
  	 * @return Exposes the options for inner class.
  	 */
  	protected String getBaseNS()
  	{
  		return baseNS;
  	}
  
  
  	/**
  	 * @param baseNS sets the baseNS from the inner class.
  	 */
  	protected void setBaseNS(String baseNS)
  	{
  		this.baseNS = baseNS;
  	}
  
  
  
  
  
  
  	/**
  	 * The <code>XMPIterator</code> implementation.
  	 * It first returns the node itself, then recursivly the children and qualifier of the node.
  	 *
  	 * @author  Stefan Makswit
  	 * @version $Revision$
  	 * @since   29.06.2006
  	 */
  	private class NodeIterator implements Iterator
  	{
  		/** iteration state */
  		protected static final int ITERATE_NODE = 0;
  		/** iteration state */
  		protected static final int ITERATE_CHILDREN = 1;
  		/** iteration state */
  		protected static final int ITERATE_QUALIFIER = 2;
  
  		/** the state of the iteration */
  		private int state = ITERATE_NODE;
  		/** the currently visited node */
  		private XMPNode visitedNode;
  		/** the recursively accumulated path */
  		private String path;
  		/** the iterator that goes through the children and qualifier list */
  		private Iterator childrenIterator = null;
  		/** index of node with parent, only interesting for arrays */
  		private int index = 0;
  		/** the iterator for each child */
  		private Iterator subIterator = Collections.EMPTY_LIST.iterator();
  		/** the cached <code>PropertyInfo</code> to return */
  		private XMPPropertyInfo returnProperty = null;
  
  
  		/**
  		 * Default constructor
  		 */
  		public NodeIterator()
  		{
  			// EMPTY
  		}
  
  
  		/**
  		 * Constructor for the node iterator.
  		 * @param visitedNode the currently visited node
  		 * @param parentPath the accumulated path of the node
  		 * @param index the index within the parent node (only for arrays)
  		 */
  		public NodeIterator(XMPNode visitedNode, String parentPath, int index)
  		{
  			this.visitedNode = visitedNode;
  			this.state = NodeIterator.ITERATE_NODE;
  			if (visitedNode.getOptions().isSchemaNode())
  			{
  				setBaseNS(visitedNode.getName());
  			}
  
  			// for all but the root node and schema nodes
  			path = accumulatePath(visitedNode, parentPath, index);
  		}
  
  
  		/**
  		 * Prepares the next node to return if not already done.
  		 *
  		 * @see Iterator#hasNext()
  		 */
  		public boolean hasNext()
  		{
  			if (returnProperty != null)
  			{
  				// hasNext has been called before
  				return true;
  			}
  
  			// find next node
  			if (state == ITERATE_NODE)
  			{
  				return reportNode();
  			}
  			else if (state == ITERATE_CHILDREN)
  			{
  				if (childrenIterator == null)
  				{
  					childrenIterator = visitedNode.iterateChildren();
  				}
  
  				boolean hasNext = iterateChildren(childrenIterator);
  
  				if (!hasNext  &&  visitedNode.hasQualifier()  &&  !getOptions().isOmitQualifiers())
  				{
  					state = ITERATE_QUALIFIER;
  					childrenIterator = null;
  					hasNext = hasNext();
  				}
  				return hasNext;
  			}
  			else
  			{
  				if (childrenIterator == null)
  				{
  					childrenIterator = visitedNode.iterateQualifier();
  				}
  
  				return iterateChildren(childrenIterator);
  			}
  		}
  
  
  		/**
  		 * Sets the returnProperty as next item or recurses into <code>hasNext()</code>.
  		 * @return Returns if there is a next item to return.
  		 */
  		protected boolean reportNode()
  		{
  			state = ITERATE_CHILDREN;
  			if (visitedNode.getParent() != null  &&
  				(!getOptions().isJustLeafnodes()  ||  !visitedNode.hasChildren()))
  			{
  				returnProperty = createPropertyInfo(visitedNode, getBaseNS(), path);
  				return true;
  			}
  			else
  			{
  				return hasNext();
  			}
  		}
  
  
  		/**
  		 * Handles the iteration of the children or qualfier
  		 * @param iterator an iterator
  		 * @return Returns if there are more elements available.
  		 */
  		private boolean iterateChildren(Iterator iterator)
  		{
  			if (skipSiblings)
  			{
  				// setSkipSiblings(false);
  				skipSiblings = false;
  				subIterator = Collections.EMPTY_LIST.iterator();
  			}
  
  			// create sub iterator for every child,
  			// if its the first child visited or the former child is finished
  			if ((!subIterator.hasNext())  &&  iterator.hasNext())
  			{
  				XMPNode child = (XMPNode) iterator.next();
  				index++;
  				subIterator = new NodeIterator(child, path, index);
  			}
  
  			if (subIterator.hasNext())
  			{
  				returnProperty = (XMPPropertyInfo) subIterator.next();
  				return true;
  			}
  			else
  			{
  				return false;
  			}
  		}
  
  
  		/**
  		 * Calls hasNext() and returnes the prepared node. Afterwards its set to null.
  		 * The existance of returnProperty indicates if there is a next node, otherwise
  		 * an exceptio is thrown.
  		 *
  		 * @see Iterator#next()
  		 */
  		public Object next()
  		{
  			if (hasNext())
  			{
  				XMPPropertyInfo result = returnProperty;
  				returnProperty = null;
  				return result;
  			}
  			else
  			{
  				throw new NoSuchElementException("There are no more nodes to return");
  			}
  		}
  
  
  		/**
  		 * Not supported.
  		 * @see Iterator#remove()
  		 */
  		public void remove()
  		{
  			throw new UnsupportedOperationException();
  		}
  
  
  		/**
  		 * @param currNode the node that will be added to the path.
  		 * @param parentPath the path up to this node.
  		 * @param currentIndex the current array index if an arrey is traversed
  		 * @return Returns the updated path.
  		 */
  		protected String accumulatePath(XMPNode currNode, String parentPath, int currentIndex)
  		{
  			String separator;
  			String segmentName;
  			if (currNode.getParent() == null  ||  currNode.getOptions().isSchemaNode())
  			{
  				return null;
  			}
  			else if (currNode.getParent().getOptions().isArray())
  			{
  				separator = "";
  				segmentName = "[" + String.valueOf(currentIndex) + "]";
  			}
  			else
  			{
  				separator = "/";
  				segmentName = currNode.getName();
  			}
  
  
  			if (parentPath == null  ||  parentPath.length() == 0)
  			{
  				return segmentName;
  			}
  			else if (getOptions().isJustLeafname())
  			{
  				return !segmentName.startsWith("?") ?
  					segmentName :
  					segmentName.substring(1); // qualifier
  			}
  			else
  			{
  				return parentPath + separator + segmentName;
  			}
  		}
  
  
  		/**
  		 * Creates a property info object from an <code>XMPNode</code>.
  		 * @param node an <code>XMPNode</code>
  		 * @param baseNS the base namespace to report
  		 * @param path the full property path
  		 * @return Returns a <code>XMPProperty</code>-object that serves representation of the node.
  		 */
  		protected XMPPropertyInfo createPropertyInfo(final XMPNode node, final String baseNS,
  				final String path)
  		{
  			final String value = node.getOptions().isSchemaNode() ? null : node.getValue();
  
  			return new XMPPropertyInfo()
  			{
  				public String getNamespace()
  				{
  					if (!node.getOptions().isSchemaNode())
  					{
  						// determine namespace of leaf node
  						QName qname = new QName(node.getName());
  						return XMPMetaFactory.getSchemaRegistry().getNamespaceURI(qname.getPrefix());
  					}
  					else
  					{
  						return baseNS;
  					}
  				}
  
  				public String getPath()
  				{
  					return path;
  				}
  
  				public String getValue()
  				{
  					return value;
  				}
  
  				public PropertyOptions getOptions()
  				{
  					return node.getOptions();
  				}
  
  				public String getLanguage()
  				{
  					// the language is not reported
  					return null;
  				}
  			};
  		}
  
  
  		/**
  		 * @return the childrenIterator
  		 */
  		protected  Iterator getChildrenIterator()
  		{
  			return childrenIterator;
  		}
  
  
  		/**
  		 * @param childrenIterator the childrenIterator to set
  		 */
  		protected void setChildrenIterator(Iterator childrenIterator)
  		{
  			this.childrenIterator = childrenIterator;
  		}
  
  
  		/**
  		 * @return Returns the returnProperty.
  		 */
  		protected XMPPropertyInfo getReturnProperty()
  		{
  			return returnProperty;
  		}
  
  
  		/**
  		 * @param returnProperty the returnProperty to set
  		 */
  		protected  void setReturnProperty(XMPPropertyInfo returnProperty)
  		{
  			this.returnProperty = returnProperty;
  		}
  	}
  
  
  	/**
  	 * This iterator is derived from the default <code>NodeIterator</code>,
  	 * and is only used for the option {@link IteratorOptions#JUST_CHILDREN}.
  	 *
  	 * @author Stefan Makswit
  	 * @version $Revision$
  	 * @since 02.10.2006
  	 */
  	private class NodeIteratorChildren extends NodeIterator
  	{
  		/** */
  		private String parentPath;
  		/** */
  		private Iterator childrenIterator;
  		/** */
  		private int index = 0;
  
  
  		/**
  		 * Constructor
  		 * @param parentNode the node which children shall be iterated.
  		 * @param parentPath the full path of the former node without the leaf node.
  		 */
  		public NodeIteratorChildren(XMPNode parentNode, String parentPath)
  		{
  			if (parentNode.getOptions().isSchemaNode())
  			{
  				setBaseNS(parentNode.getName());
  			}
  			this.parentPath = accumulatePath(parentNode, parentPath, 1);
  
  			childrenIterator = parentNode.iterateChildren();
  		}
  
  
  		/**
  		 * Prepares the next node to return if not already done.
  		 *
  		 * @see Iterator#hasNext()
  		 */
  		public boolean hasNext()
  		{
  			if (getReturnProperty() != null)
  			{
  				// hasNext has been called before
  				return true;
  			}
  			else if (skipSiblings)
  			{
  				return false;
  			}
  			else if (childrenIterator.hasNext())
  			{
  				XMPNode child = (XMPNode) childrenIterator.next();
  				index++;
  
  				String path = null;
  				if (child.getOptions().isSchemaNode())
  				{
  					setBaseNS(child.getName());
  				}
  				else if (child.getParent() != null)
  				{
  					// for all but the root node and schema nodes
  					path = accumulatePath(child, parentPath, index);
  				}
  
  				// report next property, skip not-leaf nodes in case options is set
  				if (!getOptions().isJustLeafnodes()  ||  !child.hasChildren())
  				{
  					setReturnProperty(createPropertyInfo(child, getBaseNS(), path));
  					return true;
  				}
  				else
  				{
  					return hasNext();
  				}
  			}
  			else
  			{
  				return false;
  			}
  		}
  	}
  }