package pipe.gui.widget; import com.esotericsoftware.kryo.io.Input; import com.esotericsoftware.kryo.io.Output; import pipe.reachability.algorithm.ExplorerUtilities; import pipe.reachability.algorithm.StateSpaceExplorer; import pipe.reachability.algorithm.TimelessTrapException; import pipe.reachability.algorithm.VanishingExplorer; import pipe.reachability.algorithm.parallel.MassiveParallelStateSpaceExplorer; import pipe.reachability.algorithm.sequential.SequentialStateSpaceExplorer; import uk.ac.imperial.io.*; import uk.ac.imperial.pipe.exceptions.InvalidRateException; import uk.ac.imperial.pipe.io.PetriNetIOImpl; import uk.ac.imperial.pipe.io.PetriNetReader; import uk.ac.imperial.pipe.models.petrinet.PetriNet; import uk.ac.imperial.state.ClassifiedState; import uk.ac.imperial.state.Record; import javax.swing.*; import javax.xml.bind.JAXBException; import java.awt.FileDialog; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.Collection; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; /** * JPanel used to load the state space exploration results from Petri nets and binary state space results. */ public class StateSpaceLoader { /** * Class logger */ private static final Logger LOGGER = Logger.getLogger(StateSpaceLoader.class.getName()); /** * Number of states to explore before reducing and writing out */ private static final int STATES_PER_THREAD = 100; /** * For loading Petri nets to explore */ private final FileDialog loadDialog; /** * Enabled if a Petri net is specified, disabled otherwise */ private JRadioButton useExistingPetriNetRadioButton; /** * Name label for the loaded Petri net */ private JTextField petriNetNameLabel; /** * Load from file radio, always enabled. Clicking on this * brings up the file loader */ private JRadioButton loadPetriNetFromFileRadioButton; /** * Main display panel with all the loading options */ private JPanel mainPanel; /** * Binary field label displaying the name of the binary state file loaded */ private JTextField stateFieldLabel; /** * Binary field label displaying the name of the binary transitions file loaded */ private JTextField transitionFieldLabel; /** * Load from binaries radio, always enabled. Clicking on this will * bring up the file loader twice, once for the states binary and * once for the transitions binary. */ private JRadioButton loadFromBinariesRadio; /** * Default petri net */ private PetriNet defaultPetriNet; /** * Temporary transitions file for generating results into */ private Path temporaryTransitions; /** * Temporary states file for generating results into */ private Path temporaryStates; /** * Last loaded Petri net via the load dialog */ private PetriNet lastLoadedPetriNet; /** * Binary transitions loaded when binary transitions radio check box is selected */ private Path binaryTransitions; /** * Binary states loaded when binary transitions radio check box is selected */ private Path binaryStates; /** * Sets up the load Petri net options with the "use current Petri net" disabled * * @param loadDialog dialog */ public StateSpaceLoader(FileDialog loadDialog) { this.loadDialog = loadDialog; useExistingPetriNetRadioButton.setEnabled(false); setUp(); } /** * Set up the binary radio button action listeners. * Clicking on the load Petri net brings up a since xml file loader * Clicking on the load from binaries brings up two loaders, once for * the states binaries and one for the transition binaries. */ private void setUp() { loadPetriNetFromFileRadioButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { loadData(); } }); loadFromBinariesRadio.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { loadBinaryFiles(); } }); } /** * Opens the file dialog and saves the selected Petri net into lastLoadedPetriNet * for use when calculating the state space exploration */ private void loadData() { loadDialog.setMode(FileDialog.LOAD); loadDialog.setTitle("Select petri net"); loadDialog.setVisible(true); File[] files = loadDialog.getFiles(); if (files.length > 0) { File path = files[0]; try { petriNetNameLabel.setText(path.getName()); PetriNetReader petriNetIO = new PetriNetIOImpl(); lastLoadedPetriNet = petriNetIO.read(path.getAbsolutePath()); } catch (JAXBException | FileNotFoundException e) { LOGGER.log(Level.SEVERE, e.getMessage()); } } } /** * Loads the transition and state binary files into the member variables */ private void loadBinaryFiles() { loadDialog.setMode(FileDialog.LOAD); loadDialog.setTitle("Load transitions file"); loadDialog.setVisible(true); File[] files = loadDialog.getFiles(); if (files.length > 0) { File file = files[0]; binaryTransitions = Paths.get(file.toURI()); transitionFieldLabel.setText(file.getName()); } else { return; } loadDialog.setTitle("Load states file"); loadDialog.setVisible(true); File[] statesFiles = loadDialog.getFiles(); if (statesFiles.length > 0) { File file = statesFiles[0]; binaryStates = Paths.get(file.toURI()); stateFieldLabel.setText(file.getName()); } else { LOGGER.log(Level.INFO, "No file loaded"); } } /** * Sets up the load Petri net options with "use current Petri net" set to * the petriNet parameter * * @param petriNet current Petri net * @param loadDialog dialog */ public StateSpaceLoader(PetriNet petriNet, FileDialog loadDialog) { defaultPetriNet = petriNet; this.loadDialog = loadDialog; setUp(); } public boolean isBinaryLoadChecked() { return loadFromBinariesRadio.isSelected(); } public PetriNet getPetriNet() { return useExistingPetriNetRadioButton.isSelected() ? defaultPetriNet : lastLoadedPetriNet; } public JPanel getMainPanel() { return mainPanel; } /** * Calculates the steady state exploration of a Petri net and stores its results * in a temporary file. * <p> * These results are then read in and turned into a graphical representation using mxGraph * which is displayed to the user * </p> * @param creator explorer creator * @param vanishingCreator vanishing creator * @param threads across which to spread work * @return state space explorer results * @throws TimelessTrapException unable to exit cyclic vanishing state * @throws InterruptedException thread interrupted * @throws ExecutionException task aborted due to exception * @throws IOException error doing IO * @throws InvalidRateException functional rate expression invalid * @throws StateSpaceLoaderException if error during loading from binaries */ public StateSpaceExplorer.StateSpaceExplorerResults calculateResults(ExplorerCreator creator, VanishingExplorerCreator vanishingCreator, int threads) throws IOException, InterruptedException, ExecutionException, InvalidRateException, TimelessTrapException, StateSpaceLoaderException { if (loadFromBinariesRadio.isSelected()) { return loadFromBinaries(); } else { KryoStateIO stateWriter = new KryoStateIO(); temporaryTransitions = getTransitionsPath(); temporaryStates = getStatesPath(); PetriNet petriNet = useExistingPetriNetRadioButton.isSelected() ? defaultPetriNet : lastLoadedPetriNet; if (petriNet == null) { String message; if (useExistingPetriNetRadioButton.isSelected()) { message = "Error cannot calculate analysis metrics. Please load a Petri net/binaries."; } else { message = "Error in loaded Petri net, could not read PNML file."; } throw new StateSpaceLoaderException(message); } ExplorerUtilities explorerUtils = creator.create(petriNet); VanishingExplorer vanishingExplorer = vanishingCreator.create(explorerUtils); return generateStateSpace(stateWriter, temporaryTransitions, temporaryStates, petriNet, explorerUtils, vanishingExplorer, threads); } } /** * Loads the transitions and states from binaries * * @return state space explorer results * @throws IOException if IO error * @throws StateSpaceLoaderException if error during loading from binaries * @return state space explorer results */ private StateSpaceExplorer.StateSpaceExplorerResults loadFromBinaries() throws IOException, StateSpaceLoaderException { StateReader stateReader = new KryoStateIO(); temporaryTransitions = getTransitionsPath(); temporaryStates = getStatesPath(); return processBinaryResults(stateReader, temporaryTransitions); } /** * @return Path for state space transitions * @throws IOException if IO error occurs */ private Path getTransitionsPath() throws IOException { return loadFromBinariesRadio.isSelected() ? binaryTransitions : Files.createTempFile("transitions", ".tmp"); } /** * @return Path for state space states * @throws IOException if IO error occurs */ private Path getStatesPath() throws IOException { return loadFromBinariesRadio.isSelected() ? binaryStates : Files.createTempFile("states", ".tmp"); } /** * Writes the state space into transitions and states * * @param stateWriter writer * @param transitions to write * @param states to write * @param threads number of worker threads to use * @return state space explorer results * @throws TimelessTrapException unable to exit cyclic vanishing state * @throws InterruptedException thread interrupted * @throws ExecutionException task aborted due to exception * @throws IOException error doing IO * @throws InvalidRateException functional rate expression invalid */ private StateSpaceExplorer.StateSpaceExplorerResults generateStateSpace(StateWriter stateWriter, Path transitions, Path states, PetriNet petriNet, ExplorerUtilities explorerUtils, VanishingExplorer vanishingExplorer, int threads) throws IOException, TimelessTrapException, ExecutionException, InvalidRateException, InterruptedException { try (OutputStream transitionStream = Files.newOutputStream(transitions); OutputStream stateStream = Files.newOutputStream(states)) { try (Output transitionOutput = new Output(transitionStream); Output stateOutput = new Output(stateStream)) { return writeStateSpace(stateWriter, transitionOutput, stateOutput, petriNet, explorerUtils, vanishingExplorer, threads); } } } /** * Processes the binary results and returns their state space * * @param stateReader reader * @param transitions to process * @return state space explorer results * @throws IOException if IO error * @throws StateSpaceLoaderException if error during loading from binaries */ private StateSpaceExplorer.StateSpaceExplorerResults processBinaryResults(StateReader stateReader, Path transitions) throws IOException, StateSpaceLoaderException { try (InputStream inputStream = Files.newInputStream(transitions); Input transitionInput = new Input(inputStream)) { try { Collection<Record> records = readResults(stateReader, transitionInput); int transitionCount = getTransitionCount(records); return new StateSpaceExplorer.StateSpaceExplorerResults(transitionCount, records.size()); } catch (IOException e) { throw new StateSpaceLoaderException( "Could not parse binaries.\nAre you sure they were generated using the PIPE 5 state space explorer module?", e); } } } /** * Writes the petriNet state space out to a temporary file which is referenced by the objectOutputStream * * @param stateWriter format in which to write the results to * @param transitionOutput stream to write state space to * @param stateOutput stream to write state integer mappings to * @param explorerUtilites explorer utilities * @param threads number of worker threads to use * @param vanishingExplorer * @return state space explorer results * @throws TimelessTrapException unable to exit cyclic vanishing state * @throws InterruptedException thread interrupted * @throws ExecutionException task aborted due to exception * @throws IOException error doing IO * @throws InvalidRateException functional rate expression invalid */ private StateSpaceExplorer.StateSpaceExplorerResults writeStateSpace(StateWriter stateWriter, Output transitionOutput, Output stateOutput, PetriNet petriNet, ExplorerUtilities explorerUtilites, VanishingExplorer vanishingExplorer, int threads) throws TimelessTrapException, ExecutionException, InterruptedException, IOException, InvalidRateException { StateProcessor processor = new StateIOProcessor(stateWriter, transitionOutput, stateOutput); StateSpaceExplorer stateSpaceExplorer = getStateSpaceExplorer(explorerUtilites, vanishingExplorer, processor, threads); return stateSpaceExplorer.generate(explorerUtilites.getCurrentState()); } private StateSpaceExplorer getStateSpaceExplorer( ExplorerUtilities explorerUtilites, VanishingExplorer vanishingExplorer, StateProcessor stateProcessor, int threads) { if (threads == 1) { return new SequentialStateSpaceExplorer(explorerUtilites, vanishingExplorer, stateProcessor); } return new MassiveParallelStateSpaceExplorer(explorerUtilites, vanishingExplorer, stateProcessor, threads, STATES_PER_THREAD); } /** * Reads results of steady state exploration into a collection of records * * @param stateReader reader * @param input to process * @return state transitions with rates * @throws IOException error doing IO */ private Collection<Record> readResults(StateReader stateReader, Input input) throws IOException { MultiStateReader reader = new EntireStateReader(stateReader); return reader.readRecords(input); } /** * @param records to process * @return the number of transitions in the state space */ private int getTransitionCount(Iterable<Record> records) { int sum = 0; for (Record record : records) { sum += record.successors.size(); } return sum; } /** * Loads and processes state space * * @return results * @throws IOException error doing IO * @throws StateSpaceLoaderException if error during loading from binaries */ public Results loadStateSpace() throws StateSpaceLoaderException, IOException { KryoStateIO stateReader = new KryoStateIO(); try (InputStream inputStream = Files.newInputStream(temporaryTransitions); InputStream stateInputStream = Files.newInputStream(temporaryStates); Input transitionInput = new Input(inputStream); Input stateInput = new Input(stateInputStream)) { Collection<Record> records = readResults(stateReader, transitionInput); Map<Integer, ClassifiedState> stateMap = readMappings(stateReader, stateInput); return new Results(records, stateMap); } } /** * Reads results of the mapping of an integer state representation to * the Classified State it represents * * @param stateReader reader * @param input to process * @return state mappings * @throws IOException error doing IO */ private Map<Integer, ClassifiedState> readMappings(StateReader stateReader, Input input) throws IOException { MultiStateReader reader = new EntireStateReader(stateReader); return reader.readStates(input); } public void saveBinaryFiles() { if (temporaryStates != null && temporaryTransitions != null) { copyFile(temporaryTransitions, "Select location for temporary transitions"); copyFile(temporaryStates, "Select location for temporary states"); } } /** * @param temporary path to copy to new location * @param message displayed message in save file dialog pop up */ private void copyFile(Path temporary, String message) { loadDialog.setMode(FileDialog.SAVE); loadDialog.setTitle(message); loadDialog.setVisible(true); File[] files = loadDialog.getFiles(); if (files.length > 0) { File file = files[0]; Path path = Paths.get(file.toURI()); try { Files.copy(temporary, path, StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { LOGGER.log(Level.SEVERE, e.getMessage()); } } } public void addPetriNetRadioListener(ActionListener listener) { loadPetriNetFromFileRadioButton.addActionListener(listener); useExistingPetriNetRadioButton.addActionListener(listener); } public void addBinariesListener(ActionListener listener) { loadFromBinariesRadio.addActionListener(listener); } /** * Used in place of a lambda to create the explorer utilities needed for generating the * state space from a Petri net */ public interface ExplorerCreator { ExplorerUtilities create(PetriNet petriNet); } /** * Used in place of a lambda to create the vanishing utilities needed for * generating the state space from a Petri net */ public interface VanishingExplorerCreator { VanishingExplorer create(ExplorerUtilities utils); } /** * State space exploration results */ public class Results { /** * Transition records */ public final Collection<Record> records; /** * Classified state mappings */ public final Map<Integer, ClassifiedState> stateMappings; /** * Constructor * * @param records of results * @param stateMappings state mappings */ public Results(Collection<Record> records, Map<Integer, ClassifiedState> stateMappings) { this.records = records; this.stateMappings = stateMappings; } } }