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.Collections;
     13 import java.util.Iterator;
     14 import java.util.NoSuchElementException;
     15 
     16 import com.adobe.xmp.XMPError;
     17 import com.adobe.xmp.XMPException;
     18 import com.adobe.xmp.XMPIterator;
     19 import com.adobe.xmp.impl.xpath.XMPPath;
     20 import com.adobe.xmp.impl.xpath.XMPPathParser;
     21 import com.adobe.xmp.options.IteratorOptions;
     22 import com.adobe.xmp.options.PropertyOptions;
     23 import com.adobe.xmp.properties.XMPPropertyInfo;
     24 
     25 
     26 /**
     27  * The <code>XMPIterator</code> implementation.
     28  * Iterates the XMP Tree according to a set of options.
     29  * During the iteration the XMPMeta-object must not be changed.
     30  * Calls to <code>skipSubtree()</code> / <code>skipSiblings()</code> will affect the iteration.
     31  *
     32  * @since   29.06.2006
     33  */
     34 public class XMPIteratorImpl implements XMPIterator
     35 {
     36 	/** stores the iterator options */
     37 	private IteratorOptions options;
     38 	/** the base namespace of the property path, will be changed during the iteration */
     39 	private String baseNS = null;
     40 	/** flag to indicate that skipSiblings() has been called. */
     41 	protected boolean skipSiblings = false;
     42 	/** flag to indicate that skipSiblings() has been called. */
     43 	protected boolean skipSubtree = false;
     44 	/** the node iterator doing the work */
     45 	private Iterator nodeIterator = null;
     46 
     47 
     48 	/**
     49 	 * Constructor with optionsl initial values. If <code>propName</code> is provided,
     50 	 * <code>schemaNS</code> has also be provided.
     51 	 * @param xmp the iterated metadata object.
     52 	 * @param schemaNS the iteration is reduced to this schema (optional)
     53 	 * @param propPath the iteration is redurce to this property within the <code>schemaNS</code>
     54 	 * @param options advanced iteration options, see {@link IteratorOptions}
     55 	 * @throws XMPException If the node defined by the paramters is not existing.
     56 	 */
     57 	public XMPIteratorImpl(XMPMetaImpl xmp, String schemaNS, String propPath,
     58 			IteratorOptions options) throws XMPException
     59 	{
     60 		// make sure that options is defined at least with defaults
     61 		this.options = options != null ? options : new IteratorOptions();
     62 
     63 		// the start node of the iteration depending on the schema and property filter
     64 		XMPNode startNode = null;
     65 		String initialPath = null;
     66 		boolean baseSchema = schemaNS != null  &&  schemaNS.length() > 0;
     67 		boolean baseProperty = propPath != null  &&  propPath.length() > 0;
     68 
     69 		if (!baseSchema  &&  !baseProperty)
     70 		{
     71 			// complete tree will be iterated
     72 			startNode = xmp.getRoot();
     73 		}
     74 		else if (baseSchema  &&  baseProperty)
     75 		{
     76 			// Schema and property node provided
     77 			XMPPath path = XMPPathParser.expandXPath(schemaNS, propPath);
     78 
     79 			// base path is the prop path without the property leaf
     80 			XMPPath basePath = new XMPPath();
     81 			for (int i = 0; i < path.size() - 1; i++)
     82 			{
     83 				basePath.add(path.getSegment(i));
     84 			}
     85 
     86 			startNode = XMPNodeUtils.findNode(xmp.getRoot(), path, false, null);
     87 			baseNS = schemaNS;
     88 			initialPath = basePath.toString();
     89 		}
     90 		else if (baseSchema  &&  !baseProperty)
     91 		{
     92 			// Only Schema provided
     93 			startNode = XMPNodeUtils.findSchemaNode(xmp.getRoot(), schemaNS, false);
     94 		}
     95 		else // !baseSchema  &&  baseProperty
     96 		{
     97 			// No schema but property provided -> error
     98 			throw new XMPException("Schema namespace URI is required", XMPError.BADSCHEMA);
     99 		}
    100 
    101 
    102 		// create iterator
    103 		if (startNode != null)
    104 		{
    105 			if (!this.options.isJustChildren())
    106 			{
    107 				nodeIterator = new NodeIterator(startNode, initialPath, 1);
    108 			}
    109 			else
    110 			{
    111 				nodeIterator = new NodeIteratorChildren(startNode, initialPath);
    112 			}
    113 		}
    114 		else
    115 		{
    116 			// create null iterator
    117 			nodeIterator = Collections.EMPTY_LIST.iterator();
    118 		}
    119 	}
    120 
    121 
    122 	/**
    123 	 * @see XMPIterator#skipSubtree()
    124 	 */
    125 	public void skipSubtree()
    126 	{
    127 		this.skipSubtree = true;
    128 	}
    129 
    130 
    131 	/**
    132 	 * @see XMPIterator#skipSiblings()
    133 	 */
    134 	public void skipSiblings()
    135 	{
    136 		skipSubtree();
    137 		this.skipSiblings = true;
    138 	}
    139 
    140 
    141 	/**
    142 	 * @see java.util.Iterator#hasNext()
    143 	 */
    144 	public boolean hasNext()
    145 	{
    146 		return nodeIterator.hasNext();
    147 	}
    148 
    149 
    150 	/**
    151 	 * @see java.util.Iterator#next()
    152 	 */
    153 	public Object next()
    154 	{
    155 		return nodeIterator.next();
    156 	}
    157 
    158 
    159 	/**
    160 	 * @see java.util.Iterator#remove()
    161 	 */
    162 	public void remove()
    163 	{
    164 		throw new UnsupportedOperationException("The XMPIterator does not support remove().");
    165 	}
    166 
    167 
    168 	/**
    169 	 * @return Exposes the options for inner class.
    170 	 */
    171 	protected IteratorOptions getOptions()
    172 	{
    173 		return options;
    174 	}
    175 
    176 
    177 	/**
    178 	 * @return Exposes the options for inner class.
    179 	 */
    180 	protected String getBaseNS()
    181 	{
    182 		return baseNS;
    183 	}
    184 
    185 
    186 	/**
    187 	 * @param baseNS sets the baseNS from the inner class.
    188 	 */
    189 	protected void setBaseNS(String baseNS)
    190 	{
    191 		this.baseNS = baseNS;
    192 	}
    193 
    194 
    195 
    196 
    197 
    198 
    199 	/**
    200 	 * The <code>XMPIterator</code> implementation.
    201 	 * It first returns the node itself, then recursivly the children and qualifier of the node.
    202 	 *
    203 	 * @since   29.06.2006
    204 	 */
    205 	private class NodeIterator implements Iterator
    206 	{
    207 		/** iteration state */
    208 		protected static final int ITERATE_NODE = 0;
    209 		/** iteration state */
    210 		protected static final int ITERATE_CHILDREN = 1;
    211 		/** iteration state */
    212 		protected static final int ITERATE_QUALIFIER = 2;
    213 
    214 		/** the state of the iteration */
    215 		private int state = ITERATE_NODE;
    216 		/** the currently visited node */
    217 		private XMPNode visitedNode;
    218 		/** the recursively accumulated path */
    219 		private String path;
    220 		/** the iterator that goes through the children and qualifier list */
    221 		private Iterator childrenIterator = null;
    222 		/** index of node with parent, only interesting for arrays */
    223 		private int index = 0;
    224 		/** the iterator for each child */
    225 		private Iterator subIterator = Collections.EMPTY_LIST.iterator();
    226 		/** the cached <code>PropertyInfo</code> to return */
    227 		private XMPPropertyInfo returnProperty = null;
    228 
    229 
    230 		/**
    231 		 * Default constructor
    232 		 */
    233 		public NodeIterator()
    234 		{
    235 			// EMPTY
    236 		}
    237 
    238 
    239 		/**
    240 		 * Constructor for the node iterator.
    241 		 * @param visitedNode the currently visited node
    242 		 * @param parentPath the accumulated path of the node
    243 		 * @param index the index within the parent node (only for arrays)
    244 		 */
    245 		public NodeIterator(XMPNode visitedNode, String parentPath, int index)
    246 		{
    247 			this.visitedNode = visitedNode;
    248 			this.state = NodeIterator.ITERATE_NODE;
    249 			if (visitedNode.getOptions().isSchemaNode())
    250 			{
    251 				setBaseNS(visitedNode.getName());
    252 			}
    253 
    254 			// for all but the root node and schema nodes
    255 			path = accumulatePath(visitedNode, parentPath, index);
    256 		}
    257 
    258 
    259 		/**
    260 		 * Prepares the next node to return if not already done.
    261 		 *
    262 		 * @see Iterator#hasNext()
    263 		 */
    264 		public boolean hasNext()
    265 		{
    266 			if (returnProperty != null)
    267 			{
    268 				// hasNext has been called before
    269 				return true;
    270 			}
    271 
    272 			// find next node
    273 			if (state == ITERATE_NODE)
    274 			{
    275 				return reportNode();
    276 			}
    277 			else if (state == ITERATE_CHILDREN)
    278 			{
    279 				if (childrenIterator == null)
    280 				{
    281 					childrenIterator = visitedNode.iterateChildren();
    282 				}
    283 
    284 				boolean hasNext = iterateChildren(childrenIterator);
    285 
    286 				if (!hasNext  &&  visitedNode.hasQualifier()  &&  !getOptions().isOmitQualifiers())
    287 				{
    288 					state = ITERATE_QUALIFIER;
    289 					childrenIterator = null;
    290 					hasNext = hasNext();
    291 				}
    292 				return hasNext;
    293 			}
    294 			else
    295 			{
    296 				if (childrenIterator == null)
    297 				{
    298 					childrenIterator = visitedNode.iterateQualifier();
    299 				}
    300 
    301 				return iterateChildren(childrenIterator);
    302 			}
    303 		}
    304 
    305 
    306 		/**
    307 		 * Sets the returnProperty as next item or recurses into <code>hasNext()</code>.
    308 		 * @return Returns if there is a next item to return.
    309 		 */
    310 		protected boolean reportNode()
    311 		{
    312 			state = ITERATE_CHILDREN;
    313 			if (visitedNode.getParent() != null  &&
    314 				(!getOptions().isJustLeafnodes()  ||  !visitedNode.hasChildren()))
    315 			{
    316 				returnProperty = createPropertyInfo(visitedNode, getBaseNS(), path);
    317 				return true;
    318 			}
    319 			else
    320 			{
    321 				return hasNext();
    322 			}
    323 		}
    324 
    325 
    326 		/**
    327 		 * Handles the iteration of the children or qualfier
    328 		 * @param iterator an iterator
    329 		 * @return Returns if there are more elements available.
    330 		 */
    331 		private boolean iterateChildren(Iterator iterator)
    332 		{
    333 			if (skipSiblings)
    334 			{
    335 				// setSkipSiblings(false);
    336 				skipSiblings = false;
    337 				subIterator = Collections.EMPTY_LIST.iterator();
    338 			}
    339 
    340 			// create sub iterator for every child,
    341 			// if its the first child visited or the former child is finished
    342 			if ((!subIterator.hasNext())  &&  iterator.hasNext())
    343 			{
    344 				XMPNode child = (XMPNode) iterator.next();
    345 				index++;
    346 				subIterator = new NodeIterator(child, path, index);
    347 			}
    348 
    349 			if (subIterator.hasNext())
    350 			{
    351 				returnProperty = (XMPPropertyInfo) subIterator.next();
    352 				return true;
    353 			}
    354 			else
    355 			{
    356 				return false;
    357 			}
    358 		}
    359 
    360 
    361 		/**
    362 		 * Calls hasNext() and returnes the prepared node. Afterwards its set to null.
    363 		 * The existance of returnProperty indicates if there is a next node, otherwise
    364 		 * an exceptio is thrown.
    365 		 *
    366 		 * @see Iterator#next()
    367 		 */
    368 		public Object next()
    369 		{
    370 			if (hasNext())
    371 			{
    372 				XMPPropertyInfo result = returnProperty;
    373 				returnProperty = null;
    374 				return result;
    375 			}
    376 			else
    377 			{
    378 				throw new NoSuchElementException("There are no more nodes to return");
    379 			}
    380 		}
    381 
    382 
    383 		/**
    384 		 * Not supported.
    385 		 * @see Iterator#remove()
    386 		 */
    387 		public void remove()
    388 		{
    389 			throw new UnsupportedOperationException();
    390 		}
    391 
    392 
    393 		/**
    394 		 * @param currNode the node that will be added to the path.
    395 		 * @param parentPath the path up to this node.
    396 		 * @param currentIndex the current array index if an arrey is traversed
    397 		 * @return Returns the updated path.
    398 		 */
    399 		protected String accumulatePath(XMPNode currNode, String parentPath, int currentIndex)
    400 		{
    401 			String separator;
    402 			String segmentName;
    403 			if (currNode.getParent() == null  ||  currNode.getOptions().isSchemaNode())
    404 			{
    405 				return null;
    406 			}
    407 			else if (currNode.getParent().getOptions().isArray())
    408 			{
    409 				separator = "";
    410 				segmentName = "[" + String.valueOf(currentIndex) + "]";
    411 			}
    412 			else
    413 			{
    414 				separator = "/";
    415 				segmentName = currNode.getName();
    416 			}
    417 
    418 
    419 			if (parentPath == null  ||  parentPath.length() == 0)
    420 			{
    421 				return segmentName;
    422 			}
    423 			else if (getOptions().isJustLeafname())
    424 			{
    425 				return !segmentName.startsWith("?") ?
    426 					segmentName :
    427 					segmentName.substring(1); // qualifier
    428 			}
    429 			else
    430 			{
    431 				return parentPath + separator + segmentName;
    432 			}
    433 		}
    434 
    435 
    436 		/**
    437 		 * Creates a property info object from an <code>XMPNode</code>.
    438 		 * @param node an <code>XMPNode</code>
    439 		 * @param baseNS the base namespace to report
    440 		 * @param path the full property path
    441 		 * @return Returns a <code>XMPProperty</code>-object that serves representation of the node.
    442 		 */
    443 		protected XMPPropertyInfo createPropertyInfo(final XMPNode node, final String baseNS,
    444 				final String path)
    445 		{
    446 			final Object value = node.getOptions().isSchemaNode() ? null : node.getValue();
    447 
    448 			return new XMPPropertyInfo()
    449 			{
    450 				public String getNamespace()
    451 				{
    452 					return baseNS;
    453 				}
    454 
    455 				public String getPath()
    456 				{
    457 					return path;
    458 				}
    459 
    460 				public Object getValue()
    461 				{
    462 					return value;
    463 				}
    464 
    465 				public PropertyOptions getOptions()
    466 				{
    467 					return node.getOptions();
    468 				}
    469 
    470 				public String getLanguage()
    471 				{
    472 					// the language is not reported
    473 					return null;
    474 				}
    475 			};
    476 		}
    477 
    478 
    479 		/**
    480 		 * @return the childrenIterator
    481 		 */
    482 		protected  Iterator getChildrenIterator()
    483 		{
    484 			return childrenIterator;
    485 		}
    486 
    487 
    488 		/**
    489 		 * @param childrenIterator the childrenIterator to set
    490 		 */
    491 		protected void setChildrenIterator(Iterator childrenIterator)
    492 		{
    493 			this.childrenIterator = childrenIterator;
    494 		}
    495 
    496 
    497 		/**
    498 		 * @return Returns the returnProperty.
    499 		 */
    500 		protected XMPPropertyInfo getReturnProperty()
    501 		{
    502 			return returnProperty;
    503 		}
    504 
    505 
    506 		/**
    507 		 * @param returnProperty the returnProperty to set
    508 		 */
    509 		protected  void setReturnProperty(XMPPropertyInfo returnProperty)
    510 		{
    511 			this.returnProperty = returnProperty;
    512 		}
    513 	}
    514 
    515 
    516 	/**
    517 	 * This iterator is derived from the default <code>NodeIterator</code>,
    518 	 * and is only used for the option {@link IteratorOptions#JUST_CHILDREN}.
    519 	 *
    520 	 * @since 02.10.2006
    521 	 */
    522 	private class NodeIteratorChildren extends NodeIterator
    523 	{
    524 		/** */
    525 		private String parentPath;
    526 		/** */
    527 		private Iterator childrenIterator;
    528 		/** */
    529 		private int index = 0;
    530 
    531 
    532 		/**
    533 		 * Constructor
    534 		 * @param parentNode the node which children shall be iterated.
    535 		 * @param parentPath the full path of the former node without the leaf node.
    536 		 */
    537 		public NodeIteratorChildren(XMPNode parentNode, String parentPath)
    538 		{
    539 			if (parentNode.getOptions().isSchemaNode())
    540 			{
    541 				setBaseNS(parentNode.getName());
    542 			}
    543 			this.parentPath = accumulatePath(parentNode, parentPath, 1);
    544 
    545 			childrenIterator = parentNode.iterateChildren();
    546 		}
    547 
    548 
    549 		/**
    550 		 * Prepares the next node to return if not already done.
    551 		 *
    552 		 * @see Iterator#hasNext()
    553 		 */
    554 		public boolean hasNext()
    555 		{
    556 			if (getReturnProperty() != null)
    557 			{
    558 				// hasNext has been called before
    559 				return true;
    560 			}
    561 			else if (skipSiblings)
    562 			{
    563 				return false;
    564 			}
    565 			else if (childrenIterator.hasNext())
    566 			{
    567 				XMPNode child = (XMPNode) childrenIterator.next();
    568 				index++;
    569 
    570 				String path = null;
    571 				if (child.getOptions().isSchemaNode())
    572 				{
    573 					setBaseNS(child.getName());
    574 				}
    575 				else if (child.getParent() != null)
    576 				{
    577 					// for all but the root node and schema nodes
    578 					path = accumulatePath(child, parentPath, index);
    579 				}
    580 
    581 				// report next property, skip not-leaf nodes in case options is set
    582 				if (!getOptions().isJustLeafnodes()  ||  !child.hasChildren())
    583 				{
    584 					setReturnProperty(createPropertyInfo(child, getBaseNS(), path));
    585 					return true;
    586 				}
    587 				else
    588 				{
    589 					return hasNext();
    590 				}
    591 			}
    592 			else
    593 			{
    594 				return false;
    595 			}
    596 		}
    597 	}
    598 }