This commit is contained in:
El Haji Fofana 2023-03-22 09:33:44 +01:00
commit 7b8589d781
303 changed files with 11082 additions and 0 deletions

62
README.md Normal file
View 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
View 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>

View file

@ -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();
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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();
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -0,0 +1,5 @@
package org.insa.graphs.algorithm.carpooling;
public class CarPoolingGraphicObserver implements CarPoolingObserver {
}

View file

@ -0,0 +1,5 @@
package org.insa.graphs.algorithm.carpooling;
public interface CarPoolingObserver {
}

View file

@ -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);
}
}

View file

@ -0,0 +1,5 @@
package org.insa.graphs.algorithm.carpooling;
public class CarPoolingTextObserver implements CarPoolingObserver {
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -0,0 +1,5 @@
package org.insa.graphs.algorithm.packageswitch;
public class PackageSwitchGraphicObserver implements PackageSwitchObserver {
}

View file

@ -0,0 +1,5 @@
package org.insa.graphs.algorithm.packageswitch;
public interface PackageSwitchObserver {
}

View file

@ -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);
}
}

View file

@ -0,0 +1,5 @@
package org.insa.graphs.algorithm.packageswitch;
public class PackageSwitchTextObserver implements PackageSwitchObserver {
}

View file

@ -0,0 +1,9 @@
package org.insa.graphs.algorithm.shortestpath;
public class AStarAlgorithm extends DijkstraAlgorithm {
public AStarAlgorithm(ShortestPathData data) {
super(data);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}
}

View file

@ -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() + "]";
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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
}
}

View file

@ -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);
}
}

View file

@ -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));
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,16 @@
package org.insa.graphs.algorithm.utils;
public class EmptyPriorityQueueException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
*
*/
public EmptyPriorityQueueException() {
}
}

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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.";
}
}

View file

@ -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.";
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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());
}
}
}

87
be-graphes-gui/pom.xml Normal file
View 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>

View file

@ -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() {
}
}

View file

@ -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);
}
}
};
}
}

View file

@ -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();
}

View file

@ -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);
}

View file

@ -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);
}
}

View 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);
}
});
}
}

View file

@ -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