From f22c1c93fa59617294a4da61e8898eb67e356da8 Mon Sep 17 00:00:00 2001 From: Holt59 Date: Sat, 3 Mar 2018 20:06:38 +0100 Subject: [PATCH] Make solution panel a bit more generic using reflections. --- src/main/org/insa/algo/AbstractInputData.java | 84 +++++++- .../StronglyConnectedComponentsData.java | 16 +- ...testPathPanel.java => AlgorithmPanel.java} | 41 ++-- src/main/org/insa/graphics/MainWindow.java | 6 +- ...hSolutionPanel.java => SolutionPanel.java} | 196 +++++++++--------- 5 files changed, 209 insertions(+), 134 deletions(-) rename src/main/org/insa/graphics/{ShortestPathPanel.java => AlgorithmPanel.java} (87%) rename src/main/org/insa/graphics/{ShortestPathSolutionPanel.java => SolutionPanel.java} (52%) diff --git a/src/main/org/insa/algo/AbstractInputData.java b/src/main/org/insa/algo/AbstractInputData.java index 9fba4bd..93d7dca 100644 --- a/src/main/org/insa/algo/AbstractInputData.java +++ b/src/main/org/insa/algo/AbstractInputData.java @@ -1,22 +1,100 @@ package org.insa.algo; +import org.insa.graph.Arc; import org.insa.graph.Graph; public abstract class AbstractInputData { - protected Graph graph; + public enum Mode { + TIME, LENGTH + } /** - * Create a new AbstractInputData instance with the given graph. + * + * + */ + public interface ArcFilter { + + /** + * @param arc + * + * @return true if the given arc is allowed. + */ + public boolean isAllowed(Arc arc); + + } + + // Graph + protected Graph graph; + + // Mode for the computation of the costs. + private final AbstractInputData.Mode mode; + + // Arc filter. + private final AbstractInputData.ArcFilter arcFilter; + + /** + * Create a new AbstractInputData instance for the given graph, mode and filter. * * @param graph */ - protected AbstractInputData(Graph graph) { + protected AbstractInputData(Graph graph, Mode mode, ArcFilter arcFilter) { this.graph = graph; + this.mode = mode; + this.arcFilter = arcFilter; } + /** + * Create a new AbstractInputData instance for the given graph and mode, with no + * filtering on the arc. + * + * @param graph + * @param mode + */ + protected AbstractInputData(Graph graph, Mode mode) { + this(graph, mode, new AbstractInputData.ArcFilter() { + @Override + public boolean isAllowed(Arc arc) { + return true; + } + }); + } + + /** + * Create a new AbstractInputData instance for the given graph, with default + * mode (LENGHT), with no filtering on the arc. + * + * @param graph + * @param mode + */ + protected AbstractInputData(Graph graph) { + this(graph, Mode.LENGTH, new AbstractInputData.ArcFilter() { + @Override + public boolean isAllowed(Arc arc) { + return true; + } + }); + } + + /** + * @return Graph associated with this input. + */ public Graph getGraph() { return graph; } + /** + * @return Mode of the algorithm (time or length). + */ + public Mode getMode() { + return mode; + } + + /** + * @return true if the given arc is allowed. + */ + public boolean isAllowed(Arc arc) { + return this.arcFilter.isAllowed(arc); + } + } diff --git a/src/main/org/insa/algo/strongconnectivity/StronglyConnectedComponentsData.java b/src/main/org/insa/algo/strongconnectivity/StronglyConnectedComponentsData.java index 5c99af4..a42ab71 100644 --- a/src/main/org/insa/algo/strongconnectivity/StronglyConnectedComponentsData.java +++ b/src/main/org/insa/algo/strongconnectivity/StronglyConnectedComponentsData.java @@ -5,12 +5,12 @@ import org.insa.graph.Graph; public class StronglyConnectedComponentsData extends AbstractInputData { - /** - * - * @param graph - */ - public StronglyConnectedComponentsData(Graph graph) { - super(graph); - } - + /** + * + * @param graph + */ + public StronglyConnectedComponentsData(Graph graph) { + super(graph); + } + } diff --git a/src/main/org/insa/graphics/ShortestPathPanel.java b/src/main/org/insa/graphics/AlgorithmPanel.java similarity index 87% rename from src/main/org/insa/graphics/ShortestPathPanel.java rename to src/main/org/insa/graphics/AlgorithmPanel.java index 5ff48b7..bffebdc 100644 --- a/src/main/org/insa/graphics/ShortestPathPanel.java +++ b/src/main/org/insa/graphics/AlgorithmPanel.java @@ -25,16 +25,16 @@ import javax.swing.JRadioButton; import javax.swing.border.EmptyBorder; import org.insa.algo.AbstractAlgorithm; +import org.insa.algo.AbstractInputData; +import org.insa.algo.AbstractInputData.ArcFilter; +import org.insa.algo.AbstractInputData.Mode; import org.insa.algo.AlgorithmFactory; -import org.insa.algo.shortestpath.ShortestPathAlgorithm; -import org.insa.algo.shortestpath.ShortestPathData.ArcFilter; -import org.insa.algo.shortestpath.ShortestPathData.Mode; import org.insa.graph.Arc; import org.insa.graph.Node; import org.insa.graph.RoadInformation.AccessMode; import org.insa.graphics.NodesInputPanel.InputChangedEvent; -public class ShortestPathPanel extends JPanel { +public class AlgorithmPanel extends JPanel { /** * @@ -53,17 +53,17 @@ public class ShortestPathPanel extends JPanel { protected static final int START_EVENT_ID = 0x1; private final List nodes; - private final Mode mode; + private final AbstractInputData.Mode mode; private final Class> algoClass; - private final ArcFilter arcFilter; + private final AbstractInputData.ArcFilter arcFilter; private final boolean graphicVisualization; private final boolean textualVisualization; - public StartActionEvent(Class> algoClass, List nodes, Mode mode, - ArcFilter arcFilter, boolean graphicVisualization, boolean textualVisualization) { - super(ShortestPathPanel.this, START_EVENT_ID, START_EVENT_COMMAND); + public StartActionEvent(Class> algoClass, List nodes, AbstractInputData.Mode mode, + AbstractInputData.ArcFilter arcFilter, boolean graphicVisualization, boolean textualVisualization) { + super(AlgorithmPanel.this, START_EVENT_ID, START_EVENT_COMMAND); this.nodes = nodes; this.mode = mode; this.algoClass = algoClass; @@ -82,14 +82,14 @@ public class ShortestPathPanel extends JPanel { /** * @return Mode associated with this event. */ - public Mode getMode() { + public AbstractInputData.Mode getMode() { return this.mode; } /** * @return Arc filter associated with this event. */ - public ArcFilter getArcFilter() { + public AbstractInputData.ArcFilter getArcFilter() { return this.arcFilter; } @@ -120,7 +120,7 @@ public class ShortestPathPanel extends JPanel { protected NodesInputPanel nodesInputPanel; // Solution - protected ShortestPathSolutionPanel solutionPanel; + protected SolutionPanel solutionPanel; // Component that can be enabled/disabled. private ArrayList components = new ArrayList<>(); @@ -132,7 +132,7 @@ public class ShortestPathPanel extends JPanel { /** */ - public ShortestPathPanel(Component parent) { + public AlgorithmPanel(Component parent, Class> baseAlgorithm) { super(); setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); @@ -152,7 +152,7 @@ public class ShortestPathPanel extends JPanel { // Add algorithm selection JComboBox algoSelect = new JComboBox<>( - AlgorithmFactory.getAlgorithmNames(ShortestPathAlgorithm.class).toArray(new String[0])); + AlgorithmFactory.getAlgorithmNames(baseAlgorithm).toArray(new String[0])); algoSelect.setBackground(Color.WHITE); algoSelect.setAlignmentX(Component.LEFT_ALIGNMENT); add(algoSelect); @@ -168,7 +168,7 @@ public class ShortestPathPanel extends JPanel { add(this.nodesInputPanel); components.add(this.nodesInputPanel); - JComboBox arcFilterSelect = new JComboBox<>(new ArcFilter[] { new ArcFilter() { + JComboBox arcFilterSelect = new JComboBox<>(new AbstractInputData.ArcFilter[] { new AbstractInputData.ArcFilter() { @Override public boolean isAllowed(Arc arc) { return true; @@ -178,7 +178,7 @@ public class ShortestPathPanel extends JPanel { public String toString() { return "All arcs are allowed"; } - }, new ArcFilter() { + }, new AbstractInputData.ArcFilter() { @Override public boolean isAllowed(Arc arc) { return arc.getRoadInformation().getAccessRestrictions().isAllowedFor(AccessMode.MOTORCAR) @@ -250,7 +250,7 @@ public class ShortestPathPanel extends JPanel { add(modeAndObserverPanel); - solutionPanel = new ShortestPathSolutionPanel(parent); + solutionPanel = new SolutionPanel(parent); solutionPanel.setAlignmentX(Component.LEFT_ALIGNMENT); solutionPanel.setVisible(false); add(Box.createVerticalStrut(10)); @@ -265,13 +265,12 @@ public class ShortestPathPanel extends JPanel { startAlgoButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - Mode mode = lengthModeButton.isSelected() ? Mode.LENGTH : Mode.TIME; + AbstractInputData.Mode mode = lengthModeButton.isSelected() ? AbstractInputData.Mode.LENGTH : AbstractInputData.Mode.TIME; for (ActionListener lis: startActionListeners) { lis.actionPerformed(new StartActionEvent( - AlgorithmFactory.getAlgorithmClass(ShortestPathAlgorithm.class, - (String) algoSelect.getSelectedItem()), - nodesInputPanel.getNodeForInputs(), mode, (ArcFilter) arcFilterSelect.getSelectedItem(), + AlgorithmFactory.getAlgorithmClass(baseAlgorithm, (String) algoSelect.getSelectedItem()), + nodesInputPanel.getNodeForInputs(), mode, (AbstractInputData.ArcFilter) arcFilterSelect.getSelectedItem(), graphicObserver.isSelected(), textObserver.isSelected())); } } diff --git a/src/main/org/insa/graphics/MainWindow.java b/src/main/org/insa/graphics/MainWindow.java index c4c6522..dee15fe 100644 --- a/src/main/org/insa/graphics/MainWindow.java +++ b/src/main/org/insa/graphics/MainWindow.java @@ -58,7 +58,7 @@ import org.insa.graph.io.BinaryGraphReaderInsa2018; import org.insa.graph.io.BinaryPathReader; import org.insa.graph.io.GraphReader; import org.insa.graph.io.MapMismatchException; -import org.insa.graphics.ShortestPathPanel.StartActionEvent; +import org.insa.graphics.AlgorithmPanel.StartActionEvent; import org.insa.graphics.drawing.BasicGraphPalette; import org.insa.graphics.drawing.BlackAndWhiteGraphPalette; import org.insa.graphics.drawing.Drawing; @@ -107,7 +107,7 @@ public class MainWindow extends JFrame { private JSplitPane mainPanel; // Shortest path panel - private ShortestPathPanel spPanel; + private AlgorithmPanel spPanel; // List of items that cannot be used without a graph private ArrayList graphLockItems = new ArrayList(); @@ -148,7 +148,7 @@ public class MainWindow extends JFrame { this.drawing = this.basicDrawing; - spPanel = new ShortestPathPanel(this); + spPanel = new AlgorithmPanel(this, ShortestPathAlgorithm.class); spPanel.addStartActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { diff --git a/src/main/org/insa/graphics/ShortestPathSolutionPanel.java b/src/main/org/insa/graphics/SolutionPanel.java similarity index 52% rename from src/main/org/insa/graphics/ShortestPathSolutionPanel.java rename to src/main/org/insa/graphics/SolutionPanel.java index a90c134..d059c4d 100644 --- a/src/main/org/insa/graphics/ShortestPathSolutionPanel.java +++ b/src/main/org/insa/graphics/SolutionPanel.java @@ -4,48 +4,45 @@ import java.awt.Color; import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.io.BufferedOutputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +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.JComboBox; -import javax.swing.JFileChooser; import javax.swing.JLabel; -import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JTextArea; import javax.swing.UIManager; import javax.swing.border.CompoundBorder; import javax.swing.border.EmptyBorder; -import org.insa.algo.shortestpath.ShortestPathData; -import org.insa.algo.shortestpath.ShortestPathData.Mode; +import org.insa.algo.AbstractInputData; +import org.insa.algo.AbstractSolution; import org.insa.algo.shortestpath.ShortestPathSolution; import org.insa.graph.Graph; -import org.insa.graph.io.BinaryPathWriter; +import org.insa.graph.Path; import org.insa.graphics.drawing.Drawing; import org.insa.graphics.drawing.overlays.PathOverlay; -public class ShortestPathSolutionPanel extends JPanel implements DrawingChangeListener, GraphChangeListener { +public class SolutionPanel extends JPanel implements DrawingChangeListener, GraphChangeListener { /** * */ private static final long serialVersionUID = 1L; - private class ShortestPathBundle { + private class SolutionBundle { // Solution - private final ShortestPathSolution solution; + private final AbstractSolution solution; // Path Overlay (not final due to redraw) - private PathOverlay overlay = null; + private List overlays = new ArrayList<>(); /** * Create a new bundle with the given solution and create a new overlay @@ -54,72 +51,88 @@ public class ShortestPathSolutionPanel extends JPanel implements DrawingChangeLi * @param solution Solution for this bundle, must not be null. * */ - public ShortestPathBundle(ShortestPathSolution solution) { + public SolutionBundle(AbstractSolution solution) { this.solution = solution; - if (this.solution.isFeasible()) { - this.overlay = drawing.drawPath(this.solution.getPath()); - } + this.overlays = createOverlaysFromSolution(); } /** * @return Solution associated with this bundle. */ - public ShortestPathSolution getSolution() { + public AbstractSolution getSolution() { return this.solution; } /** * @return Data assocaited with this bundle. */ - public ShortestPathData getData() { + public AbstractInputData getData() { return this.solution.getInputData(); } /** - * @return Overlay associated with this bundle, or null. + * @return Overlays associated with this bundle, or null. */ - public PathOverlay getOverlay() { - return this.overlay; + public List getOverlays() { + return this.overlays; } /** * Re-draw the current overlay (if any) on the new drawing. * */ - public void updateOverlay(Drawing newDrawing) { - if (this.overlay != null) { - PathOverlay oldOverlay = this.overlay; - this.overlay = newDrawing.drawPath(this.solution.getPath()); - this.overlay.setVisible(oldOverlay.isVisible()); - oldOverlay.delete(); + public void updateOverlays() { + List oldOverlays = this.overlays; + this.overlays = createOverlaysFromSolution(); + for (int i = 0; i < oldOverlays.size(); ++i) { + oldOverlays.get(i).delete(); } } + private List createOverlaysFromSolution() { + List overlays = new ArrayList<>(); + if (solution.isFeasible()) { + Method[] methods = this.solution.getClass().getDeclaredMethods(); + for (Method method: methods) { + if (method.getReturnType().equals(Path.class) && method.getParameterCount() == 0) { + try { + Path path = (Path) method.invoke(this.solution); + overlays.add(drawing.drawPath(path)); + } + catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + // This has been check before, so should never happen... + e.printStackTrace(); + } + } + } + } + return overlays; + } + /* * (non-Javadoc) * * @see java.lang.Object#toString() */ public String toString() { - return "Shortest-path from #" + this.getData().getOrigin().getId() + " to #" - + this.getData().getDestination().getId() + " [" + this.getData().getMode().toString().toLowerCase() - + "]"; + return getData().toString(); } + } // Solution private Drawing drawing; // Solution selector - private final JComboBox solutionSelect; + private final JComboBox solutionSelect; // Map solution -> panel private final JTextArea informationPanel; // Current bundle - private ShortestPathBundle currentBundle = null; + private SolutionBundle currentBundle = null; - public ShortestPathSolutionPanel(Component parent) { + public SolutionPanel(Component parent) { super(); setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); setBorder(new CompoundBorder(BorderFactory.createMatteBorder(1, 0, 1, 0, Color.LIGHT_GRAY), @@ -147,17 +160,15 @@ public class ShortestPathSolutionPanel extends JPanel implements DrawingChangeLi @Override public void actionPerformed(ActionEvent e) { - PathOverlay overlay = currentBundle.getOverlay(); - if (overlay == null) { - return; - } - if (overlay.isVisible()) { - overlay.setVisible(false); - clearButton.setText("Show"); - } - else { - overlay.setVisible(true); - clearButton.setText("Hide"); + for (PathOverlay overlay: currentBundle.getOverlays()) { + if (overlay.isVisible()) { + overlay.setVisible(false); + clearButton.setText("Show"); + } + else { + overlay.setVisible(true); + clearButton.setText("Hide"); + } } } }); @@ -166,26 +177,29 @@ public class ShortestPathSolutionPanel extends JPanel implements DrawingChangeLi saveButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - String filepath = System.getProperty("user.dir"); - filepath += File.separator + String.format("path_%s_%d_%d.path", - currentBundle.getData().getGraph().getMapId().toLowerCase().replaceAll("[^a-z0-9_]", "_"), - currentBundle.getData().getOrigin().getId(), currentBundle.getData().getDestination().getId()); - JFileChooser fileChooser = new JFileChooser(); - fileChooser.setSelectedFile(new File(filepath)); - fileChooser.setApproveButtonText("Save"); - - if (fileChooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) { - File file = fileChooser.getSelectedFile(); - try { - BinaryPathWriter writer = new BinaryPathWriter( - new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)))); - writer.writePath(currentBundle.getSolution().getPath()); - } - catch (IOException e1) { - JOptionPane.showMessageDialog(parent, "Unable to write path to the selected file."); - e1.printStackTrace(); - } - } + // String filepath = System.getProperty("user.dir"); + // filepath += File.separator + String.format("path_%s_%d_%d.path", + // currentBundle.getData().getGraph().getMapId().toLowerCase().replaceAll("[^a-z0-9_]", + // "_"), + // currentBundle.getData().getOrigin().getId(), + // currentBundle.getData().getDestination().getId()); + // JFileChooser fileChooser = new JFileChooser(); + // fileChooser.setSelectedFile(new File(filepath)); + // fileChooser.setApproveButtonText("Save"); + // + // if (fileChooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) { + // File file = fileChooser.getSelectedFile(); + // try { + // BinaryPathWriter writer = new BinaryPathWriter( + // new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)))); + // writer.writePath(currentBundle.getSolution().getPath()); + // } + // catch (IOException e1) { + // JOptionPane.showMessageDialog(parent, "Unable to write path to the selected + // file."); + // e1.printStackTrace(); + // } + // } } }); @@ -200,23 +214,25 @@ public class ShortestPathSolutionPanel extends JPanel implements DrawingChangeLi solutionSelect.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - ShortestPathBundle bundle = (ShortestPathBundle) solutionSelect.getSelectedItem(); + SolutionBundle bundle = (SolutionBundle) solutionSelect.getSelectedItem(); // Handle case when the JComboBox is empty. if (bundle == null) { return; } - if (currentBundle != null && currentBundle.getOverlay() != null) { - currentBundle.getOverlay().setVisible(false); + if (currentBundle != null) { + for (PathOverlay overlay: currentBundle.getOverlays()) { + overlay.setVisible(false); + } } updateInformationLabel(bundle); buttonPanel.setVisible(bundle.getSolution().isFeasible()); clearButton.setText(bundle.getSolution().isFeasible() ? "Hide" : "Show"); - if (bundle.getOverlay() != null) { - bundle.getOverlay().setVisible(true); + for (PathOverlay overlay: bundle.getOverlays()) { + overlay.setVisible(true); } currentBundle = bundle; @@ -226,31 +242,13 @@ public class ShortestPathSolutionPanel extends JPanel implements DrawingChangeLi } public void addSolution(ShortestPathSolution solution) { - ShortestPathBundle bundle = new ShortestPathBundle(solution); + SolutionBundle bundle = new SolutionBundle(solution); solutionSelect.addItem(bundle); solutionSelect.setSelectedItem(bundle); } - protected void updateInformationLabel(ShortestPathBundle bundle) { - ShortestPathData data = bundle.getData(); - String info = null; - if (!bundle.getSolution().isFeasible()) { - info = String.format("No path found from node #%d to node #%d.", data.getOrigin().getId(), - data.getDestination().getId()); - } - else { - info = String.format("Found a path from node #%d to node #%d", data.getOrigin().getId(), - data.getDestination().getId()); - if (data.getMode() == Mode.LENGTH) { - info = String.format("%s, %.4f kilometers.", info, - (bundle.getSolution().getPath().getLength() / 1000.0)); - } - else { - info = String.format("%s, %.4f minutes.", info, - (bundle.getSolution().getPath().getMinimumTravelTime() / 60.0)); - } - } - informationPanel.setText(info); + protected void updateInformationLabel(SolutionBundle bundle) { + informationPanel.setText(bundle.getSolution().toString()); } @Override @@ -263,19 +261,19 @@ public class ShortestPathSolutionPanel extends JPanel implements DrawingChangeLi solutionSelect.actionPerformed(null); } else { - ShortestPathBundle bundle = (ShortestPathBundle) this.solutionSelect.getSelectedItem(); - if (bundle != null && bundle.getOverlay() != null) { - bundle.getOverlay().setVisible(false); + SolutionBundle bundle = (SolutionBundle) this.solutionSelect.getSelectedItem(); + if (bundle != null) { + for (PathOverlay overlay: bundle.getOverlays()) { + overlay.setVisible(false); + } } } - } @Override public void newGraphLoaded(Graph graph) { for (int i = 0; i < this.solutionSelect.getItemCount(); ++i) { - PathOverlay overlay = this.solutionSelect.getItemAt(i).getOverlay(); - if (overlay != null) { + for (PathOverlay overlay: this.solutionSelect.getItemAt(i).getOverlays()) { overlay.delete(); } } @@ -294,7 +292,7 @@ public class ShortestPathSolutionPanel extends JPanel implements DrawingChangeLi @Override public void onRedrawRequest() { for (int i = 0; i < this.solutionSelect.getItemCount(); ++i) { - this.solutionSelect.getItemAt(i).updateOverlay(drawing); + this.solutionSelect.getItemAt(i).updateOverlays(); } }