/* * $Id$ * This file is a part of the Arakhne Foundation Classes, http://www.arakhne.org/afc * * Copyright (c) 2000-2012 Stephane GALLAND. * Copyright (c) 2005-10, Multiagent Team, Laboratoire Systemes et Transports, * Universite de Technologie de Belfort-Montbeliard. * Copyright (c) 2013-2016 The original authors, and other authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.arakhne.afc.math.graph; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.Set; import java.util.TreeSet; import org.eclipse.xtext.xbase.lib.Pure; /** * This class is an iterator on a graph. * * <p>The behaviour of the iterator is strongly influenced by the constructor's parameters. * The two most important parameters are {@code allowManyReplies} and * {@code assumeOrientedSegments}. * * <p>The {@code allowManyReplies} parameter indicates if a segment could be replied many * types, or not, by the iterator. This parameter permits to control the behaviour of * the iterator against graph cycles. * * <p>The {@code assumeOrientedSegments} parameter indicates how the segments are considered * by the iterator. If {@code assumeOrientedSegments} is <code>true</code> it means * that a segment reached by one of its end point is different than the same segment reached * by the other end point. If {@code assumeOrientedSegments} is <code>false</code> it means * that the end points of the segments are not take into account. This parameter is usefull * only if {@code allowManyReplies} is set to <code>false</code>. * * @param <PT> is the type of node in the graph * @param <ST> is the type of edge in the graph * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 13.0 */ public class GraphIterator<ST extends GraphSegment<ST, PT>, PT extends GraphPoint<PT, ST>> implements Iterator<ST> { private final boolean allowManyReplies; private final boolean assumeOrientedSegments; private final GraphCourseModel<ST, PT> courseModel; private final Set<GraphIterationElement<ST, PT>> visited; private GraphIterationElement<ST, PT> current; private final WeakReference<Graph<ST, PT>> graph; /** * @param graph1 is the graph associated to this iterator. * @param segment is the segment from which to start. * @param point is the segment's point indicating the direction. * @param allowManyReplies1 may be <code>true</code> to allow to reply many times * the same segment, otherwhise <code>false</code>. * @param assumeOrientedSegments1 may be <code>true</code> to assume that the same segment has two different * instances for graph iteration: the first instance is associated the first point of the segment and the second * instance is associated to the last point of the segment. If this parameter is <code>false</code> to assume that * the end points of a segment are not distinguished. * @param distanceToReachStartingPoint is the distance to reach the starting point. * It must be negative or nul. */ public GraphIterator(Graph<ST, PT> graph1, ST segment, PT point, boolean allowManyReplies1, boolean assumeOrientedSegments1, double distanceToReachStartingPoint) { this(graph1, null, segment, point, allowManyReplies1, assumeOrientedSegments1, distanceToReachStartingPoint); } /** * @param graph1 is the graph associated to this iterator. * @param courseModel1 is the course model to use. * @param segment is the segment from which to start. * @param point is the segment's point indicating the direction. * @param allowManyReplies1 may be <code>true</code> to allow to reply many times the * same segment, otherwhise <code>false</code>. * @param assumeOrientedSegments1 may be <code>true</code> to assume that the same segment has two different * instances for graph iteration: the first instance is associated the first point of the segment and the second * instance is associated to the last point of the segment. If this parameter is <code>false</code> to assume that * the end points of a segment are not distinguished. * @param distanceToReachStartingPoint is the distance to reach the starting point. * It must be negative or nul. */ public GraphIterator(Graph<ST, PT> graph1, GraphCourseModel<ST, PT> courseModel1, ST segment, PT point, boolean allowManyReplies1, boolean assumeOrientedSegments1, double distanceToReachStartingPoint) { this(graph1, courseModel1, segment, point, allowManyReplies1, assumeOrientedSegments1, distanceToReachStartingPoint, Double.POSITIVE_INFINITY); } /** * @param graph1 is the graph associated to this iterator. * @param courseModel1 is the course model to use. * @param segment is the segment from which to start. * @param point is the segment's point indicating the direction. * @param allowManyReplies1 may be <code>true</code> to allow to reply many times the * same segment, otherwhise <code>false</code>. * @param assumeOrientedSegments1 may be <code>true</code> to assume that the same segment has two different * instances for graph iteration: the first instance is associated the first point of the segment and the second * instance is associated to the last point of the segment. If this parameter is <code>false</code> to assume that * the end points of a segment are not distinguished. * @param distanceToReachStartingPoint is the distance to reach the starting point. * @param distanceToConsumeAfter is the distance to consume after traversing the segment. * It must be negative or nul. */ protected GraphIterator( Graph<ST, PT> graph1, GraphCourseModel<ST, PT> courseModel1, ST segment, PT point, boolean allowManyReplies1, boolean assumeOrientedSegments1, double distanceToReachStartingPoint, double distanceToConsumeAfter) { this.graph = new WeakReference<>(graph1); GraphCourseModel<ST, PT> courseM = courseModel1; if (courseM == null) { courseM = new BreadthFirstGraphCourseModel<>(); } this.courseModel = courseM; this.allowManyReplies = allowManyReplies1; this.assumeOrientedSegments = assumeOrientedSegments1; final GraphIterationElement<ST, PT> firstElement = newIterationElement( null, segment, point, (distanceToReachStartingPoint > 0) ? 0 : distanceToReachStartingPoint, distanceToConsumeAfter); this.courseModel.addIterationElement(firstElement); if (!this.allowManyReplies) { final GraphIterationElementComparator<ST, PT> comparator = createVisitedSegmentComparator( this.assumeOrientedSegments); assert comparator != null; this.visited = new TreeSet<>(comparator); this.visited.add(firstElement); } else { this.visited = null; } } /** Invoked when a comparator on visited segments is required. * * @param assumeOrientedSegments1 may be <code>true</code> to assume that the same segment has two different * instances for graph iteration: the first instance is associated the first point of the segment and the second * instance is associated to the last point of the segment. If this parameter is <code>false</code> to assume that * the end points of a segment are not distinguished. * @return the graph element iterator, or <code>null</code> to use the * default comparation behaviour of the <code>GraphIterationElement</code>. */ @Pure protected GraphIterationElementComparator<ST, PT> createVisitedSegmentComparator(boolean assumeOrientedSegments1) { return new GraphIterationElementComparator<>(assumeOrientedSegments1); } /** Replies the graph on which this iterator is iterating. * * @return the graph. */ @Pure Graph<ST, PT> getGraph() { return this.graph.get(); } /** Clear the temporary buffers of this graph iterator. */ void clear() { if (this.visited != null) { this.visited.clear(); } this.current = null; } /** Replies the next segments. * * @param avoid_visited_segments is <code>true</code> to avoid to reply already visited segments, otherwise <code>false</code> * @param element is the element from which the next segments must be replied. * @return the list of the following segments * @see #next() */ @SuppressWarnings("checkstyle:nestedifdepth") protected final List<GraphIterationElement<ST, PT>> getNextSegments(boolean avoid_visited_segments, GraphIterationElement<ST, PT> element) { assert this.allowManyReplies || this.visited != null; if (element != null) { final ST segment = element.getSegment(); final PT point = element.getPoint(); if ((segment != null) && (point != null)) { final PT pts = segment.getOtherSidePoint(point); if (pts != null) { final double distanceToReach = element.getDistanceToReachSegment() + segment.getLength(); GraphIterationElement<ST, PT> candidate; final double restToConsume = element.distanceToConsume - segment.getLength(); final List<GraphIterationElement<ST, PT>> list = new ArrayList<>(); for (final ST theSegment : pts.getConnectedSegmentsStartingFrom(segment)) { if (!theSegment.equals(segment)) { candidate = newIterationElement( segment, theSegment, pts, distanceToReach, restToConsume); if ((this.allowManyReplies) || (!avoid_visited_segments) || (!this.visited.contains(candidate))) { list.add(candidate); } } } return list; } } } throw new NoSuchElementException(); } /** Replies the next element without removing it from the iterator list. * * @return the next element without removing it from the iterator list. */ protected GraphIterationElement<ST, PT> getNextElement() { return this.courseModel.getNextIterationElement(); } @Pure @Override public boolean hasNext() { if (this.courseModel.isEmpty()) { clear(); return false; } return true; } @Override public final ST next() { final GraphIterationElement<ST, PT> theElement = nextElement(); final ST sgmt = theElement.getSegment(); if (sgmt != null) { return sgmt; } clear(); throw new NoSuchElementException(); } /** Replies the next segment. * * @return the next segment */ public final GraphIterationElement<ST, PT> nextElement() { if (!this.courseModel.isEmpty()) { final GraphIterationElement<ST, PT> theElement = this.courseModel.removeNextIterationElement(); if (theElement != null) { final List<GraphIterationElement<ST, PT>> list = getNextSegments(true, theElement); final Iterator<GraphIterationElement<ST, PT>> iterator; boolean hasFollowingSegments = false; GraphIterationElement<ST, PT> elt; if (this.courseModel.isReversedRestitution()) { iterator = new ReverseIterator<>(list); } else { iterator = list.iterator(); } while (iterator.hasNext()) { elt = iterator.next(); if (canGotoIntoElement(elt)) { hasFollowingSegments = true; this.courseModel.addIterationElement(elt); if (!this.allowManyReplies) { this.visited.add(elt); } } } theElement.setTerminalSegment(!hasFollowingSegments); theElement.replied = true; this.current = theElement; return this.current; } } clear(); throw new NoSuchElementException(); } /** Create an instance of GraphIterationElement. * * @param previous_segment is the previous element that permits to reach this object during an iteration * @param segment is the current segment * @param point is the point on which the iteration arrived on the current segment. * @param distanceToReach is the distance that is already consumed to reach the segment. * @param distanceToConsume is the rest of distance to consume including the segment. * @return a graph iteration element. */ protected GraphIterationElement<ST, PT> newIterationElement( ST previous_segment, ST segment, PT point, double distanceToReach, double distanceToConsume) { return new GraphIterationElement<>( previous_segment, segment, point, distanceToReach, distanceToConsume); } /** Replies if the specified element could be added into the list of futher elements. * * @param element the element to test. * @return <code>true</code> if the given element is addable into the associated list. */ @Pure protected boolean canGotoIntoElement(GraphIterationElement<ST, PT> element) { return true; } /** Ignore the elements after the specified element. * * @param element the reference element. */ public void ignoreElementsAfter(GraphIterationElement<ST, PT> element) { final List<GraphIterationElement<ST, PT>> nexts = getNextSegments(false, element); this.courseModel.removeIterationElements(nexts); } /** Ignore the elements after the specified element. */ public void ignoreElementsAfter() { if (this.current != null) { ignoreElementsAfter(this.current); } else { clear(); throw new NoSuchElementException(); } } @Override public void remove() { if (this.current != null) { ignoreElementsAfter(this.current); if (this.visited != null) { this.visited.remove(this.current); } } else { clear(); throw new NoSuchElementException(); } } /** Replies if this iterator is assumed that a segment may be replied many times. * * @return <code>true</code> if this iterator allows cycles, otherwise <code>false</code> */ @Pure public final boolean isManySegmentReplyEnabled() { return this.allowManyReplies; } /** Replies if this iterator is assumed oriented segments or not. * * @return <code>true</code> if this iterator assumes oriented segments, otherwise <code>false</code> */ @Pure public final boolean isOrientedSegmentSupportEnabled() { return this.assumeOrientedSegments; } /** Reverse iterator. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 13.0 */ private static class ReverseIterator<E> implements Iterator<E> { private int savedSize; private int index; private final List<E> list; ReverseIterator(List<E> list1) { this.savedSize = (list1 == null) ? 0 : list1.size(); this.index = this.savedSize - 1; this.list = list1; } @Pure @Override public boolean hasNext() { return this.list != null && this.index >= 0 && this.index < this.list.size(); } @Override public E next() { if (this.list == null) { throw new NoSuchElementException(); } if (this.savedSize != this.list.size()) { throw new ConcurrentModificationException(); } if (this.index >= this.list.size()) { throw new NoSuchElementException(); } final E elt = this.list.get(this.index); --this.index; return elt; } } }