001/* An actor that modifies record tokens. 002 * 003 * Copyright (c) 2015 The Regents of the University of California. 004 * All rights reserved. 005 * 006 * '$Author: crawl $' 007 * '$Date: 2015-08-26 21:01:58 +0000 (Wed, 26 Aug 2015) $' 008 * '$Revision: 33647 $' 009 * 010 * Permission is hereby granted, without written agreement and without 011 * license or royalty fees, to use, copy, modify, and distribute this 012 * software and its documentation for any purpose, provided that the above 013 * copyright notice and the following two paragraphs appear in all copies 014 * of this software. 015 * 016 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 017 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 018 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 019 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 020 * SUCH DAMAGE. 021 * 022 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 023 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 024 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 025 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 026 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 027 * ENHANCEMENTS, OR MODIFICATIONS. 028 * 029 */ 030package org.kepler.actor; 031 032import java.util.AbstractMap.SimpleEntry; 033import java.util.ArrayList; 034import java.util.HashMap; 035import java.util.HashSet; 036import java.util.Iterator; 037import java.util.LinkedList; 038import java.util.List; 039import java.util.Map; 040import java.util.Set; 041 042import ptolemy.actor.TypedAtomicActor; 043import ptolemy.actor.TypedIOPort; 044import ptolemy.data.RecordToken; 045import ptolemy.data.Token; 046import ptolemy.data.expr.Parameter; 047import ptolemy.data.expr.StringParameter; 048import ptolemy.data.type.BaseType; 049import ptolemy.data.type.MonotonicFunction; 050import ptolemy.data.type.RecordType; 051import ptolemy.data.type.Type; 052import ptolemy.graph.Inequality; 053import ptolemy.graph.InequalityTerm; 054import ptolemy.kernel.CompositeEntity; 055import ptolemy.kernel.util.Attribute; 056import ptolemy.kernel.util.IllegalActionException; 057import ptolemy.kernel.util.NameDuplicationException; 058import ptolemy.kernel.util.SingletonAttribute; 059 060/** 061 * An actor that modifies the contents of record tokens. The token to 062 * be modified is read in the <i>input</i> port. The <i>path</i> parameter 063 * specifies the path within the input record token to modify. The format 064 * of the path starts with <code>root</code> and contains the name of each 065 * nested label within the record token. For example, if the input record 066 * is <code>{a={b={c=1}}}</code>, the path to the <code>c</code> field is 067 * is <code>root.a.b.c</code>. The <i>value</i> input specifies a token 068 * to add or replace at the specified path in the input token. 069 * <p> This actor supports the following operations: 070 * <ul> 071 * <li>add<p>The token in <i>value</i> is added to the input token at the 072 * path described by <i>path</i>. 073 * </li> 074 * <li>extract<p>The part of the input token described by the <i>path</i> is 075 * output. 076 * </li> 077 * <li>remove<p>The part of input token described by the <i>path</i> 078 * is removed.</p> 079 * </li> 080 * <li>replace<p>The part of the input token described by the <i>path</i> 081 * is replaced with the contents read from <i>value</i>.</p> 082 * </li> 083 * </ul> 084 * 085 * @author Philippe Huynh, Daniel Crawl 086 * @version $Id: RecordOperation.java 33647 2015-08-26 21:01:58Z crawl $ 087 * 088 */ 089public class RecordOperation extends TypedAtomicActor { 090 091 public RecordOperation(CompositeEntity container, String name) 092 throws NameDuplicationException, IllegalActionException { 093 super(container, name); 094 095 path = new StringParameter(this, "path"); 096 path.setExpression("root"); 097 098 input = new TypedIOPort(this, "input", true, false); 099 new SingletonAttribute(input, "_showName"); 100 101 value = new TypedIOPort(this, "value", true, false); 102 new SingletonAttribute(value, "_showName"); 103 104 output = new TypedIOPort(this, "output", false, true); 105 new SingletonAttribute(output, "_showName"); 106 107 operation = new StringParameter(this, "operation"); 108 operation.addChoice("add"); 109 operation.addChoice("extract"); 110 operation.addChoice("remove"); 111 operation.addChoice("replace"); 112 operation.setExpression(_operationValue); 113 } 114 115 @Override 116 public void attributeChanged(Attribute attribute) throws IllegalActionException { 117 if (attribute == operation) { 118 String valueStr = operation.getExpression(); 119 120 if (valueStr.equals("add") || 121 valueStr.equals("extract") || 122 valueStr.equals("remove") || 123 valueStr.equals("replace")) { 124 _operationValue = valueStr; 125 } else { 126 throw new IllegalActionException(this, "Unknown type of updateChoice: " + value); 127 } 128 } else { 129 super.attributeChanged(attribute); 130 } 131 } 132 133 @Override 134 public void fire() throws IllegalActionException { 135 136 super.fire(); 137 String recPathValue = path.getExpression(); 138 RecordToken recInValue = (RecordToken) input.get(0); 139 RecordToken recValue = null; 140 141 // System.out.println("input 142 // record:"+recInValue+":path:"+recPathValue+":choice:"+_updateChoiceValue); 143 144 if(value.numberOfSources() > 0) { 145 Token token = value.get(0); 146 if (_operationValue.equals("add") || _operationValue.equals("replace")) { 147 recValue = (RecordToken) token; 148 } 149 } 150 151 SimpleEntry<String, Token> rootse = 152 new SimpleEntry<String, Token>("root", RecordToken.NIL); 153 TreeNode<SimpleEntry<String, Token>> root = 154 new TreeNode<SimpleEntry<String, Token>>(rootse); 155 _createTree(recInValue, root); 156 if(_debugging) { 157 _printTree(root); 158 } 159 160 // Searching the concerned node 161 String[] pathArray = recPathValue.split("\\."); 162 TreeNode<SimpleEntry<String, Token>> targetNode = root; 163 for (int i = 0; i < pathArray.length; i++) { 164 // System.out.println("path:"+ pathArray[i]); 165 final String refEntry = pathArray[i]; 166 Comparable<SimpleEntry<String, Token>> searchKey = new Comparable<SimpleEntry<String, Token>>() { 167 @Override 168 public int compareTo(SimpleEntry<String, Token> treeData) { 169 if (treeData == null) { 170 return 1; 171 } 172 return treeData.getKey().compareTo(refEntry); 173 } 174 }; 175 targetNode = targetNode.findTreeNode(searchKey); 176 if (targetNode == null) { 177 throw new IllegalActionException(this, "Search path is incorrect!"); 178 } else { 179 // System.out.println("targetNode:"+targetNode.data.getKey()); 180 } 181 } 182 // remove option 183 if (_operationValue.equals("remove")) { 184 targetNode.parent.delete(targetNode); 185 } 186 // add option 187 else if (_operationValue.equals("add")) { 188 if (targetNode.isLeaf()) { 189 throw new IllegalActionException(this, "Search path is incorrect!, try with an upper level path"); 190 } else { 191 _createTree(recValue, targetNode); 192 } 193 } 194 // replace option 195 else if (_operationValue.equals("replace")) { 196 if (!(recPathValue.equals("root"))) { 197 TreeNode<SimpleEntry<String, Token>> parentNode = targetNode.parent; 198 targetNode.parent.delete(targetNode); 199 _createTree(recValue, parentNode); 200 } else { 201 root = new TreeNode<SimpleEntry<String, Token>>(rootse); 202 _createTree(recValue, root); 203 } 204 } 205 // extract option 206 else if (_operationValue.equals("extract")) { 207 if (targetNode.isLeaf()) { 208 String[] labels = new String[1]; 209 Token[] values = new Token[1]; 210 labels[0] = targetNode.data.getKey(); 211 values[0] = targetNode.data.getValue(); 212 RecordToken newToken = new RecordToken(labels, values); 213 targetNode.data.setValue(newToken); 214 } 215 root = targetNode; 216 root.parent = null; 217 } 218 219 // printTree(root); 220 _createRecord(root); 221 // System.out.println("final record:"+root.data.getValue()); 222 // RecordReviserFunction A=new RecordReviserFunction(); 223 // System.out.println("MinimalOutputTerm:"+A.getValue()); 224 output.send(0, (RecordToken) root.data.getValue()); 225 } 226 227 @Override 228 public void preinitialize() throws IllegalActionException { 229 super.preinitialize(); 230 231 // if operation is add or replace, make sure portValue is connected. 232 if (_operationValue.equals("add") || _operationValue.equals("replace")) { 233 if (value.numberOfSources() == 0) { 234 throw new IllegalActionException(this, 235 "Input port portValue must be connected for add or replace operations."); 236 } 237 } 238 239 } 240 241 /////////////////////////////////////////////////////////////////// 242 //// public fields //// 243 244 /** The record token to operate on. */ 245 public TypedIOPort input; 246 247 /** The value to add or replace in the input. */ 248 public TypedIOPort value; 249 250 /** The resulting record token. */ 251 public TypedIOPort output; 252 253 /** The path to operate on. */ 254 public Parameter path; 255 256 /** 257 * The operation to perform: add, extract, remove, or replace. <b>NOTE:</b> 258 * Do not change while the workflow is executing. 259 */ 260 public Parameter operation; 261 262 /////////////////////////////////////////////////////////////////// 263 //// protected methods //// 264 265 /** Return custom type constraints specified by the RecordOperationFunction 266 * class. 267 */ 268 @Override 269 protected Set<Inequality> _customTypeConstraints() { 270 Set<Inequality> result = new HashSet<Inequality>(); 271 result.add(new Inequality(new RecordOperationFunction(), output.getTypeTerm())); 272 return result; 273 } 274 275 /** Returns null since we define custom type constraints. */ 276 @Override 277 protected Set<Inequality> _defaultTypeConstraints() { 278 return null; 279 } 280 281 /////////////////////////////////////////////////////////////////// 282 //// private methods //// 283 284 private void _createTree(RecordToken rec, TreeNode<SimpleEntry<String, Token>> node) { 285 for (String name : rec.labelSet()) { 286 Token token = rec.get(name); 287 if (token instanceof RecordToken) { 288 TreeNode<SimpleEntry<String, Token>> child = node 289 .addChild(new SimpleEntry<String, Token>(name, RecordToken.NIL)); 290 _createTree((RecordToken) token, child); 291 } else { 292 node.addChild(new SimpleEntry<String, Token>(name, token)); 293 } 294 } 295 } 296 297 private void _createRecord(TreeNode<SimpleEntry<String, Token>> tempRoot) throws IllegalActionException { 298 299 if (tempRoot.isLeaf()) { 300 return; 301 } 302 List<TreeNode<SimpleEntry<String, Token>>> listOfLeaf = new ArrayList<TreeNode<SimpleEntry<String, Token>>>(); 303 304 for (TreeNode<SimpleEntry<String, Token>> node : tempRoot) { 305 if (node.isLeaf()) { 306 listOfLeaf.add(node); 307 } 308 // System.out.println("loop 309 // leaf:"+node.data.getKey()+":"+node.data.getValue()); 310 } 311 // System.out.println("number of leaf:"+listOfLeaf.size()); 312 313 for (TreeNode<SimpleEntry<String, Token>> next : listOfLeaf) { 314 String[] labels = { next.data.getKey() }; 315 Token[] values = { next.data.getValue() }; 316 RecordToken token = new RecordToken(labels, values); 317 // System.out.println(token+"parent:"+next.parent.data.getValue()); 318 if (next.parent.data.getValue() == RecordToken.NIL) { 319 next.parent.data.setValue(token); 320 // System.out.println("NIL"); 321 } else { 322 next.parent.data.setValue(RecordToken.merge((RecordToken) next.parent.data.getValue(), token)); 323 // System.out.println("MERGE:"+next.parent.data.getValue()); 324 } 325 next.parent.delete(next); 326 } 327 _createRecord(tempRoot); 328 } 329 330 private void _printTree(TreeNode<SimpleEntry<String, Token>> treeRoot) { 331 332 for (TreeNode<SimpleEntry<String, Token>> node : treeRoot) { 333 String indent = _createIndent(node.getLevel()); 334 _debug(indent + node.data.getKey() + " " + node.data.getValue()); 335 } 336 } 337 338 private String _createIndent(int depth) { 339 StringBuilder sb = new StringBuilder(); 340 for (int i = 0; i < depth; i++) { 341 sb.append("Level" + i + ": "); 342 } 343 return sb.toString(); 344 } 345 346 /** A class to set the port types. */ 347 private class RecordOperationFunction extends MonotonicFunction { 348 349 private void _createTypeTree(RecordType rec, TreeNode<SimpleEntry<String, Type>> node) { 350 for (String name : (Set<String>) (rec.labelSet())) { 351 Type type = rec.get(name); 352 if (type instanceof RecordType) { 353 TreeNode<SimpleEntry<String, Type>> child = node 354 .addChild(new SimpleEntry<String, Type>(name, RecordType.EMPTY_RECORD)); 355 _createTypeTree((RecordType) type, child); 356 } else { 357 node.addChild(new SimpleEntry<String, Type>(name, type)); 358 } 359 } 360 } 361 362 private void _createTypeRecord(TreeNode<SimpleEntry<String, Type>> tempRoot) throws IllegalActionException { 363 364 if (tempRoot.isLeaf()) { 365 return; 366 } 367 List<TreeNode<SimpleEntry<String, Type>>> listOfLeaf = new ArrayList<TreeNode<SimpleEntry<String, Type>>>(); 368 369 for (TreeNode<SimpleEntry<String, Type>> node : tempRoot) { 370 if (node.isLeaf()) { 371 listOfLeaf.add(node); 372 } 373 // System.out.println("loop 374 // leaf:"+node.data.getKey()+":"+node.data.getValue()); 375 } 376 // System.out.println("number of leaf:"+listOfLeaf.size()); 377 for (TreeNode<SimpleEntry<String, Type>> next : listOfLeaf) { 378 String[] labels = { next.data.getKey() }; 379 Type[] values = { next.data.getValue() }; 380 RecordType type = new RecordType(labels, values); 381 // System.out.println(token+"parent:"+next.parent.data.getValue()); 382 if (next.parent.data.getValue() == RecordType.EMPTY_RECORD) { 383 next.parent.data.setValue(type); 384 // System.out.println("NIL"); 385 } else { 386 // System.out.println("MERGE"); 387 next.parent.data.setValue(_mergeTypeRecord((RecordType) next.parent.data.getValue(), type)); 388 } 389 next.parent.delete(next); 390 } 391 _createTypeRecord(tempRoot); 392 } 393 394 private RecordType _mergeTypeRecord(RecordType a, RecordType b) { 395 396 Map<String, Type> fields = new HashMap<String, Type>(); 397 398 for (String label : (Set<String>) a.labelSet()) { 399 fields.put(label, a.get(label)); 400 } 401 for (String label : (Set<String>) b.labelSet()) { 402 fields.put(label, b.get(label)); 403 } 404 return new RecordType(fields.keySet().toArray(new String[fields.size()]), 405 fields.values().toArray(new Type[fields.size()])); 406 } 407 408 /* 409 * private void _printTypeTree(TreeNode<SimpleEntry<String,Type>> 410 * treeRoot) { 411 * 412 * for (TreeNode<SimpleEntry<String,Type>> node : treeRoot) { String 413 * indent = _createIndent(node.getLevel()); System.out.println(indent + 414 * node.data.getKey()+" "+ node.data.getValue()); } } 415 */ 416 417 /** 418 * Return the function result. 419 * 420 * @return A Type. 421 */ 422 @Override 423 public Object getValue() throws IllegalActionException { 424 425 String recPathValue = path.getExpression(); 426 RecordType recInType; 427 RecordType recValue = null; 428 429 if (!(input.getType() instanceof RecordType)) { 430 return BaseType.UNKNOWN; 431 } 432 recInType = (RecordType) input.getType(); 433 434 if (_operationValue.equals("add") || _operationValue.equals("replace")) { 435 if (!(value.getType() instanceof RecordType)) { 436 return BaseType.UNKNOWN; 437 } 438 recValue = (RecordType) value.getType(); 439 } 440 441 SimpleEntry<String, Type> rootse = new SimpleEntry<String, Type>("root", RecordType.EMPTY_RECORD); 442 TreeNode<SimpleEntry<String, Type>> root = new TreeNode<SimpleEntry<String, Type>>(rootse); 443 _createTypeTree(recInType, root); 444 // _printTypeTree(root); 445 446 // Searching the concerned node 447 String[] pathArray = recPathValue.split("\\."); 448 TreeNode<SimpleEntry<String, Type>> targetNode = root; 449 // TreeNode<SimpleEntry<String,Type>> targetNodeold; 450 for (int i = 0; i < pathArray.length; i++) { 451 // targetNodeold = targetNode; 452 // System.out.println("path:"+ pathArray[i]); 453 final String refEntry = pathArray[i]; 454 Comparable<SimpleEntry<String, Type>> searchKey = new Comparable<SimpleEntry<String, Type>>() { 455 @Override 456 public int compareTo(SimpleEntry<String, Type> treeData) { 457 if (treeData == null) { 458 return 1; 459 } 460 return treeData.getKey().compareTo(refEntry); 461 // boolean nodeOk = 462 // (treeData.getKey()).equals(refEntry); 463 // return nodeOk ? 0 : 1; 464 } 465 }; 466 targetNode = targetNode.findTreeNode(searchKey); 467 if (targetNode == null) { 468 // _printTypeTree(targetNodeold); 469 throw new IllegalActionException(RecordOperation.this, "Search path is incorrect!"); 470 } else { 471 // System.out.println("targetNode:"+ 472 // targetNode.data.getKey()+":"+targetNode.data.getValue()); 473 } 474 } 475 // remove option 476 if (_operationValue.equals("remove")) { 477 targetNode.parent.delete(targetNode); 478 } 479 // add option 480 else if (_operationValue.equals("add")) { 481 if (targetNode.isLeaf()) { 482 throw new IllegalActionException(RecordOperation.this, 483 "Search path is incorrect!, try with an upper level path"); 484 } else { 485 _createTypeTree(recValue, targetNode); 486 } 487 } 488 // replace option 489 else if (_operationValue.equals("replace")) { 490 if (!(recPathValue.equals("root"))) { 491 TreeNode<SimpleEntry<String, Type>> parentNode = targetNode.parent; 492 targetNode.parent.delete(targetNode); 493 _createTypeTree(recValue, parentNode); 494 } else { 495 root = new TreeNode<SimpleEntry<String, Type>>(rootse); 496 _createTypeTree(recValue, root); 497 } 498 } 499 // extract option 500 else if (_operationValue.equals("extract")) { 501 if (targetNode.isLeaf()) { 502 String[] labels = { targetNode.data.getKey() }; 503 Type[] values = { targetNode.data.getValue() }; 504 RecordType type = new RecordType(labels, values); 505 targetNode.data.setValue(type); 506 } 507 root = targetNode; 508 root.parent = null; 509 } 510 511 // _printTypeTree(root); 512 _createTypeRecord(root); 513 // System.out.println("final type:"+root.data.getValue()); 514 return root.data.getValue(); 515 } 516 517 /** 518 * Return all the InequalityTerms for all input ports in an array. 519 * 520 * @return An array of InequalityTerm. 521 */ 522 @Override 523 public InequalityTerm[] getVariables() { 524 Iterator<TypedIOPort> inputPorts = inputPortList().iterator(); 525 LinkedList<InequalityTerm> result = new LinkedList<InequalityTerm>(); 526 while (inputPorts.hasNext()) { 527 TypedIOPort port = inputPorts.next(); 528 InequalityTerm term = port.getTypeTerm(); 529 if (term.isSettable()) { 530 result.add(term); 531 } 532 } 533 return result.toArray(new InequalityTerm[0]); 534 } 535 } 536 537 /** A class for trees. 538 * 539 * This appears to be from: 540 * https://code.google.com/p/yet-another-tree-structure/source/browse/trunk/src/com/tree/?r=32 541 * 542 * Released under an Apache 2.0 license. 543 * 544 */ 545 private static class TreeNodeIter<T> implements Iterator<TreeNode<T>> { 546 547 enum ProcessStages { 548 ProcessParent, ProcessChildCurNode, ProcessChildSubNode 549 } 550 551 private TreeNode<T> treeNode; 552 553 public TreeNodeIter(TreeNode<T> treeNode) { 554 this.treeNode = treeNode; 555 this.doNext = ProcessStages.ProcessParent; 556 this.childrenCurNodeIter = treeNode.children.iterator(); 557 } 558 559 private ProcessStages doNext; 560 private TreeNode<T> next; 561 private Iterator<TreeNode<T>> childrenCurNodeIter; 562 private Iterator<TreeNode<T>> childrenSubNodeIter; 563 564 @Override 565 public boolean hasNext() { 566 567 if (this.doNext == ProcessStages.ProcessParent) { 568 this.next = this.treeNode; 569 this.doNext = ProcessStages.ProcessChildCurNode; 570 return true; 571 } 572 573 if (this.doNext == ProcessStages.ProcessChildCurNode) { 574 if (childrenCurNodeIter.hasNext()) { 575 TreeNode<T> childDirect = childrenCurNodeIter.next(); 576 childrenSubNodeIter = childDirect.iterator(); 577 this.doNext = ProcessStages.ProcessChildSubNode; 578 return hasNext(); 579 } 580 581 else { 582 this.doNext = null; 583 return false; 584 } 585 } 586 587 if (this.doNext == ProcessStages.ProcessChildSubNode) { 588 if (childrenSubNodeIter.hasNext()) { 589 this.next = childrenSubNodeIter.next(); 590 return true; 591 } else { 592 this.next = null; 593 this.doNext = ProcessStages.ProcessChildCurNode; 594 return hasNext(); 595 } 596 } 597 598 return false; 599 } 600 601 @Override 602 public TreeNode<T> next() { 603 return this.next; 604 } 605 606 @Override 607 public void remove() { 608 throw new UnsupportedOperationException(); 609 } 610 611 } 612 613 /** A class for trees. 614 * 615 * This appears to be from: 616 * https://code.google.com/p/yet-another-tree-structure/source/browse/trunk/src/com/tree/?r=32 617 * 618 * Released under an Apache 2.0 license. 619 * 620 */ 621 private static class TreeNode<T> implements Iterable<TreeNode<T>> { 622 623 public T data; 624 public TreeNode<T> parent; 625 public List<TreeNode<T>> children; 626 627 public boolean isRoot() { 628 return parent == null; 629 } 630 631 public boolean isLeaf() { 632 return children.size() == 0; 633 } 634 635 private List<TreeNode<T>> elementsIndex; 636 637 public TreeNode(T data) { 638 this.data = data; 639 this.children = new LinkedList<TreeNode<T>>(); 640 this.elementsIndex = new LinkedList<TreeNode<T>>(); 641 this.elementsIndex.add(this); 642 } 643 644 public TreeNode<T> addChild(T child) { 645 TreeNode<T> childNode = new TreeNode<T>(child); 646 childNode.parent = this; 647 this.children.add(childNode); 648 this.registerChildForSearch(childNode); 649 return childNode; 650 } 651 652 public int getLevel() { 653 if (this.isRoot()) 654 return 0; 655 else 656 return parent.getLevel() + 1; 657 } 658 659 /* 660 * public int treeSize() { return elementsIndex.size(); } 661 * 662 * public void printList() { for (TreeNode<T> element : 663 * this.elementsIndex) { T elData = element.data; 664 * System.out.println("data:"+elData); } } 665 */ 666 667 private void registerChildForSearch(TreeNode<T> node) { 668 elementsIndex.add(node); 669 if (parent != null) 670 parent.registerChildForSearch(node); 671 } 672 673 public TreeNode<T> findTreeNode(Comparable<T> cmp) { 674 for (TreeNode<T> element : this.elementsIndex) { 675 T elData = element.data; 676 if (cmp.compareTo(elData) == 0) 677 return element; 678 } 679 680 return null; 681 } 682 683 @Override 684 public String toString() { 685 return data != null ? data.toString() : "[data null]"; 686 } 687 688 @Override 689 public Iterator<TreeNode<T>> iterator() { 690 TreeNodeIter<T> iter = new TreeNodeIter<T>(this); 691 return iter; 692 } 693 694 public void delete(TreeNode<T> node) { 695 children.remove(node); 696 // not enough for the elementsIndex; 697 elementsIndex.remove(node); 698 if (parent != null) 699 parent.delete(node); 700 } 701 } 702 703 /////////////////////////////////////////////////////////////////// 704 //// private fields //// 705 706 /** The operation to perform. */ 707 private String _operationValue = "remove"; 708 709}