maybe
This commit is contained in:
commit
7b8589d781
303 changed files with 11082 additions and 0 deletions
62
README.md
Normal file
62
README.md
Normal file
|
@ -0,0 +1,62 @@
|
|||
# Graph & Algorithm project — INSA Toulouse
|
||||
|
||||
## How to start?
|
||||
|
||||
You will not be able to use this repository to save your work, you need to copy / import / fork it to
|
||||
your favorite Git platform.
|
||||
|
||||
### Importing to [Github](https://github.com), [Bitbucket](https://bitbucket.org) or [Gitlab](https://gitlab.com):
|
||||
|
||||
You first need to register and then log in to the platform you want. The steps to import the project are detailed below:
|
||||
|
||||
#### Github
|
||||
|
||||
1. In the upper-right corner of any page, click the **"+"** icon, then click **Import repository**, or go to [https://github.com/new/import](https://github.com/new/import).
|
||||
2. Paste the following URL in the first input:
|
||||
[https://gitea.typename.fr/INSA/be-graphes.git](https://gitea.typename.fr/INSA/be-graphes.git)
|
||||
3. Choose the name you want for the repository.
|
||||
4. Click *Begin import*.
|
||||
5. Wait for completion... Done!
|
||||
|
||||
#### Bitbucket
|
||||
|
||||
1. On the left panel of any page, click the **"+"** icon, then **Repository**, and then **Import**, or directly go to [https://bitbucket.org/repo/import](https://bitbucket.org/repo/import).
|
||||
2. Paste the following URL in the first input (select Git as source if not already selected):
|
||||
[https://gitea.typename.fr/INSA/be-graphes.git](https://gitea.typename.fr/INSA/be-graphes.git)
|
||||
3. Choose the name you want for repository, and select Git as the *Version control system*.
|
||||
4. Click *Import repository*.
|
||||
5. Wait for completion... Done!
|
||||
|
||||
#### Gitlab
|
||||
|
||||
1. In the upper-right corner of any page, click the **"+"** icon, then **New project**, or directly go to [https://gitlab.com/projects/new](https://gitlab.com/projects/new).
|
||||
2. Select the **Import project** tab, and then click **Repo by URL** (right-most option).
|
||||
3. Paste the following URL in the first input:
|
||||
[https://gitea.typename.fr/INSA/be-graphes.git](https://gitea.typename.fr/INSA/be-graphes.git)
|
||||
4. Choose the name you want for the repository.
|
||||
5. Click *Create project*.
|
||||
6. Wait for completion... Done!
|
||||
|
||||
### Importing to another repository provider *[not recommended]*:
|
||||
|
||||
1. Create a new **empty** repository (no README, no LICENSE) on your provider. Let's assume the URL of your repository is `$URL_REPOSITORY`.
|
||||
2. Clone this repository somewhere:
|
||||
|
||||
```bash
|
||||
git clone https://gitea.typename.fr/INSA/be-graphes.git
|
||||
```
|
||||
|
||||
3. Go inside the newly cloned repository and update the **remote**:
|
||||
|
||||
```bash
|
||||
cd be-graphes
|
||||
git remote set-url origin $URL_REPOSITORY
|
||||
```
|
||||
|
||||
4. Push to your repository:
|
||||
|
||||
```bash
|
||||
push -u origin master
|
||||
```
|
||||
|
||||
Another way is to do a bare clone and then mirror it to your repository: [https://help.github.com/articles/importing-a-git-repository-using-the-command-line/](https://help.github.com/articles/importing-a-git-repository-using-the-command-line/)
|
25
be-graphes-algos/pom.xml
Normal file
25
be-graphes-algos/pom.xml
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0"?>
|
||||
<project
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.insa.graphs</groupId>
|
||||
<artifactId>be-graphes-all</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>be-graphes-algos</artifactId>
|
||||
<name>be-graphes-algos</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.insa.graphs</groupId>
|
||||
<artifactId>be-graphes-model</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,87 @@
|
|||
package org.insa.graphs.algorithm;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Base class for algorithm classes.
|
||||
*
|
||||
* @param <Observer> Observer type for the algorithm.
|
||||
*/
|
||||
public abstract class AbstractAlgorithm<Observer> {
|
||||
|
||||
// Input data for the algorithm
|
||||
protected final AbstractInputData data;
|
||||
|
||||
// List of observers for the algorithm
|
||||
protected final ArrayList<Observer> observers;
|
||||
|
||||
/**
|
||||
* Create a new algorithm with an empty list of observers.
|
||||
*
|
||||
* @param data Input data for the algorithm.
|
||||
*/
|
||||
protected AbstractAlgorithm(AbstractInputData data) {
|
||||
this.data = data;
|
||||
this.observers = new ArrayList<Observer>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new algorithm with the given list of observers.
|
||||
*
|
||||
* @param data Input data for the algorithm.
|
||||
* @param observers Initial list of observers for the algorithm.
|
||||
*/
|
||||
protected AbstractAlgorithm(AbstractInputData data, ArrayList<Observer> observers) {
|
||||
this.data = data;
|
||||
this.observers = observers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an observer to this algorithm.
|
||||
*
|
||||
* @param observer Observer to add to this algorithm.
|
||||
*/
|
||||
public void addObserver(Observer observer) {
|
||||
observers.add(observer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The list of observers for this algorithm.
|
||||
*/
|
||||
public ArrayList<Observer> getObservers() {
|
||||
return observers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Input for this algorithm.
|
||||
*/
|
||||
public AbstractInputData getInputData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the algorithm and return the solution.
|
||||
*
|
||||
* This methods internally time the call to doRun() and update the result of the
|
||||
* call with the computed solving time.
|
||||
*
|
||||
* @return The solution found by the algorithm (may not be a feasible solution).
|
||||
*/
|
||||
public AbstractSolution run() {
|
||||
Instant start = Instant.now();
|
||||
AbstractSolution solution = this.doRun();
|
||||
solution.setSolvingTime(Duration.between(start, Instant.now()));
|
||||
return solution;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract method that should be implemented by child class.
|
||||
*
|
||||
* @return The solution found, must not be null (use an infeasible or unknown
|
||||
* status if necessary).
|
||||
*/
|
||||
protected abstract AbstractSolution doRun();
|
||||
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package org.insa.graphs.algorithm;
|
||||
|
||||
import org.insa.graphs.model.Arc;
|
||||
import org.insa.graphs.model.Graph;
|
||||
//import org.insa.graphs.model.GraphStatistics;
|
||||
|
||||
/**
|
||||
* Base class for algorithm input data classes. This class contains the basic
|
||||
* data that are required by most graph algorithms, i.e. a graph, a mode (time /
|
||||
* length) and a filter for the arc.
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractInputData {
|
||||
|
||||
/**
|
||||
* Enum specifying the top mode of the algorithms.
|
||||
*
|
||||
* @see ArcInspector
|
||||
*/
|
||||
public enum Mode {
|
||||
TIME, LENGTH
|
||||
}
|
||||
|
||||
// Graph
|
||||
private final Graph graph;
|
||||
|
||||
// Arc filter.
|
||||
protected final ArcInspector arcInspector;
|
||||
|
||||
/**
|
||||
* Create a new AbstractInputData instance for the given graph, mode and filter.
|
||||
*
|
||||
* @param graph Graph for this input data.
|
||||
* @param arcInspector Arc inspector for this input data.
|
||||
*/
|
||||
protected AbstractInputData(Graph graph, ArcInspector arcInspector) {
|
||||
this.graph = graph;
|
||||
this.arcInspector = arcInspector;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Graph associated with this input.
|
||||
*/
|
||||
public Graph getGraph() {
|
||||
return graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the cost associated with the given arc according to the underlying
|
||||
* arc inspector.
|
||||
*
|
||||
* @param arc Arc for which cost should be retrieved.
|
||||
*
|
||||
* @return Cost for the given arc.
|
||||
*
|
||||
* @see ArcInspector
|
||||
*/
|
||||
public double getCost(Arc arc) {
|
||||
return this.arcInspector.getCost(arc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Mode associated with this input data.
|
||||
*
|
||||
* @see Mode
|
||||
*/
|
||||
public Mode getMode() {
|
||||
return this.arcInspector.getMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the maximum speed associated with this input data, or
|
||||
* {@link GraphStatistics#NO_MAXIMUM_SPEED} if none is associated. The maximum
|
||||
* speed associated with input data is different from the maximum speed
|
||||
* associated with graph (accessible via {@link Graph#getGraphInformation()}).
|
||||
*
|
||||
* @return The maximum speed for this inspector, or
|
||||
* {@link GraphStatistics#NO_MAXIMUM_SPEED} if none is set.
|
||||
*/
|
||||
|
||||
/* Apparently, getMaximumSpeed is never initialized in arcInspectors.
|
||||
* => Do not use.
|
||||
public int getMaximumSpeed() {
|
||||
return this.arcInspector.getMaximumSpeed();
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check if the given arc is allowed for the filter corresponding to this input.
|
||||
*
|
||||
* @param arc Arc to check.
|
||||
*
|
||||
* @return true if the given arc is allowed.
|
||||
*
|
||||
* @see ArcInspector
|
||||
*/
|
||||
public boolean isAllowed(Arc arc) {
|
||||
return this.arcInspector.isAllowed(arc);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package org.insa.graphs.algorithm;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Base class for solution classes returned by the algorithm. This class
|
||||
* contains the basic information that any solution should have: status of the
|
||||
* solution (unknown, infeasible, etc.), solving time and the original input
|
||||
* data.
|
||||
*/
|
||||
public abstract class AbstractSolution {
|
||||
|
||||
/**
|
||||
* Possible status for a solution.
|
||||
*
|
||||
*/
|
||||
public enum Status {
|
||||
UNKNOWN, INFEASIBLE, FEASIBLE, OPTIMAL,
|
||||
};
|
||||
|
||||
// Status of the solution.
|
||||
private final Status status;
|
||||
|
||||
// Solving time for the solution.
|
||||
private Duration solvingTime;
|
||||
|
||||
// Original input of the solution.
|
||||
private final AbstractInputData data;
|
||||
|
||||
/**
|
||||
* Create a new abstract solution with unknown status.
|
||||
*
|
||||
* @param data
|
||||
*/
|
||||
protected AbstractSolution(AbstractInputData data) {
|
||||
this.data = data;
|
||||
this.solvingTime = Duration.ZERO;
|
||||
this.status = Status.UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param data
|
||||
* @param status
|
||||
*/
|
||||
protected AbstractSolution(AbstractInputData data, Status status) {
|
||||
this.data = data;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Original input for this solution.
|
||||
*/
|
||||
public AbstractInputData getInputData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Status of this solution.
|
||||
*/
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Solving time of this solution.
|
||||
*/
|
||||
public Duration getSolvingTime() {
|
||||
return solvingTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the solving time of this solution.
|
||||
*
|
||||
* @param solvingTime Solving time for the solution.
|
||||
*/
|
||||
protected void setSolvingTime(Duration solvingTime) {
|
||||
this.solvingTime = solvingTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the solution is feasible or optimal.
|
||||
*/
|
||||
public boolean isFeasible() {
|
||||
return status == Status.FEASIBLE || status == Status.OPTIMAL;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
package org.insa.graphs.algorithm;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.insa.graphs.algorithm.shortestpath.AStarAlgorithm;
|
||||
import org.insa.graphs.algorithm.shortestpath.BellmanFordAlgorithm;
|
||||
import org.insa.graphs.algorithm.shortestpath.DijkstraAlgorithm;
|
||||
import org.insa.graphs.algorithm.shortestpath.ShortestPathAlgorithm;
|
||||
import org.insa.graphs.algorithm.weakconnectivity.WeaklyConnectedComponentsAlgorithm;
|
||||
|
||||
/**
|
||||
* Factory class used to register and retrieve algorithms based on their common
|
||||
* ancestor and name.
|
||||
*
|
||||
*/
|
||||
public class AlgorithmFactory {
|
||||
|
||||
// Map between algorithm names and class.
|
||||
private final static Map<Class<? extends AbstractAlgorithm<?>>, Map<String, Class<? extends AbstractAlgorithm<?>>>> ALGORITHMS = new IdentityHashMap<>();
|
||||
|
||||
static {
|
||||
// Register weakly-connected components algorithm:
|
||||
registerAlgorithm(WeaklyConnectedComponentsAlgorithm.class, "WCC basic",
|
||||
WeaklyConnectedComponentsAlgorithm.class);
|
||||
|
||||
// Register shortest path algorithm:
|
||||
registerAlgorithm(ShortestPathAlgorithm.class, "Bellman-Ford", BellmanFordAlgorithm.class);
|
||||
registerAlgorithm(ShortestPathAlgorithm.class, "Dijkstra", DijkstraAlgorithm.class);
|
||||
registerAlgorithm(ShortestPathAlgorithm.class, "A*", AStarAlgorithm.class);
|
||||
|
||||
// Register your algorithms here:
|
||||
// registerAlgorithm(CarPoolingAlgorithm.class, "My Awesome Algorithm",
|
||||
// MyCarPoolingAlgorithm.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the given algorithm class with the given name as a child class of
|
||||
* the given base algorithm.
|
||||
*
|
||||
* @param baseAlgorithm Base algorithm class that corresponds to the newly
|
||||
* registered algorithm class (e.g., generic algorithm
|
||||
* class for the problem).
|
||||
* @param name Name for the registered algorithm class.
|
||||
* @param algoClass Algorithm class to register.
|
||||
*/
|
||||
public static void registerAlgorithm(Class<? extends AbstractAlgorithm<?>> baseAlgorithm,
|
||||
String name, Class<? extends AbstractAlgorithm<?>> algoClass) {
|
||||
if (!ALGORITHMS.containsKey(baseAlgorithm)) {
|
||||
ALGORITHMS.put(baseAlgorithm, new LinkedHashMap<>());
|
||||
}
|
||||
ALGORITHMS.get(baseAlgorithm).put(name, algoClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of the given algorithm class using the given input data.
|
||||
* Assuming algorithm correspond to a class "Algorithm", this function returns
|
||||
* an object equivalent to `new Algorithm(data)`.
|
||||
*
|
||||
* @param algorithm Class of the algorithm to create.
|
||||
* @param data Input data for the algorithm.
|
||||
*
|
||||
* @return A new instance of the given algorithm class using the given data.
|
||||
*
|
||||
* @throws Exception if something wrong happens when constructing the object,
|
||||
* i.e. the given input data does not correspond to the given
|
||||
* algorithm and/or no constructor that takes a single
|
||||
* parameter of type (data.getClass()) exists.
|
||||
*/
|
||||
public static AbstractAlgorithm<?> createAlgorithm(
|
||||
Class<? extends AbstractAlgorithm<?>> algorithm, AbstractInputData data)
|
||||
throws Exception {
|
||||
// Retrieve the set of constructors for the given algorithm class.
|
||||
Constructor<?>[] constructors = algorithm.getDeclaredConstructors();
|
||||
|
||||
// Within this set, find the constructor that can be called with "data" (only).
|
||||
AbstractAlgorithm<?> constructed = null;
|
||||
for (Constructor<?> c: constructors) {
|
||||
Class<?>[] params = c.getParameterTypes();
|
||||
if (params.length == 1 && params[0].isAssignableFrom(data.getClass())) {
|
||||
c.setAccessible(true);
|
||||
constructed = (AbstractAlgorithm<?>) c.newInstance(new Object[] { data });
|
||||
break;
|
||||
}
|
||||
}
|
||||
return constructed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the algorithm class corresponding to the given base algorithm class
|
||||
* and name. The algorithm must have been previously registered using
|
||||
* registerAlgorithm.
|
||||
*
|
||||
* @param baseAlgorithm Base algorithm class for the algorithm to retrieve.
|
||||
* @param name Name of the algorithm to retrieve.
|
||||
*
|
||||
* @return Class corresponding to the given name.
|
||||
*
|
||||
* @see #registerAlgorithm
|
||||
*/
|
||||
public static Class<? extends AbstractAlgorithm<?>> getAlgorithmClass(
|
||||
Class<? extends AbstractAlgorithm<?>> baseAlgorithm, String name) {
|
||||
return ALGORITHMS.get(baseAlgorithm).get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of names corresponding to the registered algorithm classes
|
||||
* for the given base algorithm class.
|
||||
*
|
||||
* @param baseAlgorithm Base algorithm class for the algorithm class names to
|
||||
* retrieve.
|
||||
*
|
||||
* @return Names of the currently registered algorithms.
|
||||
*
|
||||
* @see #registerAlgorithm
|
||||
*/
|
||||
public static Set<String> getAlgorithmNames(
|
||||
Class<? extends AbstractAlgorithm<?>> baseAlgorithm) {
|
||||
if (!ALGORITHMS.containsKey(baseAlgorithm)) {
|
||||
return new TreeSet<>();
|
||||
}
|
||||
return ALGORITHMS.get(baseAlgorithm).keySet();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
package org.insa.graphs.algorithm;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractInputData.Mode;
|
||||
import org.insa.graphs.model.Arc;
|
||||
//import org.insa.graphs.model.GraphStatistics;
|
||||
|
||||
/**
|
||||
* This class can be used to indicate to an algorithm which arcs can be used and
|
||||
* the costs of the usable arcs..
|
||||
*
|
||||
*/
|
||||
public interface ArcInspector {
|
||||
|
||||
/**
|
||||
* Check if the given arc can be used (is allowed).
|
||||
*
|
||||
* @param arc Arc to check.
|
||||
*
|
||||
* @return true if the given arc is allowed.
|
||||
*/
|
||||
public boolean isAllowed(Arc arc);
|
||||
|
||||
/**
|
||||
* Find the cost of the given arc.
|
||||
*
|
||||
* @param arc Arc for which the cost should be returned.
|
||||
*
|
||||
* @return Cost of the arc.
|
||||
*/
|
||||
public double getCost(Arc arc);
|
||||
|
||||
/**
|
||||
* @return The maximum speed for this inspector, or
|
||||
* {@link GraphStatistics#NO_MAXIMUM_SPEED} if none is set.
|
||||
*/
|
||||
/* public int getMaximumSpeed(); */
|
||||
|
||||
/**
|
||||
* @return Mode for this arc inspector.
|
||||
*/
|
||||
public Mode getMode();
|
||||
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
package org.insa.graphs.algorithm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractInputData.Mode;
|
||||
import org.insa.graphs.model.Arc;
|
||||
//import org.insa.graphs.model.GraphStatistics;
|
||||
import org.insa.graphs.model.AccessRestrictions.AccessMode;
|
||||
import org.insa.graphs.model.AccessRestrictions.AccessRestriction;
|
||||
|
||||
public class ArcInspectorFactory {
|
||||
|
||||
/**
|
||||
* @return List of all arc filters in this factory.
|
||||
*/
|
||||
public static List<ArcInspector> getAllFilters() {
|
||||
List<ArcInspector> filters = new ArrayList<>();
|
||||
|
||||
// Common filters:
|
||||
|
||||
// No filter (all arcs allowed):
|
||||
filters.add(new ArcInspector() {
|
||||
@Override
|
||||
public boolean isAllowed(Arc arc) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getCost(Arc arc) {
|
||||
return arc.getLength();
|
||||
}
|
||||
|
||||
/*
|
||||
@Override
|
||||
public int getMaximumSpeed() {
|
||||
return GraphStatistics.NO_MAXIMUM_SPEED;
|
||||
}
|
||||
*/
|
||||
|
||||
@Override
|
||||
public Mode getMode() {
|
||||
return Mode.LENGTH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Shortest path, all roads allowed";
|
||||
}
|
||||
});
|
||||
|
||||
// Only road allowed for cars and length:
|
||||
filters.add(new ArcInspector() {
|
||||
@Override
|
||||
public boolean isAllowed(Arc arc) {
|
||||
return arc.getRoadInformation().getAccessRestrictions()
|
||||
.isAllowedForAny(AccessMode.MOTORCAR, EnumSet.complementOf(EnumSet
|
||||
.of(AccessRestriction.FORBIDDEN, AccessRestriction.PRIVATE)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getCost(Arc arc) {
|
||||
return arc.getLength();
|
||||
}
|
||||
|
||||
/*
|
||||
@Override
|
||||
public int getMaximumSpeed() {
|
||||
return GraphStatistics.NO_MAXIMUM_SPEED;
|
||||
}
|
||||
*/
|
||||
|
||||
@Override
|
||||
public Mode getMode() {
|
||||
return Mode.LENGTH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Shortest path, only roads open for cars";
|
||||
}
|
||||
});
|
||||
|
||||
// Only road allowed for cars and time:
|
||||
|
||||
filters.add(new ArcInspector() {
|
||||
@Override
|
||||
public boolean isAllowed(Arc arc) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getCost(Arc arc) {
|
||||
return arc.getMinimumTravelTime();
|
||||
}
|
||||
|
||||
/*
|
||||
@Override
|
||||
public int getMaximumSpeed() {
|
||||
return GraphStatistics.NO_MAXIMUM_SPEED;
|
||||
}
|
||||
*/
|
||||
|
||||
@Override
|
||||
public Mode getMode() {
|
||||
return Mode.TIME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Fastest path, all roads allowed";
|
||||
}
|
||||
});
|
||||
|
||||
filters.add(new ArcInspector() {
|
||||
@Override
|
||||
public boolean isAllowed(Arc arc) {
|
||||
return arc.getRoadInformation().getAccessRestrictions()
|
||||
.isAllowedForAny(AccessMode.MOTORCAR, EnumSet.complementOf(EnumSet
|
||||
.of(AccessRestriction.FORBIDDEN, AccessRestriction.PRIVATE)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getCost(Arc arc) {
|
||||
return arc.getMinimumTravelTime();
|
||||
}
|
||||
|
||||
/*
|
||||
@Override
|
||||
public int getMaximumSpeed() {
|
||||
return GraphStatistics.NO_MAXIMUM_SPEED;
|
||||
}
|
||||
*/
|
||||
|
||||
@Override
|
||||
public Mode getMode() {
|
||||
return Mode.TIME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Fastest path, only roads open for cars";
|
||||
}
|
||||
});
|
||||
|
||||
// Non-private roads for pedestrian and bicycle:
|
||||
filters.add(new ArcInspector() {
|
||||
|
||||
static final int maxPedestrianSpeed = 5 ;
|
||||
|
||||
@Override
|
||||
public boolean isAllowed(Arc arc) {
|
||||
return arc.getRoadInformation().getAccessRestrictions()
|
||||
.isAllowedForAny(AccessMode.FOOT, EnumSet.complementOf(EnumSet
|
||||
.of(AccessRestriction.FORBIDDEN, AccessRestriction.PRIVATE)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getCost(Arc arc) {
|
||||
return arc.getTravelTime(
|
||||
Math.min(maxPedestrianSpeed, arc.getRoadInformation().getMaximumSpeed()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Fastest path for pedestrian";
|
||||
}
|
||||
|
||||
/*
|
||||
@Override
|
||||
public int getMaximumSpeed() {
|
||||
return 5;
|
||||
}
|
||||
*/
|
||||
|
||||
@Override
|
||||
public Mode getMode() {
|
||||
return Mode.TIME;
|
||||
}
|
||||
});
|
||||
|
||||
// Add your own filters here (do not forget to implement toString()
|
||||
// to get an understandable output!):
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package org.insa.graphs.algorithm.carpooling;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractAlgorithm;
|
||||
|
||||
public abstract class CarPoolingAlgorithm extends AbstractAlgorithm<CarPoolingObserver> {
|
||||
|
||||
protected CarPoolingAlgorithm(CarPoolingData data) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CarPoolingSolution run() {
|
||||
return (CarPoolingSolution) super.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected abstract CarPoolingSolution doRun();
|
||||
|
||||
@Override
|
||||
public CarPoolingData getInputData() {
|
||||
return (CarPoolingData) super.getInputData();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package org.insa.graphs.algorithm.carpooling;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractInputData;
|
||||
import org.insa.graphs.algorithm.ArcInspector;
|
||||
import org.insa.graphs.model.Graph;
|
||||
|
||||
public class CarPoolingData extends AbstractInputData {
|
||||
|
||||
protected CarPoolingData(Graph graph, ArcInspector arcFilter) {
|
||||
super(graph, arcFilter);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.insa.graphs.algorithm.carpooling;
|
||||
|
||||
public class CarPoolingGraphicObserver implements CarPoolingObserver {
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.insa.graphs.algorithm.carpooling;
|
||||
|
||||
public interface CarPoolingObserver {
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package org.insa.graphs.algorithm.carpooling;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractSolution;
|
||||
|
||||
public class CarPoolingSolution extends AbstractSolution {
|
||||
|
||||
protected CarPoolingSolution(CarPoolingData data, Status status) {
|
||||
super(data, status);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.insa.graphs.algorithm.carpooling;
|
||||
|
||||
public class CarPoolingTextObserver implements CarPoolingObserver {
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package org.insa.graphs.algorithm.packageswitch;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractAlgorithm;
|
||||
|
||||
public abstract class PackageSwitchAlgorithm extends AbstractAlgorithm<PackageSwitchObserver> {
|
||||
|
||||
/**
|
||||
* Create a new PackageSwitchAlgorithm with the given data.
|
||||
*
|
||||
* @param data
|
||||
*/
|
||||
protected PackageSwitchAlgorithm(PackageSwitchData data) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PackageSwitchSolution run() {
|
||||
return (PackageSwitchSolution) super.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected abstract PackageSwitchSolution doRun();
|
||||
|
||||
@Override
|
||||
public PackageSwitchData getInputData() {
|
||||
return (PackageSwitchData) super.getInputData();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package org.insa.graphs.algorithm.packageswitch;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractInputData;
|
||||
import org.insa.graphs.algorithm.ArcInspector;
|
||||
import org.insa.graphs.model.Graph;
|
||||
|
||||
public class PackageSwitchData extends AbstractInputData {
|
||||
|
||||
protected PackageSwitchData(Graph graph, ArcInspector arcFilter) {
|
||||
super(graph, arcFilter);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.insa.graphs.algorithm.packageswitch;
|
||||
|
||||
public class PackageSwitchGraphicObserver implements PackageSwitchObserver {
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.insa.graphs.algorithm.packageswitch;
|
||||
|
||||
public interface PackageSwitchObserver {
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package org.insa.graphs.algorithm.packageswitch;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractSolution;
|
||||
|
||||
public class PackageSwitchSolution extends AbstractSolution {
|
||||
|
||||
protected PackageSwitchSolution(PackageSwitchData data, Status status) {
|
||||
super(data, status);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package org.insa.graphs.algorithm.packageswitch;
|
||||
|
||||
public class PackageSwitchTextObserver implements PackageSwitchObserver {
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package org.insa.graphs.algorithm.shortestpath;
|
||||
|
||||
public class AStarAlgorithm extends DijkstraAlgorithm {
|
||||
|
||||
public AStarAlgorithm(ShortestPathData data) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package org.insa.graphs.algorithm.shortestpath;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractSolution.Status;
|
||||
import org.insa.graphs.model.Arc;
|
||||
import org.insa.graphs.model.Graph;
|
||||
import org.insa.graphs.model.Node;
|
||||
import org.insa.graphs.model.Path;
|
||||
|
||||
public class BellmanFordAlgorithm extends ShortestPathAlgorithm {
|
||||
|
||||
public BellmanFordAlgorithm(ShortestPathData data) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ShortestPathSolution doRun() {
|
||||
|
||||
// Retrieve the graph.
|
||||
ShortestPathData data = getInputData();
|
||||
Graph graph = data.getGraph();
|
||||
|
||||
final int nbNodes = graph.size();
|
||||
|
||||
// Initialize array of distances.
|
||||
double[] distances = new double[nbNodes];
|
||||
Arrays.fill(distances, Double.POSITIVE_INFINITY);
|
||||
distances[data.getOrigin().getId()] = 0;
|
||||
|
||||
// Notify observers about the first event (origin processed).
|
||||
notifyOriginProcessed(data.getOrigin());
|
||||
|
||||
// Initialize array of predecessors.
|
||||
Arc[] predecessorArcs = new Arc[nbNodes];
|
||||
|
||||
// Actual algorithm, we will assume the graph does not contain negative
|
||||
// cycle...
|
||||
boolean found = false;
|
||||
for (int i = 0; !found && i < nbNodes; ++i) {
|
||||
found = true;
|
||||
for (Node node: graph.getNodes()) {
|
||||
for (Arc arc: node.getSuccessors()) {
|
||||
|
||||
// Small test to check allowed roads...
|
||||
if (!data.isAllowed(arc)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Retrieve weight of the arc.
|
||||
double w = data.getCost(arc);
|
||||
double oldDistance = distances[arc.getDestination().getId()];
|
||||
double newDistance = distances[node.getId()] + w;
|
||||
|
||||
if (Double.isInfinite(oldDistance) && Double.isFinite(newDistance)) {
|
||||
notifyNodeReached(arc.getDestination());
|
||||
}
|
||||
|
||||
// Check if new distances would be better, if so update...
|
||||
if (newDistance < oldDistance) {
|
||||
found = false;
|
||||
distances[arc.getDestination().getId()] = distances[node.getId()] + w;
|
||||
predecessorArcs[arc.getDestination().getId()] = arc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShortestPathSolution solution = null;
|
||||
|
||||
// Destination has no predecessor, the solution is infeasible...
|
||||
if (predecessorArcs[data.getDestination().getId()] == null) {
|
||||
solution = new ShortestPathSolution(data, Status.INFEASIBLE);
|
||||
}
|
||||
else {
|
||||
|
||||
// The destination has been found, notify the observers.
|
||||
notifyDestinationReached(data.getDestination());
|
||||
|
||||
// Create the path from the array of predecessors...
|
||||
ArrayList<Arc> arcs = new ArrayList<>();
|
||||
Arc arc = predecessorArcs[data.getDestination().getId()];
|
||||
while (arc != null) {
|
||||
arcs.add(arc);
|
||||
arc = predecessorArcs[arc.getOrigin().getId()];
|
||||
}
|
||||
|
||||
// Reverse the path...
|
||||
Collections.reverse(arcs);
|
||||
|
||||
// Create the final solution.
|
||||
solution = new ShortestPathSolution(data, Status.OPTIMAL, new Path(graph, arcs));
|
||||
}
|
||||
|
||||
return solution;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package org.insa.graphs.algorithm.shortestpath;
|
||||
|
||||
public class DijkstraAlgorithm extends ShortestPathAlgorithm {
|
||||
|
||||
public DijkstraAlgorithm(ShortestPathData data) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ShortestPathSolution doRun() {
|
||||
final ShortestPathData data = getInputData();
|
||||
ShortestPathSolution solution = null;
|
||||
// TODO:
|
||||
return solution;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package org.insa.graphs.algorithm.shortestpath;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractAlgorithm;
|
||||
import org.insa.graphs.model.Node;
|
||||
|
||||
public abstract class ShortestPathAlgorithm extends AbstractAlgorithm<ShortestPathObserver> {
|
||||
|
||||
protected ShortestPathAlgorithm(ShortestPathData data) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShortestPathSolution run() {
|
||||
return (ShortestPathSolution) super.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected abstract ShortestPathSolution doRun();
|
||||
|
||||
@Override
|
||||
public ShortestPathData getInputData() {
|
||||
return (ShortestPathData) super.getInputData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all observers that the origin has been processed.
|
||||
*
|
||||
* @param node Origin.
|
||||
*/
|
||||
public void notifyOriginProcessed(Node node) {
|
||||
for (ShortestPathObserver obs: getObservers()) {
|
||||
obs.notifyOriginProcessed(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all observers that a node has been reached for the first time.
|
||||
*
|
||||
* @param node Node that has been reached.
|
||||
*/
|
||||
public void notifyNodeReached(Node node) {
|
||||
for (ShortestPathObserver obs: getObservers()) {
|
||||
obs.notifyNodeReached(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all observers that a node has been marked, i.e. its final value has
|
||||
* been set.
|
||||
*
|
||||
* @param node Node that has been marked.
|
||||
*/
|
||||
public void notifyNodeMarked(Node node) {
|
||||
for (ShortestPathObserver obs: getObservers()) {
|
||||
obs.notifyNodeMarked(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all observers that the destination has been reached.
|
||||
*
|
||||
* @param node Destination.
|
||||
*/
|
||||
public void notifyDestinationReached(Node node) {
|
||||
for (ShortestPathObserver obs: getObservers()) {
|
||||
obs.notifyDestinationReached(node);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package org.insa.graphs.algorithm.shortestpath;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractInputData;
|
||||
import org.insa.graphs.algorithm.ArcInspector;
|
||||
import org.insa.graphs.model.Graph;
|
||||
import org.insa.graphs.model.Node;
|
||||
|
||||
public class ShortestPathData extends AbstractInputData {
|
||||
|
||||
// Origin and destination nodes.
|
||||
private final Node origin, destination;
|
||||
|
||||
/**
|
||||
* Construct a new instance of ShortestPathInputData with the given parameters.
|
||||
*
|
||||
* @param graph Graph in which the path should be looked for.
|
||||
* @param origin Origin node of the path.
|
||||
* @param destination Destination node of the path.
|
||||
* @param arcInspector Filter for arcs (used to allow only a specific set of
|
||||
* arcs in the graph to be used).
|
||||
*/
|
||||
public ShortestPathData(Graph graph, Node origin, Node destination, ArcInspector arcInspector) {
|
||||
super(graph, arcInspector);
|
||||
this.origin = origin;
|
||||
this.destination = destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Origin node for the path.
|
||||
*/
|
||||
public Node getOrigin() {
|
||||
return origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Destination node for the path.
|
||||
*/
|
||||
public Node getDestination() {
|
||||
return destination;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Shortest-path from #" + origin.getId() + " to #" + destination.getId() + " ["
|
||||
+ this.arcInspector.toString().toLowerCase() + "]";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package org.insa.graphs.algorithm.shortestpath;
|
||||
|
||||
import org.insa.graphs.model.Node;
|
||||
|
||||
public interface ShortestPathObserver {
|
||||
|
||||
/**
|
||||
* Notify the observer that the origin has been processed.
|
||||
*
|
||||
* @param node Origin.
|
||||
*/
|
||||
public void notifyOriginProcessed(Node node);
|
||||
|
||||
/**
|
||||
* Notify the observer that a node has been reached for the first
|
||||
* time.
|
||||
*
|
||||
* @param node Node that has been reached.
|
||||
*/
|
||||
public void notifyNodeReached(Node node);
|
||||
|
||||
/**
|
||||
* Notify the observer that a node has been marked, i.e. its final
|
||||
* value has been set.
|
||||
*
|
||||
* @param node Node that has been marked.
|
||||
*/
|
||||
public void notifyNodeMarked(Node node);
|
||||
|
||||
/**
|
||||
* Notify the observer that the destination has been reached.
|
||||
*
|
||||
* @param node Destination.
|
||||
*/
|
||||
public void notifyDestinationReached(Node node);
|
||||
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package org.insa.graphs.algorithm.shortestpath;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractInputData.Mode;
|
||||
import org.insa.graphs.model.Arc;
|
||||
import org.insa.graphs.model.Path;
|
||||
import org.insa.graphs.algorithm.AbstractSolution;
|
||||
|
||||
public class ShortestPathSolution extends AbstractSolution {
|
||||
|
||||
// Optimal solution.
|
||||
private final Path path;
|
||||
|
||||
/**
|
||||
* Create a new infeasible shortest-path solution for the given input and
|
||||
* status.
|
||||
*
|
||||
* @param data Original input data for this solution.
|
||||
* @param status Status of the solution (UNKNOWN / INFEASIBLE).
|
||||
*/
|
||||
public ShortestPathSolution(ShortestPathData data, Status status) {
|
||||
super(data, status);
|
||||
this.path = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new shortest-path solution.
|
||||
*
|
||||
* @param data Original input data for this solution.
|
||||
* @param status Status of the solution (FEASIBLE / OPTIMAL).
|
||||
* @param path Path corresponding to the solution.
|
||||
*/
|
||||
public ShortestPathSolution(ShortestPathData data, Status status, Path path) {
|
||||
super(data, status);
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShortestPathData getInputData() {
|
||||
return (ShortestPathData) super.getInputData();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The path of this solution, if any.
|
||||
*/
|
||||
public Path getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String info = null;
|
||||
if (!isFeasible()) {
|
||||
info = String.format("No path found from node #%d to node #%d",
|
||||
getInputData().getOrigin().getId(), getInputData().getDestination().getId());
|
||||
}
|
||||
else {
|
||||
double cost = 0;
|
||||
for (Arc arc: getPath().getArcs()) {
|
||||
cost += getInputData().getCost(arc);
|
||||
}
|
||||
info = String.format("Found a path from node #%d to node #%d",
|
||||
getInputData().getOrigin().getId(), getInputData().getDestination().getId());
|
||||
if (getInputData().getMode() == Mode.LENGTH) {
|
||||
info = String.format("%s, %.4f kilometers", info, cost / 1000.0);
|
||||
}
|
||||
else {
|
||||
info = String.format("%s, %.4f minutes", info, cost / 60.0);
|
||||
}
|
||||
}
|
||||
info += " in " + getSolvingTime().getSeconds() + " seconds.";
|
||||
return info;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package org.insa.graphs.algorithm.shortestpath;
|
||||
|
||||
import java.io.PrintStream;
|
||||
|
||||
import org.insa.graphs.model.Node;
|
||||
|
||||
public class ShortestPathTextObserver implements ShortestPathObserver {
|
||||
|
||||
private final PrintStream stream;
|
||||
|
||||
public ShortestPathTextObserver(PrintStream stream) {
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyOriginProcessed(Node node) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyNodeReached(Node node) {
|
||||
stream.println("Node " + node.getId() + " reached.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyNodeMarked(Node node) {
|
||||
stream.println("Node " + node.getId() + " marked.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyDestinationReached(Node node) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,205 @@
|
|||
package org.insa.graphs.algorithm.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Implements a binary heap containing elements of type E.
|
||||
*
|
||||
* Note that all comparisons are based on the compareTo method, hence E must
|
||||
* implement Comparable
|
||||
*
|
||||
* @author Mark Allen Weiss
|
||||
* @author DLB
|
||||
*/
|
||||
public class BinaryHeap<E extends Comparable<E>> implements PriorityQueue<E> {
|
||||
|
||||
// Number of elements in heap.
|
||||
private int currentSize;
|
||||
|
||||
// The heap array.
|
||||
protected final ArrayList<E> array;
|
||||
|
||||
/**
|
||||
* Construct a new empty binary heap.
|
||||
*/
|
||||
public BinaryHeap() {
|
||||
this.currentSize = 0;
|
||||
this.array = new ArrayList<E>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a copy of the given heap.
|
||||
*
|
||||
* @param heap Binary heap to copy.
|
||||
*/
|
||||
public BinaryHeap(BinaryHeap<E> heap) {
|
||||
this.currentSize = heap.currentSize;
|
||||
this.array = new ArrayList<E>(heap.array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an element at the given index.
|
||||
*
|
||||
* @param index Index at which the element should be set.
|
||||
* @param value Element to set.
|
||||
*/
|
||||
private void arraySet(int index, E value) {
|
||||
if (index == this.array.size()) {
|
||||
this.array.add(value);
|
||||
}
|
||||
else {
|
||||
this.array.set(index, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Index of the parent of the given index.
|
||||
*/
|
||||
protected int indexParent(int index) {
|
||||
return (index - 1) / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Index of the left child of the given index.
|
||||
*/
|
||||
protected int indexLeft(int index) {
|
||||
return index * 2 + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to percolate up in the heap.
|
||||
*
|
||||
* @param index Index at which the percolate begins.
|
||||
*/
|
||||
private void percolateUp(int index) {
|
||||
E x = this.array.get(index);
|
||||
|
||||
for (; index > 0
|
||||
&& x.compareTo(this.array.get(indexParent(index))) < 0; index = indexParent(
|
||||
index)) {
|
||||
E moving_val = this.array.get(indexParent(index));
|
||||
this.arraySet(index, moving_val);
|
||||
}
|
||||
|
||||
this.arraySet(index, x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to percolate down in the heap.
|
||||
*
|
||||
* @param index Index at which the percolate begins.
|
||||
*/
|
||||
private void percolateDown(int index) {
|
||||
int ileft = indexLeft(index);
|
||||
int iright = ileft + 1;
|
||||
|
||||
if (ileft < this.currentSize) {
|
||||
E current = this.array.get(index);
|
||||
E left = this.array.get(ileft);
|
||||
boolean hasRight = iright < this.currentSize;
|
||||
E right = (hasRight) ? this.array.get(iright) : null;
|
||||
|
||||
if (!hasRight || left.compareTo(right) < 0) {
|
||||
// Left is smaller
|
||||
if (left.compareTo(current) < 0) {
|
||||
this.arraySet(index, left);
|
||||
this.arraySet(ileft, current);
|
||||
this.percolateDown(ileft);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Right is smaller
|
||||
if (right.compareTo(current) < 0) {
|
||||
this.arraySet(index, right);
|
||||
this.arraySet(iright, current);
|
||||
this.percolateDown(iright);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return this.currentSize == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return this.currentSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insert(E x) {
|
||||
int index = this.currentSize++;
|
||||
this.arraySet(index, x);
|
||||
this.percolateUp(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(E x) throws ElementNotFoundException {
|
||||
// TODO:
|
||||
}
|
||||
|
||||
@Override
|
||||
public E findMin() throws EmptyPriorityQueueException {
|
||||
if (isEmpty())
|
||||
throw new EmptyPriorityQueueException();
|
||||
return this.array.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public E deleteMin() throws EmptyPriorityQueueException {
|
||||
E minItem = findMin();
|
||||
E lastItem = this.array.get(--this.currentSize);
|
||||
this.arraySet(0, lastItem);
|
||||
this.percolateDown(0);
|
||||
return minItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a multi-lines string representing a sorted view of this binary heap.
|
||||
*
|
||||
* @return a string containing a sorted view this binary heap.
|
||||
*/
|
||||
public String toStringSorted() {
|
||||
return BinaryHeapFormatter.toStringSorted(this, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a multi-lines string representing a sorted view of this binary heap.
|
||||
*
|
||||
* @param maxElement Maximum number of elements to display. or {@code -1} to
|
||||
* display all the elements.
|
||||
*
|
||||
* @return a string containing a sorted view this binary heap.
|
||||
*/
|
||||
public String toStringSorted(int maxElement) {
|
||||
return BinaryHeapFormatter.toStringSorted(this, maxElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a multi-lines string representing a tree view of this binary heap.
|
||||
*
|
||||
* @return a string containing a tree view of this binary heap.
|
||||
*/
|
||||
public String toStringTree() {
|
||||
return BinaryHeapFormatter.toStringTree(this, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a multi-lines string representing a tree view of this binary heap.
|
||||
*
|
||||
* @param maxDepth Maximum depth of the tree to display.
|
||||
*
|
||||
* @return a string containing a tree view of this binary heap.
|
||||
*/
|
||||
public String toStringTree(int maxDepth) {
|
||||
return BinaryHeapFormatter.toStringTree(this, maxDepth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return BinaryHeapFormatter.toStringTree(this, 8);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
package org.insa.graphs.algorithm.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class BinaryHeapFormatter {
|
||||
|
||||
/**
|
||||
* This class is used by {@link #toStringTree}, and simply contains three string
|
||||
* accumulating. This is an immutable class.
|
||||
*
|
||||
*/
|
||||
private static class Context {
|
||||
|
||||
// Output text:
|
||||
public final String acu;
|
||||
|
||||
// Margin to get back exactly under the current position:
|
||||
public final String margin;
|
||||
|
||||
// Last margin used for the last child of a node. The markers are different:
|
||||
public final String lastmargin;
|
||||
|
||||
/**
|
||||
* Creaet a new {@code Context}.
|
||||
*
|
||||
* @param acu The accumulated string.
|
||||
* @param margin The current margin.
|
||||
* @param lastMargin The last margin used.
|
||||
*/
|
||||
public Context(String acu, String margin, String lastMargin) {
|
||||
this.acu = acu;
|
||||
this.margin = margin;
|
||||
this.lastmargin = lastMargin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new context by appending newlines to this context.
|
||||
*
|
||||
* @param n Number of newlines to append.
|
||||
*
|
||||
* @return a new context with {@code n} newlines appended.
|
||||
*/
|
||||
public Context appendNewlines(int n) {
|
||||
if (n <= 0) {
|
||||
return this;
|
||||
}
|
||||
else {
|
||||
return (new Context(this.acu + "\n" + this.margin, this.margin, this.lastmargin)
|
||||
.appendNewlines(n - 1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new context by appending the given string to this context.
|
||||
*
|
||||
* @param count Number of spaces to add to the margin, or {@code null} to use
|
||||
* the length of the string.
|
||||
* @param text String to append.
|
||||
*
|
||||
* @return a new context with {@code text} appended.
|
||||
*/
|
||||
public Context appendText(Integer count, String text) {
|
||||
int cnt = (count == null) ? text.length() : count;
|
||||
final String spaces = new String(new char[cnt]).replace('\0', ' ');
|
||||
return new Context(this.acu + text, this.margin + spaces, this.lastmargin + spaces);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new context by appending a branch to this context.
|
||||
*
|
||||
* @param n Number of spaces to add to the margin, or {@code null} to use
|
||||
* the length of the string.
|
||||
* @param label Name of the branch.
|
||||
*
|
||||
* @return a new context with the branch appended.
|
||||
*/
|
||||
public Context appendBranch(Integer count, String label) {
|
||||
final Context ctxt = this.appendText(count, label);
|
||||
|
||||
if (count == null) {
|
||||
return new Context(ctxt.acu + "_", ctxt.margin + "|", ctxt.margin + " ");
|
||||
}
|
||||
else {
|
||||
return new Context(ctxt.acu, ctxt.margin + "|", ctxt.margin + " ")
|
||||
.appendNewlines(1);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Input : ready to write the current node at the current context position.
|
||||
* Output : the last character of acu is the last character of the current node.
|
||||
*/
|
||||
protected static <E extends Comparable<E>> Context toStringLoop(BinaryHeap<E> heap,
|
||||
Context ctxt, int node, int max_depth) {
|
||||
|
||||
if (max_depth < 0) {
|
||||
return ctxt.appendText(null, "...");
|
||||
}
|
||||
else {
|
||||
E nodeval = heap.array.get(node);
|
||||
String nodevals = nodeval.toString();
|
||||
|
||||
ArrayList<Integer> childs = new ArrayList<Integer>();
|
||||
// Add childs
|
||||
int index_left = heap.indexLeft(node);
|
||||
int index_right = index_left + 1;
|
||||
|
||||
if (index_left < heap.size()) {
|
||||
childs.add(index_left);
|
||||
}
|
||||
if (index_right < heap.size()) {
|
||||
childs.add(index_right);
|
||||
}
|
||||
|
||||
Context ctxt2 = childs.isEmpty() ? ctxt.appendText(null, nodevals)
|
||||
: ctxt.appendBranch(1, nodevals);
|
||||
|
||||
for (int ch = 0; ch < childs.size(); ch++) {
|
||||
boolean is_last = (ch == childs.size() - 1);
|
||||
int child = childs.get(ch);
|
||||
|
||||
if (is_last) {
|
||||
Context ctxt3 = new Context(ctxt2.acu, ctxt2.lastmargin, ctxt2.lastmargin);
|
||||
ctxt2 = new Context(toStringLoop(heap, ctxt3.appendText(null, "___"), child,
|
||||
max_depth - 1).acu, ctxt2.margin, ctxt2.lastmargin);
|
||||
}
|
||||
else {
|
||||
ctxt2 = new Context(toStringLoop(heap, ctxt2.appendText(null, "___"), child,
|
||||
max_depth - 1).acu, ctxt2.margin, ctxt2.lastmargin).appendNewlines(2);
|
||||
}
|
||||
}
|
||||
|
||||
return ctxt2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a multi-lines string representing a tree view of the given binary
|
||||
* heap.
|
||||
*
|
||||
* @param heap The binary heap to display.
|
||||
* @param maxDepth Maximum depth of the tree to display.
|
||||
*
|
||||
* @return a string containing a tree view of the given binary heap.
|
||||
*/
|
||||
public static <E extends Comparable<E>> String toStringTree(BinaryHeap<E> heap, int maxDepth) {
|
||||
final Context init_context = new Context(" ", " ", " ");
|
||||
final Context result = toStringLoop(heap, init_context, 0, maxDepth);
|
||||
return result.acu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a multi-lines string representing a sorted view of the given binary
|
||||
* heap.
|
||||
*
|
||||
* @param heap The binary heap to display.
|
||||
* @param max_elements Maximum number of elements to display. or {@code -1} to
|
||||
* display all the elements.
|
||||
*
|
||||
* @return a string containing a sorted view the given binary heap.
|
||||
*/
|
||||
public static <E extends Comparable<E>> String toStringSorted(BinaryHeap<E> heap,
|
||||
int max_elements) {
|
||||
String result = "";
|
||||
final BinaryHeap<E> copy = new BinaryHeap<E>(heap);
|
||||
|
||||
final String truncate;
|
||||
if (max_elements < 0 || max_elements >= heap.size()) {
|
||||
truncate = "";
|
||||
}
|
||||
else {
|
||||
truncate = ", only " + max_elements + " elements are shown";
|
||||
}
|
||||
|
||||
result += "======== Sorted HEAP (size = " + heap.size() + truncate + ") ========\n\n";
|
||||
|
||||
while (!copy.isEmpty() && max_elements-- != 0) {
|
||||
result += copy.deleteMin() + "\n";
|
||||
}
|
||||
|
||||
result += "\n-------- End of heap --------";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
final BinaryHeap<Integer> heap = new BinaryHeap<Integer>();
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
heap.insert(i);
|
||||
}
|
||||
|
||||
System.out.println(heap.toStringSorted(-1));
|
||||
System.out.println(heap.toStringTree(6));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package org.insa.graphs.algorithm.utils;
|
||||
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class BinarySearchTree<E extends Comparable<E>> implements PriorityQueue<E> {
|
||||
|
||||
// Underlying implementation
|
||||
private final SortedSet<E> sortedSet;
|
||||
|
||||
/**
|
||||
* Create a new empty binary search tree.
|
||||
*/
|
||||
public BinarySearchTree() {
|
||||
this.sortedSet = new TreeSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a copy of the given binary search tree.
|
||||
*
|
||||
* @param bst Binary search tree to copy.
|
||||
*/
|
||||
public BinarySearchTree(BinarySearchTree<E> bst) {
|
||||
this.sortedSet = new TreeSet<>(bst.sortedSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return sortedSet.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return sortedSet.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insert(E x) {
|
||||
sortedSet.add(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(E x) throws ElementNotFoundException {
|
||||
if (!sortedSet.remove(x)) {
|
||||
throw new ElementNotFoundException(x);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public E findMin() throws EmptyPriorityQueueException {
|
||||
if (isEmpty()) {
|
||||
throw new EmptyPriorityQueueException();
|
||||
}
|
||||
return sortedSet.first();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E deleteMin() throws EmptyPriorityQueueException {
|
||||
E min = findMin();
|
||||
remove(min);
|
||||
return min;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package org.insa.graphs.algorithm.utils;
|
||||
|
||||
public class ElementNotFoundException extends RuntimeException {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
// Element not found
|
||||
private final Object element;
|
||||
|
||||
/**
|
||||
* @param element Element that was not found.
|
||||
*/
|
||||
public ElementNotFoundException(Object element) {
|
||||
this.element = element;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The element that was not found.
|
||||
*/
|
||||
public Object getElement() {
|
||||
return this.element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "element not found: " + element;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package org.insa.graphs.algorithm.utils;
|
||||
|
||||
public class EmptyPriorityQueueException extends RuntimeException {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public EmptyPriorityQueueException() {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package org.insa.graphs.algorithm.utils;
|
||||
|
||||
/**
|
||||
* Interface representing a basic priority queue.
|
||||
*
|
||||
* Implementation should enforce the required complexity of each method.
|
||||
*
|
||||
*/
|
||||
public interface PriorityQueue<E extends Comparable<E>> {
|
||||
|
||||
/**
|
||||
* Check if the priority queue is empty.
|
||||
*
|
||||
* <p>
|
||||
* <b>Complexity:</b> <i>O(1)</i>
|
||||
* </p>
|
||||
*
|
||||
* @return true if the queue is empty, false otherwise.
|
||||
*/
|
||||
public boolean isEmpty();
|
||||
|
||||
/**
|
||||
* Get the number of elements in this queue.
|
||||
*
|
||||
* <p>
|
||||
* <b>Complexity:</b> <i>O(1)</i>
|
||||
* </p>
|
||||
*
|
||||
* @return Current size (number of elements) of this queue.
|
||||
*/
|
||||
public int size();
|
||||
|
||||
/**
|
||||
* Insert the given element into the queue.
|
||||
*
|
||||
* <p>
|
||||
* <b>Complexity:</b> <i>O(log n)</i>
|
||||
* </p>
|
||||
*
|
||||
* @param x Item to insert.
|
||||
*/
|
||||
public void insert(E x);
|
||||
|
||||
/**
|
||||
* Remove the given element from the priority queue.
|
||||
*
|
||||
* <p>
|
||||
* <b>Complexity:</b> <i>O(log n)</i>
|
||||
* </p>
|
||||
*
|
||||
* @param x Item to remove.
|
||||
*/
|
||||
public void remove(E x) throws ElementNotFoundException;
|
||||
|
||||
/**
|
||||
* Retrieve (but not remove) the smallest item in the queue.
|
||||
*
|
||||
* <p>
|
||||
* <b>Complexity:</b> <i>O(1)</i>
|
||||
* </p>
|
||||
*
|
||||
* @return The smallest item in the queue.
|
||||
*
|
||||
* @throws EmptyPriorityQueueException if this queue is empty.
|
||||
*/
|
||||
public E findMin() throws EmptyPriorityQueueException;
|
||||
|
||||
/**
|
||||
* Remove and return the smallest item from the priority queue.
|
||||
*
|
||||
* <p>
|
||||
* <b>Complexity:</b> <i>O(log n)</i>
|
||||
* </p>
|
||||
*
|
||||
* @return The smallest item in the queue.
|
||||
*
|
||||
* @throws EmptyPriorityQueueException if this queue is empty.
|
||||
*/
|
||||
public E deleteMin() throws EmptyPriorityQueueException;
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package org.insa.graphs.algorithm.weakconnectivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.insa.graphs.model.Node;
|
||||
|
||||
public interface WeaklyConnectedComponentObserver {
|
||||
|
||||
/**
|
||||
* Notify that the algorithm is entering a new component.
|
||||
*
|
||||
* @param curNode Starting node for the component.
|
||||
*/
|
||||
public void notifyStartComponent(Node curNode);
|
||||
|
||||
/**
|
||||
* Notify that a new node has been found for the current component.
|
||||
*
|
||||
* @param node New node found for the current component.
|
||||
*/
|
||||
public void notifyNewNodeInComponent(Node node);
|
||||
|
||||
/**
|
||||
* Notify that the algorithm has computed a new component.
|
||||
*
|
||||
* @param nodes List of nodes in the component.
|
||||
*/
|
||||
public void notifyEndComponent(ArrayList<Node> nodes);
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package org.insa.graphs.algorithm.weakconnectivity;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.insa.graphs.model.Node;
|
||||
|
||||
public class WeaklyConnectedComponentTextObserver implements WeaklyConnectedComponentObserver {
|
||||
|
||||
// Number of the current component.
|
||||
private int numComponent = 1;
|
||||
|
||||
// Output stream
|
||||
PrintStream stream;
|
||||
|
||||
public WeaklyConnectedComponentTextObserver(PrintStream stream) {
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyStartComponent(Node curNode) {
|
||||
stream.print("Entering component #" + numComponent + " from node #" + curNode.getId() + "... ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyNewNodeInComponent(Node node) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyEndComponent(ArrayList<Node> nodes) {
|
||||
stream.println(nodes.size() + " nodes found.");
|
||||
stream.flush();
|
||||
numComponent += 1;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
package org.insa.graphs.algorithm.weakconnectivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractAlgorithm;
|
||||
import org.insa.graphs.algorithm.AbstractSolution.Status;
|
||||
import org.insa.graphs.model.Arc;
|
||||
import org.insa.graphs.model.Graph;
|
||||
import org.insa.graphs.model.Node;
|
||||
|
||||
public class WeaklyConnectedComponentsAlgorithm
|
||||
extends AbstractAlgorithm<WeaklyConnectedComponentObserver> {
|
||||
|
||||
/**
|
||||
* @param data Input data for this algorithm.
|
||||
*/
|
||||
public WeaklyConnectedComponentsAlgorithm(WeaklyConnectedComponentsData data) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WeaklyConnectedComponentsSolution run() {
|
||||
return (WeaklyConnectedComponentsSolution) super.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WeaklyConnectedComponentsData getInputData() {
|
||||
return (WeaklyConnectedComponentsData) super.getInputData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all observers that the algorithm is entering a new component.
|
||||
*
|
||||
* @param curNode Starting node for the component.
|
||||
*/
|
||||
protected void notifyStartComponent(Node curNode) {
|
||||
for (WeaklyConnectedComponentObserver obs: getObservers()) {
|
||||
obs.notifyStartComponent(curNode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all observers that a new node has been found for the current
|
||||
* component.
|
||||
*
|
||||
* @param node New node found for the current component.
|
||||
*/
|
||||
protected void notifyNewNodeInComponent(Node node) {
|
||||
for (WeaklyConnectedComponentObserver obs: getObservers()) {
|
||||
obs.notifyNewNodeInComponent(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all observers that the algorithm has computed a new component.
|
||||
*
|
||||
* @param nodes List of nodes in the component.
|
||||
*/
|
||||
protected void notifyEndComponent(ArrayList<Node> nodes) {
|
||||
for (WeaklyConnectedComponentObserver obs: getObservers()) {
|
||||
obs.notifyEndComponent(nodes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return An adjacency list for the undirected graph equivalent to the stored
|
||||
* graph.
|
||||
*/
|
||||
protected ArrayList<HashSet<Integer>> createUndirectedGraph() {
|
||||
int nNodes = getInputData().getGraph().size();
|
||||
ArrayList<HashSet<Integer>> res = new ArrayList<HashSet<Integer>>(nNodes);
|
||||
for (int i = 0; i < nNodes; ++i) {
|
||||
res.add(new HashSet<Integer>());
|
||||
}
|
||||
|
||||
for (Node node: getInputData().getGraph().getNodes()) {
|
||||
for (Arc arc: node.getSuccessors()) {
|
||||
res.get(node.getId()).add(arc.getDestination().getId());
|
||||
if (arc.getRoadInformation().isOneWay()) {
|
||||
res.get(arc.getDestination().getId()).add(node.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a breadth first search algorithm on the given undirected graph
|
||||
* (adjacency list), starting at node cur, and marking nodes in marked.
|
||||
*
|
||||
* @param marked
|
||||
* @param cur
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected ArrayList<Node> bfs(ArrayList<HashSet<Integer>> ugraph, boolean[] marked, int cur) {
|
||||
Graph graph = getInputData().getGraph();
|
||||
ArrayList<Node> component = new ArrayList<Node>();
|
||||
|
||||
// Using a queue because we are doing a BFS
|
||||
Queue<Integer> queue = new LinkedList<Integer>();
|
||||
|
||||
// Notify observers about the current component.
|
||||
notifyStartComponent(graph.get(cur));
|
||||
|
||||
// Add original node and loop until the queue is empty.
|
||||
queue.add(cur);
|
||||
marked[cur] = true;
|
||||
while (!queue.isEmpty()) {
|
||||
Node node = graph.get(queue.remove());
|
||||
component.add(node);
|
||||
|
||||
// Notify observers
|
||||
notifyNewNodeInComponent(node);
|
||||
|
||||
for (Integer destId: ugraph.get(node.getId())) {
|
||||
Node dest = graph.get(destId);
|
||||
if (!marked[dest.getId()]) {
|
||||
queue.add(destId);
|
||||
marked[destId] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notifyEndComponent(component);
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WeaklyConnectedComponentsSolution doRun() {
|
||||
|
||||
Graph graph = getInputData().getGraph();
|
||||
ArrayList<HashSet<Integer>> ugraph = createUndirectedGraph();
|
||||
boolean[] marked = new boolean[graph.size()];
|
||||
Arrays.fill(marked, false);
|
||||
|
||||
ArrayList<ArrayList<Node>> components = new ArrayList<ArrayList<Node>>();
|
||||
|
||||
// perform algorithm
|
||||
int cur = 0;
|
||||
while (cur < marked.length) {
|
||||
// Apply BFS
|
||||
components.add(this.bfs(ugraph, marked, cur));
|
||||
|
||||
// Find next non-marked
|
||||
for (; cur < marked.length && marked[cur]; ++cur)
|
||||
;
|
||||
}
|
||||
|
||||
return new WeaklyConnectedComponentsSolution(getInputData(), Status.OPTIMAL, components);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package org.insa.graphs.algorithm.weakconnectivity;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractInputData;
|
||||
import org.insa.graphs.model.Graph;
|
||||
|
||||
public class WeaklyConnectedComponentsData extends AbstractInputData {
|
||||
|
||||
/**
|
||||
* @param graph Graph for which components should be retrieved.
|
||||
*/
|
||||
public WeaklyConnectedComponentsData(Graph graph) {
|
||||
super(graph, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Weakly-connected components from #0.";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package org.insa.graphs.algorithm.weakconnectivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractSolution;
|
||||
import org.insa.graphs.model.Node;
|
||||
|
||||
public class WeaklyConnectedComponentsSolution extends AbstractSolution {
|
||||
|
||||
// Components
|
||||
private ArrayList<ArrayList<Node>> components;
|
||||
|
||||
protected WeaklyConnectedComponentsSolution(WeaklyConnectedComponentsData data) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
protected WeaklyConnectedComponentsSolution(WeaklyConnectedComponentsData data, Status status,
|
||||
ArrayList<ArrayList<Node>> components) {
|
||||
super(data, status);
|
||||
this.components = components;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WeaklyConnectedComponentsData getInputData() {
|
||||
return (WeaklyConnectedComponentsData) super.getInputData();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Components of the solution, if any.
|
||||
*/
|
||||
public ArrayList<ArrayList<Node>> getComponents() {
|
||||
return components;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
int nIsolated = 0;
|
||||
int nGt10 = 0;
|
||||
for (ArrayList<Node> component: components) {
|
||||
if (component.size() == 1) {
|
||||
nIsolated += 1;
|
||||
}
|
||||
else if (component.size() > 10) {
|
||||
nGt10 += 1;
|
||||
}
|
||||
}
|
||||
return "Found " + components.size() + " components (" + nGt10 + " with more than 10 nodes, "
|
||||
+ nIsolated + " isolated nodes) in " + getSolvingTime().getSeconds() + " seconds.";
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package org.insa.graphs.algorithm.utils;
|
||||
|
||||
public class BinaryHeapTest extends PriorityQueueTest {
|
||||
|
||||
@Override
|
||||
public PriorityQueue<MutableInteger> createQueue() {
|
||||
return new BinaryHeap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PriorityQueue<MutableInteger> createQueue(PriorityQueue<MutableInteger> queue) {
|
||||
return new BinaryHeap<>((BinaryHeap<MutableInteger>) queue);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package org.insa.graphs.algorithm.utils;
|
||||
|
||||
public class BinarySearchTreeTest extends PriorityQueueTest {
|
||||
|
||||
@Override
|
||||
public PriorityQueue<MutableInteger> createQueue() {
|
||||
return new BinarySearchTree<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PriorityQueue<MutableInteger> createQueue(PriorityQueue<MutableInteger> queue) {
|
||||
return new BinarySearchTree<>((BinarySearchTree<MutableInteger>) queue);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,320 @@
|
|||
package org.insa.graphs.algorithm.utils;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.junit.Assume;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameter;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public abstract class PriorityQueueTest {
|
||||
|
||||
/**
|
||||
* Needs to be implemented by child class to actually provide priority queue
|
||||
* implementation.
|
||||
*
|
||||
* @return A new instance of a PriorityQueue implementation.
|
||||
*/
|
||||
public abstract PriorityQueue<MutableInteger> createQueue();
|
||||
|
||||
/**
|
||||
* Needs to be implemented by child class to actually provide priority queue
|
||||
* implementation.
|
||||
*
|
||||
* @param queue Queue to copy.
|
||||
*
|
||||
* @return Copy of the given queue.
|
||||
*/
|
||||
public abstract PriorityQueue<MutableInteger> createQueue(PriorityQueue<MutableInteger> queue);
|
||||
|
||||
protected static class MutableInteger implements Comparable<MutableInteger> {
|
||||
|
||||
// Actual value
|
||||
private int value;
|
||||
|
||||
public MutableInteger(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The integer value stored inside this MutableInteger.
|
||||
*/
|
||||
public int get() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the integer value stored inside this MutableInteger.
|
||||
*
|
||||
* @param value New value to set.
|
||||
*/
|
||||
public void set(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(MutableInteger other) {
|
||||
return Integer.compare(this.value, other.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Integer.toString(get());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
protected static class TestParameters<E extends Comparable<E>> {
|
||||
|
||||
// Data to insert
|
||||
public final E[] data;
|
||||
public final int[] deleteOrder;
|
||||
|
||||
public TestParameters(E[] data, int[] deleteOrder) {
|
||||
this.data = data;
|
||||
this.deleteOrder = deleteOrder;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Set of parameters.
|
||||
*
|
||||
*/
|
||||
@Parameters
|
||||
public static Collection<Object> data() {
|
||||
Collection<Object> objects = new ArrayList<>();
|
||||
|
||||
// Empty queue
|
||||
objects.add(new TestParameters<>(new MutableInteger[0], new int[0]));
|
||||
|
||||
// Queue with 50 elements from 0 to 49, inserted in order and deleted in order.
|
||||
objects.add(new TestParameters<>(
|
||||
IntStream.range(0, 50).mapToObj(MutableInteger::new).toArray(MutableInteger[]::new),
|
||||
IntStream.range(0, 50).toArray()));
|
||||
|
||||
// Queue with 20 elements from 0 to 19, inserted in order, deleted in the given
|
||||
// order.
|
||||
objects.add(new TestParameters<>(
|
||||
IntStream.range(0, 20).mapToObj(MutableInteger::new).toArray(MutableInteger[]::new),
|
||||
new int[] { 12, 17, 18, 19, 4, 5, 3, 2, 0, 9, 10, 16, 8, 14, 13, 15, 7, 6, 1,
|
||||
11 }));
|
||||
|
||||
// Queue with 7 elements.
|
||||
objects.add(
|
||||
new TestParameters<>(
|
||||
Arrays.stream(new int[] { 8, 1, 6, 3, 4, 5, 9 })
|
||||
.mapToObj(MutableInteger::new).toArray(MutableInteger[]::new),
|
||||
new int[] { 6, 5, 0, 1, 4, 2, 3 }));
|
||||
|
||||
// Queue with 7 elements.
|
||||
objects.add(
|
||||
new TestParameters<>(
|
||||
Arrays.stream(new int[] { 1, 7, 4, 8, 9, 6, 5 })
|
||||
.mapToObj(MutableInteger::new).toArray(MutableInteger[]::new),
|
||||
new int[] { 2, 0, 1, 3, 4, 5, 6 }));
|
||||
|
||||
// Queue with 13 elements.
|
||||
objects.add(new TestParameters<>(
|
||||
Arrays.stream(new int[] { 1, 7, 2, 8, 9, 3, 4, 10, 11, 12, 13, 5, 6 })
|
||||
.mapToObj(MutableInteger::new).toArray(MutableInteger[]::new),
|
||||
new int[] { 3, 4, 0, 2, 5, 6, 1, 7, 8, 9, 10, 11, 12 }));
|
||||
|
||||
return objects;
|
||||
}
|
||||
|
||||
@Parameter
|
||||
public TestParameters<MutableInteger> parameters;
|
||||
|
||||
// Actual queue.
|
||||
private PriorityQueue<MutableInteger> queue;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
// Create the range queue
|
||||
this.queue = createQueue();
|
||||
|
||||
for (MutableInteger v: parameters.data) {
|
||||
this.queue.insert(v);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsEmpty() {
|
||||
assertEquals(parameters.data.length == 0, this.queue.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSize() {
|
||||
assertEquals(parameters.data.length, this.queue.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsert() {
|
||||
PriorityQueue<MutableInteger> queue = createQueue();
|
||||
int size = 0;
|
||||
for (MutableInteger x: parameters.data) {
|
||||
queue.insert(x);
|
||||
assertEquals(++size, queue.size());
|
||||
}
|
||||
assertEquals(parameters.data.length, queue.size());
|
||||
MutableInteger[] range = Arrays.copyOf(parameters.data, parameters.data.length);
|
||||
Arrays.sort(range);
|
||||
|
||||
for (MutableInteger mi: range) {
|
||||
assertEquals(mi.get(), queue.deleteMin().value);
|
||||
assertEquals(--size, queue.size());
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = EmptyPriorityQueueException.class)
|
||||
public void testEmptyFindMin() {
|
||||
Assume.assumeTrue(queue.isEmpty());
|
||||
queue.findMin();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindMin() {
|
||||
Assume.assumeFalse(queue.isEmpty());
|
||||
assertEquals(Collections.min(Arrays.asList(parameters.data)).get(), queue.findMin().get());
|
||||
}
|
||||
|
||||
@Test(expected = EmptyPriorityQueueException.class)
|
||||
public void testEmptyDeleteMin() {
|
||||
Assume.assumeTrue(queue.isEmpty());
|
||||
queue.deleteMin();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteMin() {
|
||||
int size = parameters.data.length;
|
||||
assertEquals(size, queue.size());
|
||||
MutableInteger[] range = Arrays.copyOf(parameters.data, parameters.data.length);
|
||||
Arrays.sort(range);
|
||||
for (MutableInteger x: range) {
|
||||
assertEquals(x, queue.deleteMin());
|
||||
size -= 1;
|
||||
assertEquals(size, queue.size());
|
||||
}
|
||||
assertEquals(0, queue.size());
|
||||
assertTrue(queue.isEmpty());
|
||||
}
|
||||
|
||||
@Test(expected = ElementNotFoundException.class)
|
||||
public void testRemoveEmpty() {
|
||||
Assume.assumeTrue(queue.isEmpty());
|
||||
queue.remove(new MutableInteger(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveNotFound() {
|
||||
Assume.assumeFalse(queue.isEmpty());
|
||||
List<MutableInteger> data = Arrays.asList(parameters.data);
|
||||
MutableInteger min = new MutableInteger(Collections.min(data).get() - 1),
|
||||
max = new MutableInteger(Collections.max(data).get() + 1);
|
||||
try {
|
||||
queue.remove(min);
|
||||
fail("Expected exception " + ElementNotFoundException.class.getName());
|
||||
}
|
||||
catch (ElementNotFoundException e) {
|
||||
assertEquals(min, e.getElement());
|
||||
}
|
||||
try {
|
||||
queue.remove(max);
|
||||
fail("Expected exception " + ElementNotFoundException.class.getName());
|
||||
}
|
||||
catch (ElementNotFoundException e) {
|
||||
assertEquals(max, e.getElement());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteThenRemove() {
|
||||
Assume.assumeFalse(queue.isEmpty());
|
||||
while (!queue.isEmpty()) {
|
||||
MutableInteger min = queue.deleteMin();
|
||||
try {
|
||||
queue.remove(min);
|
||||
fail("Expected exception " + ElementNotFoundException.class.getName());
|
||||
}
|
||||
catch (ElementNotFoundException e) {
|
||||
assertEquals(min, e.getElement());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveTwice() {
|
||||
Assume.assumeFalse(queue.isEmpty());
|
||||
for (MutableInteger data: parameters.data) {
|
||||
PriorityQueue<MutableInteger> copyQueue = this.createQueue(this.queue);
|
||||
copyQueue.remove(data);
|
||||
try {
|
||||
copyQueue.remove(data);
|
||||
fail("Expected exception " + ElementNotFoundException.class.getName());
|
||||
}
|
||||
catch (ElementNotFoundException e) {
|
||||
assertEquals(data, e.getElement());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemove() {
|
||||
int size1 = queue.size();
|
||||
for (int i = 0; i < parameters.deleteOrder.length; ++i) {
|
||||
// Remove from structure
|
||||
queue.remove(parameters.data[parameters.deleteOrder[i]]);
|
||||
|
||||
// Copy the remaining elements
|
||||
PriorityQueue<MutableInteger> copyTree = createQueue(queue);
|
||||
|
||||
// Retrieve all remaining elements in both structures
|
||||
ArrayList<MutableInteger> remains_in = new ArrayList<>(),
|
||||
remains_cp = new ArrayList<>();
|
||||
for (int j = i + 1; j < parameters.deleteOrder.length; ++j) {
|
||||
remains_in.add(parameters.data[parameters.deleteOrder[j]]);
|
||||
remains_cp.add(copyTree.deleteMin());
|
||||
}
|
||||
|
||||
Collections.sort(remains_in);
|
||||
|
||||
// Check that the copy is now empty, and that both list contains all
|
||||
// elements.
|
||||
assertTrue(copyTree.isEmpty());
|
||||
assertEquals(remains_in, remains_cp);
|
||||
|
||||
// Check that the size of the original tree is correct.
|
||||
assertEquals(--size1, queue.size());
|
||||
}
|
||||
assertTrue(queue.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveThenAdd() {
|
||||
Assume.assumeFalse(queue.isEmpty());
|
||||
int min = Collections.min(Arrays.asList(parameters.data)).get();
|
||||
for (MutableInteger mi: parameters.data) {
|
||||
queue.remove(mi);
|
||||
assertEquals(parameters.data.length - 1, queue.size());
|
||||
mi.set(--min);
|
||||
queue.insert(mi);
|
||||
assertEquals(parameters.data.length, queue.size());
|
||||
assertEquals(min, queue.findMin().get());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
87
be-graphes-gui/pom.xml
Normal file
87
be-graphes-gui/pom.xml
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?xml version="1.0"?>
|
||||
<project
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.insa.graphs</groupId>
|
||||
<artifactId>be-graphes-all</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<mapsforge.version>0.13.0</mapsforge.version>
|
||||
</properties>
|
||||
|
||||
<artifactId>be-graphes-gui</artifactId>
|
||||
<name>be-graphes-gui</name>
|
||||
|
||||
<!-- Repository for Salamander (dependency of Mapsforge). -->
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.insa.graphs</groupId>
|
||||
<artifactId>be-graphes-model</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.insa.graphs</groupId>
|
||||
<artifactId>be-graphes-algos</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.mapsforge/mapsforge-core -->
|
||||
<dependency>
|
||||
<groupId>net.sf.kxml</groupId>
|
||||
<artifactId>kxml2</artifactId>
|
||||
<version>2.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.mapsforge/mapsforge-map-reader -->
|
||||
<dependency>
|
||||
<groupId>org.mapsforge</groupId>
|
||||
<artifactId>mapsforge-themes</artifactId>
|
||||
<version>${mapsforge.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.mapsforge/mapsforge-map -->
|
||||
<dependency>
|
||||
<groupId>org.mapsforge</groupId>
|
||||
<artifactId>mapsforge-map</artifactId>
|
||||
<version>${mapsforge.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.mapsforge/mapsforge-map-awt -->
|
||||
<dependency>
|
||||
<groupId>org.mapsforge</groupId>
|
||||
<artifactId>mapsforge-map-awt</artifactId>
|
||||
<version>${mapsforge.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.mapsforge/mapsforge-map-reader -->
|
||||
<dependency>
|
||||
<groupId>org.mapsforge</groupId>
|
||||
<artifactId>mapsforge-themes</artifactId>
|
||||
<version>${mapsforge.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.mapsforge/mapsforge-map-reader -->
|
||||
<dependency>
|
||||
<groupId>org.mapsforge</groupId>
|
||||
<artifactId>mapsforge-map-reader</artifactId>
|
||||
<version>${mapsforge.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
|
@ -0,0 +1,381 @@
|
|||
package org.insa.graphs.gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Font;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractAlgorithm;
|
||||
import org.insa.graphs.algorithm.AlgorithmFactory;
|
||||
import org.insa.graphs.algorithm.ArcInspector;
|
||||
import org.insa.graphs.algorithm.ArcInspectorFactory;
|
||||
import org.insa.graphs.gui.NodesInputPanel.InputChangedEvent;
|
||||
import org.insa.graphs.gui.drawing.Drawing;
|
||||
import org.insa.graphs.gui.utils.ColorUtils;
|
||||
import org.insa.graphs.model.Node;
|
||||
|
||||
public class AlgorithmPanel extends JPanel implements DrawingChangeListener {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public class StartActionEvent extends ActionEvent {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 4090710269781229078L;
|
||||
|
||||
protected static final String START_EVENT_COMMAND = "allInputFilled";
|
||||
|
||||
protected static final int START_EVENT_ID = 0x1;
|
||||
|
||||
private final List<Node> nodes;
|
||||
private final Class<? extends AbstractAlgorithm<?>> algoClass;
|
||||
|
||||
private final ArcInspector arcFilter;
|
||||
|
||||
private final boolean graphicVisualization;
|
||||
private final boolean textualVisualization;
|
||||
|
||||
public StartActionEvent(Class<? extends AbstractAlgorithm<?>> algoClass, List<Node> nodes,
|
||||
ArcInspector arcFilter, boolean graphicVisualization,
|
||||
boolean textualVisualization) {
|
||||
super(AlgorithmPanel.this, START_EVENT_ID, START_EVENT_COMMAND);
|
||||
this.nodes = nodes;
|
||||
this.algoClass = algoClass;
|
||||
this.graphicVisualization = graphicVisualization;
|
||||
this.textualVisualization = textualVisualization;
|
||||
this.arcFilter = arcFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Nodes associated with this event.
|
||||
*/
|
||||
public List<Node> getNodes() {
|
||||
return this.nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Arc filter associated with this event.
|
||||
*/
|
||||
public ArcInspector getArcFilter() {
|
||||
return this.arcFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Algorithm class associated with this event.
|
||||
*/
|
||||
public Class<? extends AbstractAlgorithm<?>> getAlgorithmClass() {
|
||||
return this.algoClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if graphic visualization is enabled.
|
||||
*/
|
||||
public boolean isGraphicVisualizationEnabled() {
|
||||
return this.graphicVisualization;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if textual visualization is enabled.
|
||||
*/
|
||||
public boolean isTextualVisualizationEnabled() {
|
||||
return this.textualVisualization;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Input panels for node.
|
||||
protected NodesInputPanel nodesInputPanel;
|
||||
|
||||
// Solution
|
||||
protected SolutionPanel solutionPanel;
|
||||
|
||||
// Component that can be enabled/disabled.
|
||||
private ArrayList<JComponent> components = new ArrayList<>();
|
||||
|
||||
// Graphic / Text checkbox observer
|
||||
private final JCheckBox graphicObserverCheckbox, textualObserverCheckbox;
|
||||
|
||||
private JButton startAlgoButton;
|
||||
|
||||
// Start listeners
|
||||
List<ActionListener> startActionListeners = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Create a new AlgorithmPanel with the given parameters.
|
||||
*
|
||||
* @param parent Parent component for this panel. Only use for centering
|
||||
* dialogs.
|
||||
* @param baseAlgorithm Base algorithm for this algorithm panel.
|
||||
* @param title Title of the panel.
|
||||
* @param nodeNames Names of the input nodes.
|
||||
* @param enableArcFilterSelection <code>true</code> to enable
|
||||
* {@link ArcInspector} selection.
|
||||
*
|
||||
* @see ArcInspectorFactory
|
||||
*/
|
||||
public AlgorithmPanel(Component parent, Class<? extends AbstractAlgorithm<?>> baseAlgorithm,
|
||||
String title, String[] nodeNames, boolean enableArcFilterSelection) {
|
||||
super();
|
||||
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
|
||||
|
||||
setBorder(new EmptyBorder(15, 15, 15, 15));
|
||||
|
||||
// Set title.
|
||||
add(createTitleLabel(title));
|
||||
add(Box.createVerticalStrut(8));
|
||||
|
||||
// Add algorithm selection
|
||||
JComboBox<String> algoSelect = createAlgoritmSelectComboBox(baseAlgorithm);
|
||||
if (algoSelect.getItemCount() > 1) {
|
||||
add(algoSelect);
|
||||
components.add(algoSelect);
|
||||
}
|
||||
|
||||
// Add inputs for node.
|
||||
this.nodesInputPanel = createNodesInputPanel(nodeNames);
|
||||
add(this.nodesInputPanel);
|
||||
components.add(this.nodesInputPanel);
|
||||
|
||||
JComboBox<ArcInspector> arcFilterSelect = new JComboBox<>(
|
||||
ArcInspectorFactory.getAllFilters().toArray(new ArcInspector[0]));
|
||||
arcFilterSelect.setBackground(Color.WHITE);
|
||||
|
||||
// Add mode selection
|
||||
JPanel modeAndObserverPanel = new JPanel();
|
||||
modeAndObserverPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
modeAndObserverPanel.setLayout(new GridBagLayout());
|
||||
|
||||
graphicObserverCheckbox = new JCheckBox("Graphic");
|
||||
graphicObserverCheckbox.setSelected(true);
|
||||
textualObserverCheckbox = new JCheckBox("Textual");
|
||||
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
|
||||
c.gridy = 2;
|
||||
c.gridx = 0;
|
||||
c.weightx = 0;
|
||||
modeAndObserverPanel.add(new JLabel("Visualization: "), c);
|
||||
c.gridx = 1;
|
||||
c.weightx = 1;
|
||||
modeAndObserverPanel.add(graphicObserverCheckbox, c);
|
||||
c.gridx = 2;
|
||||
c.weightx = 1;
|
||||
modeAndObserverPanel.add(textualObserverCheckbox, c);
|
||||
|
||||
if (enableArcFilterSelection) {
|
||||
c.gridy = 1;
|
||||
c.gridx = 0;
|
||||
c.weightx = 0;
|
||||
modeAndObserverPanel.add(new JLabel("Mode: "), c);
|
||||
c.gridx = 1;
|
||||
c.gridwidth = 2;
|
||||
c.weightx = 1;
|
||||
modeAndObserverPanel.add(arcFilterSelect, c);
|
||||
}
|
||||
|
||||
components.add(arcFilterSelect);
|
||||
components.add(textualObserverCheckbox);
|
||||
|
||||
add(modeAndObserverPanel);
|
||||
|
||||
solutionPanel = new SolutionPanel(parent);
|
||||
solutionPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
solutionPanel.setVisible(false);
|
||||
add(Box.createVerticalStrut(10));
|
||||
add(solutionPanel);
|
||||
|
||||
// Bottom panel
|
||||
JPanel bottomPanel = new JPanel();
|
||||
bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.LINE_AXIS));
|
||||
|
||||
startAlgoButton = new JButton("Start");
|
||||
startAlgoButton.setEnabled(false);
|
||||
startAlgoButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
for (ActionListener lis: startActionListeners) {
|
||||
lis.actionPerformed(new StartActionEvent(
|
||||
AlgorithmFactory.getAlgorithmClass(baseAlgorithm,
|
||||
(String) algoSelect.getSelectedItem()),
|
||||
nodesInputPanel.getNodeForInputs(),
|
||||
(ArcInspector) arcFilterSelect.getSelectedItem(),
|
||||
graphicObserverCheckbox.isSelected(),
|
||||
textualObserverCheckbox.isSelected()));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
JButton hideButton = new JButton("Hide");
|
||||
hideButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
nodesInputPanel.setEnabled(false);
|
||||
setVisible(false);
|
||||
}
|
||||
});
|
||||
|
||||
bottomPanel.add(startAlgoButton);
|
||||
bottomPanel.add(Box.createHorizontalGlue());
|
||||
bottomPanel.add(hideButton);
|
||||
|
||||
components.add(hideButton);
|
||||
|
||||
bottomPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
|
||||
add(Box.createVerticalStrut(8));
|
||||
add(bottomPanel);
|
||||
|
||||
nodesInputPanel.addInputChangedListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
InputChangedEvent evt = (InputChangedEvent) e;
|
||||
startAlgoButton.setEnabled(allNotNull(evt.getNodes()));
|
||||
}
|
||||
});
|
||||
|
||||
addComponentListener(new ComponentAdapter() {
|
||||
|
||||
@Override
|
||||
public void componentShown(ComponentEvent e) {
|
||||
setEnabled(true);
|
||||
nodesInputPanel.setVisible(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentHidden(ComponentEvent e) {
|
||||
setEnabled(false);
|
||||
nodesInputPanel.setVisible(false);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the title JLabel for this panel.
|
||||
*
|
||||
* @param title Title for the label.
|
||||
*
|
||||
* @return A new JLabel containing the given title with proper font.
|
||||
*/
|
||||
protected JLabel createTitleLabel(String title) {
|
||||
JLabel titleLabel = new JLabel(title);
|
||||
titleLabel.setBackground(Color.RED);
|
||||
titleLabel.setHorizontalAlignment(JLabel.LEFT);
|
||||
titleLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
Font font = titleLabel.getFont();
|
||||
font = font.deriveFont(Font.BOLD, 18);
|
||||
titleLabel.setFont(font);
|
||||
return titleLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the combo box for the algorithm selection.
|
||||
*
|
||||
* @param baseAlgorithm Base algorithm for which the select box should be
|
||||
* created.
|
||||
*
|
||||
* @return A new JComboBox containing algorithms for the given base algorithm.
|
||||
*
|
||||
* @see AlgorithmFactory
|
||||
*/
|
||||
protected JComboBox<String> createAlgoritmSelectComboBox(
|
||||
Class<? extends AbstractAlgorithm<?>> baseAlgorithm) {
|
||||
JComboBox<String> algoSelect = new JComboBox<>(
|
||||
AlgorithmFactory.getAlgorithmNames(baseAlgorithm).toArray(new String[0]));
|
||||
algoSelect.setBackground(Color.WHITE);
|
||||
algoSelect.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
return algoSelect;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a node input panel with the given node input names.
|
||||
*
|
||||
* @param nodeNames Field names for the inputs to create.
|
||||
*
|
||||
* @return A new NodesInputPanel containing inputs for the given names.
|
||||
*/
|
||||
protected NodesInputPanel createNodesInputPanel(String[] nodeNames) {
|
||||
NodesInputPanel panel = new NodesInputPanel();
|
||||
panel.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
for (int i = 0; i < nodeNames.length; ++i) {
|
||||
panel.addTextField(nodeNames[i] + ": ", ColorUtils.getColor(i));
|
||||
}
|
||||
panel.setEnabled(false);
|
||||
return panel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given list of nodes does not contain any <code>null</code>
|
||||
* value.
|
||||
*
|
||||
* @param nodes List of {@link Node} to check.
|
||||
*
|
||||
* @return <code>true</code> if the list does not contain any <code>null</code>
|
||||
* value, <code>false</code> otherwise.
|
||||
*/
|
||||
protected boolean allNotNull(List<Node> nodes) {
|
||||
boolean allNotNull = true;
|
||||
for (Node node: nodes) {
|
||||
allNotNull = allNotNull && node != null;
|
||||
}
|
||||
return allNotNull;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
super.setEnabled(enabled);
|
||||
nodesInputPanel.setEnabled(enabled);
|
||||
solutionPanel.setEnabled(enabled);
|
||||
for (JComponent component: components) {
|
||||
component.setEnabled(enabled);
|
||||
}
|
||||
graphicObserverCheckbox.setEnabled(enabled);
|
||||
enabled = enabled && allNotNull(this.nodesInputPanel.getNodeForInputs());
|
||||
startAlgoButton.setEnabled(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new start action listener to this class.
|
||||
*
|
||||
* @param listener Listener to add.
|
||||
*/
|
||||
public void addStartActionListener(ActionListener listener) {
|
||||
this.startActionListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawingLoaded(Drawing oldDrawing, Drawing newDrawing) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRedrawRequest() {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package org.insa.graphs.gui;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.swing.JOptionPane;
|
||||
|
||||
public class BlockingActionFactory {
|
||||
|
||||
// List of running actions.
|
||||
private ArrayList<RunningAction> actions = new ArrayList<>();
|
||||
|
||||
// Parent component.
|
||||
private Component parentComponent;
|
||||
|
||||
public BlockingActionFactory(Component parentComponent) {
|
||||
this.parentComponent = parentComponent;
|
||||
}
|
||||
|
||||
public void addAction(RunningAction action) {
|
||||
actions.add(action);
|
||||
}
|
||||
|
||||
public ActionListener createBlockingAction(ActionListener listener) {
|
||||
return new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
boolean accepted = true;
|
||||
|
||||
// Check if actions...
|
||||
for (int i = 0; i < actions.size() && accepted; ++i) {
|
||||
RunningAction action = actions.get(i);
|
||||
// If action is running, ask user...
|
||||
if (action.isRunning()) {
|
||||
if (JOptionPane.showConfirmDialog(parentComponent, "Action {"
|
||||
+ action.getInformation()
|
||||
+ "} is running, do you want to stop it?") == JOptionPane.OK_OPTION) {
|
||||
action.interrupt();
|
||||
}
|
||||
else {
|
||||
accepted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If action is accepted, run it...
|
||||
if (accepted) {
|
||||
listener.actionPerformed(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package org.insa.graphs.gui;
|
||||
|
||||
import org.insa.graphs.gui.drawing.Drawing;
|
||||
|
||||
public interface DrawingChangeListener {
|
||||
|
||||
/**
|
||||
* Event fired when a new drawing is loaded.
|
||||
*
|
||||
* @param oldDrawing Old drawing, may be null if no drawing exits prior to this
|
||||
* one.
|
||||
* @param newDrawing New drawing.
|
||||
*/
|
||||
public void onDrawingLoaded(Drawing oldDrawing, Drawing newDrawing);
|
||||
|
||||
/**
|
||||
* Event fired when a redraw request is emitted - This is typically emitted
|
||||
* after a onDrawingLoaded event, but not always, and request that elements are
|
||||
* drawn again on the new drawing.
|
||||
*
|
||||
*/
|
||||
public void onRedrawRequest();
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package org.insa.graphs.gui;
|
||||
|
||||
import org.insa.graphs.model.Graph;
|
||||
|
||||
public interface GraphChangeListener {
|
||||
|
||||
/**
|
||||
* Event fire when a new graph has been loaded.
|
||||
*
|
||||
* @param graph The new graph.
|
||||
*/
|
||||
public void newGraphLoaded(Graph graph);
|
||||
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
package org.insa.graphs.gui;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JProgressBar;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
|
||||
import org.insa.graphs.model.Arc;
|
||||
import org.insa.graphs.model.Node;
|
||||
import org.insa.graphs.model.RoadInformation;
|
||||
import org.insa.graphs.model.io.GraphReaderObserver;
|
||||
|
||||
/**
|
||||
* One-time use GraphReaderObserver that display progress in three different
|
||||
* JProgressBar.
|
||||
*
|
||||
* @author Mikael
|
||||
*
|
||||
*/
|
||||
public class GraphReaderProgressBar extends JDialog implements GraphReaderObserver {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = -1;
|
||||
|
||||
// Index...
|
||||
private static final int NODE = 0, DESC = 1, ARC = 2;
|
||||
|
||||
// Progress bar
|
||||
private final JProgressBar[] progressBars = new JProgressBar[3];
|
||||
|
||||
// Current element read, and modulo.
|
||||
private int[] counters = new int[]{ 0, 0, 0 };
|
||||
private int[] modulos = new int[3];
|
||||
|
||||
public GraphReaderProgressBar(JFrame owner) {
|
||||
super(owner);
|
||||
this.setVisible(false);
|
||||
setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
|
||||
|
||||
final String[] infos = { "nodes", "road informations", "arcs" };
|
||||
|
||||
JPanel pane = new JPanel();
|
||||
pane.setLayout(new BoxLayout(pane, BoxLayout.PAGE_AXIS));
|
||||
pane.setBorder(new EmptyBorder(15, 15, 15, 15));
|
||||
pane.add(Box.createVerticalGlue());
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
JLabel label = new JLabel("Reading " + infos[i] + "... ");
|
||||
label.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
progressBars[i] = new JProgressBar();
|
||||
progressBars[i].setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||
pane.add(label);
|
||||
pane.add(progressBars[i]);
|
||||
}
|
||||
|
||||
pane.add(Box.createVerticalGlue());
|
||||
setContentPane(pane);
|
||||
pack();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyStartReading(String mapId) {
|
||||
setTitle("Reading graph " + mapId + "... ");
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyEndReading() {
|
||||
setVisible(false);
|
||||
dispose();
|
||||
}
|
||||
|
||||
protected void initProgressBar(int index, int max) {
|
||||
progressBars[index].setMaximum(max);
|
||||
modulos[index] = Math.max(max / 100, 1);
|
||||
}
|
||||
|
||||
protected void incCounter(int index) {
|
||||
counters[index] += 1;
|
||||
if (counters[index] % modulos[index] == 0) {
|
||||
progressBars[index].setValue(counters[index]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyStartReadingNodes(int nNodes) {
|
||||
initProgressBar(NODE, nNodes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyNewNodeRead(Node node) {
|
||||
incCounter(NODE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyStartReadingDescriptors(int nDesc) {
|
||||
initProgressBar(DESC, nDesc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyNewDescriptorRead(RoadInformation desc) {
|
||||
incCounter(DESC);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyStartReadingArcs(int nArcs) {
|
||||
initProgressBar(ARC, nArcs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyNewArcRead(Arc arc) {
|
||||
incCounter(ARC);
|
||||
}
|
||||
|
||||
}
|
862
be-graphes-gui/src/main/java/org/insa/graphs/gui/MainWindow.java
Normal file
862
be-graphes-gui/src/main/java/org/insa/graphs/gui/MainWindow.java
Normal file
|
@ -0,0 +1,862 @@
|
|||
package org.insa.graphs.gui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuBar;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JSplitPane;
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.KeyStroke;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.Timer;
|
||||
import javax.swing.UIManager;
|
||||
import javax.swing.border.CompoundBorder;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
|
||||
import org.insa.graphs.algorithm.AbstractSolution;
|
||||
import org.insa.graphs.algorithm.AlgorithmFactory;
|
||||
import org.insa.graphs.algorithm.carpooling.CarPoolingAlgorithm;
|
||||
import org.insa.graphs.algorithm.packageswitch.PackageSwitchAlgorithm;
|
||||
import org.insa.graphs.algorithm.shortestpath.ShortestPathAlgorithm;
|
||||
import org.insa.graphs.algorithm.shortestpath.ShortestPathData;
|
||||
import org.insa.graphs.algorithm.shortestpath.ShortestPathSolution;
|
||||
import org.insa.graphs.algorithm.shortestpath.ShortestPathTextObserver;
|
||||
import org.insa.graphs.algorithm.weakconnectivity.WeaklyConnectedComponentTextObserver;
|
||||
import org.insa.graphs.algorithm.weakconnectivity.WeaklyConnectedComponentsAlgorithm;
|
||||
import org.insa.graphs.algorithm.weakconnectivity.WeaklyConnectedComponentsData;
|
||||
import org.insa.graphs.gui.AlgorithmPanel.StartActionEvent;
|
||||
import org.insa.graphs.gui.drawing.BasicGraphPalette;
|
||||
import org.insa.graphs.gui.drawing.BlackAndWhiteGraphPalette;
|
||||
import org.insa.graphs.gui.drawing.Drawing;
|
||||
import org.insa.graphs.gui.drawing.GraphPalette;
|
||||
import org.insa.graphs.gui.drawing.components.BasicDrawing;
|
||||
import org.insa.graphs.gui.drawing.components.MapViewDrawing;
|
||||
import org.insa.graphs.gui.observers.ShortestPathGraphicObserver;
|
||||
import org.insa.graphs.gui.observers.WeaklyConnectedComponentGraphicObserver;
|
||||
import org.insa.graphs.gui.utils.FileUtils;
|
||||
import org.insa.graphs.gui.utils.FileUtils.FolderType;
|
||||
import org.insa.graphs.model.Graph;
|
||||
import org.insa.graphs.model.Path;
|
||||
import org.insa.graphs.model.io.BinaryGraphReader;
|
||||
import org.insa.graphs.model.io.BinaryPathReader;
|
||||
import org.insa.graphs.model.io.GraphReader;
|
||||
import org.insa.graphs.model.io.MapMismatchException;
|
||||
|
||||
public class MainWindow extends JFrame {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final String WINDOW_TITLE = "BE Graphes INSA";
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final int THREAD_TIMER_DELAY = 1000; // in milliseconds
|
||||
|
||||
// Current graph.
|
||||
protected Graph graph;
|
||||
|
||||
// Path to the last opened graph file.
|
||||
private String graphFilePath;
|
||||
|
||||
// Drawing and click adapter.
|
||||
protected Drawing drawing;
|
||||
private final MapViewDrawing mapViewDrawing;
|
||||
private final BasicDrawing basicDrawing;
|
||||
|
||||
private final GraphPalette basicPalette, blackAndWhitePalette;
|
||||
private GraphPalette currentPalette;
|
||||
|
||||
// Main panel.
|
||||
private final JSplitPane mainPanel;
|
||||
|
||||
// Algorithm panels
|
||||
private final List<AlgorithmPanel> algoPanels = new ArrayList<>();
|
||||
private final AlgorithmPanel wccPanel, spPanel, cpPanel, psPanel;
|
||||
|
||||
// Path panel
|
||||
private final PathsPanel pathPanel;
|
||||
|
||||
// List of items that cannot be used without a graph
|
||||
private final ArrayList<JMenuItem> graphLockItems = new ArrayList<JMenuItem>();
|
||||
|
||||
// Label containing the map ID of the current graph.
|
||||
private JLabel graphInfoPanel;
|
||||
|
||||
// Thread information
|
||||
private Timer threadTimer;
|
||||
private JPanel threadPanel;
|
||||
|
||||
// Log stream and print stream
|
||||
private StreamCapturer logStream;
|
||||
|
||||
private PrintStream printStream;
|
||||
|
||||
// Current running thread
|
||||
private ThreadWrapper currentThread;
|
||||
|
||||
// Factory
|
||||
private BlockingActionFactory baf;
|
||||
|
||||
// Observers
|
||||
private List<DrawingChangeListener> drawingChangeListeners = new ArrayList<>();
|
||||
private List<GraphChangeListener> graphChangeListeneres = new ArrayList<>();
|
||||
|
||||
public MainWindow() {
|
||||
super(WINDOW_TITLE);
|
||||
|
||||
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
|
||||
setLayout(new BorderLayout());
|
||||
|
||||
setMinimumSize(new Dimension(800, 600));
|
||||
|
||||
// Create drawing and action listeners...
|
||||
this.basicDrawing = new BasicDrawing();
|
||||
this.mapViewDrawing = new MapViewDrawing();
|
||||
this.drawing = basicDrawing;
|
||||
|
||||
// Createa palettes
|
||||
this.basicPalette = new BasicGraphPalette();
|
||||
this.blackAndWhitePalette = new BlackAndWhiteGraphPalette();
|
||||
this.currentPalette = this.basicPalette;
|
||||
|
||||
wccPanel = new AlgorithmPanel(this, WeaklyConnectedComponentsAlgorithm.class,
|
||||
"Weakly-Connected Components", new String[] {}, false);
|
||||
wccPanel.addStartActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
StartActionEvent evt = (StartActionEvent) e;
|
||||
WeaklyConnectedComponentsData data = new WeaklyConnectedComponentsData(graph);
|
||||
|
||||
WeaklyConnectedComponentsAlgorithm wccAlgorithm = null;
|
||||
try {
|
||||
wccAlgorithm = (WeaklyConnectedComponentsAlgorithm) AlgorithmFactory
|
||||
.createAlgorithm(evt.getAlgorithmClass(), data);
|
||||
}
|
||||
catch (Exception e1) {
|
||||
JOptionPane.showMessageDialog(MainWindow.this,
|
||||
"An error occurred while creating the specified algorithm.",
|
||||
"Internal error: Algorithm instantiation failure",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
e1.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
wccPanel.setEnabled(false);
|
||||
|
||||
if (evt.isGraphicVisualizationEnabled()) {
|
||||
wccAlgorithm.addObserver(new WeaklyConnectedComponentGraphicObserver(drawing));
|
||||
}
|
||||
if (evt.isTextualVisualizationEnabled()) {
|
||||
wccAlgorithm.addObserver(new WeaklyConnectedComponentTextObserver(printStream));
|
||||
}
|
||||
|
||||
// We love Java...
|
||||
final WeaklyConnectedComponentsAlgorithm copyAlgorithm = wccAlgorithm;
|
||||
launchThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AbstractSolution solution = copyAlgorithm.run();
|
||||
wccPanel.solutionPanel.addSolution(solution, false);
|
||||
wccPanel.solutionPanel.setVisible(true);
|
||||
wccPanel.setEnabled(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
spPanel = new AlgorithmPanel(this, ShortestPathAlgorithm.class, "Shortest-Path",
|
||||
new String[] { "Origin", "Destination" }, true);
|
||||
spPanel.addStartActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
StartActionEvent evt = (StartActionEvent) e;
|
||||
ShortestPathData data = new ShortestPathData(graph, evt.getNodes().get(0),
|
||||
evt.getNodes().get(1), evt.getArcFilter());
|
||||
|
||||
ShortestPathAlgorithm spAlgorithm = null;
|
||||
try {
|
||||
spAlgorithm = (ShortestPathAlgorithm) AlgorithmFactory
|
||||
.createAlgorithm(evt.getAlgorithmClass(), data);
|
||||
}
|
||||
catch (Exception e1) {
|
||||
JOptionPane.showMessageDialog(MainWindow.this,
|
||||
"An error occurred while creating the specified algorithm.",
|
||||
"Internal error: Algorithm instantiation failure",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
e1.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
spPanel.setEnabled(false);
|
||||
|
||||
if (evt.isGraphicVisualizationEnabled()) {
|
||||
spAlgorithm.addObserver(new ShortestPathGraphicObserver(drawing));
|
||||
}
|
||||
if (evt.isTextualVisualizationEnabled()) {
|
||||
spAlgorithm.addObserver(new ShortestPathTextObserver(printStream));
|
||||
}
|
||||
|
||||
final ShortestPathAlgorithm copyAlgorithm = spAlgorithm;
|
||||
launchThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Run the algorithm.
|
||||
ShortestPathSolution solution = copyAlgorithm.run();
|
||||
// Add the solution to the solution panel (but do not display
|
||||
// overlay).
|
||||
spPanel.solutionPanel.addSolution(solution, false);
|
||||
// If the solution is feasible, add the path to the path panel.
|
||||
if (solution.isFeasible()) {
|
||||
pathPanel.addPath(solution.getPath());
|
||||
}
|
||||
// Show the solution panel and enable the shortest-path panel.
|
||||
spPanel.solutionPanel.setVisible(true);
|
||||
spPanel.setEnabled(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
cpPanel = new AlgorithmPanel(this, CarPoolingAlgorithm.class, "Car-Pooling", new String[] {
|
||||
"Origin Car", "Origin Pedestrian", "Destination Car", "Destination Pedestrian" },
|
||||
true);
|
||||
|
||||
psPanel = new AlgorithmPanel(this, PackageSwitchAlgorithm.class, "Car-Pooling",
|
||||
new String[] { "Oribin A", "Origin B", "Destination A", "Destination B" }, true);
|
||||
|
||||
// add algorithm panels
|
||||
algoPanels.add(wccPanel);
|
||||
algoPanels.add(spPanel);
|
||||
algoPanels.add(cpPanel);
|
||||
algoPanels.add(psPanel);
|
||||
|
||||
this.pathPanel = new PathsPanel(this);
|
||||
|
||||
// Add click listeners to both drawing.
|
||||
|
||||
for (AlgorithmPanel panel: algoPanels) {
|
||||
this.basicDrawing.addDrawingClickListener(panel.nodesInputPanel);
|
||||
this.mapViewDrawing.addDrawingClickListener(panel.nodesInputPanel);
|
||||
this.graphChangeListeneres.add(panel.nodesInputPanel);
|
||||
this.graphChangeListeneres.add(panel.solutionPanel);
|
||||
this.drawingChangeListeners.add(panel.nodesInputPanel);
|
||||
this.drawingChangeListeners.add(panel.solutionPanel);
|
||||
this.drawingChangeListeners.add(panel);
|
||||
}
|
||||
|
||||
this.graphChangeListeneres.add(pathPanel);
|
||||
this.drawingChangeListeners.add(pathPanel);
|
||||
|
||||
// Create action factory.
|
||||
this.currentThread = new ThreadWrapper(this);
|
||||
this.baf = new BlockingActionFactory(this);
|
||||
this.baf.addAction(currentThread);
|
||||
|
||||
// Click adapter
|
||||
ActionListener openMapActionListener = new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
JFileChooser chooser = FileUtils.createFileChooser(FolderType.Map);
|
||||
if (chooser.showOpenDialog(MainWindow.this) == JFileChooser.APPROVE_OPTION) {
|
||||
graphFilePath = chooser.getSelectedFile().getAbsolutePath();
|
||||
|
||||
// Note: Don't use a try-resources block since loadGraph is asynchronous.
|
||||
final DataInputStream stream;
|
||||
try {
|
||||
stream = new DataInputStream(new BufferedInputStream(
|
||||
new FileInputStream(chooser.getSelectedFile())));
|
||||
}
|
||||
catch (IOException e1) {
|
||||
JOptionPane.showMessageDialog(MainWindow.this,
|
||||
"Cannot open the selected file.");
|
||||
return;
|
||||
}
|
||||
loadGraph(new BinaryGraphReader(stream));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setJMenuBar(createMenuBar(openMapActionListener));
|
||||
|
||||
// Initial panel to show "Open Map... "
|
||||
JPanel openPanel = new JPanel();
|
||||
openPanel.setLayout(new BoxLayout(openPanel, BoxLayout.PAGE_AXIS));
|
||||
JButton openButton = new JButton("Open Map... ");
|
||||
openButton.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
openButton.addActionListener(openMapActionListener);
|
||||
openButton.setFocusPainted(false);
|
||||
openPanel.add(Box.createVerticalGlue());
|
||||
openPanel.add(openButton);
|
||||
openPanel.add(Box.createVerticalGlue());
|
||||
|
||||
addWindowListener(new WindowAdapter() {
|
||||
public void windowClosing(WindowEvent e) {
|
||||
int confirmed = JOptionPane.showConfirmDialog(MainWindow.this,
|
||||
"Are you sure you want to close the application?", "Exit Confirmation",
|
||||
JOptionPane.YES_NO_OPTION);
|
||||
|
||||
if (confirmed == JOptionPane.YES_OPTION) {
|
||||
dispose();
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Create graph area
|
||||
mainPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
|
||||
|
||||
JTextArea infoPanel = new JTextArea();
|
||||
infoPanel.setMinimumSize(new Dimension(200, 50));
|
||||
infoPanel.setBackground(Color.WHITE);
|
||||
infoPanel.setLineWrap(true);
|
||||
infoPanel.setEditable(false);
|
||||
this.logStream = new StreamCapturer(infoPanel);
|
||||
this.printStream = new PrintStream(this.logStream);
|
||||
|
||||
JPanel rightComponent = new JPanel();
|
||||
rightComponent.setLayout(new GridBagLayout());
|
||||
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
c.gridx = 0;
|
||||
c.gridy = 0;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
rightComponent.add(pathPanel, c);
|
||||
|
||||
c.gridy = 1;
|
||||
for (AlgorithmPanel panel: algoPanels) {
|
||||
panel.setVisible(false);
|
||||
rightComponent.add(panel, c);
|
||||
}
|
||||
|
||||
c = new GridBagConstraints();
|
||||
c.gridx = 0;
|
||||
c.gridy = 2;
|
||||
c.weightx = 1;
|
||||
c.weighty = 1;
|
||||
c.fill = GridBagConstraints.BOTH;
|
||||
c.gridheight = GridBagConstraints.REMAINDER;
|
||||
rightComponent.add(new JScrollPane(infoPanel), c);
|
||||
|
||||
mainPanel.setResizeWeight(0.8);
|
||||
mainPanel.setDividerSize(5);
|
||||
|
||||
mainPanel.setBackground(Color.WHITE);
|
||||
mainPanel.setLeftComponent(openPanel);
|
||||
mainPanel.setRightComponent(rightComponent);
|
||||
this.add(mainPanel, BorderLayout.CENTER);
|
||||
|
||||
// Top Panel
|
||||
this.add(createStatusBar(), BorderLayout.SOUTH);
|
||||
|
||||
// Notify everythin
|
||||
notifyDrawingLoaded(null, drawing);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param runnable
|
||||
* @param canInterrupt
|
||||
*/
|
||||
private void launchThread(Runnable runnable, boolean canInterrupt) {
|
||||
if (canInterrupt) {
|
||||
currentThread.setThread(new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
threadTimer.restart();
|
||||
threadPanel.setVisible(true);
|
||||
runnable.run();
|
||||
clearCurrentThread();
|
||||
}
|
||||
}));
|
||||
}
|
||||
else {
|
||||
currentThread.setThread(new Thread(runnable));
|
||||
}
|
||||
currentThread.startThread();
|
||||
}
|
||||
|
||||
private void launchThread(Runnable runnable) {
|
||||
launchThread(runnable, true);
|
||||
}
|
||||
|
||||
protected void clearCurrentThread() {
|
||||
threadTimer.stop();
|
||||
threadPanel.setVisible(false);
|
||||
currentThread.setThread(null);
|
||||
if (spPanel.isVisible()) {
|
||||
spPanel.setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all listeners that a new graph has been loaded.
|
||||
*/
|
||||
private void notifyNewGraphLoaded() {
|
||||
for (GraphChangeListener listener: graphChangeListeneres) {
|
||||
listener.newGraphLoaded(graph);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all listeners that a new drawing has been set up.
|
||||
*
|
||||
* @param oldDrawing
|
||||
* @param newDrawing
|
||||
*/
|
||||
private void notifyDrawingLoaded(Drawing oldDrawing, Drawing newDrawing) {
|
||||
for (DrawingChangeListener listener: drawingChangeListeners) {
|
||||
listener.onDrawingLoaded(oldDrawing, newDrawing);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all listeners that a redraw request is emitted.
|
||||
*/
|
||||
private void notifyRedrawRequest() {
|
||||
for (DrawingChangeListener listener: drawingChangeListeners) {
|
||||
listener.onRedrawRequest();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the stored graph on the drawing.
|
||||
*/
|
||||
private void drawGraph(Class<? extends Drawing> newClass, GraphPalette palette) {
|
||||
|
||||
// Save old divider location
|
||||
int oldLocation = mainPanel.getDividerLocation();
|
||||
|
||||
// Set drawing if not set
|
||||
if (!(mainPanel.getLeftComponent() instanceof Drawing)) {
|
||||
mainPanel.setLeftComponent((Component) this.drawing);
|
||||
mainPanel.setDividerLocation(oldLocation);
|
||||
// Need to re-validate or the drawing will not have the
|
||||
// correct size prior to drawing, which can cause issue.
|
||||
this.revalidate();
|
||||
}
|
||||
|
||||
boolean isNewGraph = newClass == null;
|
||||
boolean isMapView = (isNewGraph && drawing == mapViewDrawing)
|
||||
|| (!isNewGraph && newClass.equals(MapViewDrawing.class));
|
||||
|
||||
// We need to draw MapView, we have to check if the file exists.
|
||||
File mfile = null;
|
||||
if (isMapView) {
|
||||
String mfpath = graphFilePath.substring(0, graphFilePath.lastIndexOf(".map"))
|
||||
+ ".mapfg";
|
||||
mfile = new File(mfpath);
|
||||
if (!mfile.exists()) {
|
||||
if (JOptionPane.showConfirmDialog(this,
|
||||
"The associated mapsforge (.mapfg) file has not been found, do you want to specify it manually?",
|
||||
"File not found",
|
||||
JOptionPane.YES_NO_CANCEL_OPTION) == JOptionPane.YES_OPTION) {
|
||||
JFileChooser chooser = new JFileChooser(mfile.getParentFile());
|
||||
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
|
||||
mfile = chooser.getSelectedFile();
|
||||
}
|
||||
else {
|
||||
mfile = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
mfile = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Runnable runnable = null;
|
||||
|
||||
if (isMapView && mfile != null) {
|
||||
final File mfileFinal = mfile;
|
||||
// It is a mapview drawing and the file was found, so:
|
||||
// 1. We create the drawing if necessary.
|
||||
if (drawing != mapViewDrawing) {
|
||||
drawing.clear();
|
||||
drawing = mapViewDrawing;
|
||||
mainPanel.setLeftComponent(mapViewDrawing);
|
||||
mainPanel.setDividerLocation(oldLocation);
|
||||
notifyDrawingLoaded(basicDrawing, mapViewDrawing);
|
||||
drawing.clear();
|
||||
isNewGraph = true;
|
||||
mainPanel.revalidate();
|
||||
}
|
||||
if (isNewGraph) {
|
||||
drawing.clear();
|
||||
runnable = new Runnable() {
|
||||
public void run() {
|
||||
((MapViewDrawing) drawing).drawGraph(mfileFinal);
|
||||
notifyRedrawRequest();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
else if (!isMapView || (isMapView && mfile == null && isNewGraph)) {
|
||||
if (drawing == mapViewDrawing) {
|
||||
mapViewDrawing.clear();
|
||||
drawing = basicDrawing;
|
||||
mainPanel.setLeftComponent(basicDrawing);
|
||||
mainPanel.setDividerLocation(oldLocation);
|
||||
notifyDrawingLoaded(mapViewDrawing, basicDrawing);
|
||||
isNewGraph = true;
|
||||
}
|
||||
if (isNewGraph || palette != this.currentPalette) {
|
||||
this.currentPalette = palette;
|
||||
drawing.clear();
|
||||
runnable = new Runnable() {
|
||||
public void run() {
|
||||
drawing.drawGraph(graph, palette);
|
||||
notifyRedrawRequest();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (runnable != null) {
|
||||
launchThread(runnable, false);
|
||||
}
|
||||
else {
|
||||
drawing.clearOverlays();
|
||||
notifyRedrawRequest();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param newClass
|
||||
*/
|
||||
private void drawGraph(Class<? extends Drawing> newClass) {
|
||||
drawGraph(newClass, new BasicGraphPalette());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private void drawGraph() {
|
||||
drawGraph(null, this.currentPalette);
|
||||
}
|
||||
|
||||
private void loadGraph(GraphReader reader) {
|
||||
launchThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GraphReaderProgressBar progressBar = new GraphReaderProgressBar(MainWindow.this);
|
||||
progressBar.setLocationRelativeTo(mainPanel.getLeftComponent());
|
||||
reader.addObserver(progressBar);
|
||||
try {
|
||||
graph = reader.read();
|
||||
reader.close();
|
||||
}
|
||||
catch (Exception exception) {
|
||||
progressBar.setVisible(false);
|
||||
progressBar.dispose();
|
||||
progressBar = null;
|
||||
JOptionPane.showMessageDialog(MainWindow.this,
|
||||
"<html><p>Unable to read graph from the selected file:</p><p>"
|
||||
+ exception.getMessage() + "</p>");
|
||||
exception.printStackTrace(System.out);
|
||||
return;
|
||||
}
|
||||
|
||||
// In case of....
|
||||
progressBar.setVisible(false);
|
||||
progressBar.dispose();
|
||||
progressBar = null;
|
||||
|
||||
String info = graph.getMapId();
|
||||
if (graph.getMapName() != null && !graph.getMapName().isEmpty()) {
|
||||
// The \u200e character is the left-to-right mark, we need to avoid issue with
|
||||
// name that are right-to-left (e.g. arabic names).
|
||||
info += " - " + graph.getMapName() + "\u200e";
|
||||
}
|
||||
info += ", " + graph.size() + " nodes, " + graph.getGraphInformation().getArcCount()
|
||||
+ " arcs.";
|
||||
graphInfoPanel.setText(info);
|
||||
|
||||
drawGraph();
|
||||
|
||||
notifyNewGraphLoaded();
|
||||
|
||||
for (JMenuItem item: graphLockItems) {
|
||||
item.setEnabled(true);
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show and enable the given AlgorithmPanel (and hide all others).
|
||||
*
|
||||
* @param algorithmPanel
|
||||
*/
|
||||
private void enableAlgorithmPanel(AlgorithmPanel algorithmPanel) {
|
||||
int dividerLocation = mainPanel.getDividerLocation();
|
||||
for (AlgorithmPanel panel: algoPanels) {
|
||||
panel.setVisible(panel == algorithmPanel);
|
||||
}
|
||||
mainPanel.setDividerLocation(dividerLocation);
|
||||
}
|
||||
|
||||
private JMenuBar createMenuBar(ActionListener openMapActionListener) {
|
||||
|
||||
// Open Map item...
|
||||
JMenuItem openMapItem = new JMenuItem("Open Map... ", KeyEvent.VK_O);
|
||||
openMapItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.ALT_MASK));
|
||||
openMapItem.addActionListener(baf.createBlockingAction(openMapActionListener));
|
||||
|
||||
// Open Path item...
|
||||
JMenuItem openPathItem = new JMenuItem("Open Path... ", KeyEvent.VK_P);
|
||||
openPathItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, ActionEvent.ALT_MASK));
|
||||
openPathItem.addActionListener(baf.createBlockingAction(new ActionListener() {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
JFileChooser chooser = FileUtils.createFileChooser(FolderType.PathInput);
|
||||
if (chooser.showOpenDialog(MainWindow.this) == JFileChooser.APPROVE_OPTION) {
|
||||
try (BinaryPathReader reader = new BinaryPathReader(new DataInputStream(new BufferedInputStream(
|
||||
new FileInputStream(chooser.getSelectedFile()))))){
|
||||
Path path = reader.readPath(graph);
|
||||
pathPanel.addPath(path);
|
||||
}
|
||||
catch (MapMismatchException exception) {
|
||||
JOptionPane.showMessageDialog(MainWindow.this,
|
||||
"The selected file does not contain a path for the current graph.");
|
||||
}
|
||||
catch (IOException e1) {
|
||||
JOptionPane.showMessageDialog(MainWindow.this,
|
||||
"Cannot open the selected file.");
|
||||
}
|
||||
catch (Exception exception) {
|
||||
JOptionPane.showMessageDialog(MainWindow.this,
|
||||
"Unable to read path from the selected file.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
graphLockItems.add(openPathItem);
|
||||
|
||||
// Close item
|
||||
JMenuItem closeItem = new JMenuItem("Quit", KeyEvent.VK_Q);
|
||||
closeItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, ActionEvent.ALT_MASK));
|
||||
closeItem.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
MainWindow.this.dispatchEvent(
|
||||
new WindowEvent(MainWindow.this, WindowEvent.WINDOW_CLOSING));
|
||||
}
|
||||
});
|
||||
|
||||
// Build the first menu.
|
||||
JMenu fileMenu = new JMenu("File");
|
||||
fileMenu.add(openMapItem);
|
||||
fileMenu.add(openPathItem);
|
||||
fileMenu.addSeparator();
|
||||
fileMenu.add(closeItem);
|
||||
|
||||
// Second menu
|
||||
JMenuItem drawGraphItem = new JMenuItem("Redraw", KeyEvent.VK_R);
|
||||
drawGraphItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, ActionEvent.ALT_MASK));
|
||||
drawGraphItem.addActionListener(baf.createBlockingAction(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
drawGraph(BasicDrawing.class, basicPalette);
|
||||
}
|
||||
}));
|
||||
graphLockItems.add(drawGraphItem);
|
||||
JMenuItem drawGraphBWItem = new JMenuItem("Redraw (B&W)", KeyEvent.VK_B);
|
||||
drawGraphBWItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_B, ActionEvent.ALT_MASK));
|
||||
drawGraphBWItem.addActionListener(baf.createBlockingAction(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
drawGraph(BasicDrawing.class, blackAndWhitePalette);
|
||||
}
|
||||
}));
|
||||
graphLockItems.add(drawGraphBWItem);
|
||||
JMenuItem drawGraphMapsforgeItem = new JMenuItem("Redraw (Map)", KeyEvent.VK_M);
|
||||
drawGraphMapsforgeItem
|
||||
.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, ActionEvent.ALT_MASK));
|
||||
drawGraphMapsforgeItem.addActionListener(baf.createBlockingAction(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
drawGraph(MapViewDrawing.class);
|
||||
}
|
||||
}));
|
||||
graphLockItems.add(drawGraphMapsforgeItem);
|
||||
|
||||
JMenu graphMenu = new JMenu("Graph");
|
||||
graphMenu.add(drawGraphItem);
|
||||
graphMenu.add(drawGraphBWItem);
|
||||
graphMenu.addSeparator();
|
||||
graphMenu.add(drawGraphMapsforgeItem);
|
||||
|
||||
// Algo menu
|
||||
JMenu algoMenu = new JMenu("Algorithms");
|
||||
|
||||
// Weakly connected components
|
||||
JMenuItem wccItem = new JMenuItem("Weakly Connected Components");
|
||||
wccItem.addActionListener(baf.createBlockingAction(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
enableAlgorithmPanel(wccPanel);
|
||||
}
|
||||
}));
|
||||
|
||||
// Shortest path
|
||||
JMenuItem spItem = new JMenuItem("Shortest-Path");
|
||||
spItem.addActionListener(baf.createBlockingAction(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
enableAlgorithmPanel(spPanel);
|
||||
}
|
||||
}));
|
||||
|
||||
// Car pooling
|
||||
JMenuItem cpItem = new JMenuItem("Car Pooling");
|
||||
cpItem.addActionListener(baf.createBlockingAction(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
enableAlgorithmPanel(cpPanel);
|
||||
}
|
||||
}));
|
||||
|
||||
// Car pooling
|
||||
JMenuItem psItem = new JMenuItem("Package Switch");
|
||||
psItem.addActionListener(baf.createBlockingAction(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
enableAlgorithmPanel(psPanel);
|
||||
}
|
||||
}));
|
||||
|
||||
graphLockItems.add(wccItem);
|
||||
graphLockItems.add(spItem);
|
||||
graphLockItems.add(cpItem);
|
||||
graphLockItems.add(psItem);
|
||||
|
||||
algoMenu.add(wccItem);
|
||||
algoMenu.addSeparator();
|
||||
algoMenu.add(spItem);
|
||||
algoMenu.add(cpItem);
|
||||
algoMenu.add(psItem);
|
||||
|
||||
// Create the menu bar.
|
||||
JMenuBar menuBar = new JMenuBar();
|
||||
|
||||
menuBar.add(fileMenu);
|
||||
menuBar.add(graphMenu);
|
||||
menuBar.add(algoMenu);
|
||||
|
||||
for (JMenuItem item: graphLockItems) {
|
||||
item.setEnabled(false);
|
||||
}
|
||||
|
||||
return menuBar;
|
||||
}
|
||||
|
||||
private JPanel createStatusBar() {
|
||||
// create the status bar panel and shove it down the bottom of the frame
|
||||
JPanel statusPanel = new JPanel();
|
||||
statusPanel.setBorder(
|
||||
new CompoundBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.GRAY),
|
||||
new EmptyBorder(0, 15, 0, 15)));
|
||||
statusPanel.setPreferredSize(new Dimension(getWidth(), 38));
|
||||
statusPanel.setLayout(new BorderLayout());
|
||||
|
||||
graphInfoPanel = new JLabel();
|
||||
graphInfoPanel.setHorizontalAlignment(SwingConstants.LEFT);
|
||||
statusPanel.add(graphInfoPanel, BorderLayout.WEST);
|
||||
|
||||
JLabel threadInfo = new JLabel("Thread running... ");
|
||||
JLabel threadTimerLabel = new JLabel("00:00:00");
|
||||
JButton threadButton = new JButton("Stop");
|
||||
threadButton.addActionListener(new ActionListener() {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (currentThread.isRunning()) {
|
||||
int confirmed = JOptionPane.showConfirmDialog(MainWindow.this,
|
||||
"Are you sure you want to kill the running thread?",
|
||||
"Kill Confirmation", JOptionPane.YES_NO_OPTION);
|
||||
if (confirmed == JOptionPane.YES_OPTION) {
|
||||
currentThread.interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
threadTimer = new Timer(THREAD_TIMER_DELAY, new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
long seconds = currentThread.getDuration().getSeconds();
|
||||
threadTimerLabel.setText(String.format("%02d:%02d:%02d", seconds / 3600,
|
||||
seconds / 60 % 60, seconds % 60));
|
||||
}
|
||||
});
|
||||
threadTimer.setInitialDelay(0);
|
||||
|
||||
threadPanel = new JPanel();
|
||||
threadPanel.add(threadInfo);
|
||||
threadPanel.add(threadTimerLabel);
|
||||
threadPanel.add(threadButton);
|
||||
threadPanel.setVisible(false);
|
||||
statusPanel.add(threadPanel, BorderLayout.EAST);
|
||||
|
||||
return statusPanel;
|
||||
}
|
||||
|
||||
public static void main(final String[] args) {
|
||||
|
||||
// Try to set system look and feel.
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
MainWindow w = new MainWindow();
|
||||
w.setExtendedState(JFrame.MAXIMIZED_BOTH);
|
||||
w.setVisible(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,424 @@
|
|||
package org.insa.graphs.gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.Insets;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
import org.insa.graphs.gui.drawing.Drawing;
|
||||
import org.insa.graphs.gui.drawing.Drawing.AlphaMode;
|
||||
import org.insa.graphs.gui.drawing.DrawingClickListener;
|
||||
import org.insa.graphs.gui.drawing.overlays.MarkerOverlay;
|
||||
import org.insa.graphs.model.Graph;
|
||||
import org.insa.graphs.model.Node;
|
||||
import org.insa.graphs.model.Point;
|
||||
|
||||
public class NodesInputPanel extends JPanel
|
||||
implements DrawingClickListener, DrawingChangeListener, GraphChangeListener {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final Color DEFAULT_MARKER_COLOR = Color.BLUE;
|
||||
|
||||
/**
|
||||
* Utility class that can be used to find a node from coordinates in a "fast"
|
||||
* way.
|
||||
*
|
||||
*/
|
||||
private static class NodeFinder {
|
||||
|
||||
// Graph associated with this node finder.
|
||||
private Graph graph;
|
||||
|
||||
/**
|
||||
* @param graph
|
||||
*/
|
||||
public NodeFinder(Graph graph) {
|
||||
this.graph = graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param point
|
||||
*
|
||||
* @return the closest node to the given point, or null if no node is "close
|
||||
* enough".
|
||||
*/
|
||||
public Node findClosestNode(Point point) {
|
||||
Node minNode = null;
|
||||
double minDis = Double.POSITIVE_INFINITY;
|
||||
for (Node node: graph.getNodes()) {
|
||||
double dlon = point.getLongitude() - node.getPoint().getLongitude();
|
||||
double dlat = point.getLatitude() - node.getPoint().getLatitude();
|
||||
double dis = dlon * dlon + dlat * dlat; // No need to square
|
||||
if (dis < minDis) {
|
||||
minNode = node;
|
||||
minDis = dis;
|
||||
}
|
||||
}
|
||||
return minNode;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Event data send when a node input has changed.
|
||||
*
|
||||
*/
|
||||
public class InputChangedEvent extends ActionEvent {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 3440024811352247731L;
|
||||
|
||||
protected static final String ALL_INPUT_FILLED_EVENT_COMMAND = "allInputFilled";
|
||||
|
||||
protected static final int ALL_INPUT_FILLED_EVENT_ID = 0x1;
|
||||
|
||||
// List of nodes
|
||||
List<Node> nodes;
|
||||
|
||||
public InputChangedEvent(List<Node> nodes2) {
|
||||
super(NodesInputPanel.this, ALL_INPUT_FILLED_EVENT_ID, ALL_INPUT_FILLED_EVENT_COMMAND);
|
||||
this.nodes = nodes2;
|
||||
}
|
||||
|
||||
List<Node> getNodes() {
|
||||
return Collections.unmodifiableList(nodes);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Node inputs and markers.
|
||||
private final ArrayList<JTextField> nodeInputs = new ArrayList<>();
|
||||
private final Map<JTextField, MarkerOverlay> markerTrackers = new IdentityHashMap<JTextField, MarkerOverlay>();
|
||||
|
||||
// Component that can be enabled/disabled.
|
||||
private ArrayList<JComponent> components = new ArrayList<>();
|
||||
private int inputToFillIndex;
|
||||
|
||||
// ActionListener called when all inputs are filled.
|
||||
private ArrayList<ActionListener> inputChangeListeners = new ArrayList<>();
|
||||
|
||||
// Drawing and graph
|
||||
private Drawing drawing;
|
||||
private Graph graph;
|
||||
private NodeFinder nodeFinder;
|
||||
|
||||
/**
|
||||
* Create a new NodesInputPanel.
|
||||
*
|
||||
*/
|
||||
public NodesInputPanel() {
|
||||
super(new GridBagLayout());
|
||||
initInputToFill();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an InputChanged listener to this panel. This listener will be notified by
|
||||
* a {@link InputChangedEvent} each time an input in this panel change (click,
|
||||
* clear, manual input).
|
||||
*
|
||||
* @param listener Listener to add.
|
||||
*
|
||||
* @see InputChangedEvent
|
||||
*/
|
||||
public void addInputChangedListener(ActionListener listener) {
|
||||
inputChangeListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisible(boolean visible) {
|
||||
super.setVisible(visible);
|
||||
for (JTextField input: nodeInputs) {
|
||||
MarkerOverlay marker = markerTrackers.getOrDefault(input, null);
|
||||
if (marker != null) {
|
||||
marker.setVisible(visible && !input.getText().trim().isEmpty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
for (JComponent component: components) {
|
||||
component.setEnabled(enabled);
|
||||
}
|
||||
super.setEnabled(enabled);
|
||||
if (enabled) {
|
||||
// Enable: Check if there is an input to fill, otherwize find the next one.
|
||||
if (getInputToFill() == null) {
|
||||
nextInputToFill();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Disable, next input to fill = -1.
|
||||
this.inputToFillIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
for (JTextField field: nodeInputs) {
|
||||
field.setText("");
|
||||
markerTrackers.put(field, null);
|
||||
}
|
||||
initInputToFill();
|
||||
}
|
||||
|
||||
public void addTextField(String label) {
|
||||
addTextField(label, DEFAULT_MARKER_COLOR);
|
||||
}
|
||||
|
||||
public void addTextField(String label, Color markerColor) {
|
||||
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
c.insets = new Insets(3, 3, 3, 3);
|
||||
|
||||
JLabel jLabel = new JLabel(label);
|
||||
jLabel.setFont(jLabel.getFont().deriveFont(~Font.BOLD));
|
||||
JTextField textField = new JTextField();
|
||||
jLabel.setLabelFor(textField);
|
||||
|
||||
c.gridx = 0;
|
||||
c.gridy = nodeInputs.size();
|
||||
c.weightx = 0;
|
||||
c.gridwidth = 1;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
add(jLabel, c);
|
||||
|
||||
c.gridx = 1;
|
||||
c.gridy = nodeInputs.size();
|
||||
c.weightx = 1;
|
||||
c.gridwidth = 1;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
add(textField, c);
|
||||
|
||||
JButton clearButton = new JButton("Clear");
|
||||
c.gridx = 2;
|
||||
c.gridy = nodeInputs.size();
|
||||
c.weightx = 0;
|
||||
c.gridwidth = 1;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
add(clearButton, c);
|
||||
|
||||
JButton clickButton = new JButton("Click");
|
||||
c.gridx = 3;
|
||||
c.gridy = nodeInputs.size();
|
||||
c.weightx = 0;
|
||||
c.gridwidth = 1;
|
||||
c.fill = GridBagConstraints.HORIZONTAL;
|
||||
add(clickButton, c);
|
||||
|
||||
// Did not find something easier that this... ?
|
||||
textField.getDocument().addDocumentListener(new DocumentListener() {
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
insertUpdate(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
insertUpdate(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
|
||||
// Draw marker if possible
|
||||
Node curnode = getNodeForInput(textField);
|
||||
MarkerOverlay tracker = markerTrackers.getOrDefault(textField, null);
|
||||
if (curnode != null) {
|
||||
if (tracker == null) {
|
||||
tracker = drawing.drawMarker(curnode.getPoint(), markerColor, Color.BLACK,
|
||||
AlphaMode.TRANSPARENT);
|
||||
markerTrackers.put(textField, tracker);
|
||||
}
|
||||
else {
|
||||
tracker.moveTo(curnode.getPoint());
|
||||
}
|
||||
tracker.setVisible(true);
|
||||
}
|
||||
else if (tracker != null) {
|
||||
tracker.setVisible(false);
|
||||
if (getInputToFill() == null) {
|
||||
nextInputToFill();
|
||||
}
|
||||
}
|
||||
|
||||
// Create array of nodes
|
||||
List<Node> nodes = getNodeForInputs();
|
||||
|
||||
// Trigger change event.
|
||||
for (ActionListener lis: inputChangeListeners) {
|
||||
lis.actionPerformed(new InputChangedEvent(nodes));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
clearButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
textField.setText("");
|
||||
setInputToFill(textField);
|
||||
}
|
||||
});
|
||||
|
||||
clickButton.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
setInputToFill(textField);
|
||||
}
|
||||
});
|
||||
|
||||
nodeInputs.add(textField);
|
||||
components.add(textField);
|
||||
components.add(clearButton);
|
||||
components.add(clickButton);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Current graph associated with this input panel.
|
||||
*/
|
||||
protected Graph getGraph() {
|
||||
return this.graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The node for the given text field, or null if the content of the text
|
||||
* field is invalid.
|
||||
*/
|
||||
protected Node getNodeForInput(JTextField textfield) {
|
||||
try {
|
||||
Node node = graph.get(Integer.valueOf(textfield.getText().trim()));
|
||||
return node;
|
||||
}
|
||||
catch (IllegalArgumentException | IndexOutOfBoundsException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return List of nodes associated with the input. Some nodes may be null if
|
||||
* their associated input is invalid.
|
||||
*/
|
||||
public List<Node> getNodeForInputs() {
|
||||
List<Node> nodes = new ArrayList<>(nodeInputs.size());
|
||||
for (JTextField input: nodeInputs) {
|
||||
nodes.add(getNodeForInput(input));
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next input that should be filled by a click, or null if none should
|
||||
* be filled.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected JTextField getInputToFill() {
|
||||
if (inputToFillIndex < 0 || inputToFillIndex >= nodeInputs.size()) {
|
||||
return null;
|
||||
}
|
||||
return nodeInputs.get(inputToFillIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the next input to fill.
|
||||
*/
|
||||
protected void initInputToFill() {
|
||||
inputToFillIndex = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the next input to fill to the given text field.
|
||||
*
|
||||
* @param input
|
||||
*/
|
||||
protected void setInputToFill(JTextField input) {
|
||||
inputToFillIndex = nodeInputs.indexOf(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the next input to fill, if any.
|
||||
*/
|
||||
protected void nextInputToFill() {
|
||||
boolean found = false;
|
||||
if (inputToFillIndex == -1) {
|
||||
inputToFillIndex = 0;
|
||||
}
|
||||
for (int i = 0; i < nodeInputs.size() && !found; ++i) {
|
||||
int nextIndex = (i + inputToFillIndex) % nodeInputs.size();
|
||||
JTextField input = nodeInputs.get(nextIndex);
|
||||
if (input.getText().trim().isEmpty()) {
|
||||
inputToFillIndex = nextIndex;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
inputToFillIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(Point point) {
|
||||
JTextField input = getInputToFill();
|
||||
if (input != null) {
|
||||
Node node = nodeFinder.findClosestNode(point);
|
||||
input.setText(String.valueOf(node.getId()));
|
||||
nextInputToFill();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void newGraphLoaded(Graph graph) {
|
||||
if (graph != this.graph) {
|
||||
this.clear();
|
||||
this.graph = graph;
|
||||
|
||||
nodeFinder = new NodeFinder(graph);
|
||||
|
||||
// Disable if previously disabled...
|
||||
setEnabled(this.isEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawingLoaded(Drawing oldDrawing, Drawing newDrawing) {
|
||||
if (newDrawing != drawing) {
|
||||
this.drawing = newDrawing;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRedrawRequest() {
|
||||
for (JTextField input: nodeInputs) {
|
||||
MarkerOverlay tracker = markerTrackers.getOrDefault(input, null);
|
||||
if (tracker != null) {
|
||||
MarkerOverlay newMarker = this.drawing.drawMarker(tracker.getPoint(),
|
||||
tracker.getColor(), Color.BLACK, AlphaMode.TRANSPARENT);
|
||||
markerTrackers.put(input, newMarker);
|
||||
newMarker.setVisible(tracker.isVisible());
|
||||
tracker.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue