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 }