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}