001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.math3.geometry.euclidean.oned;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.Iterator;
022import java.util.List;
023import java.util.NoSuchElementException;
024
025import org.apache.commons.math3.geometry.Point;
026import org.apache.commons.math3.geometry.partitioning.AbstractRegion;
027import org.apache.commons.math3.geometry.partitioning.BSPTree;
028import org.apache.commons.math3.geometry.partitioning.BoundaryProjection;
029import org.apache.commons.math3.geometry.partitioning.SubHyperplane;
030import org.apache.commons.math3.util.Precision;
031
032/** This class represents a 1D region: a set of intervals.
033 * @version $Id: IntervalsSet.java 1563714 2014-02-02 20:55:14Z tn $
034 * @since 3.0
035 */
036public class IntervalsSet extends AbstractRegion<Euclidean1D, Euclidean1D> implements Iterable<double[]> {
037
038    /** Default value for tolerance. */
039    private static final double DEFAULT_TOLERANCE = 1.0e-10;
040
041    /** Build an intervals set representing the whole real line.
042     * @param tolerance tolerance below which points are considered identical.
043     * @since 3.3
044     */
045    public IntervalsSet(final double tolerance) {
046        super(tolerance);
047    }
048
049    /** Build an intervals set corresponding to a single interval.
050     * @param lower lower bound of the interval, must be lesser or equal
051     * to {@code upper} (may be {@code Double.NEGATIVE_INFINITY})
052     * @param upper upper bound of the interval, must be greater or equal
053     * to {@code lower} (may be {@code Double.POSITIVE_INFINITY})
054     * @param tolerance tolerance below which points are considered identical.
055     * @since 3.3
056     */
057    public IntervalsSet(final double lower, final double upper, final double tolerance) {
058        super(buildTree(lower, upper, tolerance), tolerance);
059    }
060
061    /** Build an intervals set from an inside/outside BSP tree.
062     * <p>The leaf nodes of the BSP tree <em>must</em> have a
063     * {@code Boolean} attribute representing the inside status of
064     * the corresponding cell (true for inside cells, false for outside
065     * cells). In order to avoid building too many small objects, it is
066     * recommended to use the predefined constants
067     * {@code Boolean.TRUE} and {@code Boolean.FALSE}</p>
068     * @param tree inside/outside BSP tree representing the intervals set
069     * @param tolerance tolerance below which points are considered identical.
070     * @since 3.3
071     */
072    public IntervalsSet(final BSPTree<Euclidean1D> tree, final double tolerance) {
073        super(tree, tolerance);
074    }
075
076    /** Build an intervals set from a Boundary REPresentation (B-rep).
077     * <p>The boundary is provided as a collection of {@link
078     * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the
079     * interior part of the region on its minus side and the exterior on
080     * its plus side.</p>
081     * <p>The boundary elements can be in any order, and can form
082     * several non-connected sets (like for example polygons with holes
083     * or a set of disjoints polyhedrons considered as a whole). In
084     * fact, the elements do not even need to be connected together
085     * (their topological connections are not used here). However, if the
086     * boundary does not really separate an inside open from an outside
087     * open (open having here its topological meaning), then subsequent
088     * calls to the {@link
089     * org.apache.commons.math3.geometry.partitioning.Region#checkPoint(org.apache.commons.math3.geometry.Point)
090     * checkPoint} method will not be meaningful anymore.</p>
091     * <p>If the boundary is empty, the region will represent the whole
092     * space.</p>
093     * @param boundary collection of boundary elements
094     * @param tolerance tolerance below which points are considered identical.
095     * @since 3.3
096     */
097    public IntervalsSet(final Collection<SubHyperplane<Euclidean1D>> boundary,
098                        final double tolerance) {
099        super(boundary, tolerance);
100    }
101
102    /** Build an intervals set representing the whole real line.
103     * @deprecated as of 3.1 replaced with {@link #IntervalsSet(double)}
104     */
105    @Deprecated
106    public IntervalsSet() {
107        this(DEFAULT_TOLERANCE);
108    }
109
110    /** Build an intervals set corresponding to a single interval.
111     * @param lower lower bound of the interval, must be lesser or equal
112     * to {@code upper} (may be {@code Double.NEGATIVE_INFINITY})
113     * @param upper upper bound of the interval, must be greater or equal
114     * to {@code lower} (may be {@code Double.POSITIVE_INFINITY})
115     * @deprecated as of 3.3 replaced with {@link #IntervalsSet(double, double, double)}
116     */
117    @Deprecated
118    public IntervalsSet(final double lower, final double upper) {
119        this(lower, upper, DEFAULT_TOLERANCE);
120    }
121
122    /** Build an intervals set from an inside/outside BSP tree.
123     * <p>The leaf nodes of the BSP tree <em>must</em> have a
124     * {@code Boolean} attribute representing the inside status of
125     * the corresponding cell (true for inside cells, false for outside
126     * cells). In order to avoid building too many small objects, it is
127     * recommended to use the predefined constants
128     * {@code Boolean.TRUE} and {@code Boolean.FALSE}</p>
129     * @param tree inside/outside BSP tree representing the intervals set
130     * @deprecated as of 3.3, replaced with {@link #IntervalsSet(BSPTree, double)}
131     */
132    @Deprecated
133    public IntervalsSet(final BSPTree<Euclidean1D> tree) {
134        this(tree, DEFAULT_TOLERANCE);
135    }
136
137    /** Build an intervals set from a Boundary REPresentation (B-rep).
138     * <p>The boundary is provided as a collection of {@link
139     * SubHyperplane sub-hyperplanes}. Each sub-hyperplane has the
140     * interior part of the region on its minus side and the exterior on
141     * its plus side.</p>
142     * <p>The boundary elements can be in any order, and can form
143     * several non-connected sets (like for example polygons with holes
144     * or a set of disjoints polyhedrons considered as a whole). In
145     * fact, the elements do not even need to be connected together
146     * (their topological connections are not used here). However, if the
147     * boundary does not really separate an inside open from an outside
148     * open (open having here its topological meaning), then subsequent
149     * calls to the {@link
150     * org.apache.commons.math3.geometry.partitioning.Region#checkPoint(org.apache.commons.math3.geometry.Point)
151     * checkPoint} method will not be meaningful anymore.</p>
152     * <p>If the boundary is empty, the region will represent the whole
153     * space.</p>
154     * @param boundary collection of boundary elements
155     * @deprecated as of 3.3, replaced with {@link #IntervalsSet(Collection, double)}
156     */
157    @Deprecated
158    public IntervalsSet(final Collection<SubHyperplane<Euclidean1D>> boundary) {
159        this(boundary, DEFAULT_TOLERANCE);
160    }
161
162    /** Build an inside/outside tree representing a single interval.
163     * @param lower lower bound of the interval, must be lesser or equal
164     * to {@code upper} (may be {@code Double.NEGATIVE_INFINITY})
165     * @param upper upper bound of the interval, must be greater or equal
166     * to {@code lower} (may be {@code Double.POSITIVE_INFINITY})
167     * @param tolerance tolerance below which points are considered identical.
168     * @return the built tree
169     */
170    private static BSPTree<Euclidean1D> buildTree(final double lower, final double upper,
171                                                  final double tolerance) {
172        if (Double.isInfinite(lower) && (lower < 0)) {
173            if (Double.isInfinite(upper) && (upper > 0)) {
174                // the tree must cover the whole real line
175                return new BSPTree<Euclidean1D>(Boolean.TRUE);
176            }
177            // the tree must be open on the negative infinity side
178            final SubHyperplane<Euclidean1D> upperCut =
179                new OrientedPoint(new Vector1D(upper), true, tolerance).wholeHyperplane();
180            return new BSPTree<Euclidean1D>(upperCut,
181                               new BSPTree<Euclidean1D>(Boolean.FALSE),
182                               new BSPTree<Euclidean1D>(Boolean.TRUE),
183                               null);
184        }
185        final SubHyperplane<Euclidean1D> lowerCut =
186            new OrientedPoint(new Vector1D(lower), false, tolerance).wholeHyperplane();
187        if (Double.isInfinite(upper) && (upper > 0)) {
188            // the tree must be open on the positive infinity side
189            return new BSPTree<Euclidean1D>(lowerCut,
190                                            new BSPTree<Euclidean1D>(Boolean.FALSE),
191                                            new BSPTree<Euclidean1D>(Boolean.TRUE),
192                                            null);
193        }
194
195        // the tree must be bounded on the two sides
196        final SubHyperplane<Euclidean1D> upperCut =
197            new OrientedPoint(new Vector1D(upper), true, tolerance).wholeHyperplane();
198        return new BSPTree<Euclidean1D>(lowerCut,
199                                        new BSPTree<Euclidean1D>(Boolean.FALSE),
200                                        new BSPTree<Euclidean1D>(upperCut,
201                                                                 new BSPTree<Euclidean1D>(Boolean.FALSE),
202                                                                 new BSPTree<Euclidean1D>(Boolean.TRUE),
203                                                                 null),
204                                        null);
205
206    }
207
208    /** {@inheritDoc} */
209    @Override
210    public IntervalsSet buildNew(final BSPTree<Euclidean1D> tree) {
211        return new IntervalsSet(tree, getTolerance());
212    }
213
214    /** {@inheritDoc} */
215    @Override
216    protected void computeGeometricalProperties() {
217        if (getTree(false).getCut() == null) {
218            setBarycenter((Point<Euclidean1D>) Vector1D.NaN);
219            setSize(((Boolean) getTree(false).getAttribute()) ? Double.POSITIVE_INFINITY : 0);
220        } else {
221            double size = 0.0;
222            double sum = 0.0;
223            for (final Interval interval : asList()) {
224                size += interval.getSize();
225                sum  += interval.getSize() * interval.getBarycenter();
226            }
227            setSize(size);
228            if (Double.isInfinite(size)) {
229                setBarycenter((Point<Euclidean1D>) Vector1D.NaN);
230            } else if (size >= Precision.SAFE_MIN) {
231                setBarycenter((Point<Euclidean1D>) new Vector1D(sum / size));
232            } else {
233                setBarycenter((Point<Euclidean1D>) ((OrientedPoint) getTree(false).getCut().getHyperplane()).getLocation());
234            }
235        }
236    }
237
238    /** Get the lowest value belonging to the instance.
239     * @return lowest value belonging to the instance
240     * ({@code Double.NEGATIVE_INFINITY} if the instance doesn't
241     * have any low bound, {@code Double.POSITIVE_INFINITY} if the
242     * instance is empty)
243     */
244    public double getInf() {
245        BSPTree<Euclidean1D> node = getTree(false);
246        double  inf  = Double.POSITIVE_INFINITY;
247        while (node.getCut() != null) {
248            final OrientedPoint op = (OrientedPoint) node.getCut().getHyperplane();
249            inf  = op.getLocation().getX();
250            node = op.isDirect() ? node.getMinus() : node.getPlus();
251        }
252        return ((Boolean) node.getAttribute()) ? Double.NEGATIVE_INFINITY : inf;
253    }
254
255    /** Get the highest value belonging to the instance.
256     * @return highest value belonging to the instance
257     * ({@code Double.POSITIVE_INFINITY} if the instance doesn't
258     * have any high bound, {@code Double.NEGATIVE_INFINITY} if the
259     * instance is empty)
260     */
261    public double getSup() {
262        BSPTree<Euclidean1D> node = getTree(false);
263        double  sup  = Double.NEGATIVE_INFINITY;
264        while (node.getCut() != null) {
265            final OrientedPoint op = (OrientedPoint) node.getCut().getHyperplane();
266            sup  = op.getLocation().getX();
267            node = op.isDirect() ? node.getPlus() : node.getMinus();
268        }
269        return ((Boolean) node.getAttribute()) ? Double.POSITIVE_INFINITY : sup;
270    }
271
272    /** {@inheritDoc}
273     * @since 3.3
274     */
275    @Override
276    public BoundaryProjection<Euclidean1D> projectToBoundary(final Point<Euclidean1D> point) {
277
278        // get position of test point
279        final double x = ((Vector1D) point).getX();
280
281        double previous = Double.NEGATIVE_INFINITY;
282        for (final double[] a : this) {
283            if (x < a[0]) {
284                // the test point lies between the previous and the current intervals
285                // offset will be positive
286                final double previousOffset = x - previous;
287                final double currentOffset  = a[0] - x;
288                if (previousOffset < currentOffset) {
289                    return new BoundaryProjection<Euclidean1D>(point, finiteOrNullPoint(previous), previousOffset);
290                } else {
291                    return new BoundaryProjection<Euclidean1D>(point, finiteOrNullPoint(a[0]), currentOffset);
292                }
293            } else if (x <= a[1]) {
294                // the test point lies within the current interval
295                // offset will be negative
296                final double offset0 = a[0] - x;
297                final double offset1 = x - a[1];
298                if (offset0 < offset1) {
299                    return new BoundaryProjection<Euclidean1D>(point, finiteOrNullPoint(a[1]), offset1);
300                } else {
301                    return new BoundaryProjection<Euclidean1D>(point, finiteOrNullPoint(a[0]), offset0);
302                }
303            }
304            previous = a[1];
305        }
306
307        // the test point if past the last sub-interval
308        return new BoundaryProjection<Euclidean1D>(point, finiteOrNullPoint(previous), x - previous);
309
310    }
311
312    /** Build a finite point.
313     * @param x abscissa of the point
314     * @return a new point for finite abscissa, null otherwise
315     */
316    private Vector1D finiteOrNullPoint(final double x) {
317        return Double.isInfinite(x) ? null : new Vector1D(x);
318    }
319
320    /** Build an ordered list of intervals representing the instance.
321     * <p>This method builds this intervals set as an ordered list of
322     * {@link Interval Interval} elements. If the intervals set has no
323     * lower limit, the first interval will have its low bound equal to
324     * {@code Double.NEGATIVE_INFINITY}. If the intervals set has
325     * no upper limit, the last interval will have its upper bound equal
326     * to {@code Double.POSITIVE_INFINITY}. An empty tree will
327     * build an empty list while a tree representing the whole real line
328     * will build a one element list with both bounds being
329     * infinite.</p>
330     * @return a new ordered list containing {@link Interval Interval}
331     * elements
332     */
333    public List<Interval> asList() {
334        final List<Interval> list = new ArrayList<Interval>();
335        for (final double[] a : this) {
336            list.add(new Interval(a[0], a[1]));
337        }
338        return list;
339    }
340
341    /** Get the first leaf node of a tree.
342     * @param root tree root
343     * @return first leaf node
344     */
345    private BSPTree<Euclidean1D> getFirstLeaf(final BSPTree<Euclidean1D> root) {
346
347        if (root.getCut() == null) {
348            return root;
349        }
350
351        // find the smallest internal node
352        BSPTree<Euclidean1D> smallest = null;
353        for (BSPTree<Euclidean1D> n = root; n != null; n = previousInternalNode(n)) {
354            smallest = n;
355        }
356
357        return leafBefore(smallest);
358
359    }
360
361    /** Get the node corresponding to the first interval boundary.
362     * @return smallest internal node,
363     * or null if there are no internal nodes (i.e. the set is either empty or covers the real line)
364     */
365    private BSPTree<Euclidean1D> getFirstIntervalBoundary() {
366
367        // start search at the tree root
368        BSPTree<Euclidean1D> node = getTree(false);
369        if (node.getCut() == null) {
370            return null;
371        }
372
373        // walk tree until we find the smallest internal node
374        node = getFirstLeaf(node).getParent();
375
376        // walk tree until we find an interval boundary
377        while (node != null && !(isIntervalStart(node) || isIntervalEnd(node))) {
378            node = nextInternalNode(node);
379        }
380
381        return node;
382
383    }
384
385    /** Check if an internal node corresponds to the start abscissa of an interval.
386     * @param node internal node to check
387     * @return true if the node corresponds to the start abscissa of an interval
388     */
389    private boolean isIntervalStart(final BSPTree<Euclidean1D> node) {
390
391        if ((Boolean) leafBefore(node).getAttribute()) {
392            // it has an inside cell before it, it may end an interval but not start it
393            return false;
394        }
395
396        if (!(Boolean) leafAfter(node).getAttribute()) {
397            // it has an outside cell after it, it is a dummy cut away from real intervals
398            return false;
399        }
400
401        // the cell has an outside before and an inside after it
402        // it is the start of an interval
403        return true;
404
405    }
406
407    /** Check if an internal node corresponds to the end abscissa of an interval.
408     * @param node internal node to check
409     * @return true if the node corresponds to the end abscissa of an interval
410     */
411    private boolean isIntervalEnd(final BSPTree<Euclidean1D> node) {
412
413        if (!(Boolean) leafBefore(node).getAttribute()) {
414            // it has an outside cell before it, it may start an interval but not end it
415            return false;
416        }
417
418        if ((Boolean) leafAfter(node).getAttribute()) {
419            // it has an inside cell after it, it is a dummy cut in the middle of an interval
420            return false;
421        }
422
423        // the cell has an inside before and an outside after it
424        // it is the end of an interval
425        return true;
426
427    }
428
429    /** Get the next internal node.
430     * @param node current internal node
431     * @return next internal node in ascending order, or null
432     * if this is the last internal node
433     */
434    private BSPTree<Euclidean1D> nextInternalNode(BSPTree<Euclidean1D> node) {
435
436        if (childAfter(node).getCut() != null) {
437            // the next node is in the sub-tree
438            return leafAfter(node).getParent();
439        }
440
441        // there is nothing left deeper in the tree, we backtrack
442        while (isAfterParent(node)) {
443            node = node.getParent();
444        }
445        return node.getParent();
446
447    }
448
449    /** Get the previous internal node.
450     * @param node current internal node
451     * @return previous internal node in ascending order, or null
452     * if this is the first internal node
453     */
454    private BSPTree<Euclidean1D> previousInternalNode(BSPTree<Euclidean1D> node) {
455
456        if (childBefore(node).getCut() != null) {
457            // the next node is in the sub-tree
458            return leafBefore(node).getParent();
459        }
460
461        // there is nothing left deeper in the tree, we backtrack
462        while (isBeforeParent(node)) {
463            node = node.getParent();
464        }
465        return node.getParent();
466
467    }
468
469    /** Find the leaf node just before an internal node.
470     * @param node internal node at which the sub-tree starts
471     * @return leaf node just before the internal node
472     */
473    private BSPTree<Euclidean1D> leafBefore(BSPTree<Euclidean1D> node) {
474
475        node = childBefore(node);
476        while (node.getCut() != null) {
477            node = childAfter(node);
478        }
479
480        return node;
481
482    }
483
484    /** Find the leaf node just after an internal node.
485     * @param node internal node at which the sub-tree starts
486     * @return leaf node just after the internal node
487     */
488    private BSPTree<Euclidean1D> leafAfter(BSPTree<Euclidean1D> node) {
489
490        node = childAfter(node);
491        while (node.getCut() != null) {
492            node = childBefore(node);
493        }
494
495        return node;
496
497    }
498
499    /** Check if a node is the child before its parent in ascending order.
500     * @param node child node considered
501     * @return true is the node has a parent end is before it in ascending order
502     */
503    private boolean isBeforeParent(final BSPTree<Euclidean1D> node) {
504        final BSPTree<Euclidean1D> parent = node.getParent();
505        if (parent == null) {
506            return false;
507        } else {
508            return node == childBefore(parent);
509        }
510    }
511
512    /** Check if a node is the child after its parent in ascending order.
513     * @param node child node considered
514     * @return true is the node has a parent end is after it in ascending order
515     */
516    private boolean isAfterParent(final BSPTree<Euclidean1D> node) {
517        final BSPTree<Euclidean1D> parent = node.getParent();
518        if (parent == null) {
519            return false;
520        } else {
521            return node == childAfter(parent);
522        }
523    }
524
525    /** Find the child node just before an internal node.
526     * @param node internal node at which the sub-tree starts
527     * @return child node just before the internal node
528     */
529    private BSPTree<Euclidean1D> childBefore(BSPTree<Euclidean1D> node) {
530        if (isDirect(node)) {
531            // smaller abscissas are on minus side, larger abscissas are on plus side
532            return node.getMinus();
533        } else {
534            // smaller abscissas are on plus side, larger abscissas are on minus side
535            return node.getPlus();
536        }
537    }
538
539    /** Find the child node just after an internal node.
540     * @param node internal node at which the sub-tree starts
541     * @return child node just after the internal node
542     */
543    private BSPTree<Euclidean1D> childAfter(BSPTree<Euclidean1D> node) {
544        if (isDirect(node)) {
545            // smaller abscissas are on minus side, larger abscissas are on plus side
546            return node.getPlus();
547        } else {
548            // smaller abscissas are on plus side, larger abscissas are on minus side
549            return node.getMinus();
550        }
551    }
552
553    /** Check if an internal node has a direct oriented point.
554     * @param node internal node to check
555     * @return true if the oriented point is direct
556     */
557    private boolean isDirect(final BSPTree<Euclidean1D> node) {
558        return ((OrientedPoint) node.getCut().getHyperplane()).isDirect();
559    }
560
561    /** Get the abscissa of an internal node.
562     * @param node internal node to check
563     * @return abscissa
564     */
565    private double getAngle(final BSPTree<Euclidean1D> node) {
566        return ((OrientedPoint) node.getCut().getHyperplane()).getLocation().getX();
567    }
568
569    /** {@inheritDoc}
570     * <p>
571     * The iterator returns the limit values of sub-intervals in ascending order.
572     * </p>
573     * <p>
574     * The iterator does <em>not</em> support the optional {@code remove} operation.
575     * </p>
576     * @since 3.3
577     */
578    public Iterator<double[]> iterator() {
579        return new SubIntervalsIterator();
580    }
581
582    /** Local iterator for sub-intervals. */
583    private class SubIntervalsIterator implements Iterator<double[]> {
584
585        /** Current node. */
586        private BSPTree<Euclidean1D> current;
587
588        /** Sub-interval no yet returned. */
589        private double[] pending;
590
591        /** Simple constructor.
592         */
593        public SubIntervalsIterator() {
594
595            current = getFirstIntervalBoundary();
596
597            if (current == null) {
598                // all the leaf tree nodes share the same inside/outside status
599                if ((Boolean) getFirstLeaf(getTree(false)).getAttribute()) {
600                    // it is an inside node, it represents the full real line
601                    pending = new double[] {
602                        Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY
603                    };
604                } else {
605                    pending = null;
606                }
607            } else if (isIntervalEnd(current)) {
608                // the first boundary is an interval end,
609                // so the first interval starts at infinity
610                pending = new double[] {
611                    Double.NEGATIVE_INFINITY, getAngle(current)
612                };
613            } else {
614                selectPending();
615            }
616        }
617
618        /** Walk the tree to select the pending sub-interval.
619         */
620        private void selectPending() {
621
622            // look for the start of the interval
623            BSPTree<Euclidean1D> start = current;
624            while (start != null && !isIntervalStart(start)) {
625                start = nextInternalNode(start);
626            }
627
628            if (start == null) {
629                // we have exhausted the iterator
630                current = null;
631                pending = null;
632                return;
633            }
634
635            // look for the end of the interval
636            BSPTree<Euclidean1D> end = start;
637            while (end != null && !isIntervalEnd(end)) {
638                end = nextInternalNode(end);
639            }
640
641            if (end != null) {
642
643                // we have identified the interval
644                pending = new double[] {
645                    getAngle(start), getAngle(end)
646                };
647
648                // prepare search for next interval
649                current = end;
650
651            } else {
652
653                // the final interval is open toward infinity
654                pending = new double[] {
655                    getAngle(start), Double.POSITIVE_INFINITY
656                };
657
658                // there won't be any other intervals
659                current = null;
660
661            }
662
663        }
664
665        /** {@inheritDoc} */
666        public boolean hasNext() {
667            return pending != null;
668        }
669
670        /** {@inheritDoc} */
671        public double[] next() {
672            if (pending == null) {
673                throw new NoSuchElementException();
674            }
675            final double[] next = pending;
676            selectPending();
677            return next;
678        }
679
680        /** {@inheritDoc} */
681        public void remove() {
682            throw new UnsupportedOperationException();
683        }
684
685    }
686
687}