AbstractPathConnector.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.geometry.euclidean.internal;

  18. import java.util.ArrayList;
  19. import java.util.List;
  20. import java.util.NavigableSet;
  21. import java.util.TreeSet;

  22. /** Abstract base class for joining unconnected path elements into connected, directional
  23.  * paths. The connection algorithm is exposed as a set of protected methods, allowing subclasses
  24.  * to define their own public API. Implementations must supply their own subclass of {@link ConnectableElement}
  25.  * specific for the objects being connected.
  26.  *
  27.  * <p>The connection algorithm proceeds as follows:
  28.  * <ul>
  29.  *      <li>Create a sorted list of {@link ConnectableElement}s.</li>
  30.  *      <li>For each element, attempt to find other elements with start points next the
  31.  *      first instance's end point by calling {@link ConnectableElement#getConnectionSearchKey()} and
  32.  *      using the returned instance to locate a search start location in the sorted element list.</li>
  33.  *      <li>Search up through the sorted list from the start location, testing each element for possible connectivity
  34.  *      with {@link ConnectableElement#canConnectTo(AbstractPathConnector.ConnectableElement)}. Collect possible
  35.  *      connections in a list. Terminate the search when
  36.  *      {@link ConnectableElement#shouldContinueConnectionSearch(AbstractPathConnector.ConnectableElement, boolean)}
  37.  *      returns false.
  38.  *      <li>Repeat the previous step searching downward through the list from the start location.</li>
  39.  *      <li>Select the best connection option from the list of possible connections, using
  40.  *      {@link #selectPointConnection(AbstractPathConnector.ConnectableElement, List)}
  41.  *      and/or {@link #selectConnection(AbstractPathConnector.ConnectableElement, List)} when multiple possibilities
  42.  *      are found.</li>
  43.  *      <li>Repeat the above steps for each element. When done, the elements represent a linked list
  44.  *      of connected paths.</li>
  45.  * </ul>
  46.  *
  47.  * <p>This class is not thread-safe.</p>
  48.  *
  49.  * @param <E> Element type
  50.  * @see ConnectableElement
  51.  */
  52. public abstract class AbstractPathConnector<E extends AbstractPathConnector.ConnectableElement<E>> {
  53.     /** List of path elements. */
  54.     private final NavigableSet<E> pathElements = new TreeSet<>();

  55.     /** View of the path element set in descending order. */
  56.     private final NavigableSet<E> pathElementsDescending = pathElements.descendingSet();

  57.     /** List used to store possible connections for the current element. */
  58.     private final List<E> possibleConnections = new ArrayList<>();

  59.     /** List used to store possible point-like (zero-length) connections for the current element. */
  60.     private final List<E> possiblePointConnections = new ArrayList<>();

  61.     /** Add a collection of path elements to the connector and attempt to connect each new element
  62.      * with previously added ones.
  63.      * @param elements path elements to connect
  64.      */
  65.     protected void connectPathElements(final Iterable<E> elements) {
  66.         elements.forEach(this::addPathElement);

  67.         for (final E element : elements) {
  68.             makeForwardConnection(element);
  69.         }
  70.     }

  71.     /** Add a single path element to the connector, leaving it unconnected until a later call to
  72.      * to {@link #connectPathElements(Iterable)} or {@link #computePathRoots()}.
  73.      * @param element value to add to the connector
  74.      * @see #connectPathElements(Iterable)
  75.      * @see #computePathRoots()
  76.      */
  77.     protected void addPathElement(final E element) {
  78.         pathElements.add(element);
  79.     }

  80.     /** Compute all connected paths and return a list of path elements representing
  81.      * the roots (start locations) of each. Each returned element is the head of a
  82.      * (possibly circular) linked list that follows a connected path.
  83.      *
  84.      * <p>The connector is reset after this call. Further calls to add elements
  85.      * will result in new paths being generated.</p>
  86.      * @return a list of root elements for the computed connected paths
  87.      */
  88.     protected List<E> computePathRoots() {
  89.         for (final E element : pathElements) {
  90.             followForwardConnections(element);
  91.         }

  92.         final List<E> rootEntries = new ArrayList<>();
  93.         E root;
  94.         for (final E element : pathElements) {
  95.             root = element.exportPath();
  96.             if (root != null) {
  97.                 rootEntries.add(root);
  98.             }
  99.         }

  100.         pathElements.clear();
  101.         possibleConnections.clear();
  102.         possiblePointConnections.clear();

  103.         return rootEntries;
  104.     }

  105.     /** Find and follow forward connections from the given start element.
  106.      * @param start element to begin the connection operation with
  107.      */
  108.     private void followForwardConnections(final E start) {
  109.         E current = start;

  110.         while (current != null && current.hasEnd() && !current.hasNext()) {
  111.             current = makeForwardConnection(current);
  112.         }
  113.     }

  114.     /** Connect the end point of the given element to the start point of another element. Returns
  115.      * the newly connected element or null if no forward connection was made.
  116.      * @param element element to connect
  117.      * @return the next element in the path or null if no connection was made
  118.      */
  119.     private E makeForwardConnection(final E element) {
  120.         findPossibleConnections(element);

  121.         E next = null;

  122.         // select from all available connections, handling point-like segments first
  123.         if (!possiblePointConnections.isEmpty()) {
  124.             next = (possiblePointConnections.size() == 1) ?
  125.                     possiblePointConnections.get(0) :
  126.                     selectPointConnection(element, possiblePointConnections);
  127.         } else if (!possibleConnections.isEmpty()) {

  128.             next = (possibleConnections.size() == 1) ?
  129.                     possibleConnections.get(0) :
  130.                     selectConnection(element, possibleConnections);
  131.         }

  132.         if (next != null) {
  133.             element.connectTo(next);
  134.         }

  135.         return next;
  136.     }

  137.     /** Find possible connections for the given element and place them in the
  138.      * {@link #possibleConnections} and {@link #possiblePointConnections} lists.
  139.      * @param element the element to find connections for
  140.      */
  141.     private void findPossibleConnections(final E element) {
  142.         possibleConnections.clear();
  143.         possiblePointConnections.clear();

  144.         if (element.hasEnd()) {
  145.             final E searchKey = element.getConnectionSearchKey();

  146.             // search up
  147.             for (final E candidate : pathElements.tailSet(searchKey)) {
  148.                 if (!addPossibleConnection(element, candidate) &&
  149.                         !element.shouldContinueConnectionSearch(candidate, true)) {
  150.                     break;
  151.                 }
  152.             }

  153.             // search down
  154.             for (final E candidate : pathElementsDescending.tailSet(searchKey, false)) {
  155.                 if (!addPossibleConnection(element, candidate) &&
  156.                         !element.shouldContinueConnectionSearch(candidate, false)) {
  157.                     break;
  158.                 }
  159.             }
  160.         }
  161.     }

  162.     /** Add the candidate to one of the connection lists if it represents a possible connection. Returns
  163.      * true if the candidate was added, otherwise false.
  164.      * @param element element to check for connections with
  165.      * @param candidate candidate connection element
  166.      * @return true if the candidate is a possible connection
  167.      */
  168.     private boolean addPossibleConnection(final E element, final E candidate) {
  169.         if (element != candidate &&
  170.                 !candidate.hasPrevious() &&
  171.                 candidate.hasStart() &&
  172.                 element.canConnectTo(candidate)) {

  173.             if (element.endPointsEq(candidate)) {
  174.                 possiblePointConnections.add(candidate);
  175.             } else {
  176.                 possibleConnections.add(candidate);
  177.             }

  178.             return true;
  179.         }

  180.         return false;
  181.     }

  182.     /** Method called to select a connection to use for a given element when multiple zero-length connections are
  183.      * available. The algorithm here attempts to choose the point most likely to produce a logical path by selecting
  184.      * the outgoing element with the smallest relative angle with the incoming element, with unconnected element
  185.      * preferred over ones that are already connected (thereby allowing other connections to occur in the path).
  186.      * @param incoming the incoming element
  187.      * @param outgoingList list of available outgoing point-like connections
  188.      * @return the connection to use
  189.      */
  190.     protected E selectPointConnection(final E incoming, final List<E> outgoingList) {

  191.         double angle;
  192.         boolean isUnconnected;

  193.         double smallestAngle = 0.0;
  194.         E bestElement = null;
  195.         boolean bestIsUnconnected = false;

  196.         for (final E outgoing : outgoingList) {
  197.             angle = Math.abs(incoming.getRelativeAngle(outgoing));
  198.             isUnconnected = !outgoing.hasNext();

  199.             if (bestElement == null || (!bestIsUnconnected && isUnconnected) ||
  200.                     (bestIsUnconnected == isUnconnected && angle < smallestAngle)) {

  201.                 smallestAngle = angle;
  202.                 bestElement = outgoing;
  203.                 bestIsUnconnected = isUnconnected;
  204.             }
  205.         }

  206.         return bestElement;
  207.     }

  208.     /** Method called to select a connection to use for a given segment when multiple non-length-zero
  209.      * connections are available. In this case, the selection of the outgoing connection depends only
  210.      * on the desired characteristics of the connected path.
  211.      * @param incoming the incoming segment
  212.      * @param outgoing list of available outgoing connections; will always contain at least
  213.      *      two elements
  214.      * @return the connection to use
  215.      */
  216.     protected abstract E selectConnection(E incoming, List<E> outgoing);

  217.     /** Class used to represent connectable path elements for use with {@link AbstractPathConnector}.
  218.      * Subclasses must fulfill the following requirements in order for path connection operations
  219.      * to work correctly:
  220.      * <ul>
  221.      *      <li>Implement {@link #compareTo(Object)} such that elements are sorted by their start
  222.      *      point locations. Other criteria may be used as well but elements with start points in close
  223.      *      proximity must be grouped together.</li>
  224.      *      <li>Implement {@link #getConnectionSearchKey()} such that it returns an instance that will be placed
  225.      *      next to elements with start points close to the current instance's end point when sorted with
  226.      *      {@link #compareTo(Object)}.</li>
  227.      *      <li>Implement {@link #shouldContinueConnectionSearch(AbstractPathConnector.ConnectableElement, boolean)}
  228.      *      such that it returns false when the search for possible connections through a sorted list of elements
  229.      *      may terminate.</li>
  230.      * </ul>
  231.      *
  232.      * @param <E> Element type
  233.      * @see AbstractPathConnector
  234.      */
  235.     public abstract static class ConnectableElement<E extends ConnectableElement<E>>
  236.         implements Comparable<E> {
  237.         /** Next connected element. */
  238.         private E next;

  239.         /** Previous connected element. */
  240.         private E previous;

  241.         /** Flag set to true when this element has exported its value to a path. */
  242.         private boolean exported;

  243.         /** Return true if the instance is connected to another element's start point.
  244.          * @return true if the instance has a next element
  245.          */
  246.         public boolean hasNext() {
  247.             return next != null;
  248.         }

  249.         /** Get the next connected element in the path, if any.
  250.          * @return the next connected segment in the path; may be null
  251.          */
  252.         public E getNext() {
  253.             return next;
  254.         }

  255.         /** Set the next connected element for this path. This is intended for
  256.          * internal use only. Callers should use the {@link #connectTo(AbstractPathConnector.ConnectableElement)}
  257.          * method instead.
  258.          * @param next next path element
  259.          */
  260.         protected void setNext(final E next) {
  261.             this.next = next;
  262.         }

  263.         /** Return true if another element is connected to this instance's start point.
  264.          * @return true if the instance has a previous element
  265.          */
  266.         public boolean hasPrevious() {
  267.             return previous != null;
  268.         }

  269.         /** Get the previous connected element in the path, if any.
  270.          * @return the previous connected element in the path; may be null
  271.          */
  272.         public E getPrevious() {
  273.             return previous;
  274.         }

  275.         /** Set the previous connected element for this path. This is intended for
  276.          * internal use only. Callers should use the {@link #connectTo(AbstractPathConnector.ConnectableElement)}
  277.          * method instead.
  278.          * @param previous previous path element
  279.          */
  280.         protected void setPrevious(final E previous) {
  281.             this.previous = previous;
  282.         }

  283.         /** Connect this instance's end point to the given element's start point. No validation
  284.          * is performed in this method. The {@link #canConnectTo(AbstractPathConnector.ConnectableElement)}
  285.          * method must have been called previously.
  286.          * @param nextElement the next element in the path
  287.          */
  288.         public void connectTo(final E nextElement) {
  289.             setNext(nextElement);
  290.             nextElement.setPrevious(getSelf());
  291.         }

  292.         /** Export the path that this element belongs to, returning the root
  293.          * segment. This method traverses all connected element, sets their
  294.          * exported flags to true, and returns the root element of the path
  295.          * (or this element in the case of a loop). Each path can only be
  296.          * exported once. Later calls to this method on this instance or any of its
  297.          * connected elements will return null.
  298.          * @return the root of the path or null if the path that this element
  299.          *      belongs to has already been exported
  300.          */
  301.         public E exportPath() {
  302.             if (markExported()) {

  303.                 // export the connected portions of the path, moving both
  304.                 // forward and backward
  305.                 E current;
  306.                 E root = getSelf();

  307.                 // forward
  308.                 current = next;
  309.                 while (current != null && current.markExported()) {
  310.                     current = current.getNext();
  311.                 }

  312.                 // backward
  313.                 current = previous;
  314.                 while (current != null && current.markExported()) {
  315.                     root = current;
  316.                     current = current.getPrevious();
  317.                 }

  318.                 return root;
  319.             }

  320.             return null;
  321.         }

  322.         /** Set the export flag for this instance to true. Returns true
  323.          * if the flag was changed and false otherwise.
  324.          * @return true if the flag was changed and false if it was
  325.          *      already set to true
  326.          */
  327.         protected boolean markExported() {
  328.             if (!exported) {
  329.                 exported = true;
  330.                 return true;
  331.             }
  332.             return false;
  333.         }

  334.         /** Return true if this instance has a start point that can be
  335.          * connected to another element's end point.
  336.          * @return true if this instance has a start point that can be
  337.          *      connected to another element's end point
  338.          */
  339.         public abstract boolean hasStart();

  340.         /** Return true if this instance has an end point that can be
  341.          * connected to another element's start point.
  342.          * @return true if this instance has an end point that can be
  343.          *      connected to another element's start point
  344.          */
  345.         public abstract boolean hasEnd();

  346.         /** Return true if the end point of this instance should be considered
  347.          * equivalent to the end point of the argument.
  348.          * @param other element to compare end points with
  349.          * @return true if this instance has an end point equivalent to that
  350.          *      of the argument
  351.          */
  352.         public abstract boolean endPointsEq(E other);

  353.         /** Return true if this instance's end point can be connected to
  354.          * the argument's start point.
  355.          * @param nextElement candidate for the next element in the path; this value
  356.          *      is guaranteed to not be null and to contain a start point
  357.          * @return true if this instance's end point can be connected to
  358.          *      the argument's start point
  359.          */
  360.         public abstract boolean canConnectTo(E nextElement);

  361.         /** Return the relative angle between this element and the argument.
  362.          * @param other element to compute the angle with
  363.          * @return the relative angle between this element and the argument
  364.          */
  365.         public abstract double getRelativeAngle(E other);

  366.         /** Get a new instance used as a search key to help locate other elements
  367.          * with start points matching this instance's end point. The only restriction
  368.          * on the returned instance is that it be compatible with the implementation
  369.          * class' {@link #compareTo(Object)} method.
  370.          * @return a new instance used to help locate other path elements with start
  371.          *      points equivalent to this instance's end point
  372.          */
  373.         public abstract E getConnectionSearchKey();

  374.         /** Return true if the search for possible connections should continue through
  375.          * the sorted set of possible path elements given the current candidate element
  376.          * and search direction. The search operation stops for the given direction
  377.          * when this method returns false.
  378.          * @param candidate last tested candidate connection element
  379.          * @param ascending true if the search is proceeding in an ascending direction;
  380.          *      false otherwise
  381.          * @return true if the connection search should continue
  382.          */
  383.         public abstract boolean shouldContinueConnectionSearch(E candidate, boolean ascending);

  384.         /** Return the current instance as the generic type.
  385.          * @return the current instance as the generic type.
  386.          */
  387.         protected abstract E getSelf();
  388.     }
  389. }