// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.plugins.graphview.core.graph; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.Set; import org.openstreetmap.josm.plugins.graphview.core.transition.Restriction; import org.openstreetmap.josm.plugins.graphview.core.transition.Segment; import org.openstreetmap.josm.plugins.graphview.core.transition.SegmentNode; /** * group of nodes and/or segments that will be evaluated independently from other groups */ abstract class EvaluationGroup { protected boolean evaluated = false; /** * array of sequences. * First index is inbound segment/start node index, * second index is outbound segment/target node index. * Will contain the segment sequence after evaluation or null if none exists. */ protected List<Segment>[][] segmentSequences; private static final List<Segment> EMPTY_SEGMENT_LIST = Collections.unmodifiableList(new ArrayList<Segment>(0)); private static final List<Restriction> EMPTY_RESTRICTION_LIST = Collections.unmodifiableList(new ArrayList<Restriction>(0)); private static class State { SegmentNode currentNode; Set<SegmentNode> visitedNodes; Collection<Restriction> activeRestrictions; List<Segment> segmentHistory; } /** * tries to find a legal sequence of segments between two segment nodes. * * @return list of segments if connection is possible, null otherwise. */ protected List<Segment> findSegmentSequence( SegmentNode firstNode, SegmentNode lastNode, Collection<Restriction> restrictions) { return findSegmentSequence(firstNode, lastNode, restrictions, EMPTY_RESTRICTION_LIST, EMPTY_RESTRICTION_LIST); } /** * tries to find a legal sequence of segments between two segments. * * @return list of segments if connection is possible, null otherwise. * The list does NOT include firstSegment and lastSegment, * but they are considered for restrictions. */ protected List<Segment> findSegmentSequence( Segment firstSegment, Segment lastSegment, Collection<Restriction> restrictions) { if (firstSegment == lastSegment) { return EMPTY_SEGMENT_LIST; } else { Collection<Restriction> initiallyActiveRestrictions = activeRestrictionsAfterSegment(firstSegment, EMPTY_RESTRICTION_LIST, restrictions); Collection<Restriction> restrictionsForbiddenAtLastNode = new HashSet<>(); for (Restriction restriction : restrictions) { if (restriction.getTos().contains(lastSegment)) { restrictionsForbiddenAtLastNode.add(restriction); } } return findSegmentSequence( firstSegment.getNode2(), lastSegment.getNode1(), restrictions, initiallyActiveRestrictions, restrictionsForbiddenAtLastNode); } } /** * tries to find a legal sequence of segments between two segment nodes. * * @param restrictions all restrictions that have to be taken into account * @param initiallyActiveRestrictions restrictions that are already active at firstNode * @param restrictionsForbiddenAtLastNode restrictions that must NOT be active at lastNode * @return list of segments if connection is possible, null otherwise. */ private List<Segment> findSegmentSequence( SegmentNode firstNode, SegmentNode lastNode, Collection<Restriction> restrictions, Collection<Restriction> initiallyActiveRestrictions, Collection<Restriction> restrictionsForbiddenAtLastNode) { if (firstNode == lastNode && !shareElement(initiallyActiveRestrictions, restrictionsForbiddenAtLastNode)) { return EMPTY_SEGMENT_LIST; } Queue<State> stateQueue = new LinkedList<>(); stateQueue.add(createStartingState(firstNode, initiallyActiveRestrictions)); /* search for a possible segment sequence */ while (stateQueue.size() > 0) { State state = stateQueue.poll(); Collection<State> subsequentStates = createSubsequentStates(state, restrictions); for (State subsequentState : subsequentStates) { if (subsequentState.currentNode == lastNode && !shareElement(subsequentState.activeRestrictions, restrictionsForbiddenAtLastNode)) { return subsequentState.segmentHistory; } } stateQueue.addAll(subsequentStates); } return null; } private static State createStartingState(SegmentNode firstNode, Collection<Restriction> initiallyActiveRestrictions) { State startingState = new State(); startingState.currentNode = firstNode; startingState.activeRestrictions = initiallyActiveRestrictions; startingState.segmentHistory = EMPTY_SEGMENT_LIST; startingState.visitedNodes = new HashSet<>(); startingState.visitedNodes.add(firstNode); return startingState; } private List<State> createSubsequentStates(State state, Collection<Restriction> allRestrictions) { List<State> subsequentStates = new ArrayList<>(); for (Segment segment : state.currentNode.getOutboundSegments()) { if (isUsableSegment(segment) && isLegalSegment(segment, state.activeRestrictions)) { State newState = new State(); newState.activeRestrictions = activeRestrictionsAfterSegment( segment, state.activeRestrictions, allRestrictions); newState.segmentHistory = new ArrayList<>(state.segmentHistory.size() + 1); newState.segmentHistory.addAll(state.segmentHistory); newState.segmentHistory.add(segment); newState.currentNode = segment.getNode2(); newState.visitedNodes = new HashSet<>(state.visitedNodes); newState.visitedNodes.add(newState.currentNode); /* add state to queue, * but avoid cycles as well as leaving the node set */ if (!state.visitedNodes.contains(newState.currentNode) && isUsableNode(newState.currentNode)) { subsequentStates.add(newState); } } } return subsequentStates; } /** * returns all restrictions from a collection that have a segment as from member * @return segment list; != null; must not be modified. * May throw an exception when modifying is attempted. */ private static List<Restriction> getRestrictionsStartedBySegment( Collection<Restriction> restrictions, Segment segment) { List<Restriction> result = EMPTY_RESTRICTION_LIST; for (Restriction restriction : restrictions) { if (restriction.getFrom() == segment) { if (result == EMPTY_RESTRICTION_LIST) { result = new ArrayList<>(restrictions.size()); } result.add(restriction); } } return result; } private static Collection<Restriction> activeRestrictionsAfterSegment(Segment segment, Collection<Restriction> activeRestrictionsBeforeSegment, Collection<Restriction> allRestrictions) { Collection<Restriction> result = EMPTY_RESTRICTION_LIST; for (Restriction restriction : activeRestrictionsBeforeSegment) { if (restriction.getVias().contains(segment)) { if (result == EMPTY_RESTRICTION_LIST) { result = new ArrayList<>(allRestrictions.size()); } result.add(restriction); } } Collection<Restriction> newRestrictions = getRestrictionsStartedBySegment(allRestrictions, segment); if (newRestrictions.size() > 0) { if (result == EMPTY_RESTRICTION_LIST) { result = newRestrictions; } else { result.addAll(newRestrictions); } } return result; } private static boolean isLegalSegment( Segment segment, Collection<Restriction> activeRestrictions) { for (Restriction restriction : activeRestrictions) { if (restriction.getTos().contains(segment)) { return false; } } return true; } /** returns true iff at least one element is contained in both collections */ protected static boolean shareElement( Collection<?> collection1, Collection<?> collection2) { for (Object element : collection1) { if (collection2.contains(element)) { return true; } } return false; } public final void evaluate(Collection<Restriction> restrictions) { if (evaluated) { return; } evaluateImpl(restrictions); evaluated = true; } /** * finds in- and outbound segments (if necessary) and segment sequences. * After calling this method, the group must be correctly evaluated * (see {@link #isCorrectlyEvaluated()}). * * @param restrictions restrictions that are used when determining possible connections, * will not be modified; != null */ protected abstract void evaluateImpl(Collection<Restriction> restrictions); /** * returns whether a node can be used while finding a segment sequence * @param node node to check; != null */ protected abstract boolean isUsableNode(SegmentNode node); /** * returns whether a segment can be used while finding a segment sequence * @param segment segment to check; != null */ protected abstract boolean isUsableSegment(Segment segment); }