/* * $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.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.NoSuchElementException; import org.eclipse.xtext.xbase.lib.Pure; import org.arakhne.afc.vmutil.ReflectionUtil; /** * This class describes a path inside a graph. * * @param <GP> is the type of the graph graph itself. * @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 */ @SuppressWarnings("checkstyle:methodcount") public class GraphPath<GP extends GraphPath<GP, ST, PT>, ST extends GraphSegment<ST, PT>, PT extends GraphPoint<PT, ST>> implements GraphSegmentList<ST, PT>, Cloneable { /** Package access to avoid comiplation error. */ List<ST> segmentList = new LinkedList<>(); private PT startingPoint; private PT endingPoint; private boolean isReversable; private double length; /** Construct a path. */ public GraphPath() { this.isReversable = true; } /** * @param segment is the segment from which to start. * @param startingPoint1 is the segment's point indicating the direction. */ public GraphPath(ST segment, PT startingPoint1) { this.segmentList.add(segment); this.startingPoint = startingPoint1; this.endingPoint = segment.getOtherSidePoint(startingPoint1); this.isReversable = false; } /** Replies if this first segment could be reversed * when the second segment is inserted to fit the * order of the insertions. * * <p>Let s1 and s2 two segments respectively linked * to the points [p1, p2] and [p1, p3]. * Let the following code:<pre><code> * GraphPath path = new GraphPath(); * path.add(s1); * path.add(s2);</code></pre> * If the path is not reversable, it is becoming * {@code [s2, s1]} because the order of s1 * is preserved. If the path is reversable, it * is becoming {@code [s1, s2]} because the first * segment is reverted to fit the order of the calls * to the add function. * * <p>Let s1 and s2 the same segments as previously. * Let the following code:<pre><code> * GraphPath path = new GraphPath(); * path.add(s1, p2); * path.add(s2);</code></pre> * The first segment is not reversable because of * the call to the add function with the connection * as parameter. The path is becoming * {@code s1, s2}, and nothing else. * * @return <code>true</code> if the first segment * could be reversed; otherwise <code>false</code>. */ @Pure public boolean isFirstSegmentReversable() { return this.isReversable; } /** Set if this first segment could be reversed * when the second segment is inserted to fit the * order of the insertions. * * <p>Let s1 and s2 two segments respectively linked * to the points [p1, p2] and [p1, p3]. * Let the following code:<pre><code> * GraphPath path = new GraphPath(); * path.add(s1); * path.add(s2);</code></pre> * If the path is not reversable, it is becoming * {@code [s2, s1]} because the order of s1 * is preserved. If the path is reversable, it * is becoming {@code [s1, s2]} because the first * segment is reverted to fit the order of the calls * to the add function. * * <p>Let s1 and s2 the same segments as previously. * Let the following code:<pre><code> * GraphPath path = new GraphPath(); * path.add(s1, p2); * path.add(s2);</code></pre> * The first segment is not reversable because of * the call to the add function with the connection * as parameter. The path is becoming * {@code s1, s2}, and nothing else. * * @param isReversable1 is <code>true</code> if the first * segment could be reversed; otherwise <code>false</code>. */ public void setFirstSegmentReversable(boolean isReversable1) { this.isReversable = isReversable1; } @Pure @Override public int size() { return this.segmentList.size(); } @Pure @Override public boolean isEmpty() { return this.segmentList.isEmpty(); } @Pure @Override public boolean contains(Object obj) { return this.segmentList.contains(obj); } @Pure @Override public Iterator<ST> iterator() { return this.segmentList.iterator(); } @Pure @Override public Iterator<PT> pointIterator() { return new PointIterator<>(this.startingPoint, this.segmentList.iterator()); } @Pure @Override public Iterable<PT> points() { return new PointIterable(); } @Pure @Override public Object[] toArray() { return this.segmentList.toArray(); } @Override public <T> T[] toArray(T[] array) { return this.segmentList.toArray(array); } @Pure @Override public boolean containsAll(Collection<?> collection) { return this.segmentList.containsAll(collection); } @Override public boolean add(ST segment, PT point) { assert segment != null; assert point != null; // This is the first segment in the path. if (this.segmentList.isEmpty()) { this.startingPoint = point; this.endingPoint = segment.getOtherSidePoint(point); this.segmentList.clear(); this.segmentList.add(segment); this.length = segment.getLength(); return true; } // The segment should be added to the end of the path if (this.endingPoint.equals(point)) { this.endingPoint = segment.getOtherSidePoint(point); this.segmentList.add(segment); this.length += segment.getLength(); return true; } // The segment should be added to the beginning of the path if (this.startingPoint.equals(point)) { if (this.segmentList.size() == 1 && this.isReversable) { this.segmentList.add(segment); this.startingPoint = this.endingPoint; this.endingPoint = segment.getOtherSidePoint(point); this.isReversable = false; } else { this.startingPoint = segment.getOtherSidePoint(point); this.segmentList.add(0, segment); } this.length += segment.getLength(); return true; } // The segment is not connected to the ends of the path. return false; } /** {@inheritDoc} * * @throws IndexOutOfBoundsException if the index is invalid. * @throws IllegalArgumentException is the given segment cannot be connected. */ @Override public void add(int index, ST segment) { if (index < 0 || index > this.segmentList.size()) { throw new IndexOutOfBoundsException(); } // The path is empty, so that the segment is the first one. if (this.segmentList.isEmpty()) { if (this.segmentList.add(segment)) { this.startingPoint = segment.getBeginPoint(); this.endingPoint = segment.getEndPoint(); this.length += segment.getLength(); return; } throw new IllegalArgumentException(); } // Detect the candidates to the connection final PT candidate; this.isReversable = false; if (index < this.segmentList.size()) { candidate = getStartingPointFor(index); } else { candidate = this.endingPoint; } // Check the connection validity if (candidate != null) { final PT first = segment.getBeginPoint(); final PT last = segment.getEndPoint(); if (index == 0 && (candidate.equals(first) || candidate.equals(last))) { this.segmentList.add(0, segment); this.startingPoint = segment.getOtherSidePoint(this.startingPoint); this.length += segment.getLength(); return; } else if (index == this.segmentList.size() && (candidate.equals(first) || candidate.equals(last))) { this.segmentList.add(segment); this.endingPoint = segment.getOtherSidePoint(this.endingPoint); this.length += segment.getLength(); return; } else if (candidate.equals(first) || candidate.equals(last)) { this.segmentList.add(index, segment); this.length += segment.getLength(); return; } } throw new IllegalArgumentException(); } @Override public boolean add(ST segment) { // The path is empty, so that the segment is the first one. if (this.startingPoint == null) { this.segmentList.clear(); if (this.segmentList.add(segment)) { this.startingPoint = segment.getBeginPoint(); this.endingPoint = segment.getEndPoint(); this.length += segment.getLength(); return true; } return false; } // The segment is connectable to the last point PT endPoint = this.endingPoint; if (endPoint != null && endPoint.isConnectedSegment(segment) && this.segmentList.add(segment)) { this.endingPoint = segment.getOtherSidePoint(endPoint); this.length += segment.getLength(); return true; } // The segment is connectable to the first point endPoint = this.startingPoint; if (endPoint != null && endPoint.isConnectedSegment(segment)) { try { if (this.segmentList.size() == 1 && this.isReversable) { this.segmentList.add(segment); this.startingPoint = this.endingPoint; this.endingPoint = segment.getOtherSidePoint(endPoint); this.isReversable = false; } else { this.segmentList.add(0, segment); this.startingPoint = segment.getOtherSidePoint(endPoint); } this.length += segment.getLength(); return true; } catch (IndexOutOfBoundsException e) { // } } // Unable to connect the segment to the ends. return false; } @Pure @Override public PT getLastPoint() { return this.endingPoint; } @Pure @Override public PT getFirstPoint() { return this.startingPoint; } /** Replies the starting point for the segment at the given index. * * @param index is the index of the segment. * @return the starting point for the segment at the given index. */ @Pure @SuppressWarnings("checkstyle:cyclomaticcomplexity") public PT getStartingPointFor(int index) { if ((index < 1) || (this.segmentList.size() <= 1)) { if (this.startingPoint != null) { return this.startingPoint; } } else { int idx = index; ST currentSegment = this.segmentList.get(idx); ST previousSegment = this.segmentList.get(--idx); // Because the two segments are the same // we must go deeper in the path elements // to detect the right segment int count = 0; while ((previousSegment != null) && (previousSegment.equals(currentSegment))) { currentSegment = previousSegment; idx--; previousSegment = (idx >= 0) ? this.segmentList.get(idx) : null; count++; } if (count > 0) { PT sp = null; if (previousSegment != null) { final PT p1 = currentSegment.getBeginPoint(); final PT p2 = currentSegment.getEndPoint(); final PT p3 = previousSegment.getBeginPoint(); final PT p4 = previousSegment.getEndPoint(); assert p1 != null && p2 != null && p3 != null && p4 != null; if (p1.equals(p3) || p1.equals(p4)) { sp = p1; } else if (p2.equals(p3) || p2.equals(p4)) { sp = p2; } } else { sp = this.startingPoint; } if (sp != null) { return ((count % 2) == 0) ? sp : currentSegment.getOtherSidePoint(sp); } } else if ((currentSegment != null) && (previousSegment != null)) { // if the two segments are different // it is simple to detect the // common point final PT p1 = currentSegment.getBeginPoint(); final PT p2 = currentSegment.getEndPoint(); final PT p3 = previousSegment.getBeginPoint(); final PT p4 = previousSegment.getEndPoint(); assert p1 != null && p2 != null && p3 != null && p4 != null; if (p1.equals(p3) || p1.equals(p4)) { return p1; } if (p2.equals(p3) || p2.equals(p4)) { return p2; } } } return null; } @Pure @Override public ST getLastSegment() { if (!this.segmentList.isEmpty()) { return this.segmentList.get(this.segmentList.size() - 1); } return null; } @Pure @Override public ST getAntepenulvianSegment() { if (this.segmentList.size() >= 2) { return this.segmentList.get(this.segmentList.size() - 2); } return null; } @Pure @Override public ST getSecondSegment() { if (this.segmentList.size() > 1) { return this.segmentList.get(1); } return null; } @Pure @Override public ST getFirstSegment() { if (!this.segmentList.isEmpty()) { return this.segmentList.get(0); } return null; } @Override public boolean addAll(Collection<? extends ST> collection) { boolean listChanged = false; final Iterator<? extends ST> iterator1 = collection.iterator(); ST element; while (iterator1.hasNext()) { element = iterator1.next(); if (add(element)) { listChanged = true; } } return listChanged; } @Override public boolean addAll(int index, Collection<? extends ST> collection) { boolean changed = false; int idx = index; for (final ST s : collection) { try { add(idx, s); changed = true; ++idx; } catch (IndexOutOfBoundsException | IllegalArgumentException e) { // } } return changed; } @Override public boolean remove(Object obj) { if ((obj != null) && (obj instanceof GraphSegment<?, ?>)) { remove(this.segmentList.indexOf(obj)); return true; } return false; } @Override public ST remove(int index) { if ((index > 0) && (index < this.segmentList.size())) { PT toConnect = getStartingPointFor(index); PT current = toConnect; final ST oldSegment = this.segmentList.remove(index); ST segment; this.length -= oldSegment.getLength(); if (this.length < 0) { this.length = 0; } while (toConnect != null && index < this.segmentList.size()) { segment = this.get(index); current = segment.getOtherSidePoint(current); if (toConnect.equals(current)) { toConnect = null; } else { final ST oldSegment2 = this.segmentList.remove(index); this.length -= oldSegment2.getLength(); if (this.length < 0) { this.length = 0; } } } if (toConnect != null) { this.endingPoint = toConnect; } return oldSegment; } else if (index == 0 && !this.segmentList.isEmpty()) { final ST oldSegment = this.segmentList.remove(index); this.length -= oldSegment.getLength(); if (this.length < 0) { this.length = 0; } if (this.segmentList.isEmpty()) { this.startingPoint = null; this.endingPoint = null; this.isReversable = true; } else { this.startingPoint = oldSegment.getOtherSidePoint(this.startingPoint); } return oldSegment; } return null; } /** * Package access to avoid compilation error. * You must not call this function directly. * * @param index the reference index. * @param inclusive indicates if the element at the reference index is included in the removed elements. * @return <code>true</code> or <code>false</code> */ boolean removeUntil(int index, boolean inclusive) { if (index >= 0) { boolean changed = false; PT startPoint = this.startingPoint; ST segment; int limit = index; if (inclusive) { ++limit; } for (int i = 0; i < limit; ++i) { segment = this.segmentList.remove(0); this.length -= segment.getLength(); if (this.length < 0) { this.length = 0; } startPoint = segment.getOtherSidePoint(startPoint); changed = true; } if (changed) { if (this.segmentList.isEmpty()) { this.startingPoint = null; this.endingPoint = null; this.isReversable = true; } else { this.startingPoint = startPoint; } return true; } } return false; } /** Remove the path's elements before the * specified one which is starting * at the specified point. The specified element will * be removed. * * <p>This function removes until the <i>first occurence</i> * of the given object. * * @param obj is the segment to remove * @param pt is the point on which the segment was connected * as its first point. * @return <code>true</code> on success, otherwise <code>false</code> */ public boolean removeUntil(ST obj, PT pt) { return removeUntil(indexOf(obj, pt), true); } /** Remove the path's elements before the * specified one. The specified element will * also be removed. * * <p>This function removes until the <i>first occurence</i> * of the given object. * * @param obj the reference segment. * @return <code>true</code> on success, otherwise <code>false</code> */ @Override public boolean removeUntil(ST obj) { return removeUntil(this.segmentList.indexOf(obj), true); } /** Remove the path's elements before the * specified one. The specified element will * not be removed. * * <p>This function removes until the <i>first occurence</i> * of the given object. * * @param obj the reference element. * @return <code>true</code> on success, otherwise <code>false</code> */ @Override public boolean removeBefore(ST obj) { return removeUntil(this.segmentList.indexOf(obj), false); } /** Remove the path's elements before the * specified one which is starting * at the specified point. The specified element will * not be removed. * * <p>This function removes until the <i>first occurence</i> * of the given object. * * @param obj is the segment to remove * @param pt is the point on which the segment was connected * as its first point. * @return <code>true</code> on success, otherwise <code>false</code> */ @Override public boolean removeBefore(ST obj, PT pt) { return removeUntil(indexOf(obj, pt), false); } /** Remove the path's elements before the * specified one. The specified element will * not be removed. * * <p>This function removes until the <i>last occurence</i> * of the given object. * * @param obj the reference element. * @return <code>true</code> on success, otherwise <code>false</code> */ @Override public boolean removeBeforeLast(ST obj) { return removeUntil(this.segmentList.lastIndexOf(obj), false); } /** Remove the path's elements before the * specified one which is starting * at the specified point. The specified element will * not be removed. * * <p>This function removes until the <i>last occurence</i> * of the given object. * * @param obj is the segment to remove * @param pt is the point on which the segment was connected * as its first point. * @return <code>true</code> on success, otherwise <code>false</code> */ @Override public boolean removeBeforeLast(ST obj, PT pt) { return removeUntil(lastIndexOf(obj, pt), false); } /** Remove the path's elements before the * specified one which is starting * at the specified point. The specified element will * be removed. * * <p>This function removes until the <i>last occurence</i> * of the given object. * * @param obj is the segment to remove * @param pt is the point on which the segment was connected * as its first point. * @return <code>true</code> on success, otherwise <code>false</code> */ public boolean removeUntilLast(ST obj, PT pt) { return removeUntil(lastIndexOf(obj, pt), true); } /** Remove the path's elements before the * specified one. The specified element will * also be removed. * * <p>This function removes until the <i>last occurence</i> * of the given object. * * @param obj the reference segment. * @return <code>true</code> on success, otherwise <code>false</code> */ @Override public boolean removeUntilLast(ST obj) { return removeUntil(this.segmentList.lastIndexOf(obj), true); } private boolean removeAfter(int index, boolean inclusive) { if (index >= 0) { boolean changed = false; PT startPoint = this.startingPoint; ST segment; int limit = index; if (!inclusive) { ++limit; } if (limit == 0) { if (!this.segmentList.isEmpty()) { changed = true; this.segmentList.clear(); this.endingPoint = null; this.startingPoint = null; this.isReversable = true; this.length = 0; } } else { this.length = 0; final int segmentCount = this.segmentList.size(); for (int i = 0; i < segmentCount && i < limit; ++i) { segment = this.segmentList.get(i); this.length += segment.getLength(); startPoint = segment.getOtherSidePoint(startPoint); } this.endingPoint = startPoint; for (int i = segmentCount - 1; i >= limit; --i) { this.segmentList.remove(i); changed = true; } } return changed; } return false; } /** Remove the path's elements after the * specified one which is starting * at the specified point. The specified element will * not be removed. * * <p>This function removes after the <i>first occurence</i> * of the given object. * * @param obj is the segment to remove * @param pt is the point on which the segment was connected * as its first point. * @return <code>true</code> on success, otherwise <code>false</code> */ public boolean removeAfter(ST obj, PT pt) { return removeAfter(indexOf(obj, pt), false); } /** Remove the path's elements after the * specified one. The specified element will * not be removed. * * <p>This function removes after the <i>first occurence</i> * of the given object. * * @param obj the reference segment. * @return <code>true</code> on success, otherwise <code>false</code> */ public boolean removeAfter(ST obj) { return removeAfter(this.segmentList.indexOf(obj), false); } /** Remove the path's elements after the * specified one. The specified element will * not be removed. * * <p>This function removes after the <i>last occurence</i> * of the given object. * * @param obj the reference segment. * @return <code>true</code> on success, otherwise <code>false</code> */ public boolean removeAfterLast(ST obj) { return removeAfter(this.segmentList.lastIndexOf(obj), false); } /** Remove the path's elements after the * specified one which is starting * at the specified point. The specified element will * not be removed. * * <p>This function removes after the <i>last occurence</i> * of the given object. * * @param obj is the segment to remove * @param pt is the point on which the segment was connected * as its first point. * @return <code>true</code> on success, otherwise <code>false</code> */ public boolean removeAfterLast(ST obj, PT pt) { return removeAfter(lastIndexOf(obj, pt), false); } /** Remove the path's elements after the * specified one which is starting * at the specified point. The specified element will * be removed. * * <p>This function removes after the <i>last occurence</i> * of the given object. * * @param obj is the segment to remove * @param pt is the point on which the segment was connected * as its first point. * @return <code>true</code> on success, otherwise <code>false</code> */ public boolean removeFromLast(ST obj, PT pt) { return removeAfter(lastIndexOf(obj, pt), true); } /** Remove the path's elements after the * specified one. The specified element will * also be removed. * * <p>This function removes until the <i>last occurence</i> * of the given object. * * @param obj the reference segment. * @return <code>true</code> on success, otherwise <code>false</code> */ public boolean removeFromLast(ST obj) { return removeAfter(this.segmentList.lastIndexOf(obj), true); } /** Remove the path's elements after the * specified one which is starting * at the specified point. The specified element will * be removed. * * <p>This function removes after the <i>first occurence</i> * of the given object. * * @param obj is the segment to remove * @param pt is the point on which the segment was connected * as its first point. * @return <code>true</code> on success, otherwise <code>false</code> */ public boolean removeFrom(ST obj, PT pt) { return removeAfter(indexOf(obj, pt), true); } /** Remove the path's elements after the * specified one. The specified element will * also be removed. * * <p>This function removes until the <i>first occurence</i> * of the given object. * * @param obj the reference segment. * @return <code>true</code> on success, otherwise <code>false</code> */ public boolean removeFrom(ST obj) { return removeAfter(this.segmentList.indexOf(obj), true); } private int indexOf(ST obj, PT startPoint) { final Iterator<ST> iterator = this.segmentList.iterator(); PT current = this.startingPoint; ST segment; int i = 0; while (iterator.hasNext()) { segment = iterator.next(); if (segment.equals(obj) && current.equals(startPoint)) { return i; } ++i; current = segment.getOtherSidePoint(current); } return -1; } @Pure @Override public int indexOf(Object obj) { return this.segmentList.indexOf(obj); } private int lastIndexOf(ST obj, PT startPoint) { PT current = this.endingPoint; ST segment; int i = this.segmentList.size() - 1; while (i >= 0) { segment = this.segmentList.get(i); current = segment.getOtherSidePoint(current); if (segment.equals(obj) && current.equals(startPoint)) { return i; } --i; } return -1; } @Pure @Override public int lastIndexOf(Object obj) { return this.segmentList.lastIndexOf(obj); } @Override public boolean removeAll(Collection<?> collection) { return filterList(collection, true); } private boolean filterList(Collection<?> collection, boolean pushOutside) { boolean listChanged = false; boolean removed; ST segment; ST previous = null; PT pt = this.startingPoint; PT first = null; PT last = null; final Iterator<ST> iterator = this.segmentList.iterator(); while (iterator.hasNext()) { removed = false; segment = iterator.next(); if (pushOutside == collection.contains(segment)) { iterator.remove(); this.length -= segment.getLength(); if (this.length < 0) { this.length = 0; } removed = true; listChanged = true; } else if (previous != null) { if (last != null && last.equals(pt)) { previous = segment; } else { iterator.remove(); this.length -= segment.getLength(); if (this.length < 0) { this.length = 0; } removed = true; listChanged = true; } } else { previous = segment; } if (!removed && first == null) { first = pt; } pt = segment.getOtherSidePoint(pt); if (!removed) { last = pt; } } if (this.segmentList.isEmpty()) { this.startingPoint = null; this.endingPoint = null; this.isReversable = true; } else { this.startingPoint = first; this.endingPoint = last; } return listChanged; } @Override public boolean retainAll(Collection<?> collection) { return filterList(collection, false); } @Override public void clear() { this.segmentList.clear(); this.startingPoint = null; this.endingPoint = null; this.isReversable = true; this.length = 0; } @Override public ST get(int index) { return this.segmentList.get(index); } @Override public ST set(int index, ST element) { if (index >= 0 && index < this.segmentList.size()) { final ST oldSegment = this.segmentList.get(index); if (!oldSegment.equals(element)) { ST segment; for (int i = this.segmentList.size() - 1; i >= index; --i) { segment = this.segmentList.remove(i); this.length -= segment.getLength(); if (this.length < 0) { this.length = 0; } this.endingPoint = segment.getOtherSidePoint(this.endingPoint); } add(element); return oldSegment; } } return null; } @Pure @Override public ListIterator<ST> listIterator() { return this.segmentList.listIterator(); } @Pure @Override public ListIterator<ST> listIterator(int index) { return this.segmentList.listIterator(index); } @Pure @Override public List<ST> subList(int fromIndex, int toIndex) { return this.segmentList.subList(fromIndex, toIndex); } @Pure @Override public String toString() { return ReflectionUtil.toString(this); } /** Revert the order of the graph segment in this path. */ public void invert() { final PT p = this.startingPoint; this.startingPoint = this.endingPoint; this.endingPoint = p; final int middle = this.segmentList.size() / 2; ST segment; for (int i = 0, j = this.segmentList.size() - 1; i < middle; ++i, --j) { segment = this.segmentList.get(i); this.segmentList.set(i, this.segmentList.get(j)); this.segmentList.set(j, segment); } } @Pure @SuppressWarnings("unchecked") @Override public GP clone() { try { final GP clone = (GP) super.clone(); clone.segmentList = new LinkedList<>(); clone.segmentList.addAll(this.segmentList); return clone; } catch (CloneNotSupportedException e) { throw new Error(e); } } private GP splitAt(int index, boolean inclusive) { final GP secondPath = clone(); if (index >= 0) { removeAfter(index, inclusive); secondPath.removeUntil(index, !inclusive); } else { secondPath.clear(); } return secondPath; } /** Split this path and retains the first part of the * part in this object and reply the second part. * The first occurrence of this specified element * will be in the second part. * * <p>This function removes until the <i>last occurence</i> * of the given object. * * @param obj the reference segment. * @return the rest of the path after the first occurence of the given element. */ public GP splitAt(ST obj) { return splitAt(this.segmentList.indexOf(obj), true); } /** Split this path and retains the first part of the * part in this object and reply the second part. * The first occurrence of this specified element * will be in the second part. * * <p>This function removes until the <i>last occurence</i> * of the given object. * * @param obj the reference segment. * @param startPoint is the starting point of the searched segment. * @return the rest of the path after the first occurence of the given element. */ public GP splitAt(ST obj, PT startPoint) { return splitAt(indexOf(obj, startPoint), true); } /** Split this path and retains the first part of the * part in this object and reply the second part. * The segment at the given position will be in the second part. * * @param position the segment index in the path. * @return the rest of the path after the element at the given position. */ public GP splitAt(int position) { return splitAt(position, true); } /** Split this path and retains the first part of the * part in this object and reply the second part. * The last occurence of the specified element * will be in the first part. * * <p>This function removes until the <i>last occurence</i> * of the given object. * * @param obj the reference segment. * @return the rest of the path after the last occurence of the given element. */ public GP splitAfterLast(ST obj) { return splitAt(this.segmentList.lastIndexOf(obj), false); } /** Split this path and retains the first part of the * part in this object and reply the second part. * The last occurence of the specified element * will be in the first part. * * <p>This function removes until the <i>last occurence</i> * of the given object. * * @param obj is the segment to search for. * @param startPoint is the starting point of the searched segment. * @return the rest of the path after the last occurence of the given element. */ public GP splitAfterLast(ST obj, PT startPoint) { return splitAt(lastIndexOf(obj, startPoint), false); } /** Split this path and retains the first part of the * part in this object and reply the second part. * The last occurence of the specified element * will be in the second part. * * <p>This function removes until the <i>last occurence</i> * of the given object. * * @param obj the reference segment. * @param startPoint is the starting point of the searched segment. * @return the rest of the path after the last occurence of the given element. */ public GP splitAtLast(ST obj, PT startPoint) { return splitAt(lastIndexOf(obj, startPoint), true); } /** Split this path and retains the first part of the * part in this object and reply the second part. * The last occurence of the specified element * will be in the second part. * * <p>This function removes until the <i>last occurence</i> * of the given object. * * @param obj the reference segment. * @return the rest of the path after the last occurence of the given element. */ public GP splitAtLast(ST obj) { return splitAt(this.segmentList.lastIndexOf(obj), true); } /** Split this path and retains the first part of the * part in this object and reply the second part. * The first occurrence of specified element will be * in the first part. * * <p>This function removes until the <i>last occurence</i> * of the given object. * * @param obj the reference segment. * @return the rest of the path after the first occurence of the given element. */ public GP splitAfter(ST obj) { return splitAt(this.segmentList.indexOf(obj), false); } /** Split this path and retains the first part of the * part in this object and reply the second part. * The first occurrence of specified element will be * in the first part. * * <p>This function removes until the <i>last occurence</i> * of the given object. * * @param obj the reference segment. * @param startPoint is the starting point of the searched segment. * @return the rest of the path after the first occurence of the given element. */ public GP splitAfter(ST obj, PT startPoint) { return splitAt(indexOf(obj, startPoint), false); } /** Replies the length of the path. * * @return the length of the path. */ @Pure public double getLength() { return this.length; } /** * Iterable on points. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 13.0 */ private class PointIterable implements Iterable<PT> { /** Construct the iterable of points. */ PointIterable() { // } @Pure @SuppressWarnings("synthetic-access") @Override public Iterator<PT> iterator() { return new PointIterator<>(GraphPath.this.startingPoint, GraphPath.this.segmentList.iterator()); } } /** * Iterator on points. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 13.0 */ private static class PointIterator<ST extends GraphSegment<ST, PT>, PT extends GraphPoint<PT, ST>> implements Iterator<PT> { private PT point; private final Iterator<ST> sgmtIterator; /** * @param startPoint the starting point. * @param it the segment iterator to use. */ PointIterator(PT startPoint, Iterator<ST> it) { this.sgmtIterator = it; this.point = startPoint; } @Pure @Override public boolean hasNext() { return this.point != null; } @Override public PT next() { final PT toReturn = this.point; if (toReturn == null) { throw new NoSuchElementException(); } if (this.sgmtIterator.hasNext()) { final ST sgmt = this.sgmtIterator.next(); this.point = sgmt.getOtherSidePoint(this.point); } else { this.point = null; } return toReturn; } } }