• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 	 */
XMPIteratorImpl(XMPMetaImpl xmp, String schemaNS, String propPath, IteratorOptions options)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 	 */
skipSubtree()125 	public void skipSubtree()
126 	{
127 		this.skipSubtree = true;
128 	}
129 
130 
131 	/**
132 	 * @see XMPIterator#skipSiblings()
133 	 */
skipSiblings()134 	public void skipSiblings()
135 	{
136 		skipSubtree();
137 		this.skipSiblings = true;
138 	}
139 
140 
141 	/**
142 	 * @see java.util.Iterator#hasNext()
143 	 */
hasNext()144 	public boolean hasNext()
145 	{
146 		return nodeIterator.hasNext();
147 	}
148 
149 
150 	/**
151 	 * @see java.util.Iterator#next()
152 	 */
next()153 	public Object next()
154 	{
155 		return nodeIterator.next();
156 	}
157 
158 
159 	/**
160 	 * @see java.util.Iterator#remove()
161 	 */
remove()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 	 */
getOptions()171 	protected IteratorOptions getOptions()
172 	{
173 		return options;
174 	}
175 
176 
177 	/**
178 	 * @return Exposes the options for inner class.
179 	 */
getBaseNS()180 	protected String getBaseNS()
181 	{
182 		return baseNS;
183 	}
184 
185 
186 	/**
187 	 * @param baseNS sets the baseNS from the inner class.
188 	 */
setBaseNS(String baseNS)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 		 */
NodeIterator()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 		 */
NodeIterator(XMPNode visitedNode, String parentPath, int index)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 		 */
hasNext()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 		 */
reportNode()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 		 */
iterateChildren(Iterator iterator)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 		 */
next()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 		 */
remove()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 		 */
accumulatePath(XMPNode currNode, String parentPath, int currentIndex)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 		 */
createPropertyInfo(final XMPNode node, final String baseNS, final String path)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 		 */
getChildrenIterator()482 		protected  Iterator getChildrenIterator()
483 		{
484 			return childrenIterator;
485 		}
486 
487 
488 		/**
489 		 * @param childrenIterator the childrenIterator to set
490 		 */
setChildrenIterator(Iterator childrenIterator)491 		protected void setChildrenIterator(Iterator childrenIterator)
492 		{
493 			this.childrenIterator = childrenIterator;
494 		}
495 
496 
497 		/**
498 		 * @return Returns the returnProperty.
499 		 */
getReturnProperty()500 		protected XMPPropertyInfo getReturnProperty()
501 		{
502 			return returnProperty;
503 		}
504 
505 
506 		/**
507 		 * @param returnProperty the returnProperty to set
508 		 */
setReturnProperty(XMPPropertyInfo returnProperty)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 }