diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..30f97862 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +ecdar@cs.aau.dk. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. \ No newline at end of file diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 00000000..0f73619e --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,96 @@ +# Contributing +When contributing to this repository, make your own fork and create pull requests to this repo from there. + +## Issues +If you find a bug or a missing feature, feel free to create an issue. The system is continuously under development and suggestions are always welcome. + +### Bugs +To increase the chances of the issue being resolved, please include the following (or use the `Bug Report` issue template): +- Concise title that states the issue +- An in-depth description of the issue + - What happened + - What was expected to happen + - Suggestion on possible cause [Not required] +- Images (if relevant) + +### Feature +To increase the chances of the issue being resolved, please include the following (or use the `Feature Request` issue template): +- Concise title that describes the feature +- An in-depth description of the feature + - What is the feature + - Use case + - How should it work for the user + - Suggestions for implementation [Not required] +- Reasoning behind the request + - Added value + - Improved user experience + +> :information_source: To help organising the issue, please attach relevant tags (eg. `bug`, `feature`, etc.). + +## Pull Requests +Pull requests are continuously being reviewed and merged. In order to ease this process, please open a pull request as draft, as long as it is under development, to notify anyone else that a given feature/issue is being worked on. + +Additionally, please add `Closes #{ISSUE_ID}` if the pull request is linked to a specific issue. If a PR addresses multiple pull requests, please add `Closes #{ISSUE_ID}` for each one. + +A CI workflow is executed against all pull requests and must succeed before the pull request can be merged, so please make sure that you check the workflow status and potential error messages. + +## Tests +All non-UI tests are executed as part of the CI workflow and hence must succeed before merging. The tests are written with JUnit and relevant tests should be added when new code is added. If you are new to JUnit, you can check out syntax and structure [here](https://junit.org/junit5/docs/current/user-guide/). + +The test suite can be executed locally by running: +```shell +./gradlew test +``` + +> :information_source: Currently, the codebase has high coupling, which has made testing difficult and the test suite very small. + +### UI Tests +For features that are highly coupled with the interface, a second test suite has been added under `src/test/java/ecdar/ui`. These tests are excluded from the `test` task are can be executed by running: +```shell +./gradlew uiTest +``` +These tests are more intensive to run and utilizes a robot for interacting with a running process of the GUI. The tests are implemented using [TestFX](https://github.com/TestFX/TestFX). As these tests are more intensive, they are not run as part of the standard CI workflow. + +You should prefer writing non-UI tests, as they are less demanding and are part of the CI workflow. + +## Code Organisation +The code within the project is structure based on the Model-View-ViewModel (**MVVM**) architectural pattern. However, the terms _Abstraction_, _Presentation_, and _Controller_ are used instead of _Model_, _View_, and _View-Model_ respectively. +This means that each element in the system consists of: +- An _Abstraction_ (located in `abstractions` package). +- A _Controller_ (located in `controllers` package). +- A _Presentation_ (located in `presentations` package). + - Most of the presentations are related to an `FXML` markup file that specifies the look of the presentation. These files are located in `src/main/resources/ecdar/presentations`. + +In addition to these, a `utility` package is used for additional business logic to improve separation of concern and enhance the testability of the system. + +### Abstractions +The abstractions are used to represent logical elements, such as `components`, `locations`, and `edges`. These classes should mostly be pure data objects. They are used to save and load data to and from existing project files. + +### Controllers +The controllers contain the business logic of the system. They function as the link between the UI and the abstractions. +This is implemented such that an action performed to an element in the UI triggers a method inside the controller, which then alters the state of the related abstraction. + +They implement the `Initializable` interface and are initialized through their associated presentation when an instance of that is instantiated. Hierarchically, a presentation therefore contains a controller. + +Each controller controls an instance of its related abstraction. If an action to one element should affect another element, this effect is enforced through the controller. + +### Presentations +As mentioned above, most of the presentations are split into a Java class and an FXML markup file. The Java class can be seen as a shell to initialize the FXML element from inside the business logic. It initializes the related controller and ensures that any needed elements are set within it. This allows the controllers to be initialized without any UI elements, which is very useful for testing, while ensuring that they are correctly connected while the UI is running. + +The FXML files are markup specifying how the elements should look in the UI and have a reference to the related controller. Each element that should be addressable or changeable from the controller has an `fx:id` that is directly referenced as a member inside the controller. The direct connection to a controller allows events, such as `onPressed`, to trigger the correct methods in the controller and also helps IDEs identified any potentially missing methods or members. + +> :question: **Why use both a controller and a presentation Java file?**\ +> The advantage of during this is that the `controller` can contain all the business logic and bindings to the FXML elements, while the `presentation` can be used to instantiate and reference the UI elements inside the Java code. The `controller` should contain the logic and is bound within the FXML file, so the `presentation` Java file should be seen as a shell. + +### Utility +To increase the testability and separation of concern further, the `utility` package is introduced. This package includes useful functionality that is either used in multiple unrelated classes or outside the responsibility of the given class. + +An example of one of the classes located in this package is the `UndoRedoStack` used to keep track of actions performed by the user. + +### Miscellaneous +Besides the packages mentioned above, some larger functionalities are located in their own packages. Here is a small description of each: +- `backend`: Responsible for the communication with the engines and model checking. +- `code_analysis`: Responsible for analysing the elements of the current project and construct messages if errors or warnings are encountered. +- `issues`: Classes for representing `Errors`, `Issues`, and `Warnings`. +- `model_canvas.arrow_heads`: Arrowheads used in the UI to visualize the direction of edges. +- `mutation [Deprecrated]`: Functionality for supporting mutation testing of components. **This feature is currently not implemented in the engines and is therefore currently not supported**. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..ab912f27 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,44 @@ +--- +name: Bug +about: File a bug report +title: '[BUG] ' +labels: bug +assignees: '' +--- + +<!-- +Note: Please search to see if an issue already exists for the bug you encountered. +--> + +### Description +<!-- +Should include: +1. What happened +2. What was expected to happen +3. Suggestion on possible cause [Not required] +--> +#### Where was the error encountered +- [ ] Using a distribution from the main ECDAR repository + + +- [ ] Using a local JDK + - **Used JDK** (ex. Zulu 11.62.17 JDK FX 11.0.18): + - **Operating System**: + - [ ] Linux + - [ ] MacOS Arm + - [ ] MacOS Intel + - [ ] Windows + +### Steps To Reproduce +<!-- +Example: steps to reproduce the behavior: +1. In this environment... +2. With this config... +3. Run '...' +4. See error... +--> + +### Images +<!-- +Links? References? Anything that will give us more context about the issue that you are encountering! +--> diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..8e8d9712 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,25 @@ +--- +name: Feature +about: Suggest a new feature +title: '[Feature] <title>' +labels: enhancement +assignees: '' +--- + +<!-- +Note: Please search to see if an issue already exists for the feature you request. +--> + +### Description +<!-- +Should include: +1. What is the feature +2. Use case +2. How should it work for the user +3. Suggestions for implementation [Not required] +--> + +### Reason For Request +<!-- +Explanation for the request and how it will improve the system/user experience +--> \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..4330290c --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,4 @@ +Closes #{ISSUE_ID} + +### Changes/Additions: +- \ No newline at end of file diff --git a/README.md b/README.md index 382e33fd..47f4663d 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,84 @@ # Ecdar - Ecdar is an abbreviation of Environment for Compositional Design and Analysis of Real Time Systems. -This repo contains the source code for the graphical user interface, in order to run queries you will need the +This repo contains the source code for the graphical user interface. In order to run queries you will need the j-ecdar and revaal executables. +> :information_source: If the goal is to use ECDAR, please goto the [main ECDAR repository](https://github.com/Ecdar/ECDAR), which contains releases for all supported platforms. These releases contain all dependencies, including the engines and a JRE. + +## Screenshots +| <img src="presentation/Retailer.png" width="400"> <img src="presentation/Administration.png" width="400"> | <img src="presentation/UniversityExample.png" width="400"> | +|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------| + +## H-UPPAAL +This project is a hard fork of https://github.com/ulriknyman/H-Uppaal. + +<a id="dependencies"></a> ## Dependencies -This repository utilizes the Ecdar-Proto repository for structuring the communication between the GUI and the engines. This dependency is implemented as a submodule which needs to be pulled and updated. If you have not yet cloned the code from this repository (the GUI), you can clone both the GUI and the submodule containing the Proto repository by running the following command: +This section covers what dependencies are currently needed by the GUI. + +### JVM +As with all Java applications, a working JVM is required to run the project. + +You will need Java version 11 containing JavaFX. We suggest downloading Azul's Java 11 from https://www.azul.com/downloads/?version=java-11-lts&package=jdk-fx, as this is the version used by the main development team. -``` sh +### Ecdar-ProtoBuf +This repository utilizes the [Ecdar-ProtoBuf repository](https://github.com/Ecdar/Ecdar-ProtoBuf) for the communication with the engines. This dependency is implemented as a submodule that needs to be pulled and updated. If you have not yet cloned the code from this repository (the GUI), you can clone both the GUI and the submodule containing the ProtoBuf repository by running the following command: + +```shell git clone --recurse-submodules git@github.com:Ecdar/Ecdar-GUI.git ``` -If you have already cloned this repository, you can clone the Proto submodule by running the following command, from a terminal inside the GUI repository directory: +If you have already cloned this repository, you can clone the ProtoBuf submodule by running the following command from a terminal in the GUI repository directory: -``` sh +```shell git submodule update --init --recursive ``` -## How to Run -You will need a working JVM verion 11 with java FX in order to run the GUI. We suggest downloading from https://www.azul.com/downloads/?version=java-11-lts&package=jdk-fx. +### Engines (needed for model-checking) +In order to use the model-checking capabilities of the system, it is necessary to download at least one engine for the used operating system and place it in the `lib` directory. + +> :information_source: The latest version of each engine can be downloaded from: +> * https://github.com/Ecdar/j-Ecdar +> * https://github.com/Ecdar/Reveaal -To run the gui use the gradle wrapper script +The engines can then be configured in the GUI as described in [Engine Configuration](#engine_configuration). -``` sh -./gradlew(.bat) run +## How to Run +After having retrieved the code and acquired all the dependencies mentioned in [Dependencies](#dependencies), the GUI can be started using the following command: +```shell +./gradlew run ``` +> :information_source: All Gradle commands in this document are Unix specific, for Windows users, replace `./gradlew` with `./gradlew.bat`. + +<a id="engine_configuration"></a> ## Engine Configuration -Download the latest version of the engine from: +In order to utilize the model-checking capabilities of the system, at least one engine must be configured. +The distributions available at [ECDAR](https://github.com/Ecdar/ECDAR) will automatically load the default engines on startup, but this is currently not working when running the GUI through Gradle. +For the same reason, the `Reset Engines` button will clear the engines but will not be able to load the packaged once. - * https://github.com/Ecdar/j-Ecdar - * https://github.com/Ecdar/Reveaal +An engine can be added through the configurator found under `Options > Engines Options` in the menubar, which opens the pop-up shown below. -Unpack and move the downloaded files to the `lib` folder. You can also configure custom engine locations from the GUI. +<img src="presentation/EngineConfiguration.png" alt="Engine Configuration Pop-up"> +> :information_source: If you accidentally removed or changed an engine, these changes can be reverted be pressing `Cancel` or by clicking outside the pop-up. Consequently, if any changes should be saved, **MAKE SURE TO PRESS `Save`** -## Screenshots +### Address +The _Address_ is either the address of a server running the engine (for remote execution) or a path to a local engine binary (for this, the _Local_ checkbox must be checked). -| <img src="presentation/Retailer.png" width="400"> <img src="presentation/Administration.png" width="400"> | <img src="presentation/UniversityExample.png" width="400"> | -|------------------------------------------------------------------------------------------------------------|------------------------------------------------------------| +### Port range +The GUI uses gRPC for the communication with the engines and will therefore need at least one free port. This range directly limits the number of instances of the engine that will be started. +> :warning: Make sure AT LEAST one port is free within the specified range. For instance, the default port range for Reveaal is _5032_ - _5040_. -Sample Projects ----- -See sample projects in the `samples` folder. +### Default +If an engine is marked with _Default_, all added queries will be assigned that engine. -H-UPPAAL ----------- -This project is a hard fork of https://github.com/ulriknyman/H-Uppaal. +## Exemplary Projects +To get started and get an idea of what the system can be used for, multiple examples can be found in the `examples` directory. +These projects include preconfigured models and queries to execute against them. +For the theoretical background and what the tool can be used for, please check out the latest research links at [here](https://ulrik.blog.aau.dk/ecdar/). +# Contributing +If you are interested in contributing to the project, please read the [contributing](.github/CONTRIBUTING.md) file. Here you will find guides on how to create issues and commit changes to the repository. \ No newline at end of file diff --git a/examples/AGTest/Queries.json b/examples/AGTest/Queries.json index f367b70a..89fff7bd 100644 --- a/examples/AGTest/Queries.json +++ b/examples/AGTest/Queries.json @@ -5,7 +5,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "refinement: ((A1 || G2) \\ A2) \u003c\u003d G1", @@ -13,7 +13,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "refinement: ((A1 || G1) \\ A2) \u003c\u003d Imp", @@ -21,6 +21,6 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" } ] \ No newline at end of file diff --git a/examples/CarAlarm/Model/Queries.json b/examples/CarAlarm/Model/Queries.json index d9a20d12..b5164aa2 100644 --- a/examples/CarAlarm/Model/Queries.json +++ b/examples/CarAlarm/Model/Queries.json @@ -5,7 +5,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "specification: Alarm", @@ -13,7 +13,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "reachability: Alarm.L11", @@ -21,7 +21,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "reachability: Alarm.L12", @@ -29,7 +29,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "reachability: Alarm.L9", @@ -37,7 +37,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "specification: (Alarm: A[] not Alarm.L7 || Alarm.x\u003c\u003d30)", @@ -45,6 +45,6 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" } ] diff --git a/examples/EcdarUniversity/Queries.json b/examples/EcdarUniversity/Queries.json index 2e1c4137..0e19ca11 100644 --- a/examples/EcdarUniversity/Queries.json +++ b/examples/EcdarUniversity/Queries.json @@ -5,7 +5,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "specification: Spec", @@ -13,7 +13,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "consistency: (Administration || Machine || Researcher)", @@ -21,7 +21,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "consistency: (Administration || Machine || Researcher)", @@ -29,7 +29,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "refinement: (Administration || Machine || Researcher) \u003c\u003d Spec", @@ -37,6 +37,6 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" } ] \ No newline at end of file diff --git a/examples/FishRetailer/Model/Queries.json b/examples/FishRetailer/Model/Queries.json index e660ab09..a2131482 100644 --- a/examples/FishRetailer/Model/Queries.json +++ b/examples/FishRetailer/Model/Queries.json @@ -5,6 +5,6 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" } ] \ No newline at end of file diff --git a/examples/SenderReceiver/Queries.json b/examples/SenderReceiver/Queries.json index 54261ba5..bf94c503 100644 --- a/examples/SenderReceiver/Queries.json +++ b/examples/SenderReceiver/Queries.json @@ -5,7 +5,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "specification: Sender", @@ -13,7 +13,7 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" }, { "query": "implementation: Sender", @@ -21,6 +21,6 @@ "isPeriodic": false, "ignoredInputs": {}, "ignoredOutputs": {}, - "backend": "Reveaal" + "engine": "Reveaal" } ] \ No newline at end of file diff --git a/presentation/EngineConfiguration.png b/presentation/EngineConfiguration.png new file mode 100644 index 00000000..38b26daa Binary files /dev/null and b/presentation/EngineConfiguration.png differ diff --git a/src/main/java/ecdar/Ecdar.java b/src/main/java/ecdar/Ecdar.java index 542b788b..8efe1da7 100644 --- a/src/main/java/ecdar/Ecdar.java +++ b/src/main/java/ecdar/Ecdar.java @@ -1,16 +1,12 @@ package ecdar; -import ecdar.abstractions.Component; -import ecdar.abstractions.Project; -import ecdar.backend.BackendDriver; +import ecdar.abstractions.*; +import ecdar.backend.BackendException; import ecdar.backend.BackendHelper; -import ecdar.backend.QueryHandler; import ecdar.backend.SimulationHandler; import ecdar.code_analysis.CodeAnalysis; -import ecdar.controllers.EcdarController; -import ecdar.presentations.BackgroundThreadPresentation; -import ecdar.presentations.EcdarPresentation; -import ecdar.presentations.UndoRedoHistoryPresentation; +import ecdar.issues.ExitStatusCodes; +import ecdar.presentations.*; import ecdar.utility.keyboard.Keybind; import ecdar.utility.keyboard.KeyboardTracker; import javafx.application.Application; @@ -53,14 +49,13 @@ public class Ecdar extends Application { private static EcdarPresentation presentation; private static BooleanProperty isUICached = new SimpleBooleanProperty(); public static BooleanProperty shouldRunBackgroundQueries = new SimpleBooleanProperty(true); - private static final BooleanProperty isSplit = new SimpleBooleanProperty(true); //Set to true to ensure correct behaviour at first toggle. - private static BackendDriver backendDriver = new BackendDriver(); - private static QueryHandler queryHandler = new QueryHandler(backendDriver); + private static final BooleanProperty isSplit = new SimpleBooleanProperty(false); private static SimulationHandler simulationHandler; private Stage debugStage; /** * Gets the absolute path to the server folder + * * @return */ public static String getServerPath() { @@ -75,6 +70,7 @@ public static String getServerPath() { /** * Gets the path to the root directory. + * * @return the path to the root directory * @throws URISyntaxException if the source code location could not be converted to an URI */ @@ -119,6 +115,7 @@ public static void main(final String[] args) { /** * Gets the project. + * * @return the project */ public static Project getProject() { @@ -147,6 +144,7 @@ public static BooleanProperty toggleLeftPane() { * Toggles whether to cache UI. * Caching reduces CPU usage on some devices. * It also increases GPU 3D engine usage on some devices. + * * @return the property specifying whether to cache */ public static BooleanProperty toggleUICache() { @@ -157,6 +155,7 @@ public static BooleanProperty toggleUICache() { /** * Toggles whether checks are run in the background. * Running checks in the background increases CPU usage and power consumption. + * * @return the property specifying whether to run checks in the background */ public static BooleanProperty toggleRunBackgroundQueries() { @@ -168,26 +167,15 @@ public static BooleanProperty toggleQueryPane() { return presentation.toggleRightPane(); } - /** - * Toggles whether to canvas is split or single. - * @return the property specifying whether the canvas is split - */ - public static BooleanProperty toggleCanvasSplit() { - isSplit.set(!isSplit.get()); - + public static BooleanProperty isSplitProperty() { return isSplit; } /** - * Returns the backend driver used to execute queries and handle simulation - * @return BackendDriver + * Toggles whether to canvas is split or single. */ - public static BackendDriver getBackendDriver() { - return backendDriver; - } - - public static QueryHandler getQueryExecutor() { - return queryHandler; + public static void toggleCanvasSplit() { + isSplit.set(!isSplit.get()); } public static SimulationHandler getSimulationHandler() { return simulationHandler; } @@ -213,10 +201,6 @@ public void start(final Stage stage) { // Print launch message the user, if terminal is being launched System.out.println("Launching ECDAR..."); - // Load or create new project - project = new Project(); - simulationHandler = new SimulationHandler(getBackendDriver()); - // Set the title for the application stage.setTitle("Ecdar " + VERSION); @@ -264,13 +248,21 @@ public void start(final Stage stage) { Ecdar.showToast("The application icon could not be loaded"); } - // We're now ready! Let the curtains fall! - stage.show(); + BackendHelper.addEngineInstanceListener(() -> { + // When the engines change, clear the backendDriver + // to prevent dangling connections and queries + try { + BackendHelper.clearEngineConnections(); + } catch (BackendException e) { + showToast("An exception was encountered during shutdown of engine connections"); + } + }); - project.reset(); + // Whenever the Runtime is requested to exit, exit the Platform first + Runtime.getRuntime().addShutdownHook(new Thread(Platform::exit)); - // Set active model - Platform.runLater(() -> EcdarController.setActiveModelForActiveCanvas(Ecdar.getProject().getComponents().get(0))); + // We're now ready! Let the curtains fall! + stage.show(); // Register a key-bind for showing debug-information KeyboardTracker.registerKeybind("DEBUG", new Keybind(new KeyCodeCombination(KeyCode.F12), () -> { @@ -309,85 +301,23 @@ public void start(final Stage stage) { })); stage.setOnCloseRequest(event -> { - BackendHelper.stopQueries(); + int statusCode = ExitStatusCodes.SHUTDOWN_SUCCESSFUL.getStatusCode(); try { - backendDriver.closeAllBackendConnections(); - queryHandler.closeAllBackendConnections(); - simulationHandler.closeAllBackendConnections(); - } catch (IOException e) { - e.printStackTrace(); + BackendHelper.clearEngineConnections(); + } catch (BackendException e) { + statusCode = ExitStatusCodes.CLOSE_ENGINE_CONNECTIONS_FAILED.getStatusCode(); } - Platform.exit(); - System.exit(0); - }); - - BackendHelper.addBackendInstanceListener(() -> { - // When the backend instances change, re-instantiate the backendDriver - // to prevent dangling connections and queries try { - backendDriver.closeAllBackendConnections(); - queryHandler.closeAllBackendConnections(); - simulationHandler.closeAllBackendConnections(); - } catch (IOException e) { - throw new RuntimeException(e); - } - - backendDriver = new BackendDriver(); - }); - } - - /** - * Initializes and resets the project. - * This can be used as a test setup. - */ - public static void setUpForTest() { - project = new Project(); - project.reset(); - - // This implicitly starts the fx-application thread - // It prevents java.lang.RuntimeException: Internal graphics not initialized yet - // https://stackoverflow.com/questions/27839441/internal-graphics-not-initialized-yet-javafx - // new JFXPanel(); - } - - public static void initializeProjectFolder() throws IOException { - // Make sure that the project directory exists - final File directory = new File(projectDirectory.get()); - FileUtils.forceMkdir(directory); - - CodeAnalysis.getErrors().addListener(new ListChangeListener<CodeAnalysis.Message>() { - @Override - public void onChanged(Change<? extends CodeAnalysis.Message> c) { - CodeAnalysis.getErrors().forEach(message -> { - System.out.println(message.getMessage()); - }); + System.exit(statusCode); + } catch (SecurityException e) { + // Forcefully shutdown the Java Runtime + Runtime.getRuntime().halt(ExitStatusCodes.GRACEFUL_SHUTDOWN_FAILED.getStatusCode()); } }); - CodeAnalysis.clearErrorsAndWarnings(); - CodeAnalysis.disable(); - getProject().clean(); - - // Deserialize the project - Ecdar.getProject().deserialize(directory); - CodeAnalysis.enable(); - // Generate all component presentations by making them the active component in the view one by one - Component initialShownComponent = null; - for (final Component component : Ecdar.getProject().getComponents()) { - // The first component should be shown - if (initialShownComponent == null) { - initialShownComponent = component; - } - EcdarController.setActiveModelForActiveCanvas(component); - } - - // If we found a component set that as active - if (initialShownComponent != null) { - EcdarController.setActiveModelForActiveCanvas(initialShownComponent); - } - serializationDone = true; + project = presentation.getController().getEditorPresentation().getController().projectPane.getController().project; } private void loadFonts() { @@ -422,6 +352,44 @@ private void loadFonts() { Font.loadFont(getClass().getResourceAsStream("fonts/roboto_mono/RobotoMono-ThinItalic.ttf"), 14); } + /** + * Initializes and resets the project. + * This can be used as a test setup. + */ + public static void setUpForTest() { + project = new Project(); + + // This implicitly starts the fx-application thread + // It prevents java.lang.RuntimeException: Internal graphics not initialized yet + // https://stackoverflow.com/questions/27839441/internal-graphics-not-initialized-yet-javafx + // new JFXPanel(); + } + + public static void initializeProjectFolder() throws IOException { + // Make sure that the project directory exists + final File directory = new File(projectDirectory.get()); + FileUtils.forceMkdir(directory); + + CodeAnalysis.getErrors().addListener((ListChangeListener<CodeAnalysis.Message>) c -> CodeAnalysis.getErrors().forEach(message -> { + System.out.println(message.getMessage()); + })); + + CodeAnalysis.clearErrorsAndWarnings(); + CodeAnalysis.disable(); + getProject().clean(); + + // Deserialize the project + getProject().deserialize(directory); + CodeAnalysis.enable(); + + // If we found a component set that as active + serializationDone = true; + } + + public static ComponentPresentation getComponentPresentationOfComponent(Component component) { + return getPresentation().getController().getEditorPresentation().getController().projectPane.getController().getComponentPresentations().stream().filter(componentPresentation -> componentPresentation.getController().getComponent().equals(component)).findFirst().orElse(null); + } + private static String getVersion() { Properties defaultProps = new Properties(); try { diff --git a/src/main/java/ecdar/abstractions/Component.java b/src/main/java/ecdar/abstractions/Component.java index 1d0d38c0..b83808a2 100644 --- a/src/main/java/ecdar/abstractions/Component.java +++ b/src/main/java/ecdar/abstractions/Component.java @@ -1,36 +1,28 @@ package ecdar.abstractions; -import com.bpodgursky.jbool_expressions.*; -import com.bpodgursky.jbool_expressions.rules.RuleSet; -import ecdar.Ecdar; -import ecdar.utility.ExpressionHelper; import ecdar.utility.UndoRedoStack; -import ecdar.utility.colors.Color; import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.Boxed; +import ecdar.utility.helpers.MouseCircular; import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import ecdar.utility.helpers.MouseCircular; -import ecdar.utility.keyboard.NudgeDirection; import javafx.beans.property.*; import javafx.beans.value.ChangeListener; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; -import javafx.collections.transformation.FilteredList; -import javafx.util.Pair; -import org.apache.commons.lang3.tuple.Triple; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import static ecdar.abstractions.Project.LOCATION; + /** * A component that models an I/O automata. */ -public class Component extends HighLevelModelObject implements Boxed { - private static final String COMPONENT = "Component"; +public class Component extends HighLevelModel implements Boxed { private static final String LOCATIONS = "locations"; private static final String EDGES = "edges"; private static final String INCLUDE_IN_PERIODIC_CHECK = "includeInPeriodicCheck"; @@ -53,7 +45,6 @@ public class Component extends HighLevelModelObject implements Boxed { // Styling properties private final Box box = new Box(); private final BooleanProperty declarationOpen = new SimpleBooleanProperty(false); - private final BooleanProperty firsTimeShown = new SimpleBooleanProperty(false); public Location previousLocationForDraggedEdge; public boolean getIsFailing(){return isFailing.get();} @@ -71,272 +62,32 @@ public Component() { /** * Creates a component with a specific name and a boolean value that chooses whether the colour for this component is chosen at random - * @param doRandomColor boolean that is true if the component should choose a colour at random and false if not + * @param color boolean that is true if the component should choose a colour at random and false if not */ - public Component(final boolean doRandomColor) { - setComponentName(); - - if(doRandomColor) { - setRandomColor(); - } + public Component(final EnabledColor color, final String name) { + setName(name); + setColor(color); // Make initial location final Location initialLocation = new Location(); - initialLocation.initialize(); + initialLocation.initialize(LOCATION + "1"); initialLocation.setType(Location.Type.INITIAL); - initialLocation.setColorIntensity(getColorIntensity()); - initialLocation.setColor(getColor()); // Place in center initialLocation.setX(box.getX() + box.getWidth() / 2); initialLocation.setY(box.getY() + box.getHeight() / 2); + initialLocation.setColor(color); - locations.add(initialLocation); + addLocation(initialLocation); initializeIOListeners(); } public Component(final JsonObject json) { - setFirsTimeShown(true); - deserialize(json); initializeIOListeners(); updateIOList(); } - /** - * Creates a clone of another component. - * Copies objects used for verification (e.g. locations, edges and the declarations). - * Does not copy UI elements (sizes and positions). - * It locations are cloned from the original component. Their ids are the same. - * Does not initialize io listeners, but copies the input and output strings. - * Reachability analysis binding is not initialized. - * @return the clone - */ - public Component cloneForVerification() { - final Component clone = new Component(); - clone.addVerificationObjects(this); - clone.setIncludeInPeriodicCheck(false); - clone.inputStrings.addAll(getInputStrings()); - clone.outputStrings.addAll(getOutputStrings()); - clone.setName(getName()); - - return clone; - } - - /** - * Adds objects used for verifications to this component. - * @param original the component to add from - */ - private void addVerificationObjects(final Component original) { - for (final Location originalLoc : original.getLocations()) { - addLocation(originalLoc.cloneForVerification()); - } - - getListOfEdgesFromDisplayableEdges(original.getDisplayableEdges()).forEach(edge -> addEdge((edge).cloneForVerification(this))); - setDeclarationsText(original.getDeclarationsText()); - } - - /** - * Applies angelic completion on this component. - */ - public void applyAngelicCompletion() { - // Cache input signature, since it could be updated when added edges - final List<String> inputStrings = new ArrayList<>(getInputStrings()); - - getLocations().forEach(location -> inputStrings.forEach(input -> { - // Get outgoing input edges that has the chosen input - final List<Edge> matchingEdges = getListOfEdgesFromDisplayableEdges(getOutgoingEdges(location)).stream().filter( - edge -> edge.getStatus().equals(EdgeStatus.INPUT) && - edge.getSync().equals(input)).collect(Collectors.toList() - ); - - // If no such edges, add a self loop without a guard - if (matchingEdges.isEmpty()) { - final Edge edge = new Edge(location, EdgeStatus.INPUT); - edge.setTargetLocation(location); - edge.addSyncNail(input); - addEdge(edge); - return; - } - - // If an edge has no guard and its target has no invariants, ignore. - // Component is already input-enabled with respect to this location and input. - if (matchingEdges.stream().anyMatch(edge -> edge.getGuard().isEmpty() && - edge.getTargetLocation().getInvariant().isEmpty())) return; - - // Extract expression for which edges to create. - // The expression is in DNF - // We create self loops for each child expression in the disjunction. - createAngelicSelfLoops(location, input, getNegatedEdgeExpression(matchingEdges)); - })); - } - - /** - * Extracts an expression that represents the negation of a list of edges. - * Multiple edges are resolved as disjunctions. - * There can be conjunction in guards. - * We translate each edge to the conjunction of its guard and the invariant of its target location - * (since it should also be satisfied). - * The result is converted to disjunctive normal form. - * @param edges the edges to extract from - * @return the expression that represents the negation - */ - private Expression<String> getNegatedEdgeExpression(final List<Edge> edges) { - final List<String> clocks = getClocks(); - return ExpressionHelper.simplifyNegatedSimpleExpressions( - RuleSet.toDNF(RuleSet.simplify(Not.of(Or.of(edges.stream() - .map(edge -> { - final List<String> clocksToReset = ExpressionHelper.getUpdateSides(edge.getUpdate()) - .keySet().stream().filter(clocks::contains).collect(Collectors.toList()); - return And.of( - ExpressionHelper.parseGuard(edge.getGuard()), - ExpressionHelper.parseInvariantButIgnore(edge.getTargetLocation().getInvariant(), clocksToReset) - ); - } - ).collect(Collectors.toList())) - )))); - } - - /** - * Creates self loops on a location to finish missing inputs with an angelic completion. - * The guard expression should be in DNF without negations. - * @param location the location to create self loops on - * @param input the input action to use in the synchronization properties - * @param guardExpression the expression that represents the guards of the self loops - */ - private void createAngelicSelfLoops(final Location location, final String input, final Expression<String> guardExpression) { - final Edge edge; - - switch (guardExpression.getExprType()) { - case Literal.EXPR_TYPE: - // If false, do not create any loops - if (!((Literal<String>) guardExpression).getValue()) break; - - // It should never be true, since that should be handled before calling this method - throw new RuntimeException("Type of expression " + guardExpression + " not accepted"); - case Variable.EXPR_TYPE: - edge = new Edge(location, EdgeStatus.INPUT); - edge.setTargetLocation(location); - edge.addSyncNail(input); - edge.addGuardNail(((Variable<String>) guardExpression).getValue()); - addEdge(edge); - break; - case And.EXPR_TYPE: - edge = new Edge(location, EdgeStatus.INPUT); - edge.setTargetLocation(location); - edge.addSyncNail(input); - edge.addGuardNail(String.join("&&", - ((And<String>) guardExpression).getChildren().stream() - .map(child -> { - if (!child.getExprType().equals(Variable.EXPR_TYPE)) - throw new RuntimeException("Child " + child + " of type " + - child.getExprType() + " in and expression " + - guardExpression + " should be a variable"); - - return ((Variable<String>) child).getValue(); - }) - .collect(Collectors.toList()) - )); - addEdge(edge); - break; - case Or.EXPR_TYPE: - guardExpression.getChildren().forEach(child -> createAngelicSelfLoops(location, input, child)); - break; - default: - throw new RuntimeException("Type of expression " + guardExpression + " not accepted"); - } - } - - /** - * Applies demonic completion on this component. - */ - public void applyDemonicCompletion() { - // Make a universal location - final Location uniLocation = new Location(this, Location.Type.UNIVERSAL, 0, 0); - addLocation(uniLocation); - - final Edge inputEdge = uniLocation.addLeftEdge("*", EdgeStatus.INPUT); - inputEdge.setIsLocked(true); - addEdge(inputEdge); - - final Edge outputEdge = uniLocation.addRightEdge("*", EdgeStatus.OUTPUT); - outputEdge.setIsLocked(true); - addEdge(outputEdge); - - // Cache input signature, since it could be updated when added edges - final List<String> inputStrings = new ArrayList<>(getInputStrings()); - - getLocations().forEach(location -> inputStrings.forEach(input -> { - // Get outgoing input edges that has the chosen input - final List<Edge> matchingEdges = getListOfEdgesFromDisplayableEdges(getOutgoingEdges(location)).stream().filter( - edge -> edge.getStatus().equals(EdgeStatus.INPUT) && - edge.getSync().equals(input)).collect(Collectors.toList() - ); - - // If no such edges, add an edge to Universal - if (matchingEdges.isEmpty()) { - final Edge edge = new Edge(location, EdgeStatus.INPUT); - edge.setTargetLocation(uniLocation); - edge.addSyncNail(input); - addEdge(edge); - return; - } - // If an edge has no guard and its target has no invariants, ignore. - // Component is already input-enabled with respect to this location and input. - if (matchingEdges.stream().anyMatch(edge -> edge.getGuard().isEmpty() && - edge.getTargetLocation().getInvariant().isEmpty())) return; - - // Extract expression for which edges to create. - // The expression is in DNF - // We create edges to Universal for each child expression in the disjunction. - createDemonicEdges(location, uniLocation, input, getNegatedEdgeExpression(matchingEdges)); - })); - } - - /** - * Creates edges to a specified Universal location from a location - * in order to finish missing inputs with a demonic completion. - * @param location the location to create self loops on - * @param universal the Universal location to create edge to - * @param input the input action to use in the synchronization properties - * @param guardExpression the expression that represents the guards of the edges to create - */ - private void createDemonicEdges(final Location location, final Location universal, final String input, final Expression<String> guardExpression) { - final Edge edge; - - switch (guardExpression.getExprType()) { - case Literal.EXPR_TYPE: - // If false, do not create any edges - if (!((Literal<String>) guardExpression).getValue()) break; - - // It should never be true, since that should be handled before calling this method - throw new RuntimeException("Type of expression " + guardExpression + " not accepted"); - case Variable.EXPR_TYPE: - edge = new Edge(location, EdgeStatus.INPUT); - edge.setTargetLocation(universal); - edge.addSyncNail(input); - edge.addGuardNail(((Variable<String>) guardExpression).getValue()); - addEdge(edge); - break; - case And.EXPR_TYPE: - edge = new Edge(location, EdgeStatus.INPUT); - edge.setTargetLocation(universal); - edge.addSyncNail(input); - edge.addGuardNail(String.join("&&", - ((And<String>) guardExpression).getChildren().stream() - .map(child -> ((Variable<String>) child).getValue()) - .collect(Collectors.toList()) - )); - addEdge(edge); - break; - case Or.EXPR_TYPE: - ((Or<String>) guardExpression).getChildren().forEach(child -> createDemonicEdges(location, universal, input, child)); - break; - default: - throw new RuntimeException("Type of expression " + guardExpression + " not accepted"); - } - } - /** * Initialises IO listeners, adding change listener to the list of edges * Also adds listeners to all current edges in edges. @@ -383,7 +134,7 @@ private void initializeIOListeners() { }); // Add listener to edges initially - edges.forEach(edge -> getEdgeOrSubEdges(edge).forEach(subEdge -> addSyncListener(listener, subEdge))); + getEdges().forEach(edge -> getEdgeOrSubEdges(edge).forEach(subEdge -> addSyncListener(listener, subEdge))); } /** @@ -396,60 +147,6 @@ private static void addSyncListener(final ChangeListener<Object> listener, final edge.ioStatus.addListener(listener); } - /** - * Method used for updating the inputstrings and outputstrings list - * Sorts the list alphabetically, ignoring case - */ - public void updateIOList() { - final List<String> localInputStrings = new ArrayList<>(); - final List<String> localOutputStrings = new ArrayList<>(); - - List<Edge> edgeList = getListOfEdgesFromDisplayableEdges(edges); - - for (final Edge edge : edgeList) { - // Extract channel id based on UPPAAL id definition - final String channel = edge.getSync().replaceAll("^([a-zA-Z_][a-zA-Z0-9_]*).*$", "$1"); - - if(edge.getStatus() == EdgeStatus.INPUT){ - if(!edge.getSync().equals("*") && !localInputStrings.contains(channel)){ - localInputStrings.add(channel); - } - } else if (edge.getStatus() == EdgeStatus.OUTPUT) { - if(!edge.getSync().equals("*") && !localOutputStrings.contains(channel)){ - localOutputStrings.add(channel); - } - } - } - - // Sort the String alphabetically, ignoring case (e.g. all strings starting with "C" are placed together) - localInputStrings.sort((item1, item2) -> item1.compareToIgnoreCase(item2)); - localOutputStrings.sort((item1, item2) -> item1.compareToIgnoreCase(item2)); - - inputStrings.setAll(localInputStrings); - outputStrings.setAll(localOutputStrings); - } - - private List<Edge> getListOfEdgesFromDisplayableEdges(List<DisplayableEdge> displayableEdges) { - List<Edge> result = new ArrayList<>(); - for (final DisplayableEdge originalEdge : displayableEdges) { - result.addAll(getEdgeOrSubEdges(originalEdge)); - } - - return result; - } - - private List<Edge> getEdgeOrSubEdges(DisplayableEdge edge) { - List<Edge> result = new ArrayList<>(); - - if(edge instanceof Edge) { - result.add((Edge) edge); - } else { - result.addAll(((GroupedEdge) edge).getEdges()); - } - - return result; - } - /** * Get all locations in this, but the initial location (if one exists). * O(n), n is # of locations in component. @@ -492,6 +189,15 @@ public boolean removeLocation(final Location location) { return locations.remove(location); } + public String getUniqueLocationId() { + final var currentIds = getLocations().stream().map(Location::getId).collect(Collectors.toSet()); + for (int counter = 1; ; counter++) { + if (!currentIds.contains(LOCATION + counter)) { + return LOCATION + counter; + } + } + } + /** * Add a failing Edge to the list of failing edges * and set the edge's failing property to true. @@ -605,6 +311,27 @@ public synchronized List<DisplayableEdge> getOutgoingEdges(final Location locati return getDisplayableEdges().filtered(edge -> location == edge.getSourceLocation()); } + public List<Edge> getListOfEdgesFromDisplayableEdges(List<DisplayableEdge> displayableEdges) { + List<Edge> result = new ArrayList<>(); + for (final DisplayableEdge originalEdge : displayableEdges) { + result.addAll(getEdgeOrSubEdges(originalEdge)); + } + + return result; + } + + private List<Edge> getEdgeOrSubEdges(DisplayableEdge edge) { + List<Edge> result = new ArrayList<>(); + + if(edge instanceof Edge) { + result.add((Edge) edge); + } else { + result.addAll(((GroupedEdge) edge).getEdges()); + } + + return result; + } + public boolean isDeclarationOpen() { return declarationOpen.get(); } @@ -655,18 +382,6 @@ public DisplayableEdge getUnfinishedEdge() { return null; } - public boolean isFirsTimeShown() { - return firsTimeShown.get(); - } - - public BooleanProperty firsTimeShownProperty() { - return firsTimeShown; - } - - public void setFirsTimeShown(final boolean firsTimeShown) { - this.firsTimeShown.set(firsTimeShown); - } - public boolean isIncludeInPeriodicCheck() { return includeInPeriodicCheck.get(); } @@ -679,157 +394,6 @@ public void setIncludeInPeriodicCheck(final boolean includeInPeriodicCheck) { this.includeInPeriodicCheck.set(includeInPeriodicCheck); } - /** - * Checks if there is currently an edge without a source location. - */ - public boolean isAnyEdgeWithoutSource() { - DisplayableEdge edgeWithoutSource = null; - - for (DisplayableEdge edge : getDisplayableEdges()) { - if (edge.sourceCircularProperty().get() instanceof MouseCircular) { - edgeWithoutSource = edge; - break; - } - } - - return edgeWithoutSource != null; - } - - @Override - public JsonObject serialize() { - final JsonObject result = super.serialize(); - result.addProperty(DECLARATIONS, getDeclarationsText()); - - final JsonArray locations = new JsonArray(); - getLocations().forEach(location -> locations.add(location.serialize())); - result.add(LOCATIONS, locations); - - final JsonArray edges = new JsonArray(); - getListOfEdgesFromDisplayableEdges(this.edges).forEach(edge -> edges.add(edge.serialize())); - - result.add(EDGES, edges); - result.addProperty(DESCRIPTION, getDescription()); - box.addProperties(result); - result.addProperty(COLOR, EnabledColor.getIdentifier(getColor())); - result.addProperty(INCLUDE_IN_PERIODIC_CHECK, isIncludeInPeriodicCheck()); - - return result; - } - - @Override - public void deserialize(final JsonObject json) { - super.deserialize(json); - - setDeclarationsText(json.getAsJsonPrimitive(DECLARATIONS).getAsString()); - - json.getAsJsonArray(LOCATIONS).forEach(jsonElement -> { - final Location newLocation = new Location((JsonObject) jsonElement); - locations.add(newLocation); - }); - - json.getAsJsonArray(EDGES).forEach(jsonElement -> { - JsonObject edgeObject = (JsonObject) jsonElement; - final Edge newEdge = new Edge((JsonObject) jsonElement, this); - - String edgeGroup = ""; - if (edgeObject.has("group")) { - edgeGroup = edgeObject.getAsJsonPrimitive("group").getAsString(); - } - - if (!edgeGroup.isEmpty()) { - GroupedEdge groupedEdge = null; - for (DisplayableEdge edge : edges) { - if (edge instanceof GroupedEdge && edge.getId().equals(edgeGroup)) { - groupedEdge = ((GroupedEdge) edge); - break; - } - } - - if (groupedEdge == null) { - GroupedEdge newGroupedEdge = new GroupedEdge(newEdge); - newGroupedEdge.setId(edgeGroup); - - edges.add(newGroupedEdge); - } else { - boolean hasSameGuardAndUpdate = groupedEdge.addEdgeToGroup(newEdge); - - if (!hasSameGuardAndUpdate) { - // The edge has the same group id as another edge, but has different guard and/or update - newEdge.setGroup(""); - edges.add(newEdge); - } - } - } else { - edges.add(newEdge); - } - }); - - setDescription(json.getAsJsonPrimitive(DESCRIPTION).getAsString()); - box.setProperties(json); - - if (box.getWidth() == 0 && box.getHeight() == 0) { - box.setWidth(locations.stream().max(Comparator.comparingDouble(Location::getX)).get().getX() + Ecdar.CANVAS_PADDING * 10); - box.setHeight(locations.stream().max(Comparator.comparingDouble(Location::getY)).get().getY() + Ecdar.CANVAS_PADDING * 10); - } - - final EnabledColor enabledColor = (json.has(COLOR) ? EnabledColor.fromIdentifier(json.getAsJsonPrimitive(COLOR).getAsString()) : null); - if (enabledColor != null) { - setColorIntensity(enabledColor.intensity); - setColor(enabledColor.color); - } else { - setRandomColor(); - for(Location loc : locations) { - loc.setColor(this.getColor()); - loc.setColorIntensity(this.getColorIntensity()); - } - } - - if(json.has(INCLUDE_IN_PERIODIC_CHECK)) { - setIncludeInPeriodicCheck(json.getAsJsonPrimitive(INCLUDE_IN_PERIODIC_CHECK).getAsBoolean()); - } else { - setIncludeInPeriodicCheck(false); - } - } - - /** - * Dyes the component and its locations. - * @param color the color to dye with - * @param intensity the intensity of the color - */ - public void dye(final Color color, final Color.Intensity intensity) { - final Color previousColor = colorProperty().get(); - final Color.Intensity previousColorIntensity = colorIntensityProperty().get(); - - final Map<Location, Pair<Color, Color.Intensity>> previousLocationColors = new HashMap<>(); - - for (final Location location : getLocations()) { - if (!location.getColor().equals(previousColor)) continue; - previousLocationColors.put(location, new Pair<>(location.getColor(), location.getColorIntensity())); - } - - UndoRedoStack.pushAndPerform(() -> { // Perform - // Color the component - setColorIntensity(intensity); - setColor(color); - - // Color all of the locations - previousLocationColors.keySet().forEach(location -> { - location.setColorIntensity(intensity); - location.setColor(color); - }); - }, () -> { // Undo - // Color the component - setColorIntensity(previousColorIntensity); - setColor(previousColor); - - // Color the locations accordingly to the previous color for them - previousLocationColors.keySet().forEach(location -> { - location.setColorIntensity(previousLocationColors.get(location).getValue()); - location.setColor(previousLocationColors.get(location).getKey()); - }); - }, String.format("Changed the color of %s to %s", this, color.name()), "color-lens"); - } - public String getDescription() { return description.get(); } @@ -850,36 +414,6 @@ public ObservableList<String> getOutputStrings() { return outputStrings; } - /** - * Generate and sets a unique id for this system - */ - private void setComponentName() { - for(int counter = 1; ; counter++) { - if(!Ecdar.getProject().getComponentNames().contains(COMPONENT + counter)){ - setName((COMPONENT + counter)); - return; - } - } - } - - /** - * Generates an id to be used by universal and inconsistent locations in this component, - * if one has already been generated, return that instead - * @return generated universal/inconsistent id - */ - String generateUniIncId(){ - final String id = getUniIncId(); - if(id != null){ - return id; - } else { - for(int counter = 0; ;counter++){ - if(!Ecdar.getProject().getUniIncIds().contains(String.valueOf(counter))){ - return String.valueOf(counter); - } - } - } - } - /** * Gets the id used by universal and inconsistent locations located in this component, * if neither universal nor inconsistent locations exist in this component it returns null. @@ -894,32 +428,6 @@ public String getUniIncId() { return null; } - /** - * Gets the id of all locations in this component - * @return ids of all locations in this component - */ - HashSet<String> getLocationIds() { - final HashSet<String> ids = new HashSet<>(); - for (final Location location : getLocations()){ - if(location.getType() != Location.Type.UNIVERSAL || location.getType() != Location.Type.INCONSISTENT){ - ids.add(location.getId().substring(Location.ID_LETTER_LENGTH)); - } - } - return ids; - } - - /** - * Gets the id of all edges in this component - * @return ids of all edges in this component - */ - HashSet<String> getEdgeIds() { - final HashSet<String> ids = new HashSet<>(); - for (final Edge edge : getEdges()){ - ids.add(edge.getId().substring(Edge.ID_LETTER_LENGTH)); - } - return ids; - } - public String getDeclarationsText() { return declarationsText.get(); } @@ -932,11 +440,6 @@ public StringProperty declarationsTextProperty() { return declarationsText; } - @Override - public Box getBox() { - return box; - } - /** * Gets the clocks defined in the declarations text. * @return the clocks @@ -969,79 +472,188 @@ public List<String> getLocalVariables() { return locals; } + public List<DisplayableEdge> getInputEdges() { + return getDisplayableEdges().filtered(edge -> edge.getStatus().equals(EdgeStatus.INPUT)); + } + + public List<DisplayableEdge> getOutputEdges() { + return getDisplayableEdges().filtered(edge -> edge.getStatus().equals(EdgeStatus.OUTPUT)); + } + /** - * Gets the local variables defined in the declarations text. - * Also gets the lower and upper bounds for these variables. - * @return Triples containing (left) name of the variable, (middle) lower bound, (right) upper bound + * Dyes the component and its locations. + * @param color the color to dye with */ - public List<Triple<String, Integer, Integer>> getLocalVariablesWithBounds() { - final List<Triple<String, Integer, Integer>> typedefs = Ecdar.getProject().getGlobalDeclarations().getTypedefs(); + public void dye(final EnabledColor color) { + final EnabledColor previousColor = colorProperty().get(); - final List<Triple<String, Integer, Integer>> locals = new ArrayList<>(); + final Map<Location, EnabledColor> previousLocationColors = new HashMap<>(); - Arrays.stream(getDeclarationsText().split(";")).forEach(statement -> { - final Matcher matcher = Pattern.compile("^\\s*(\\w+)\\s+(\\w+)(\\W|$)").matcher(statement); - if (!matcher.find()) return; + for (final Location location : getLocations()) { + if (!location.getColor().equals(previousColor)) continue; + previousLocationColors.put(location, location.getColor()); + } - final Optional<Triple<String, Integer, Integer>> typedef = typedefs.stream() - .filter(def -> def.getLeft().equals(matcher.group(1))).findAny(); - if (!typedef.isPresent()) return; + UndoRedoStack.pushAndPerform(() -> { // Perform + // Color the component + setColor(color); - locals.add(Triple.of(matcher.group(2), typedef.get().getMiddle(), typedef.get().getRight())); - }); + // Color all of the locations + previousLocationColors.keySet().forEach(location -> { + location.setColor(color); + }); + }, () -> { // Undo + // Color the component + setColor(previousColor); - return locals; + // Color the locations accordingly to the previous color for them + previousLocationColors.keySet().forEach(location -> { + location.setColor(previousLocationColors.get(location)); + }); + }, String.format("Changed the color of %s to %s", this, color.color.name()), "color-lens"); } /** - * Gets the first occurring universal location in this component. - * @return the first universal location, or null if none exists + * Checks if there is currently an edge without a source location. */ - public Location getUniversalLocation() { - final FilteredList<Location> uniLocs = getLocations().filtered(l -> l.getType().equals(Location.Type.UNIVERSAL)); + public boolean isAnyEdgeWithoutSource() { + DisplayableEdge edgeWithoutSource = null; - if (uniLocs.isEmpty()) return null; + for (DisplayableEdge edge : getDisplayableEdges()) { + if (edge.sourceCircularProperty().get() instanceof MouseCircular) { + edgeWithoutSource = edge; + break; + } + } - return uniLocs.get(0); + return edgeWithoutSource != null; } /** - * Moves all nodes left. + * Method used for updating the inputstrings and outputstrings list + * Sorts the list alphabetically, ignoring case */ - public void moveAllNodesLeft() { - getLocations().forEach(loc -> loc.setX(loc.getX() + NudgeDirection.LEFT.getXOffset())); - getDisplayableEdges().forEach(edge -> edge.getNails().forEach(nail -> nail.setX(nail.getX() + NudgeDirection.LEFT.getXOffset()))); - } + public void updateIOList() { + final List<String> localInputStrings = new ArrayList<>(); + final List<String> localOutputStrings = new ArrayList<>(); - /** - * Moves all nodes right. - */ - public void moveAllNodesRight() { - getLocations().forEach(loc -> loc.setX(loc.getX() + NudgeDirection.RIGHT.getXOffset())); - getDisplayableEdges().forEach(edge -> edge.getNails().forEach(nail -> nail.setX(nail.getX() + NudgeDirection.RIGHT.getXOffset()))); - } + List<Edge> edgeList = getListOfEdgesFromDisplayableEdges(edges); - /** - * Moves all nodes down. - */ - public void moveAllNodesDown() { - getLocations().forEach(loc -> loc.setY(loc.getY() + NudgeDirection.DOWN.getYOffset())); - getDisplayableEdges().forEach(edge -> edge.getNails().forEach(nail -> nail.setY(nail.getY() + NudgeDirection.DOWN.getYOffset()))); + for (final Edge edge : edgeList) { + // Extract channel id based on UPPAAL id definition + final String channel = edge.getSync().replaceAll("^([a-zA-Z_][a-zA-Z0-9_]*).*$", "$1"); + + if(edge.getStatus() == EdgeStatus.INPUT){ + if(!edge.getSync().equals("*") && !localInputStrings.contains(channel)){ + localInputStrings.add(channel); + } + } else if (edge.getStatus() == EdgeStatus.OUTPUT) { + if(!edge.getSync().equals("*") && !localOutputStrings.contains(channel)){ + localOutputStrings.add(channel); + } + } + } + + // Sort the String alphabetically, ignoring case (e.g. all strings starting with "C" are placed together) + localInputStrings.sort((item1, item2) -> item1.compareToIgnoreCase(item2)); + localOutputStrings.sort((item1, item2) -> item1.compareToIgnoreCase(item2)); + + inputStrings.setAll(localInputStrings); + outputStrings.setAll(localOutputStrings); } - /** - * Moves all nodes up. - */ - public void moveAllNodesUp() { - getLocations().forEach(loc -> loc.setY(loc.getY() + NudgeDirection.UP.getYOffset())); - getDisplayableEdges().forEach(edge -> edge.getNails().forEach(nail -> nail.setY(nail.getY() + NudgeDirection.UP.getYOffset()))); + @Override + public Box getBox() { + return box; } - public List<DisplayableEdge> getInputEdges() { - return getDisplayableEdges().filtered(edge -> edge.getStatus().equals(EdgeStatus.INPUT)); + @Override + public JsonObject serialize() { + final JsonObject result = super.serialize(); + result.addProperty(DECLARATIONS, getDeclarationsText()); + + final JsonArray locations = new JsonArray(); + getLocations().forEach(location -> locations.add(location.serialize())); + result.add(LOCATIONS, locations); + + final JsonArray edges = new JsonArray(); + getListOfEdgesFromDisplayableEdges(this.edges).forEach(edge -> edges.add(edge.serialize())); + + result.add(EDGES, edges); + result.addProperty(DESCRIPTION, getDescription()); + box.addProperties(result); + result.addProperty(COLOR, EnabledColor.getIdentifier(getColor().color)); + result.addProperty(INCLUDE_IN_PERIODIC_CHECK, isIncludeInPeriodicCheck()); + + return result; } - public List<DisplayableEdge> getOutputEdges() { - return getDisplayableEdges().filtered(edge -> edge.getStatus().equals(EdgeStatus.OUTPUT)); + @Override + public void deserialize(final JsonObject json) { + super.deserialize(json); + + setDeclarationsText(json.getAsJsonPrimitive(DECLARATIONS).getAsString()); + + json.getAsJsonArray(LOCATIONS).forEach(jsonElement -> { + final Location newLocation = new Location((JsonObject) jsonElement); + locations.add(newLocation); + }); + + json.getAsJsonArray(EDGES).forEach(jsonElement -> { + JsonObject edgeObject = (JsonObject) jsonElement; + final Edge newEdge = new Edge((JsonObject) jsonElement, this); + + String edgeGroup = ""; + if (edgeObject.has("group")) { + edgeGroup = edgeObject.getAsJsonPrimitive("group").getAsString(); + } + + if (!edgeGroup.isEmpty()) { + GroupedEdge groupedEdge = null; + for (DisplayableEdge edge : edges) { + if (edge instanceof GroupedEdge && edge.getId().equals(edgeGroup)) { + groupedEdge = ((GroupedEdge) edge); + break; + } + } + + if (groupedEdge == null) { + GroupedEdge newGroupedEdge = new GroupedEdge(newEdge); + newGroupedEdge.setId(edgeGroup); + + edges.add(newGroupedEdge); + } else { + boolean hasSameGuardAndUpdate = groupedEdge.addEdgeToGroup(newEdge); + + if (!hasSameGuardAndUpdate) { + // The edge has the same group id as another edge, but has different guard and/or update + newEdge.setGroup(""); + edges.add(newEdge); + } + } + } else { + edges.add(newEdge); + } + }); + + setDescription(json.getAsJsonPrimitive(DESCRIPTION).getAsString()); + box.setProperties(json); + + if (box.getWidth() == 0 && box.getHeight() == 0) { + box.setWidth(locations.stream().max(Comparator.comparingDouble(Location::getX)).get().getX() + 10 * 10); + box.setHeight(locations.stream().max(Comparator.comparingDouble(Location::getY)).get().getY() + 10 * 10); + } + + final EnabledColor enabledColor = (json.has(COLOR) ? EnabledColor.fromIdentifier(json.getAsJsonPrimitive(COLOR).getAsString()) : EnabledColor.getDefault()); + setColor(enabledColor); + for(Location loc : locations) { + loc.setColor(enabledColor); + } + + if(json.has(INCLUDE_IN_PERIODIC_CHECK)) { + setIncludeInPeriodicCheck(json.getAsJsonPrimitive(INCLUDE_IN_PERIODIC_CHECK).getAsBoolean()); + } else { + setIncludeInPeriodicCheck(false); + } } } diff --git a/src/main/java/ecdar/abstractions/Declarations.java b/src/main/java/ecdar/abstractions/Declarations.java index 2014d729..3f42d953 100644 --- a/src/main/java/ecdar/abstractions/Declarations.java +++ b/src/main/java/ecdar/abstractions/Declarations.java @@ -2,6 +2,7 @@ import ecdar.utility.colors.Color; import com.google.gson.JsonObject; +import ecdar.utility.colors.EnabledColor; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import org.apache.commons.lang3.tuple.Triple; @@ -15,7 +16,7 @@ * Overall declarations of a model. * This could be global declarations or system declarations. */ -public class Declarations extends HighLevelModelObject { +public class Declarations extends HighLevelModel { private final StringProperty declarationsText = new SimpleStringProperty(""); /** @@ -24,13 +25,13 @@ public class Declarations extends HighLevelModelObject { */ public Declarations(final String name) { setName(name); - setColor(Color.AMBER); + setColor(EnabledColor.getDefault()); } public Declarations(final JsonObject json) { deserialize(json); - setColor(Color.AMBER); + setColor(EnabledColor.getDefault()); } public String getDeclarationsText() { diff --git a/src/main/java/ecdar/abstractions/DisplayableEdge.java b/src/main/java/ecdar/abstractions/DisplayableEdge.java index aa93dc7f..52ced7b5 100644 --- a/src/main/java/ecdar/abstractions/DisplayableEdge.java +++ b/src/main/java/ecdar/abstractions/DisplayableEdge.java @@ -2,6 +2,7 @@ import ecdar.code_analysis.Nearable; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.Circular; import ecdar.utility.helpers.MouseCircular; import ecdar.utility.helpers.StringHelper; @@ -27,8 +28,7 @@ public abstract class DisplayableEdge implements Nearable { private final StringProperty update = new SimpleStringProperty(""); // Styling properties - private final ObjectProperty<Color> color = new SimpleObjectProperty<>(Color.GREY_BLUE); - private final ObjectProperty<Color.Intensity> colorIntensity = new SimpleObjectProperty<>(Color.Intensity.I700); + private final ObjectProperty<EnabledColor> color = new SimpleObjectProperty<>(EnabledColor.getDefault()); private final ObservableList<Nail> nails = FXCollections.observableArrayList(); // Circulars @@ -109,30 +109,18 @@ public StringProperty updateProperty() { return update; } - public Color getColor() { + public EnabledColor getColor() { return color.get(); } - public void setColor(final Color color) { + public void setColor(final EnabledColor color) { this.color.set(color); } - public ObjectProperty<Color> colorProperty() { + public ObjectProperty<EnabledColor> colorProperty() { return color; } - public Color.Intensity getColorIntensity() { - return colorIntensity.get(); - } - - public void setColorIntensity(final Color.Intensity colorIntensity) { - this.colorIntensity.set(colorIntensity); - } - - public ObjectProperty<Color.Intensity> colorIntensityProperty() { - return colorIntensity; - } - public void setIsHighlighted(final boolean highlight){ this.isHighlighted.set(highlight);} public boolean getIsHighlighted(){ return this.isHighlighted.get(); } diff --git a/src/main/java/ecdar/abstractions/EcdarModel.java b/src/main/java/ecdar/abstractions/EcdarModel.java deleted file mode 100644 index c298e6ae..00000000 --- a/src/main/java/ecdar/abstractions/EcdarModel.java +++ /dev/null @@ -1,8 +0,0 @@ -package ecdar.abstractions; - -/** - * Ecdar graphical model. - * This could be a component or a system. - */ -public abstract class EcdarModel extends HighLevelModelObject { -} diff --git a/src/main/java/ecdar/abstractions/EcdarSystem.java b/src/main/java/ecdar/abstractions/EcdarSystem.java index 35fe2f2b..d8fd96ae 100644 --- a/src/main/java/ecdar/abstractions/EcdarSystem.java +++ b/src/main/java/ecdar/abstractions/EcdarSystem.java @@ -1,8 +1,6 @@ package ecdar.abstractions; -import ecdar.Ecdar; import ecdar.utility.UndoRedoStack; -import ecdar.utility.colors.Color; import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.Boxed; import com.google.gson.JsonArray; @@ -21,8 +19,7 @@ * A model of a system. * The class is called EcdarSystem, since Java already has a System class. */ -public class EcdarSystem extends EcdarModel implements Boxed { - private static final String SYSTEM = "System"; +public class EcdarSystem extends HighLevelModel implements Boxed { private static final String SYSTEM_ROOT_X = "systemRootX"; private static final String INSTANCES = "componentInstances"; private static final String OPERATORS = "operators"; @@ -32,15 +29,15 @@ public class EcdarSystem extends EcdarModel implements Boxed { private final StringProperty description = new SimpleStringProperty(""); private final ObservableList<ComponentInstance> componentInstances = FXCollections.observableArrayList(); private final ObservableList<ComponentOperator> componentOperators = FXCollections.observableArrayList(); - private final ObservableList<EcdarSystemEdge> edges = FXCollections.observableArrayList(); + private final ObservableList<SystemEdge> edges = FXCollections.observableArrayList(); private final SystemRoot systemRoot = new SystemRoot(); // Styling properties private final Box box = new Box(); - public EcdarSystem() { - setSystemName(); - setRandomColor(); + public EcdarSystem(final EnabledColor color, final String name) { + this.nameProperty().set(name); + setColor(color); getBox().setWidth(600d); @@ -101,36 +98,32 @@ public void removeComponentOperator(final ComponentOperator operator) { /* Edges */ - public ObservableList<EcdarSystemEdge> getEdges() { + public ObservableList<SystemEdge> getEdges() { return edges; } - public void addEdge(final EcdarSystemEdge edge) { + public void addEdge(final SystemEdge edge) { edges.add(edge); } - public void removeEdge(final EcdarSystemEdge edge) { + public void removeEdge(final SystemEdge edge) { edges.remove(edge); } /** * Dyes the system. * @param color the color to dye with - * @param intensity the intensity of the color */ - public void dye(final Color color, final Color.Intensity intensity) { - final Color previousColor = colorProperty().get(); - final Color.Intensity previousColorIntensity = colorIntensityProperty().get(); + public void dye(final EnabledColor color) { + final EnabledColor previousColor = colorProperty().get(); UndoRedoStack.pushAndPerform(() -> { // Perform // Color the component - setColorIntensity(intensity); setColor(color); }, () -> { // Undo // Color the component - setColorIntensity(previousColorIntensity); setColor(previousColor); - }, String.format("Changed the color of %s to %s", this, color.name()), "color-lens"); + }, String.format("Changed the color of %s to %s", this, color.color.name()), "color-lens"); } @Override @@ -141,7 +134,7 @@ public JsonObject serialize() { box.addProperties(result); - result.addProperty(COLOR, EnabledColor.getIdentifier(getColor())); + result.addProperty(COLOR, EnabledColor.getIdentifier(getColor().color)); result.addProperty(SYSTEM_ROOT_X, systemRoot.getX()); @@ -170,8 +163,7 @@ public void deserialize(final JsonObject json) { final EnabledColor enabledColor = EnabledColor.fromIdentifier(json.getAsJsonPrimitive(COLOR).getAsString()); if (enabledColor != null) { - setColorIntensity(enabledColor.intensity); - setColor(enabledColor.color); + setColor(enabledColor); } systemRoot.setX(json.getAsJsonPrimitive(SYSTEM_ROOT_X).getAsDouble()); @@ -187,19 +179,7 @@ public void deserialize(final JsonObject json) { }); json.getAsJsonArray(EDGES).forEach(jsonEdge -> - getEdges().add(new EcdarSystemEdge((JsonObject) jsonEdge, this))); - } - - /** - * Generate and sets a unique id for this system - */ - public void setSystemName() { - for(int counter = 1; ; counter++) { - if(!Ecdar.getProject().getSystemNames().contains(SYSTEM + counter)){ - setName((SYSTEM + counter)); - return; - } - } + getEdges().add(new SystemEdge((JsonObject) jsonEdge, this))); } /** @@ -207,8 +187,8 @@ public void setSystemName() { * An edge is unfinished, if the has no target. * @return The unfinished edge, or null if none was found. */ - public EcdarSystemEdge getUnfinishedEdge() { - for (final EcdarSystemEdge edge : edges) { + public SystemEdge getUnfinishedEdge() { + for (final SystemEdge edge : edges) { if (!edge.isFinished()) return edge; } return null; diff --git a/src/main/java/ecdar/abstractions/Edge.java b/src/main/java/ecdar/abstractions/Edge.java index 28f7cad8..754c9947 100644 --- a/src/main/java/ecdar/abstractions/Edge.java +++ b/src/main/java/ecdar/abstractions/Edge.java @@ -97,7 +97,7 @@ public BooleanProperty failingProperty() { * @param component component to select a source and a target location within * @return the edge */ - Edge cloneForVerification(final Component component) { + public Edge cloneForVerification(final Component component) { final Edge clone = new Edge(component.findLocation(getSourceLocation().getId()), getStatus()); // Clone target location diff --git a/src/main/java/ecdar/abstractions/GroupedEdge.java b/src/main/java/ecdar/abstractions/GroupedEdge.java index 02503d9a..420762be 100644 --- a/src/main/java/ecdar/abstractions/GroupedEdge.java +++ b/src/main/java/ecdar/abstractions/GroupedEdge.java @@ -54,7 +54,6 @@ private void initializeFromEdge(Edge edge) { setGuard(edge.getGuard()); setUpdate(edge.getUpdate()); setColor(edge.getColor()); - setColorIntensity(edge.getColorIntensity()); setIsHighlighted(edge.getIsHighlighted()); setIsLocked(edge.getIsLockedProperty().getValue()); setStatus(edge.getStatus()); @@ -116,7 +115,6 @@ public Edge getBaseSubEdge() { edge.guardProperty().bind(this.guardProperty()); edge.updateProperty().bind(this.updateProperty()); edge.colorProperty().bind(this.colorProperty()); - edge.colorIntensityProperty().bind(this.colorIntensityProperty()); edge.setIsHighlighted(this.getIsHighlighted()); edge.getIsLockedProperty().bind(this.getIsLockedProperty()); edge.setGroup(this.getId()); diff --git a/src/main/java/ecdar/abstractions/HighLevelModel.java b/src/main/java/ecdar/abstractions/HighLevelModel.java new file mode 100644 index 00000000..536955b4 --- /dev/null +++ b/src/main/java/ecdar/abstractions/HighLevelModel.java @@ -0,0 +1,78 @@ +package ecdar.abstractions; + +import ecdar.presentations.DropDownMenu; +import ecdar.utility.colors.EnabledColor; +import ecdar.utility.serialize.Serializable; +import com.google.gson.JsonObject; +import javafx.beans.property.*; + +/** + * An object used for verifications. + * This could be a component, a global declarations object, or a system. + */ +public abstract class HighLevelModel implements Serializable, DropDownMenu.HasColor { + private static final String NAME = "name"; + + static final String DECLARATIONS = "declarations"; + public static final String DESCRIPTION = "description"; + static final String COLOR = "color"; + + private final StringProperty name; + private final ObjectProperty<EnabledColor> color; + private boolean temporary = false; + + public HighLevelModel() { + name = new SimpleStringProperty(""); + color = new SimpleObjectProperty<>(EnabledColor.getDefault()); + } + + public String getName() { + return name.get(); + } + + public void setName(final String name) { + this.name.unbind(); + this.name.set(name); + } + + public StringProperty nameProperty() { + return name; + } + + public EnabledColor getColor() { + return color.get(); + } + + /** + * Sets the color of the model + */ + void setColor(final EnabledColor color) { + this.color.set(color); + } + + public ObjectProperty<EnabledColor> colorProperty() { + return color; + } + + public boolean isTemporary() { + return temporary; + } + + public void setTemporary(final boolean newValue) { + this.temporary = newValue; + } + + @Override + public JsonObject serialize() { + final JsonObject result = new JsonObject(); + + result.addProperty(NAME, getName()); + + return result; + } + + @Override + public void deserialize(final JsonObject json) { + setName(json.getAsJsonPrimitive(NAME).getAsString()); + } +} diff --git a/src/main/java/ecdar/abstractions/HighLevelModelObject.java b/src/main/java/ecdar/abstractions/HighLevelModelObject.java deleted file mode 100644 index c1926c4e..00000000 --- a/src/main/java/ecdar/abstractions/HighLevelModelObject.java +++ /dev/null @@ -1,115 +0,0 @@ -package ecdar.abstractions; - -import ecdar.Ecdar; -import ecdar.presentations.DropDownMenu; -import ecdar.utility.colors.Color; -import ecdar.utility.colors.EnabledColor; -import ecdar.utility.serialize.Serializable; -import com.google.gson.JsonObject; -import javafx.beans.property.*; - -import java.util.ArrayList; -import java.util.List; -import java.util.Random; - -/** - * An object used for verifications. - * This could be a component, a global declarations object, or a system. - */ -public abstract class HighLevelModelObject implements Serializable, DropDownMenu.HasColor { - private static final String NAME = "name"; - - static final String DECLARATIONS = "declarations"; - public static final String DESCRIPTION = "description"; - static final String COLOR = "color"; - - private final StringProperty name; - private final ObjectProperty<Color> color; - private final ObjectProperty<Color.Intensity> colorIntensity; - private boolean temporary = false; - - public HighLevelModelObject() { - name = new SimpleStringProperty(""); - color = new SimpleObjectProperty<>(Color.GREY_BLUE); - colorIntensity = new SimpleObjectProperty<>(Color.Intensity.I700); - } - - public String getName() { - return name.get(); - } - - public void setName(final String name) { - this.name.unbind(); - this.name.set(name); - } - - public StringProperty nameProperty() { - return name; - } - - public Color getColor() { - return color.get(); - } - - public void setColor(final Color color) { - this.color.set(color); - } - - public ObjectProperty<Color> colorProperty() { - return color; - } - - public Color.Intensity getColorIntensity() { - return colorIntensity.get(); - } - - public void setColorIntensity(final Color.Intensity colorIntensity) { - this.colorIntensity.set(colorIntensity); - } - - public ObjectProperty<Color.Intensity> colorIntensityProperty() { - return colorIntensity; - } - - public boolean isTemporary() { - return temporary; - } - - public void setTemporary(final boolean newValue) { - this.temporary = newValue; - } - - /** - * Sets a random color. - * If some colors are not currently in use, choose among those. - * Otherwise choose a between all available colors. - */ - void setRandomColor() { - // Color the new component in such a way that we avoid clashing with other components if possible - final List<EnabledColor> availableColors = new ArrayList<>(EnabledColor.enabledColors); - Ecdar.getProject().getComponents().forEach(component -> { - availableColors.removeIf(enabledColor -> enabledColor.color.equals(component.getColor())); - }); - if (availableColors.size() == 0) { - availableColors.addAll(EnabledColor.enabledColors); - } - final int randomIndex = (new Random()).nextInt(availableColors.size()); - final EnabledColor selectedColor = availableColors.get(randomIndex); - setColorIntensity(selectedColor.intensity); - setColor(selectedColor.color); - } - - @Override - public JsonObject serialize() { - final JsonObject result = new JsonObject(); - - result.addProperty(NAME, getName()); - - return result; - } - - @Override - public void deserialize(final JsonObject json) { - setName(json.getAsJsonPrimitive(NAME).getAsString()); - } -} diff --git a/src/main/java/ecdar/abstractions/Location.java b/src/main/java/ecdar/abstractions/Location.java index 632a06ba..8b5c458f 100644 --- a/src/main/java/ecdar/abstractions/Location.java +++ b/src/main/java/ecdar/abstractions/Location.java @@ -31,7 +31,6 @@ public class Location implements Circular, Serializable, Nearable, DropDownMenu. private static final String INVARIANT_Y = "invariantY"; private static final String UNI = "U"; private static final String INC = "I"; - public static final String LOCATION = "L"; static final int ID_LETTER_LENGTH = 1; // Verification properties @@ -46,8 +45,7 @@ public class Location implements Circular, Serializable, Nearable, DropDownMenu. private final DoubleProperty y = new SimpleDoubleProperty(0d); private final DoubleProperty radius = new SimpleDoubleProperty(0d); private final SimpleDoubleProperty scale = new SimpleDoubleProperty(1d); - private final ObjectProperty<Color> color = new SimpleObjectProperty<>(Color.GREY_BLUE); - private final ObjectProperty<Color.Intensity> colorIntensity = new SimpleObjectProperty<>(Color.Intensity.I700); + private final ObjectProperty<EnabledColor> color = new SimpleObjectProperty<>(EnabledColor.getDefault()); private final DoubleProperty nicknameX = new SimpleDoubleProperty(0d); private final DoubleProperty nicknameY = new SimpleDoubleProperty(0d); @@ -66,19 +64,21 @@ public Location(final String id) { setId(id); } - public Location(final Component component, final Type type, final double x, final double y){ + public Location(final Component component, final Type type, final String id, final double x, final double y){ setX(x); setY(y); setType(type); if(type == Type.UNIVERSAL){ setIsLocked(true); - setId(UNI + component.generateUniIncId()); + setId(UNI + id); } else if (type == Type.INCONSISTENT) { setIsLocked(true); setUrgency(Location.Urgency.URGENT); - setId(INC + component.generateUniIncId()); + setId(INC + id); + } else { + setId(id); } - setColorIntensity(component.getColorIntensity()); + setColor(component.getColor()); } @@ -89,8 +89,8 @@ public Location(final JsonObject jsonObject) { /** * Generates an id for this, and binds reachability analysis. */ - public void initialize() { - setId(); + public void initialize(String id) { + setId(id); } /** @@ -100,7 +100,7 @@ public void initialize() { * Reachability analysis is not initialized. * @return the clone */ - Location cloneForVerification() { + public Location cloneForVerification() { final Location location = new Location(); location.setId(getId()); @@ -134,18 +134,6 @@ public String getId() { return id.get(); } - /** - * Generate and sets a unique id for this location - */ - private void setId() { - for(int counter = 0; ; counter++) { - if(!Ecdar.getProject().getLocationIds().contains(String.valueOf(counter))){ - id.set(LOCATION + counter); - return; - } - } - } - /** * Sets a specific id for this location * @param string id to set @@ -219,30 +207,6 @@ public DoubleProperty yProperty() { return y; } - public Color getColor() { - return color.get(); - } - - public void setColor(final Color color) { - this.color.set(color); - } - - public ObjectProperty<Color> colorProperty() { - return color; - } - - public Color.Intensity getColorIntensity() { - return colorIntensity.get(); - } - - public void setColorIntensity(final Color.Intensity colorIntensity) { - this.colorIntensity.set(colorIntensity); - } - - public ObjectProperty<Color.Intensity> colorIntensityProperty() { - return colorIntensity; - } - public double getRadius() { return radius.get(); } @@ -318,6 +282,19 @@ public String getMostDescriptiveIdentifier() { } } + @Override + public ObjectProperty<EnabledColor> colorProperty() { + return color; + } + + public void setColor(EnabledColor color) { + this.color.set(color); + } + + public EnabledColor getColor() { + return color.get(); + } + /** * Adds an edge to the left side of a location * @param syncString the string of the channel to synchronize over @@ -399,7 +376,7 @@ public JsonObject serialize() { result.addProperty(X, getX()); result.addProperty(Y, getY()); - result.addProperty(COLOR, EnabledColor.getIdentifier(getColor())); + result.addProperty(COLOR, EnabledColor.getIdentifier(getColor().color)); result.addProperty(NICKNAME_X, getNicknameX()); result.addProperty(NICKNAME_Y, getNicknameY()); @@ -420,11 +397,8 @@ public void deserialize(final JsonObject json) { setX(json.getAsJsonPrimitive(X).getAsDouble()); setY(json.getAsJsonPrimitive(Y).getAsDouble()); - final EnabledColor enabledColor = (json.has(COLOR) ? EnabledColor.fromIdentifier(json.getAsJsonPrimitive(COLOR).getAsString()) : null); - if (enabledColor != null) { - setColorIntensity(enabledColor.intensity); - setColor(enabledColor.color); - } + final EnabledColor enabledColor = (json.has(COLOR) ? EnabledColor.fromIdentifier(json.getAsJsonPrimitive(COLOR).getAsString()) : EnabledColor.getDefault()); + setColor(enabledColor); if(json.has(NICKNAME_X) && json.has(NICKNAME_Y)) { setNicknameX(json.getAsJsonPrimitive(NICKNAME_X).getAsDouble()); diff --git a/src/main/java/ecdar/abstractions/Project.java b/src/main/java/ecdar/abstractions/Project.java index 738d8097..fbf599ce 100644 --- a/src/main/java/ecdar/abstractions/Project.java +++ b/src/main/java/ecdar/abstractions/Project.java @@ -21,6 +21,9 @@ * A project of models. */ public class Project { + public static final String LOCATION = "L"; + public static final String COMPONENT = "Component"; + public static final String SYSTEM = "System"; private final static String GLOBAL_DCL_FILENAME = "GlobalDeclarations"; private final static String QUERIES_FILENAME = "Queries"; private final static String JSON_FILENAME_EXTENSION = ".json"; @@ -56,7 +59,7 @@ public ObservableList<Component> getTempComponents() { return tempComponents; } - public ObservableList<EcdarSystem> getSystemsProperty() { + public ObservableList<EcdarSystem> getSystems() { return systems; } @@ -68,10 +71,95 @@ public Declarations getGlobalDeclarations() { return globalDeclarations.get(); } - public void setGlobalDeclarations(final Declarations declarations) { + private void setGlobalDeclarations(final Declarations declarations) { globalDeclarations.set(declarations); } + public void addComponent(Component newComponent) { + components.add(newComponent); + } + + /** + * gets the id of all edges in the project and inserts it into a set + * @return the set of all edge ids + */ + Set<String> getEdgeIds(){ + final Set<String> ids = new HashSet<>(); + + for (final Component component : getComponents()) { + component.getEdges().forEach(edge -> ids.add(edge.getId().substring(Edge.ID_LETTER_LENGTH))); + } + + return ids; + } + + /** + * gets the id of all systems in the project and inserts it into a set + * @return the set of all system names + */ + HashSet<String> getSystemNames(){ + final HashSet<String> names = new HashSet<>(); + + for(final EcdarSystem system : getSystems()){ + names.add(system.getName()); + } + + return names; + } + + /** + * Gets a new GSON object. + * @return the GSON object + */ + private static Gson getNewGson() { + return new GsonBuilder().setPrettyPrinting().create(); + } + + /** + * Gets a new file writer for saving a file. + * @param filename name of file without extension. + * @return the file writer + * @throws IOException if an IO error occurs + */ + private static FileWriter getSaveFileWriter(final String filename) throws IOException { + return new FileWriter(Ecdar.projectDirectory.getValue() + File.separator + filename + ".json"); + } + + private static FileWriter getSaveFileWriter(final String filename, final String folderName) throws IOException { + return new FileWriter(Ecdar.projectDirectory.getValue() + File.separator + folderName + File.separator + filename + ".json"); + } + + /** + * Find a component by its name. + * @param name the name of the component looking for + * @return the component, or null if none is found + */ + public Component findComponent(final String name) { + for (final Component component : getComponents()) { + if (component.getName().equals(name)) return component; + } + + return null; + } + + /** + * Cleans the project. + * Be sure to disable code analysis before call and enable after call. + */ + public void clean() { + getGlobalDeclarations().clearDeclarationsText(); + + queries.clear(); + + components.clear(); + + tempComponents.clear(); + + systems.clear(); + + testPlans.clear(); + } + /** * Serializes and stores this as JSON files at a given directory. * @param directory object containing path to the desired directory to store at @@ -100,7 +188,7 @@ public void serialize(final File directory) throws IOException { } // Save systems - for (final EcdarSystem system : getSystemsProperty()) { + for (final EcdarSystem system : getSystems()) { final Writer writer = getSaveFileWriter(system.getName(), FOLDER_NAME_SYSTEMS); getNewGson().toJson(system.serialize(), writer); writer.close(); @@ -125,28 +213,6 @@ public void serialize(final File directory) throws IOException { Ecdar.showToast("Project saved."); } - /** - * Gets a new GSON object. - * @return the GSON object - */ - private static Gson getNewGson() { - return new GsonBuilder().setPrettyPrinting().create(); - } - - /** - * Gets a new file writer for saving a file. - * @param filename name of file without extension. - * @return the file writer - * @throws IOException if an IO error occurs - */ - private static FileWriter getSaveFileWriter(final String filename) throws IOException { - return new FileWriter(Ecdar.projectDirectory.getValue() + File.separator + filename + ".json"); - } - - private static FileWriter getSaveFileWriter(final String filename, final String folderName) throws IOException { - return new FileWriter(Ecdar.projectDirectory.getValue() + File.separator + folderName + File.separator + filename + ".json"); - } - /** * Reads files in a folder and deserialize this based on the files and folders. * @param projectFolder the folder where an Ecdar project are supposed to be @@ -275,7 +341,7 @@ private void deserializeSystems(final File systemsFolder) throws IOException { final List<Map.Entry<String, JsonObject>> list = new LinkedList<>(nameJsonMap.entrySet()); - list.sort(Comparator.comparing(Map.Entry::getKey)); + list.sort(Map.Entry.comparingByKey()); final List<JsonObject> orderedJsonSystems = new ArrayList<>(); @@ -287,7 +353,7 @@ private void deserializeSystems(final File systemsFolder) throws IOException { Collections.reverse(orderedJsonSystems); // Add the systems to the list - orderedJsonSystems.forEach(json -> getSystemsProperty().add(new EcdarSystem(json))); + orderedJsonSystems.forEach(json -> getSystems().add(new EcdarSystem(json))); } /** @@ -315,7 +381,7 @@ private void deserializeTestObjects(final File directory) throws IOException { final List<Map.Entry<String, JsonObject>> list = new LinkedList<>(nameJsonMap.entrySet()); - list.sort(Comparator.comparing(Map.Entry::getKey)); + list.sort(Map.Entry.comparingByKey()); final List<JsonObject> orderedJsonSystems = new ArrayList<>(); @@ -329,114 +395,4 @@ private void deserializeTestObjects(final File directory) throws IOException { // Add the test objects to the list orderedJsonSystems.forEach(json -> getTestPlans().add(new MutationTestPlan(json))); } - - /** - * Resets components. - * After this, there is only one component. - * Be sure to disable code analysis before call and enable after call. - */ - public void reset() { - clean(); - components.add(new Component(true)); - } - - /** - * Cleans the project. - * Be sure to disable code analysis before call and enable after call. - */ - public void clean() { - getGlobalDeclarations().clearDeclarationsText(); - - queries.clear(); - - components.clear(); - - tempComponents.clear(); - - systems.clear(); - - testPlans.clear(); - } - - /** - * gets the id of all locations in the project and inserts it into a set - * @return the set of all location ids - */ - Set<String> getLocationIds(){ - final Set<String> ids = new HashSet<>(); - - for (final Component component : getComponents()) { - ids.addAll(component.getLocationIds()); - } - - return ids; - } - - /** - * gets the id of all edges in the project and inserts it into a set - * @return the set of all edge ids - */ - Set<String> getEdgeIds(){ - final Set<String> ids = new HashSet<>(); - - for (final Component component : getComponents()) { - ids.addAll(component.getEdgeIds()); - } - - return ids; - } - - /** - * gets the id of all systems in the project and inserts it into a set - * @return the set of all system names - */ - HashSet<String> getSystemNames(){ - final HashSet<String> names = new HashSet<>(); - - for(final EcdarSystem system : getSystemsProperty()){ - names.add(system.getName()); - } - - return names; - } - - /** - * Gets universal/inconsistent ids for all components in the project - * @return a set of universal/inconsistent ids - */ - HashSet<String> getUniIncIds() { - final HashSet<String> ids = new HashSet<>(); - for (final Component component : getComponents()){ - ids.add(component.getUniIncId()); - } - - return ids; - } - - /** - * Gets the name of all components in the project and inserts it into a set - * @return the set of all component names - */ - HashSet<String> getComponentNames(){ - final HashSet<String> names = new HashSet<>(); - - for(final Component component : getComponents()){ - names.add(component.getName()); - } - - return names; - } - - /** - * Find a component by its name. - * @param name the name of the component looking for - * @return the component, or null if none is found - */ - public Component findComponent(final String name) { - for (final Component component : getComponents()) { - if (component.getName().equals(name)) return component; - } - - return null; - } } diff --git a/src/main/java/ecdar/abstractions/Query.java b/src/main/java/ecdar/abstractions/Query.java index b480cb07..175aa5d9 100644 --- a/src/main/java/ecdar/abstractions/Query.java +++ b/src/main/java/ecdar/abstractions/Query.java @@ -1,15 +1,23 @@ package ecdar.abstractions; -import EcdarProtoBuf.ObjectProtos; +import EcdarProtoBuf.QueryProtos; +import com.google.gson.JsonParser; import ecdar.Ecdar; import ecdar.backend.*; import ecdar.controllers.EcdarController; +import ecdar.controllers.SimulatorController; +import ecdar.utility.UndoRedoStack; +import ecdar.utility.helpers.StringValidator; +import EcdarProtoBuf.ObjectProtos; import ecdar.utility.helpers.StringHelper; import ecdar.utility.serialize.Serializable; import com.google.gson.JsonObject; import javafx.application.Platform; import javafx.beans.property.*; +import javafx.collections.ObservableList; +import java.util.ArrayList; +import java.util.NoSuchElementException; import java.util.List; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -18,7 +26,7 @@ public class Query implements Serializable { private static final String QUERY = "query"; private static final String COMMENT = "comment"; private static final String IS_PERIODIC = "isPeriodic"; - private static final String BACKEND = "backend"; + private static final String ENGINE = "engine"; private final StringProperty query = new SimpleStringProperty(""); private final StringProperty comment = new SimpleStringProperty(""); @@ -26,7 +34,7 @@ public class Query implements Serializable { private final SimpleBooleanProperty isPeriodic = new SimpleBooleanProperty(false); private final ObjectProperty<QueryState> queryState = new SimpleObjectProperty<>(QueryState.UNKNOWN); private final ObjectProperty<QueryType> type = new SimpleObjectProperty<>(); - private BackendInstance backend; + private Engine engine; private final Consumer<Boolean> successConsumer = (aBoolean) -> { @@ -100,11 +108,15 @@ public class Query implements Serializable { } }; - public Query(final String query, final String comment, final QueryState queryState) { + public Query(final String query, final String comment, final QueryState queryState, final Engine engine) { this.setQuery(query); this.comment.set(comment); this.queryState.set(queryState); - setBackend(BackendHelper.getDefaultBackendInstance()); + setEngine(engine); + } + + public Query(final String query, final String comment, final QueryState queryState) { + this(query, comment, queryState, BackendHelper.getDefaultEngine()); } public Query(final JsonObject jsonElement) { @@ -163,12 +175,12 @@ public void setIsPeriodic(final boolean isPeriodic) { this.isPeriodic.set(isPeriodic); } - public BackendInstance getBackend() { - return backend; + public Engine getEngine() { + return engine; } - public void setBackend(BackendInstance backend) { - this.backend = backend; + public void setEngine(Engine engine) { + this.engine = engine; } public void setType(QueryType type) { @@ -191,6 +203,198 @@ public Consumer<Exception> getFailureConsumer() { return failureConsumer; } + /** + * Executes the query + */ + public void execute() throws NoSuchElementException { + if (getQueryState().equals(QueryState.RUNNING) || !StringValidator.validateQuery(getQuery())) + return; + + if (getQuery().isEmpty()) { + setQueryState(QueryState.SYNTAX_ERROR); + addError("Query is empty"); + return; + } + + setQueryState(QueryState.RUNNING); + errors().set(""); + + getEngine().enqueueQuery(this, this::handleQueryResponse, this::handleQueryBackendError); + } + + private void handleQueryResponse(QueryProtos.QueryResponse value) { + if (getQueryState() == QueryState.UNKNOWN) return; + switch (value.getResultCase()) { + case REFINEMENT: + if (value.getRefinement().getSuccess()) { + setQueryState(QueryState.SUCCESSFUL); + getSuccessConsumer().accept(true); + } else { + setQueryState(QueryState.ERROR); + getFailureConsumer().accept(new BackendException.QueryErrorException(value.getRefinement().getReason())); + getSuccessConsumer().accept(false); + getStateActionConsumer().accept(value.getRefinement().getState(), + value.getRefinement().getActionList()); + } + break; + + case CONSISTENCY: + if (value.getConsistency().getSuccess()) { + setQueryState(QueryState.SUCCESSFUL); + getSuccessConsumer().accept(true); + } else { + setQueryState(QueryState.ERROR); + getFailureConsumer().accept(new BackendException.QueryErrorException(value.getConsistency().getReason())); + getSuccessConsumer().accept(false); + getStateActionConsumer().accept(value.getConsistency().getState(), + value.getConsistency().getActionList()); + } + break; + + case DETERMINISM: + if (value.getDeterminism().getSuccess()) { + setQueryState(QueryState.SUCCESSFUL); + getSuccessConsumer().accept(true); + } else { + setQueryState(QueryState.ERROR); + getFailureConsumer().accept(new BackendException.QueryErrorException(value.getDeterminism().getReason())); + getSuccessConsumer().accept(false); + getStateActionConsumer().accept(value.getDeterminism().getState(), + value.getDeterminism().getActionList()); + + } + break; + + case IMPLEMENTATION: + if (value.getImplementation().getSuccess()) { + setQueryState(QueryState.SUCCESSFUL); + getSuccessConsumer().accept(true); + } else { + setQueryState(QueryState.ERROR); + getFailureConsumer().accept(new BackendException.QueryErrorException(value.getImplementation().getReason())); + getSuccessConsumer().accept(false); + //ToDo: These errors are not implemented in the Reveaal backend. + getStateActionConsumer().accept(value.getImplementation().getState(), + new ArrayList<>()); + } + break; + + case REACHABILITY: + if (value.getReachability().getSuccess()) { + setQueryState(QueryState.SUCCESSFUL); + Ecdar.showToast("Reachability check was successful and the location can be reached."); + + //create list of edge id's + ArrayList<String> edgeIds = new ArrayList<>(); + for(var pathsList : value.getReachability().getComponentPathsList()){ + for(var id : pathsList.getEdgeIdsList().toArray()) { + edgeIds.add(id.toString()); + } + } + //highlight the edges + SimulatorController.getSimulationHandler().highlightReachabilityEdges(edgeIds); + getSuccessConsumer().accept(true); + } + else if(!value.getReachability().getSuccess()){ + Ecdar.showToast("Reachability check was successful but the location cannot be reached."); + getSuccessConsumer().accept(true); + } else { + setQueryState(QueryState.ERROR); + Ecdar.showToast("Error from backend: Reachability check was unsuccessful!"); + getFailureConsumer().accept(new BackendException.QueryErrorException(value.getReachability().getReason())); + getSuccessConsumer().accept(false); + //ToDo: These errors are not implemented in the Reveaal backend. + getStateActionConsumer().accept(value.getReachability().getState(), + new ArrayList<>()); + } + break; + + case COMPONENT: + setQueryState(QueryState.SUCCESSFUL); + getSuccessConsumer().accept(true); + JsonObject returnedComponent = (JsonObject) JsonParser.parseString(value.getComponent().getComponent().getJson()); + addGeneratedComponent(new Component(returnedComponent)); + break; + + case ERROR: + setQueryState(QueryState.ERROR); + Ecdar.showToast(value.getError()); + getFailureConsumer().accept(new BackendException.QueryErrorException(value.getError())); + getSuccessConsumer().accept(false); + break; + + case RESULT_NOT_SET: + setQueryState(QueryState.ERROR); + getSuccessConsumer().accept(false); + break; + } + } + + private void handleQueryBackendError(Throwable t) { + // If the query has been cancelled, ignore the error + if (getQueryState() == QueryState.UNKNOWN) return; + + // Due to limit information provided by the engines, we can only show the following for unreachable locations + if(getType() == QueryType.REACHABILITY){ + Ecdar.showToast("Timeout (no response from backend): The reachability query failed. This might be due to the fact that the location is not reachable."); + } + + // Each error starts with a capitalized description of the error equal to the gRPC error type encountered + String errorType = t.getMessage().split(":\\s+", 2)[0]; + + if ("DEADLINE_EXCEEDED".equals(errorType)) { + setQueryState(QueryState.ERROR); + getFailureConsumer().accept(new BackendException.QueryErrorException("The engine did not answer the request in time")); + } else { + try { + setQueryState(QueryState.ERROR); + getFailureConsumer().accept(new BackendException.QueryErrorException("The execution of this query failed with message:" + System.lineSeparator() + t.getLocalizedMessage())); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private void addGeneratedComponent(Component newComponent) { + Platform.runLater(() -> { + newComponent.setTemporary(true); + + ObservableList<Component> listOfGeneratedComponents = Ecdar.getProject().getTempComponents(); // ToDo NIELS: Refactor + Component matchedComponent = null; + + for (Component currentGeneratedComponent : listOfGeneratedComponents) { + int comparisonOfNames = currentGeneratedComponent.getName().compareTo(newComponent.getName()); + + if (comparisonOfNames == 0) { + matchedComponent = currentGeneratedComponent; + break; + } else if (comparisonOfNames < 0) { + break; + } + } + + if (matchedComponent == null) { + UndoRedoStack.pushAndPerform(() -> { // Perform + Ecdar.getProject().getTempComponents().add(newComponent); + }, () -> { // Undo + Ecdar.getProject().getTempComponents().remove(newComponent); + }, "Created new component: " + newComponent.getName(), "add-circle"); + } else { + // Remove current component with name and add the newly generated one + Component finalMatchedComponent = matchedComponent; + UndoRedoStack.pushAndPerform(() -> { // Perform + Ecdar.getProject().getTempComponents().remove(finalMatchedComponent); + Ecdar.getProject().getTempComponents().add(newComponent); + }, () -> { // Undo + Ecdar.getProject().getTempComponents().remove(newComponent); + Ecdar.getProject().getTempComponents().add(finalMatchedComponent); + }, "Created new component: " + newComponent.getName(), "add-circle"); + } + + Ecdar.getProject().addComponent(newComponent); + }); + } + /** * Getter for the state action consumer. * @@ -200,7 +404,6 @@ public BiConsumer<ObjectProtos.State, List<String>> getStateActionConsumer() { return stateActionConsumer; } - @Override public JsonObject serialize() { final JsonObject result = new JsonObject(); @@ -208,7 +411,7 @@ public JsonObject serialize() { result.addProperty(QUERY, getType().getQueryName() + ": " + getQuery()); result.addProperty(COMMENT, getComment()); result.addProperty(IS_PERIODIC, isPeriodic()); - result.addProperty(BACKEND, backend.getName()); + result.addProperty(ENGINE, engine.getName()); return result; } @@ -231,10 +434,10 @@ public void deserialize(final JsonObject json) { setIsPeriodic(json.getAsJsonPrimitive(IS_PERIODIC).getAsBoolean()); } - if (json.has(BACKEND)) { - setBackend(BackendHelper.getBackendInstanceByName(json.getAsJsonPrimitive(BACKEND).getAsString())); + if(json.has(ENGINE)) { + setEngine(BackendHelper.getEngineByName(json.getAsJsonPrimitive(ENGINE).getAsString())); } else { - setBackend(BackendHelper.getDefaultBackendInstance()); + setEngine(BackendHelper.getDefaultEngine()); } } diff --git a/src/main/java/ecdar/abstractions/QueryType.java b/src/main/java/ecdar/abstractions/QueryType.java index 0ea4b1f2..4552cffb 100644 --- a/src/main/java/ecdar/abstractions/QueryType.java +++ b/src/main/java/ecdar/abstractions/QueryType.java @@ -6,10 +6,10 @@ public enum QueryType { QUOTIENT("quotient", "\\"), SPECIFICATION("specification", "Spec"), IMPLEMENTATION("implementation", "Imp"), - LOCAL_CONSISTENCY("consistency", "lCon"), // ToDo NIELS: Will become local-consistency - GLOBAL_CONSISTENCY("global-consistency", "gCon"), - BISIM_MIN("bisim", "bsim"), - GET_COMPONENT("get-component", "get"); + CONSISTENCY("consistency", "Con"), + LOCAL_CONSISTENCY("local-consistency", "LCon"), + BISIM_MINIM("bisim-minim", "Bsim"), + GET_COMPONENT("get-component", "Get"); private final String queryName; private final String symbol; @@ -29,26 +29,23 @@ public String getSymbol() { public static QueryType fromString(String s) { switch (s.toLowerCase()) { + case "reachability": + return REACHABILITY; case "refinement": return REFINEMENT; - case "quotient": - return QUOTIENT; case "specification": return SPECIFICATION; case "implementation": return IMPLEMENTATION; case "consistency": + return CONSISTENCY; case "local-consistency": return LOCAL_CONSISTENCY; - case "global-consistency": - return GLOBAL_CONSISTENCY; - case "bisim": - return BISIM_MIN; + case "bisim-minim": + return BISIM_MINIM; case "get": case "get-component": return GET_COMPONENT; - case "reachability": - return REACHABILITY; default: return null; } diff --git a/src/main/java/ecdar/abstractions/SimpleComponentsSystemDeclarations.java b/src/main/java/ecdar/abstractions/SimpleComponentsSystemDeclarations.java deleted file mode 100644 index 190f9352..00000000 --- a/src/main/java/ecdar/abstractions/SimpleComponentsSystemDeclarations.java +++ /dev/null @@ -1,31 +0,0 @@ -package ecdar.abstractions; - -import java.util.ArrayList; -import java.util.List; - -/** - * Systems declarations for some components. - * The generation text will be generated. - */ -public class SimpleComponentsSystemDeclarations extends Declarations { - public SimpleComponentsSystemDeclarations(final Component ... components) { - super("SimpleComponentsSystemDeclarations"); - - if (components.length < 1) throw new IllegalArgumentException("Cannot contruct declarations without any components"); - - final List<String> names = new ArrayList<>(); - for (final Component component : components) names.add(component.getName()); - - final StringBuilder declarationsText = new StringBuilder("system " + String.join(", ", names) + ";\n"); - - // Add inputs and outputs of components to declarations - for (final Component component : components) { - final List<String> comp1Io = new ArrayList<>(); - for (final String inputSync : component.getInputStrings()) comp1Io.add(inputSync + "?"); - for (final String outputSync : component.getOutputStrings()) comp1Io.add(outputSync + "!"); - declarationsText.append("IO ").append(component.getName()).append(" {").append(String.join(", ", comp1Io)).append("}\n"); - } - - setDeclarationsText(declarationsText.toString()); - } -} diff --git a/src/main/java/ecdar/abstractions/EcdarSystemEdge.java b/src/main/java/ecdar/abstractions/SystemEdge.java similarity index 92% rename from src/main/java/ecdar/abstractions/EcdarSystemEdge.java rename to src/main/java/ecdar/abstractions/SystemEdge.java index 503e4831..0f91d139 100644 --- a/src/main/java/ecdar/abstractions/EcdarSystemEdge.java +++ b/src/main/java/ecdar/abstractions/SystemEdge.java @@ -10,9 +10,8 @@ /** * This is an edge in an Ecdar system. - * The class is called EcdarSystemEdge, since there is already an SystemEdge in com.uppaal.model.system. */ -public class EcdarSystemEdge { +public class SystemEdge { private static final String CHILD = "child"; private static final String PARENT = "parent"; // Verification properties @@ -26,7 +25,7 @@ public class EcdarSystemEdge { * Constructor * @param node A node of this edge. This can either be a child or a parent */ - public EcdarSystemEdge(final SystemElement node) { + public SystemEdge(final SystemElement node) { tempNode.set(node); } @@ -35,7 +34,7 @@ public EcdarSystemEdge(final SystemElement node) { * @param json the JSON object * @param system system containing the edge */ - public EcdarSystemEdge(final JsonObject json, final EcdarSystem system) { + public SystemEdge(final JsonObject json, final EcdarSystem system) { deserialize(json, system); } diff --git a/src/main/java/ecdar/backend/BackendConnection.java b/src/main/java/ecdar/backend/BackendConnection.java deleted file mode 100644 index 5bd19550..00000000 --- a/src/main/java/ecdar/backend/BackendConnection.java +++ /dev/null @@ -1,61 +0,0 @@ -package ecdar.backend; - -import EcdarProtoBuf.EcdarBackendGrpc; -import io.grpc.ManagedChannel; - -import java.util.concurrent.TimeUnit; - -public class BackendConnection { - private final Process process; - private final EcdarBackendGrpc.EcdarBackendStub stub; - private final ManagedChannel channel; - private final BackendInstance backendInstance; - - BackendConnection(BackendInstance backendInstance, Process process, EcdarBackendGrpc.EcdarBackendStub stub, ManagedChannel channel) { - this.process = process; - this.backendInstance = backendInstance; - this.stub = stub; - this.channel = channel; - } - - /** - * Get the gRPC stub of the connection to use for query execution - * - * @return the gRPC stub of this connection - */ - public EcdarBackendGrpc.EcdarBackendStub getStub() { - return stub; - } - - /** - * Get the backend instance that should be used to execute - * the query currently associated with this backend connection - * - * @return the instance of the associated executable query object, - * or null, if no executable query is currently associated - */ - public BackendInstance getBackendInstance() { - return backendInstance; - } - - /** - * Close the gRPC connection and end the process - */ - public void close() { - if (!channel.isShutdown()) { - try { - channel.shutdown(); - if (!channel.awaitTermination(45, TimeUnit.SECONDS)) { - channel.shutdownNow(); // Forcefully close the connection - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - // If the backend-instance is remote, there will not be a process - if (process != null) { - process.destroy(); - } - } -} diff --git a/src/main/java/ecdar/backend/BackendDriver.java b/src/main/java/ecdar/backend/BackendDriver.java deleted file mode 100644 index 6b05988d..00000000 --- a/src/main/java/ecdar/backend/BackendDriver.java +++ /dev/null @@ -1,184 +0,0 @@ -package ecdar.backend; - -import EcdarProtoBuf.EcdarBackendGrpc; -import ecdar.Ecdar; -import io.grpc.*; -import org.springframework.util.SocketUtils; - -import java.io.*; -import java.util.*; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.TimeUnit; - -public class BackendDriver { - private final BlockingQueue<GrpcRequest> requestQueue = new ArrayBlockingQueue<>(200); - private final Map<BackendInstance, BlockingQueue<BackendConnection>> openBackendConnections = new HashMap<>(); - private final int responseDeadline = 20000; - private final int rerunRequestDelay = 200; - private final int numberOfRetriesPerQuery = 5; - - public BackendDriver() { - // ToDo NIELS: Consider multiple consumer threads using 'for(int i = 0; i < x; i++) {}' - GrpcRequestConsumer consumer = new GrpcRequestConsumer(); - Thread consumerThread = new Thread(consumer); - consumerThread.start(); - } - - public int getResponseDeadline() { - return responseDeadline; - } - - /** - * Add a GrpcRequest to the request queue to be executed when a backend is available - * - * @param request The GrpcRequest to be executed later - */ - public void addRequestToExecutionQueue(GrpcRequest request) { - requestQueue.add(request); - } - - public void addBackendConnection(BackendConnection backendConnection) { - var relatedQueue = this.openBackendConnections.get(backendConnection.getBackendInstance()); - if (!relatedQueue.contains(backendConnection)) relatedQueue.add(backendConnection); - } - - /** - * Filters the list of open {@link BackendConnection}s to the specified {@link BackendInstance} and returns the - * first match or attempts to start a new connection if none is found. - * - * @param backend backend instance to get a connection to (e.g. Reveaal, j-Ecdar, custom_engine) - * @return a BackendConnection object linked to the backend, either from the open backend connection list - * or a newly started connection. - * @throws BackendException.NoAvailableBackendConnectionException if unable to retrieve a connection to the backend - * and unable to start a new one - */ - private BackendConnection getBackendConnection(BackendInstance backend) throws BackendException.NoAvailableBackendConnectionException { - BackendConnection connection; - try { - if (!openBackendConnections.containsKey(backend)) - openBackendConnections.put(backend, new ArrayBlockingQueue<>(backend.getNumberOfInstances() + 1)); - - // If no open connection is free, attempt to start a new one - if (openBackendConnections.get(backend).size() < 1) { - tryStartNewBackendConnection(backend); - } - - if (backend.isThreadSafe()){ - connection = openBackendConnections.get(backend).peek(); - } - else{ - // Block until a connection becomes available - connection = openBackendConnections.get(backend).take(); - } - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - return connection; - } - - /** - * Close all open backend connection and kill all locally running processes - * - * @throws IOException if any of the sockets do not respond - */ - public void closeAllBackendConnections() throws IOException { - for (BlockingQueue<BackendConnection> bq : openBackendConnections.values()) { - for (BackendConnection bc : bq) bc.close(); - } - } - - /** - * Attempts to start a new connection to the specified backend. On success, the backend is added to the associated - * queue, otherwise, nothing happens. - * - * @param backend the target backend for the connection - */ - private void tryStartNewBackendConnection(BackendInstance backend) { - Process p = null; - String hostAddress = (backend.isLocal() ? "127.0.0.1" : backend.getBackendLocation()); - long portNumber = 0; - - if (backend.isLocal()) { - try { - portNumber = SocketUtils.findAvailableTcpPort(backend.getPortStart(), backend.getPortEnd()); - } catch (IllegalStateException e) { - // No port was available in range, we assume that connections are running on all ports - return; - } - - do { - ProcessBuilder pb = new ProcessBuilder(backend.getBackendLocation(), "-p", hostAddress + ":" + portNumber); - - try { - p = pb.start(); - } catch (IOException ioException) { - Ecdar.showToast("Unable to start local backend instance"); - ioException.printStackTrace(); - return; - } - // If the process is not alive, it failed while starting up, try again - } while (!p.isAlive()); - } else { - // Filter open connections to this backend and map their used ports to an int stream - var activeEnginePorts = openBackendConnections.get(backend).stream() - .mapToInt((bi) -> Integer.parseInt(bi.getStub().getChannel().authority().split(":", 2)[1])); - - int currentPort = backend.getPortStart(); - do { - // Find port not already connected to - int tempPortNumber = currentPort; - if (activeEnginePorts.noneMatch((i) -> i == tempPortNumber)) { - portNumber = tempPortNumber; - } else { - currentPort++; - } - } while (portNumber == 0 && currentPort <= backend.getPortEnd()); - - if (currentPort > backend.getPortEnd()) { - Ecdar.showToast("Unable to connect to remote engine: " + backend.getName() + " within port range " + backend.getPortStart() + " - " + backend.getPortEnd()); - return; - } - } - - ManagedChannel channel = ManagedChannelBuilder.forTarget(hostAddress + ":" + portNumber) - .usePlaintext() - .keepAliveTime(1000, TimeUnit.MILLISECONDS) - .build(); - - EcdarBackendGrpc.EcdarBackendStub stub = EcdarBackendGrpc.newStub(channel); - BackendConnection newConnection = new BackendConnection(backend, p, stub, channel); - addBackendConnection(newConnection); - } - - private class GrpcRequestConsumer implements Runnable { - @Override - public void run() { - while (true) { - try { - GrpcRequest request = requestQueue.take(); - - try { - request.tries++; - request.execute(getBackendConnection(request.getBackend())); - } catch (BackendException.NoAvailableBackendConnectionException e) { - e.printStackTrace(); - if (request.tries < numberOfRetriesPerQuery) { - new Timer().schedule(new TimerTask() { - @Override - public void run() { - requestQueue.add(request); - } - }, rerunRequestDelay); - } else { - Ecdar.showToast("Unable to find a connection to the requested engine"); - } - return; - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - } -} diff --git a/src/main/java/ecdar/backend/BackendException.java b/src/main/java/ecdar/backend/BackendException.java index 7c77a582..db66a21c 100644 --- a/src/main/java/ecdar/backend/BackendException.java +++ b/src/main/java/ecdar/backend/BackendException.java @@ -9,12 +9,32 @@ public BackendException(final String message, final Throwable cause) { super(message, cause); } - public static class NoAvailableBackendConnectionException extends BackendException { - public NoAvailableBackendConnectionException(final String message) { + public static class NoAvailableEngineConnectionException extends BackendException { + public NoAvailableEngineConnectionException(final String message) { super(message); } - public NoAvailableBackendConnectionException(final String message, final Throwable cause) { + public NoAvailableEngineConnectionException(final String message, final Throwable cause) { + super(message, cause); + } + } + + public static class gRpcChannelShutdownException extends BackendException { + public gRpcChannelShutdownException(final String message) { + super(message); + } + + public gRpcChannelShutdownException(final String message, final Throwable cause) { + super(message, cause); + } + } + + public static class EngineProcessDestructionException extends BackendException { + public EngineProcessDestructionException(final String message) { + super(message); + } + + public EngineProcessDestructionException(final String message, final Throwable cause) { super(message, cause); } } diff --git a/src/main/java/ecdar/backend/BackendHelper.java b/src/main/java/ecdar/backend/BackendHelper.java index 442e0578..a47c7718 100644 --- a/src/main/java/ecdar/backend/BackendHelper.java +++ b/src/main/java/ecdar/backend/BackendHelper.java @@ -3,7 +3,6 @@ import EcdarProtoBuf.ComponentProtos; import ecdar.Ecdar; import ecdar.abstractions.*; -import ecdar.simulation.SimulationState; import javafx.beans.property.SimpleListProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -22,9 +21,9 @@ public final class BackendHelper { final static String TEMP_DIRECTORY = "temporary"; - private static BackendInstance defaultBackend = null; - private static ObservableList<BackendInstance> backendInstances = new SimpleListProperty<>(); - private static List<Runnable> backendInstancesUpdatedListeners = new ArrayList<>(); + private static Engine defaultEngine = null; + private static ObservableList<Engine> engines = new SimpleListProperty<>(); + private static final List<Runnable> enginesUpdatedListeners = new ArrayList<>(); /** * Stores a query as a backend XML query file in the "temporary" directory. @@ -59,185 +58,85 @@ public static String getTempDirectoryAbsolutePath() throws URISyntaxException { } /** - * Stop all running queries. + * Clears all queued queries, stops all active engines, and closes all open engine connections */ - public static void stopQueries() { - Ecdar.getProject().getQueries().forEach(Query::cancel); - } - - public static String getLocationReachableQuery(final Location endLocation, final Component component, final String query) { - return getLocationReachableQuery(endLocation, component, query, null); - } - /** - * Generates a reachability query based on the given location and component - * - * @param endLocation The location which should be checked for reachability - * @return A reachability query string - */ - public static String getLocationReachableQuery(final Location endLocation, final Component component, final String query, final SimulationState state) { - var stringBuilder = new StringBuilder(); - - // append simulation query - stringBuilder.append(query); - - // append arrow - stringBuilder.append(" -> "); - - // append start location here TODO - if (state != null){ - stringBuilder.append(getStartStateString(state)); - stringBuilder.append(";"); - } - - // append end state - stringBuilder.append(getEndStateString(component.getName(), endLocation.getId())); - - // return example: m1||M2->[L1,L4](y<3);[L2, L7](y<2) - System.out.println(stringBuilder); - return stringBuilder.toString(); - } - - private static String getStartStateString(SimulationState state) { - var stringBuilder = new StringBuilder(); - - // append locations - var locations = state.getLocations(); - stringBuilder.append("["); - var appendLocationWithSeparator = false; - for(var componentName:Ecdar.getSimulationHandler().getComponentsInSimulation()){ - var locationFound = false; - - for(var location:locations){ - if (location.getKey().equals(componentName)){ - if (appendLocationWithSeparator){ - stringBuilder.append("," + location.getValue()); - } - else{ - stringBuilder.append(location.getValue()); - } - locationFound = true; - } - if (locationFound){ - // don't go through more locations, when a location is found for the specific component that we're looking at - break; - } + public static void clearEngineConnections() throws BackendException { + BackendHelper.stopQueries(); + + BackendException exception = new BackendException("Exceptions were thrown while attempting to close engine connections"); + for (Engine engine : engines) { + try { + engine.closeConnections(); + } catch (BackendException e) { + exception.addSuppressed(e); } - appendLocationWithSeparator = true; } - stringBuilder.append("]"); - - // append clock values - var clocks = state.getSimulationClocks(); - stringBuilder.append("()"); - return stringBuilder.toString(); - } - - private static String getEndStateString(String componentName, String endLocationId) { - var stringBuilder = new StringBuilder(); - - stringBuilder.append("["); - var appendLocationWithSeparator = false; - - for (var component:Ecdar.getSimulationHandler().getComponentsInSimulation()) - { - if (component.equals(componentName)){ - if (appendLocationWithSeparator){ - stringBuilder.append("," + endLocationId); - } - else{ - stringBuilder.append(endLocationId); - } - } - else{ // add underscore to indicate, that we don't care about the end locations in the other components - if (appendLocationWithSeparator){ - stringBuilder.append(",_"); - } - else{ - stringBuilder.append("_"); - } - } - if (!appendLocationWithSeparator) { - appendLocationWithSeparator = true; - } + if (exception.getSuppressed().length > 0) { + throw exception; } - stringBuilder.append("]"); - stringBuilder.append("()"); - - return stringBuilder.toString(); } /** - * Generates a string for a deadlock query based on the component - * - * @param component The component which should be checked for deadlocks - * @return A deadlock query string + * Stop all running queries. */ - public static String getExistDeadlockQuery(final Component component) { - // Get the names of the locations of this component. Used to produce the deadlock query - final String templateName = component.getName(); - final List<String> locationNames = new ArrayList<>(); - - for (final Location location : component.getLocations()) { - locationNames.add(templateName + "." + location.getId()); - } - - return "(" + String.join(" || ", locationNames) + ") && deadlock"; + public static void stopQueries() { + Ecdar.getProject().getQueries().forEach(Query::cancel); } /** - * Returns the BackendInstance with the specified name, or null, if no such BackendInstance exists + * Returns the Engine with the specified name, or null, if no such Engine exists * - * @param backendInstanceName Name of the BackendInstance to return - * @return The BackendInstance with matching name - * or the default backend instance, if no matching backendInstance exists + * @param engineName Name of the Engine to return + * @return The Engine with matching name + * or the default engine, if no matching engine exists */ - public static BackendInstance getBackendInstanceByName(String backendInstanceName) { - Optional<BackendInstance> backendInstance = BackendHelper.backendInstances.stream().filter(bi -> bi.getName().equals(backendInstanceName)).findFirst(); - return backendInstance.orElse(BackendHelper.getDefaultBackendInstance()); + public static Engine getEngineByName(String engineName) { + Optional<Engine> engine = BackendHelper.engines.stream().filter(e -> e.getName().equals(engineName)).findFirst(); + return engine.orElse(BackendHelper.getDefaultEngine()); } /** - * Returns the default BackendInstance + * Returns the default Engine * - * @return The default BackendInstance + * @return The default Engine */ - public static BackendInstance getDefaultBackendInstance() { - return defaultBackend; + public static Engine getDefaultEngine() { + return defaultEngine; } /** - * Sets the list of BackendInstances to match the provided list + * Sets the list of engines to match the provided list * - * @param updatedBackendInstances The list of BackendInstances that should be stored + * @param updatedEngines The list of engines that should be stored */ - public static void updateBackendInstances(ArrayList<BackendInstance> updatedBackendInstances) { - BackendHelper.backendInstances = FXCollections.observableList(updatedBackendInstances); - for (Runnable runnable : BackendHelper.backendInstancesUpdatedListeners) { + public static void updateEngineInstances(ArrayList<Engine> updatedEngines) { + BackendHelper.engines = FXCollections.observableList(updatedEngines); + for (Runnable runnable : BackendHelper.enginesUpdatedListeners) { runnable.run(); } } /** - * Returns the ObservableList of BackendInstances + * Returns the ObservableList of engines * - * @return The ObservableList of BackendInstances + * @return The ObservableList of engines */ - public static ObservableList<BackendInstance> getBackendInstances() { - return BackendHelper.backendInstances; + public static ObservableList<Engine> getEngines() { + return BackendHelper.engines; } /** - * Sets the default BackendInstance to the provided object + * Sets the default Engine to the provided object * - * @param newDefaultBackend The new defaultBackend + * @param newDefaultEngine The new default engine */ - public static void setDefaultBackendInstance(BackendInstance newDefaultBackend) { - BackendHelper.defaultBackend = newDefaultBackend; + public static void setDefaultEngine(Engine newDefaultEngine) { + BackendHelper.defaultEngine = newDefaultEngine; } - public static void addBackendInstanceListener(Runnable runnable) { - BackendHelper.backendInstancesUpdatedListeners.add(runnable); + public static void addEngineInstanceListener(Runnable runnable) { + BackendHelper.enginesUpdatedListeners.add(runnable); } public static ComponentProtos.ComponentsInfo.Builder getComponentsInfoBuilder(String query) { diff --git a/src/main/java/ecdar/backend/BackendInstance.java b/src/main/java/ecdar/backend/BackendInstance.java deleted file mode 100644 index feb66ca9..00000000 --- a/src/main/java/ecdar/backend/BackendInstance.java +++ /dev/null @@ -1,137 +0,0 @@ -package ecdar.backend; - -import com.google.gson.JsonObject; -import ecdar.utility.serialize.Serializable; -import javafx.beans.property.SimpleBooleanProperty; - -public class BackendInstance implements Serializable { - private static final String NAME = "name"; - private static final String IS_LOCAL = "isLocal"; - private static final String IS_DEFAULT = "isDefault"; - private static final String LOCATION = "location"; - private static final String PORT_RANGE_START = "portRangeStart"; - private static final String PORT_RANGE_END = "portRangeEnd"; - private static final String LOCKED = "locked"; - private static final String IS_THREAD_SAFE = "isThreadSafe"; - - private String name; - private boolean isLocal; - private boolean isDefault; - private boolean isThreadSafe; - private String backendLocation; - private int portStart; - private int portEnd; - private SimpleBooleanProperty locked = new SimpleBooleanProperty(false); - - public BackendInstance() {}; - - public BackendInstance(final JsonObject jsonObject) { - deserialize(jsonObject); - }; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public boolean isLocal() { - return isLocal; - } - - public void setLocal(boolean local) { - isLocal = local; - } - - public boolean isDefault() { - return isDefault; - } - - public void setDefault(boolean aDefault) { - isDefault = aDefault; - } - - public boolean isThreadSafe() { - return isThreadSafe; - } - - public void setIsThreadSafe(boolean threadSafe) { - isThreadSafe = threadSafe; - } - - public String getBackendLocation() { - return backendLocation; - } - - public void setBackendLocation(String backendLocation) { - this.backendLocation = backendLocation; - } - - public int getPortStart() { - return portStart; - } - - public void setPortStart(int portStart) { - this.portStart = portStart; - } - - public int getPortEnd() { - return portEnd; - } - - public void setPortEnd(int portEnd) { - this.portEnd = portEnd; - } - - public int getNumberOfInstances() { - return this.portEnd - this.portStart; - } - - public void lockInstance() { - locked.set(true); - } - - public SimpleBooleanProperty getLockedProperty() { - return locked; - } - - @Override - public JsonObject serialize() { - final JsonObject result = new JsonObject(); - result.addProperty(NAME, getName()); - result.addProperty(IS_LOCAL, isLocal()); - result.addProperty(IS_DEFAULT, isDefault()); - result.addProperty(IS_THREAD_SAFE, isThreadSafe()); - result.addProperty(LOCATION, getBackendLocation()); - result.addProperty(PORT_RANGE_START, getPortStart()); - result.addProperty(PORT_RANGE_END, getPortEnd()); - result.addProperty(LOCKED, getLockedProperty().get()); - - return result; - } - - @Override - public void deserialize(final JsonObject json) { - setName(json.getAsJsonPrimitive(NAME).getAsString()); - setLocal(json.getAsJsonPrimitive(IS_LOCAL).getAsBoolean()); - setDefault(json.getAsJsonPrimitive(IS_DEFAULT).getAsBoolean()); - - try { // ToDo NIELS: Decide to either do this or simply reload defaults - setIsThreadSafe(json.getAsJsonPrimitive(IS_THREAD_SAFE).getAsBoolean()); - } catch (NullPointerException e) { - setIsThreadSafe(false); - } - - setBackendLocation(json.getAsJsonPrimitive(LOCATION).getAsString()); - setPortStart(json.getAsJsonPrimitive(PORT_RANGE_START).getAsInt()); - setPortEnd(json.getAsJsonPrimitive(PORT_RANGE_END).getAsInt()); - if (json.getAsJsonPrimitive(LOCKED).getAsBoolean()) lockInstance(); - } - - @Override - public String toString() { - return name; - } -} diff --git a/src/main/java/ecdar/backend/Engine.java b/src/main/java/ecdar/backend/Engine.java new file mode 100644 index 00000000..936178e9 --- /dev/null +++ b/src/main/java/ecdar/backend/Engine.java @@ -0,0 +1,337 @@ +package ecdar.backend; + +import EcdarProtoBuf.QueryProtos; +import com.google.gson.JsonObject; +import ecdar.Ecdar; +import ecdar.abstractions.Query; +import ecdar.utility.serialize.Serializable; +import io.grpc.stub.StreamObserver; +import javafx.beans.property.SimpleBooleanProperty; + +import java.util.*; +import java.util.concurrent.*; +import java.util.function.Consumer; + +public class Engine implements Serializable { + private static final String NAME = "name"; + private static final String IS_LOCAL = "isLocal"; + private static final String IS_DEFAULT = "isDefault"; + private static final String LOCATION = "location"; + private static final String PORT_RANGE_START = "portRangeStart"; + private static final String PORT_RANGE_END = "portRangeEnd"; + private static final String LOCKED = "locked"; + private static final String IS_THREAD_SAFE = "isThreadSafe"; + private final int responseDeadline = 20000; + private final int rerunRequestDelay = 200; + private final int numberOfRetriesPerQuery = 5; + + private String name; + private boolean isLocal; + private boolean isDefault; + private boolean isThreadSafe; + private int portStart; + private int portEnd; + private SimpleBooleanProperty locked = new SimpleBooleanProperty(false); + /** + * This is either a path to the engines executable or an IP address at which the engine is running + */ + private String engineLocation; + + private final ArrayList<EngineConnection> startedConnections = new ArrayList<>(); + private final BlockingQueue<GrpcRequest> requestQueue = new ArrayBlockingQueue<>(200); // Magic number + // ToDo NIELS: Refactor to resize queue on port range change + private final BlockingQueue<EngineConnection> availableConnections = new ArrayBlockingQueue<>(200); // Magic number + private final EngineConnectionStarter connectionStarter = new EngineConnectionStarter(this); + + public Engine() { + GrpcRequestConsumer consumer = new GrpcRequestConsumer(); + Thread consumerThread = new Thread(consumer); + consumerThread.start(); + } + + public Engine(final JsonObject jsonObject) { + this(); + deserialize(jsonObject); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isLocal() { + return isLocal; + } + + public void setLocal(boolean local) { + isLocal = local; + } + + public boolean isDefault() { + return isDefault; + } + + public void setDefault(boolean aDefault) { + isDefault = aDefault; + } + + public boolean isThreadSafe() { + return isThreadSafe; + } + + public void setIsThreadSafe(boolean threadSafe) { + isThreadSafe = threadSafe; + } + + public String getEngineLocation() { + return engineLocation; + } + + public void setEngineLocation(String engineLocation) { + this.engineLocation = engineLocation; + } + + public String getIpAddress() { + if (isLocal()) { + return "127.0.0.1"; + } else { + return getEngineLocation(); + } + } + + public int getPortStart() { + return portStart; + } + + public void setPortStart(int portStart) { + this.portStart = portStart; + } + + public int getPortEnd() { + return portEnd; + } + + public void setPortEnd(int portEnd) { + this.portEnd = portEnd; + } + + public int getNumberOfInstances() { + return this.portEnd - this.portStart + 1; + } + + public void lockInstance() { + locked.set(true); + } + + public SimpleBooleanProperty getLockedProperty() { + return locked; + } + + public ArrayList<EngineConnection> getStartedConnections() { + return startedConnections; + } + + /** + * Enqueue query for execution with consumers for success and error + * + * @param query the query to enqueue for execution + * @param successConsumer consumer for returned QueryResponse + * @param errorConsumer consumer for any throwable that might result from the execution + */ + public void enqueueQuery(Query query, Consumer<QueryProtos.QueryResponse> successConsumer, Consumer<Throwable> errorConsumer) { + GrpcRequest request = new GrpcRequest(engineConnection -> { + var componentsInfoBuilder = BackendHelper.getComponentsInfoBuilder(query.getQuery()); + + StreamObserver<QueryProtos.QueryResponse> responseObserver = new StreamObserver<>() { + @Override + public void onNext(QueryProtos.QueryResponse value) { + successConsumer.accept(value); + } + + @Override + public void onError(Throwable t) { + errorConsumer.accept(t); + setConnectionAsAvailable(engineConnection); + } + + @Override + public void onCompleted() { + // Release engine connection + setConnectionAsAvailable(engineConnection); + } + }; + + var queryBuilder = QueryProtos.QueryRequest.newBuilder() + .setUserId(1) + .setQueryId(UUID.randomUUID().hashCode()) + .setSettings(QueryProtos.QueryRequest.Settings.newBuilder().setDisableClockReduction(true)) + .setQuery(query.getType().getQueryName() + ": " + query.getQuery()) + .setComponentsInfo(componentsInfoBuilder); + + engineConnection.getStub().withDeadlineAfter(responseDeadline, TimeUnit.MILLISECONDS) + .sendQuery(queryBuilder.build(), responseObserver); + }); + + requestQueue.add(request); + } + + /** + * Signal that the EngineConnection can be used not in use and available for queries + * + * @param connection to make available + */ + public void setConnectionAsAvailable(EngineConnection connection) { + if (!availableConnections.contains(connection)) availableConnections.add(connection); + } + + /** + * Clears all queued queries, stops all active engines, and closes all open engine connections + */ + public void clear() throws BackendException { + BackendHelper.stopQueries(); + requestQueue.clear(); + closeConnections(); + } + + /** + * Filters the list of open {@link EngineConnection}s to the specified {@link Engine} and returns the + * first match or attempts to start a new connection if none is found. + * + * @return a EngineConnection object linked to the engine, either from the open engine connection list + * or a newly started connection. + * @throws BackendException.NoAvailableEngineConnectionException if unable to retrieve a connection to the engine + * and unable to start a new one + */ + private EngineConnection getConnection() throws BackendException.NoAvailableEngineConnectionException { + EngineConnection connection; + try { + // If no open connection is free, attempt to start a new one + if (availableConnections.size() < 1) { + EngineConnection newConnection = this.connectionStarter.tryStartNewConnection(); + + if (newConnection != null) { + startedConnections.add(newConnection); + } + } + + if (isThreadSafe){ + connection = availableConnections.peek(); + } + else{ + // Block until a connection becomes available + connection = availableConnections.take(); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + return connection; + } + + /** + * Close all open engine connections and kill all locally running processes + * + * @throws BackendException if one or more connections throw an exception on {@link EngineConnection#close()} + * (use getSuppressed() to see all thrown exceptions) + */ + public void closeConnections() throws BackendException { + // Create a list for storing all terminated connection + List<CompletableFuture<EngineConnection>> closeFutures = new ArrayList<>(); + BackendException exceptions = new BackendException("Exceptions were thrown while attempting to close engine connections on " + getName()); + + // Attempt to close all connections + for (EngineConnection ec : startedConnections) { + CompletableFuture<EngineConnection> closeFuture = CompletableFuture.supplyAsync(() -> { + try { + ec.close(); + } catch (BackendException.gRpcChannelShutdownException | + BackendException.EngineProcessDestructionException e) { + throw new RuntimeException(e); + } + + return ec; + }); + + closeFutures.add(closeFuture); + } + + for (CompletableFuture<EngineConnection> closeFuture : closeFutures) { + try { + EngineConnection ec = closeFuture.get(); + + availableConnections.remove(ec); + startedConnections.remove(ec); + } catch (InterruptedException | ExecutionException e) { + exceptions.addSuppressed(e.getCause()); + } + } + + if (!startedConnections.isEmpty()) throw exceptions; + } + + private class GrpcRequestConsumer implements Runnable { + @Override + public void run() { + while (true) { + try { + GrpcRequest request = requestQueue.take(); + + try { + request.tries++; + request.execute(getConnection()); + } catch (BackendException.NoAvailableEngineConnectionException e) { + e.printStackTrace(); + if (request.tries < numberOfRetriesPerQuery) { + new Timer().schedule(new TimerTask() { + @Override + public void run() { + requestQueue.add(request); + } + }, rerunRequestDelay); + } else { + Ecdar.showToast("Unable to find a connection to the requested engine"); + } + return; + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + + @Override + public JsonObject serialize() { + final JsonObject result = new JsonObject(); + result.addProperty(NAME, getName()); + result.addProperty(IS_LOCAL, isLocal()); + result.addProperty(IS_DEFAULT, isDefault()); + result.addProperty(IS_THREAD_SAFE, isThreadSafe()); + result.addProperty(LOCATION, getEngineLocation()); + result.addProperty(PORT_RANGE_START, getPortStart()); + result.addProperty(PORT_RANGE_END, getPortEnd()); + result.addProperty(LOCKED, getLockedProperty().get()); + + return result; + } + + @Override + public void deserialize(final JsonObject json) { + setName(json.getAsJsonPrimitive(NAME).getAsString()); + setLocal(json.getAsJsonPrimitive(IS_LOCAL).getAsBoolean()); + setDefault(json.getAsJsonPrimitive(IS_DEFAULT).getAsBoolean()); + setIsThreadSafe(json.has(IS_THREAD_SAFE) && json.getAsJsonPrimitive(IS_THREAD_SAFE).getAsBoolean()); + setEngineLocation(json.getAsJsonPrimitive(LOCATION).getAsString()); + setPortStart(json.getAsJsonPrimitive(PORT_RANGE_START).getAsInt()); + setPortEnd(json.getAsJsonPrimitive(PORT_RANGE_END).getAsInt()); + if (json.getAsJsonPrimitive(LOCKED).getAsBoolean()) lockInstance(); + } + + @Override + public String toString() { + return name; + } +} diff --git a/src/main/java/ecdar/backend/EngineConnection.java b/src/main/java/ecdar/backend/EngineConnection.java new file mode 100644 index 00000000..3cd9a5ff --- /dev/null +++ b/src/main/java/ecdar/backend/EngineConnection.java @@ -0,0 +1,84 @@ +package ecdar.backend; + +import EcdarProtoBuf.EcdarBackendGrpc; +import io.grpc.ManagedChannel; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class EngineConnection { + private final Engine engine; + private final EcdarBackendGrpc.EcdarBackendStub stub; + private final ManagedChannel channel; + private final Process process; + private final int port; + + EngineConnection(Engine engine, ManagedChannel channel, EcdarBackendGrpc.EcdarBackendStub stub, Process process) { + this.engine = engine; + this.stub = stub; + this.channel = channel; + this.process = process; + this.port = Integer.parseInt(getStub().getChannel().authority().split(":", 2)[1]); + } + + EngineConnection(Engine engine, ManagedChannel channel, EcdarBackendGrpc.EcdarBackendStub stub) { + this(engine, channel, stub, null); + } + + /** + * Get the gRPC stub of the connection to use for query execution + * + * @return the gRPC stub of this connection + */ + public EcdarBackendGrpc.EcdarBackendStub getStub() { + return stub; + } + + /** + * Get the engine that should be used to execute + * the query currently associated with this engine connection + * + * @return the instance of the associated executable query object, + * or null, if no executable query is currently associated + */ + public Engine getEngine() { + return engine; + } + + public int getPort() { + return port; + } + + /** + * Close the gRPC connection and end the process + * + * @throws BackendException.gRpcChannelShutdownException if an InterruptedException is encountered while trying to shut down the gRPC channel. + * @throws BackendException.EngineProcessDestructionException if the connected engine process throws an ExecutionException, an InterruptedException, or a TimeoutException. + */ + public void close() throws BackendException.gRpcChannelShutdownException, BackendException.EngineProcessDestructionException { + if (!channel.isShutdown()) { + try { + channel.shutdown(); + if (!channel.awaitTermination(45, TimeUnit.SECONDS)) { + channel.shutdownNow(); // Forcefully close the connection + } + } catch (InterruptedException e) { + // Engine location is either the file path or the IP, here we want the channel address + throw new BackendException.gRpcChannelShutdownException("The gRPC channel to \"" + this.engine.getName() + "\" instance running at: " + engine.getIpAddress() + ":" + this.port + "was interrupted during termination", e.getCause()); + } + } + + // If the engine is remote, there will not be a process + if (process != null) { + try { + java.util.concurrent.CompletableFuture<Process> terminated = process.onExit(); + process.destroy(); + terminated.get(45, TimeUnit.SECONDS); + } catch (ExecutionException | InterruptedException | TimeoutException e) { + // Add the engine location to the exception, as it contains the path to the executable + throw new BackendException.EngineProcessDestructionException("A process running: " + this.engine.getEngineLocation() + " on port " + this.port + " threw an exception during shutdown", e.getCause()); + } + } + } +} diff --git a/src/main/java/ecdar/backend/EngineConnectionStarter.java b/src/main/java/ecdar/backend/EngineConnectionStarter.java new file mode 100644 index 00000000..be8c3a2f --- /dev/null +++ b/src/main/java/ecdar/backend/EngineConnectionStarter.java @@ -0,0 +1,119 @@ +package ecdar.backend; + +import EcdarProtoBuf.EcdarBackendGrpc; +import ecdar.Ecdar; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import org.springframework.util.SocketUtils; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import java.util.stream.Stream; + +public class EngineConnectionStarter { + private final Engine engine; + private final int maxRetriesForStartingEngineProcess = 3; + + EngineConnectionStarter(Engine engine) { + this.engine = engine; + } + + /** + * Attempts to start a new connection to the specified engine. + * + * @return the started EngineConnection if successful, + * otherwise, null. + */ + protected EngineConnection tryStartNewConnection() { + EngineConnection newConnection; + + if (engine.isLocal()) { + newConnection = startLocalConnection(); + } else { + newConnection = startRemoteConnection(); + } + + // If the connection is null, no new connection was started + return newConnection; + } + + /** + * Starts a process, creates an EngineConnection to it, and returns that connection + * + * @return an EngineConnection to a local engine running in a Process or null if all ports are already in use + */ + private EngineConnection startLocalConnection() { + long port; + try { + port = SocketUtils.findAvailableTcpPort(engine.getPortStart(), engine.getPortEnd()); + } catch (IllegalStateException e) { + // All ports specified for engine are already used for running engines + return null; + } + + // Start local process of engine + Process p; + int attempts = 0; + + do { + attempts++; + ProcessBuilder pb = new ProcessBuilder(engine.getEngineLocation(), "-p", engine.getIpAddress() + ":" + port); + + try { + p = pb.start(); + } catch (IOException ioException) { + Ecdar.showToast("Unable to start local engine instance"); + ioException.printStackTrace(); + return null; + } + } while (!p.isAlive() && attempts < maxRetriesForStartingEngineProcess); + + ManagedChannel channel = startGrpcChannel(engine.getIpAddress(), port); + EcdarBackendGrpc.EcdarBackendStub stub = EcdarBackendGrpc.newStub(channel); + return new EngineConnection(engine, channel, stub, p); + } + + /** + * Creates and returns an EngineConnection to the remote engine + * + * @return an EngineConnection to a remote engine or null if all ports are already connected to + */ + private EngineConnection startRemoteConnection() { + // Get a stream of ports already used for connections + Supplier<Stream<Integer>> activeEnginePortsStream = () -> engine.getStartedConnections().stream() + .mapToInt(EngineConnection::getPort).boxed(); + + long port = engine.getPortStart(); + for (int currentPort = engine.getPortStart(); currentPort <= engine.getPortEnd(); currentPort++) { + final int tempPort = currentPort; + if (activeEnginePortsStream.get().anyMatch((i) -> i == tempPort)) { + port = currentPort; + break; + } + } + + if (port > engine.getPortEnd()) { + // All ports specified for engine are already used for connections + return null; + } + + ManagedChannel channel = startGrpcChannel(engine.getIpAddress(), port); + EcdarBackendGrpc.EcdarBackendStub stub = EcdarBackendGrpc.newStub(channel); + return new EngineConnection(engine, channel, stub); + } + + /** + * Connects a gRPC channel to the address at the specified port, expecting that an engine is running there + * + * @param address of the target engine + * @param port of the target engine at the address + * @return the created gRPC channel + */ + private ManagedChannel startGrpcChannel(final String address, final long port) { + return ManagedChannelBuilder.forTarget(address + ":" + port) + .usePlaintext() + .keepAliveTime(1000, TimeUnit.MILLISECONDS) + .build(); + } +} diff --git a/src/main/java/ecdar/backend/GrpcRequest.java b/src/main/java/ecdar/backend/GrpcRequest.java index d6c667f8..642ebfd0 100644 --- a/src/main/java/ecdar/backend/GrpcRequest.java +++ b/src/main/java/ecdar/backend/GrpcRequest.java @@ -3,20 +3,14 @@ import java.util.function.Consumer; public class GrpcRequest { - private final Consumer<BackendConnection> request; - private final BackendInstance backend; + private final Consumer<EngineConnection> request; public int tries = 0; - public GrpcRequest(Consumer<BackendConnection> request, BackendInstance backend) { + public GrpcRequest(Consumer<EngineConnection> request) { this.request = request; - this.backend = backend; } - public void execute(BackendConnection backendConnection) { - this.request.accept(backendConnection); - } - - public BackendInstance getBackend() { - return backend; + public void execute(EngineConnection engineConnection) { + this.request.accept(engineConnection); } } \ No newline at end of file diff --git a/src/main/java/ecdar/backend/QueryHandler.java b/src/main/java/ecdar/backend/QueryHandler.java deleted file mode 100644 index 40142d64..00000000 --- a/src/main/java/ecdar/backend/QueryHandler.java +++ /dev/null @@ -1,272 +0,0 @@ -package ecdar.backend; - -import EcdarProtoBuf.QueryProtos; -import EcdarProtoBuf.QueryProtos.QueryRequest.Settings; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import ecdar.Ecdar; -import ecdar.abstractions.Component; -import ecdar.abstractions.Query; -import ecdar.abstractions.QueryState; -import ecdar.abstractions.QueryType; -import ecdar.controllers.EcdarController; -import ecdar.utility.UndoRedoStack; -import ecdar.utility.helpers.StringValidator; -import io.grpc.stub.StreamObserver; -import javafx.application.Platform; -import javafx.collections.ObservableList; - -import java.util.ArrayList; -import java.util.NoSuchElementException; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -public class QueryHandler { - private final BackendDriver backendDriver; - private final ArrayList<BackendConnection> connections = new ArrayList<>(); - - public QueryHandler(BackendDriver backendDriver) { - this.backendDriver = backendDriver; - } - - /** - * Executes the specified query - * @param query query to be executed - */ - public void executeQuery(Query query) throws NoSuchElementException { - if (query.getQueryState().equals(QueryState.RUNNING) || !StringValidator.validateQuery(query.getQuery())) return; - - if (query.getQuery().isEmpty()) { - query.setQueryState(QueryState.SYNTAX_ERROR); - query.addError("Query is empty"); - return; - } - - query.setQueryState(QueryState.RUNNING); - query.errors().set(""); - - GrpcRequest request = new GrpcRequest(backendConnection -> { - connections.add(backendConnection); // Save reference for closing connection on exit - - var componentsInfoBuilder = BackendHelper.getComponentsInfoBuilder(query.getQuery()); - - StreamObserver<QueryProtos.QueryResponse> responseObserver = new StreamObserver<>() { - @Override - public void onNext(QueryProtos.QueryResponse value) { - handleQueryResponse(value, query); - } - - @Override - public void onError(Throwable t) { - handleQueryBackendError(t, query); - - // Release backend connection - backendDriver.addBackendConnection(backendConnection); - connections.remove(backendConnection); - } - - @Override - public void onCompleted() { - // Release backend connection - backendDriver.addBackendConnection(backendConnection); - connections.remove(backendConnection); - } - }; - - var queryBuilder = QueryProtos.QueryRequest.newBuilder() - .setUserId(1) - .setQueryId(UUID.randomUUID().hashCode()) - .setSettings(Settings.newBuilder().setDisableClockReduction(true)) - .setQuery(query.getType().getQueryName() + ": " + query.getQuery()) - .setComponentsInfo(componentsInfoBuilder); - - backendConnection.getStub().withDeadlineAfter(backendDriver.getResponseDeadline(), TimeUnit.MILLISECONDS) - .sendQuery(queryBuilder.build(), responseObserver); - }, query.getBackend()); - - backendDriver.addRequestToExecutionQueue(request); - } - - /** - * Close all open backend connection and kill all locally running processes - */ - public void closeAllBackendConnections() { - for (BackendConnection con : connections) { - con.close(); - } - } - - private void handleQueryResponse(QueryProtos.QueryResponse value, Query query) { - // If the query has been cancelled, ignore the result - if (query.getQueryState() == QueryState.UNKNOWN) return; - switch (value.getResultCase()) { - case REFINEMENT: - if (value.getRefinement().getSuccess()) { - query.setQueryState(QueryState.SUCCESSFUL); - query.getSuccessConsumer().accept(true); - } else { - query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getRefinement().getReason())); - query.getSuccessConsumer().accept(false); - query.getStateActionConsumer().accept(value.getRefinement().getState(), - value.getRefinement().getActionList()); - } - break; - - case CONSISTENCY: - if (value.getConsistency().getSuccess()) { - query.setQueryState(QueryState.SUCCESSFUL); - query.getSuccessConsumer().accept(true); - } else { - query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getConsistency().getReason())); - query.getSuccessConsumer().accept(false); - query.getStateActionConsumer().accept(value.getConsistency().getState(), - value.getConsistency().getActionList()); - } - break; - - case DETERMINISM: - if (value.getDeterminism().getSuccess()) { - query.setQueryState(QueryState.SUCCESSFUL); - query.getSuccessConsumer().accept(true); - } else { - query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getDeterminism().getReason())); - query.getSuccessConsumer().accept(false); - query.getStateActionConsumer().accept(value.getDeterminism().getState(), - value.getDeterminism().getActionList()); - - } - break; - - case IMPLEMENTATION: - if (value.getImplementation().getSuccess()) { - query.setQueryState(QueryState.SUCCESSFUL); - query.getSuccessConsumer().accept(true); - } else { - query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getImplementation().getReason())); - query.getSuccessConsumer().accept(false); - //ToDo: These errors are not implemented in the Reveaal backend. - query.getStateActionConsumer().accept(value.getImplementation().getState(), - new ArrayList<>()); - } - break; - - case REACHABILITY: - if (value.getReachability().getSuccess()) { - query.setQueryState(QueryState.SUCCESSFUL); - Ecdar.showToast("Reachability check was successful and the location can be reached."); - - //create list of edge id's - ArrayList<String> edgeIds = new ArrayList<>(); - for(var pathsList : value.getReachability().getComponentPathsList()){ - for(var id : pathsList.getEdgeIdsList().toArray()) { - edgeIds.add(id.toString()); - } - } - //highlight the edges - Ecdar.getSimulationHandler().highlightReachabilityEdges(edgeIds); - query.getSuccessConsumer().accept(true); - } - else if(!value.getReachability().getSuccess()){ - Ecdar.showToast("Reachability check was successful but the location cannot be reached."); - query.getSuccessConsumer().accept(true); - } else { - query.setQueryState(QueryState.ERROR); - Ecdar.showToast("Error from backend: Reachability check was unsuccessful!"); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getReachability().getReason())); - query.getSuccessConsumer().accept(false); - //ToDo: These errors are not implemented in the Reveaal backend. - query.getStateActionConsumer().accept(value.getReachability().getState(), - new ArrayList<>()); - } - break; - - case COMPONENT: - query.setQueryState(QueryState.SUCCESSFUL); - query.getSuccessConsumer().accept(true); - JsonObject returnedComponent = (JsonObject) JsonParser.parseString(value.getComponent().getComponent().getJson()); - addGeneratedComponent(new Component(returnedComponent)); - break; - - case ERROR: - query.setQueryState(QueryState.ERROR); - Ecdar.showToast(value.getError()); - query.getFailureConsumer().accept(new BackendException.QueryErrorException(value.getError())); - query.getSuccessConsumer().accept(false); - break; - - case RESULT_NOT_SET: - query.setQueryState(QueryState.ERROR); - query.getSuccessConsumer().accept(false); - break; - } - } - - private void handleQueryBackendError(Throwable t, Query query) { - // If the query has been cancelled, ignore the error - if (query.getQueryState() == QueryState.UNKNOWN) return; - - // due to lack of information from backend if the reachability check shows that a location can NOT be reached, this is the most accurate information we can provide - if(query.getType() == QueryType.REACHABILITY){ - Ecdar.showToast("Timeout (no response from backend): The reachability query failed. This might be due to the fact that the location is not reachable."); - } - - // Each error starts with a capitalized description of the error equal to the gRPC error type encountered - String errorType = t.getMessage().split(":\\s+", 2)[0]; - - if ("DEADLINE_EXCEEDED".equals(errorType)) { - query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException("The backend did not answer the request in time")); - } else { - try { - query.setQueryState(QueryState.ERROR); - query.getFailureConsumer().accept(new BackendException.QueryErrorException("The execution of this query failed with message:" + System.lineSeparator() + t.getLocalizedMessage())); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - private void addGeneratedComponent(Component newComponent) { - Platform.runLater(() -> { - newComponent.setTemporary(true); - - ObservableList<Component> listOfGeneratedComponents = Ecdar.getProject().getTempComponents(); // ToDo NIELS: Refactor - Component matchedComponent = null; - - for (Component currentGeneratedComponent : listOfGeneratedComponents) { - int comparisonOfNames = currentGeneratedComponent.getName().compareTo(newComponent.getName()); - - if (comparisonOfNames == 0) { - matchedComponent = currentGeneratedComponent; - break; - } else if (comparisonOfNames < 0) { - break; - } - } - - if (matchedComponent == null) { - UndoRedoStack.pushAndPerform(() -> { // Perform - Ecdar.getProject().getTempComponents().add(newComponent); - }, () -> { // Undo - Ecdar.getProject().getTempComponents().remove(newComponent); - }, "Created new component: " + newComponent.getName(), "add-circle"); - } else { - // Remove current component with name and add the newly generated one - Component finalMatchedComponent = matchedComponent; - UndoRedoStack.pushAndPerform(() -> { // Perform - Ecdar.getProject().getTempComponents().remove(finalMatchedComponent); - Ecdar.getProject().getTempComponents().add(newComponent); - }, () -> { // Undo - Ecdar.getProject().getTempComponents().remove(newComponent); - Ecdar.getProject().getTempComponents().add(finalMatchedComponent); - }, "Created new component: " + newComponent.getName(), "add-circle"); - } - - EcdarController.setActiveModelForActiveCanvas(newComponent); - }); - } -} diff --git a/src/main/java/ecdar/backend/SimulationHandler.java b/src/main/java/ecdar/backend/SimulationHandler.java index 4fa74b03..1dab3e5d 100644 --- a/src/main/java/ecdar/backend/SimulationHandler.java +++ b/src/main/java/ecdar/backend/SimulationHandler.java @@ -16,7 +16,6 @@ import javafx.collections.ObservableMap; import javafx.util.Pair; -import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; @@ -45,16 +44,16 @@ public class SimulationHandler { private final ObservableMap<String, BigDecimal> simulationVariables = FXCollections.observableHashMap(); private final ObservableMap<String, BigDecimal> simulationClocks = FXCollections.observableHashMap(); public ObservableList<SimulationState> traceLog = FXCollections.observableArrayList(); - private final BackendDriver backendDriver; - private final ArrayList<BackendConnection> connections = new ArrayList<>(); private List<String> ComponentsInSimulation = new ArrayList<>(); + private EngineConnection con; + /** * Empty constructor that should be used if the system or project has not be initialized yet */ - public SimulationHandler(BackendDriver backendDriver) { - this.backendDriver = backendDriver; + public SimulationHandler() { + } public void clearComponentsInSimulation() { @@ -75,6 +74,11 @@ private void initializeSimulation() { this.selectedEdge.set(null); this.traceLog.clear(); this.system = getSystem(); + + if (con == null) { + EngineConnectionStarter ecs = new EngineConnectionStarter(BackendHelper.getDefaultEngine()); + con = ecs.tryStartNewConnection(); + } } /** @@ -83,11 +87,11 @@ private void initializeSimulation() { public void initialStep() { initializeSimulation(); - GrpcRequest request = new GrpcRequest(backendConnection -> { + GrpcRequest request = new GrpcRequest(engineConnection -> { StreamObserver<SimulationStepResponse> responseObserver = new StreamObserver<>() { @Override public void onNext(QueryProtos.SimulationStepResponse value) { - // TODO this is temp solution to compile but should be fixed to handle ambiguity + // ToDo: This is temp solution to compile but should be fixed to handle ambiguity currentState.set(new SimulationState(value.getNewDecisionPoints(0))); Platform.runLater(() -> traceLog.add(currentState.get())); } @@ -95,17 +99,10 @@ public void onNext(QueryProtos.SimulationStepResponse value) { @Override public void onError(Throwable t) { Ecdar.showToast("Could not start simulation:\n" + t.getMessage()); - - // Release backend connection - backendDriver.addBackendConnection(backendConnection); - connections.remove(backendConnection); } @Override public void onCompleted() { - // Release backend connection - backendDriver.addBackendConnection(backendConnection); - connections.remove(backendConnection); } }; @@ -120,12 +117,11 @@ public void onCompleted() { .setComponentComposition(composition) .setComponentsInfo(comInfo); simStartRequest.setSimulationInfo(simInfo); - backendConnection.getStub().withDeadlineAfter(this.backendDriver.getResponseDeadline(), TimeUnit.MILLISECONDS) + engineConnection.getStub().withDeadlineAfter(20000, TimeUnit.MILLISECONDS) .startSimulation(simStartRequest.build(), responseObserver); - }, BackendHelper.getDefaultBackendInstance()); - - backendDriver.addRequestToExecutionQueue(request); - + }); + + request.execute(con); numberOfSteps++; } @@ -143,7 +139,7 @@ public void nextStep() { // removes invalid states from the log when stepping forward after previewing a previous state removeStatesFromLog(currentState.get()); - GrpcRequest request = new GrpcRequest(backendConnection -> { + GrpcRequest request = new GrpcRequest(engineConnection -> { StreamObserver<SimulationStepResponse> responseObserver = new StreamObserver<>() { @Override public void onNext(QueryProtos.SimulationStepResponse value) { @@ -155,17 +151,10 @@ public void onNext(QueryProtos.SimulationStepResponse value) { @Override public void onError(Throwable t) { Ecdar.showToast("Could not take next step in simulation\nError: " + t.getMessage()); - - // Release backend connection - backendDriver.addBackendConnection(backendConnection); - connections.remove(backendConnection); - } + } @Override public void onCompleted() { - // Release backend connection - backendDriver.addBackendConnection(backendConnection); - connections.remove(backendConnection); } }; @@ -173,6 +162,7 @@ public void onCompleted() { for (Component c : Ecdar.getProject().getComponents()) { comInfo.addComponents(ComponentProtos.Component.newBuilder().setJson(c.serialize().toString()).build()); } + comInfo.setComponentsHash(comInfo.getComponentsList().hashCode()); var simStepRequest = SimulationStepRequest.newBuilder(); var simInfo = SimulationInfo.newBuilder() @@ -185,13 +175,11 @@ public void onCompleted() { var decision = Decision.newBuilder().setEdge(edge).setSource(source); simStepRequest.setChosenDecision(decision); - backendConnection.getStub().withDeadlineAfter(this.backendDriver.getResponseDeadline(), TimeUnit.MILLISECONDS) + engineConnection.getStub().withDeadlineAfter(20000, TimeUnit.MILLISECONDS) .takeSimulationStep(simStepRequest.build(), responseObserver); - }, BackendHelper.getDefaultBackendInstance()); - - backendDriver.addRequestToExecutionQueue(request); + }); - // increments the number of steps taken during this simulation + request.execute(con); numberOfSteps++; } @@ -297,17 +285,6 @@ public boolean isSimulationRunning() { return false; // ToDo: Implement } - /** - * Close all open backend connection and kill all locally running processes - * - * @throws IOException if any of the sockets do not respond - */ - public void closeAllBackendConnections() throws IOException { - for (BackendConnection con : connections) { - con.close(); - } - } - /** * Removes all states from the trace log after the given state */ diff --git a/src/main/java/ecdar/code_analysis/CodeAnalysis.java b/src/main/java/ecdar/code_analysis/CodeAnalysis.java index d5f9bd74..c3599ceb 100644 --- a/src/main/java/ecdar/code_analysis/CodeAnalysis.java +++ b/src/main/java/ecdar/code_analysis/CodeAnalysis.java @@ -188,5 +188,4 @@ public static void enable() { public static void disable() { ENABLED = false; } - } diff --git a/src/main/java/ecdar/code_analysis/Nearable.java b/src/main/java/ecdar/code_analysis/Nearable.java index fdad43cb..94d3a5a6 100644 --- a/src/main/java/ecdar/code_analysis/Nearable.java +++ b/src/main/java/ecdar/code_analysis/Nearable.java @@ -1,7 +1,5 @@ package ecdar.code_analysis; public interface Nearable { - String generateNearString(); - } diff --git a/src/main/java/ecdar/controllers/BackendOptionsDialogController.java b/src/main/java/ecdar/controllers/BackendOptionsDialogController.java deleted file mode 100644 index 1950f30b..00000000 --- a/src/main/java/ecdar/controllers/BackendOptionsDialogController.java +++ /dev/null @@ -1,496 +0,0 @@ -package ecdar.controllers; - -import com.google.gson.JsonArray; -import com.google.gson.JsonParser; -import com.jfoenix.controls.JFXButton; -import com.jfoenix.controls.JFXRippler; -import ecdar.Ecdar; -import ecdar.backend.BackendInstance; -import ecdar.backend.BackendHelper; -import ecdar.presentations.BackendInstancePresentation; -import javafx.fxml.Initializable; -import javafx.scene.Node; -import javafx.scene.control.ToggleGroup; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; -import javafx.scene.layout.VBox; -import org.apache.commons.lang3.Range; -import org.apache.commons.lang3.SystemUtils; - -import java.io.File; -import java.io.IOException; -import java.net.InetAddress; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.UnknownHostException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.*; - -public class BackendOptionsDialogController implements Initializable { - public VBox backendInstanceList; - public JFXRippler addBackendButton; - public JFXButton closeButton; - public ToggleGroup defaultBackendToggleGroup = new ToggleGroup(); - public JFXButton saveButton; - public JFXButton resetBackendsButton; - - @Override - public void initialize(URL location, ResourceBundle resources) { - initializeBackendInstanceList(); - } - - /** - * Reverts any changes made to the backend options by reloading the options specified in the preference file, - * or to the default, if no backends are present in the preferences file. - */ - public void cancelBackendOptionsChanges() { - initializeBackendInstanceList(); - } - - /** - * Saves the changes made to the backend options to the preferences file and returns true - * if no errors where found in the backend instance definitions, otherwise false. - * - * @return whether the changes could be saved, - * meaning that no errors where found in the changes made to the backend options - */ - public boolean saveChangesToBackendOptions() { - if (this.backendInstanceListIsErrorFree()) { - ArrayList<BackendInstance> backendInstances = new ArrayList<>(); - for (Node backendInstance : backendInstanceList.getChildren()) { - if (backendInstance instanceof BackendInstancePresentation) { - backendInstances.add(((BackendInstancePresentation) backendInstance).getController().updateBackendInstance()); - } - } - - if (backendInstances.size() < 1) { - Ecdar.showToast("Please add an engine instance or press: \"" + resetBackendsButton.getText() + "\""); - return false; - } - - // Close all backend connections to avoid dangling backend connections when port range is changed - try { - Ecdar.getBackendDriver().closeAllBackendConnections(); - Ecdar.getQueryExecutor().closeAllBackendConnections(); - } catch (IOException e) { - e.printStackTrace(); - } - - BackendHelper.updateBackendInstances(backendInstances); - - JsonArray jsonArray = new JsonArray(); - for (BackendInstance bi : backendInstances) { - jsonArray.add(bi.serialize()); - } - - Ecdar.preferences.put("backend_instances", jsonArray.toString()); - - BackendInstance defaultBackend = backendInstances.stream().filter(BackendInstance::isDefault).findFirst().orElse(backendInstances.get(0)); - BackendHelper.setDefaultBackendInstance(defaultBackend); - - String defaultBackendName = (defaultBackend.getName()); - Ecdar.preferences.put("default_backend", defaultBackendName); - - return true; - } else { - return false; - } - } - - /** - * Resets the backends to the default backends present in the 'default_backends.json' file. - */ - public void resetBackendsToDefault() { - updateBackendsInGUI(getPackagedBackends()); - } - - private void initializeBackendInstanceList() { - ArrayList<BackendInstance> backends; - - // Load backends from preferences or get default - var savedBackends = Ecdar.preferences.get("backend_instances", null); - if (savedBackends != null) { - backends = getBackendsFromJsonArray( - JsonParser.parseString(savedBackends).getAsJsonArray()); - } else { - backends = getPackagedBackends(); - } - - // Style add backend button and handle click event - HBox.setHgrow(addBackendButton, Priority.ALWAYS); - addBackendButton.setMaxWidth(Double.MAX_VALUE); - addBackendButton.setOnMouseClicked((event) -> { - BackendInstancePresentation newBackendInstancePresentation = new BackendInstancePresentation(); - addBackendInstancePresentationToList(newBackendInstancePresentation); - }); - - updateBackendsInGUI(backends); - } - - /** - * Clear the backend instance list and add the newly defined backends to it. - * - * @param backends The new list of backends - */ - private void updateBackendsInGUI(ArrayList<BackendInstance> backends) { - backendInstanceList.getChildren().clear(); - - backends.forEach((bi) -> { - BackendInstancePresentation newBackendInstancePresentation = new BackendInstancePresentation(bi); - - // Bind input fields that should not be changed for packaged backends to the locked property of the backend instance - newBackendInstancePresentation.getController().backendName.disableProperty().bind(bi.getLockedProperty()); - newBackendInstancePresentation.getController().pathToBackend.disableProperty().bind(bi.getLockedProperty()); - newBackendInstancePresentation.getController().pickPathToBackend.disableProperty().bind(bi.getLockedProperty()); - newBackendInstancePresentation.getController().isLocal.disableProperty().bind(bi.getLockedProperty()); - addBackendInstancePresentationToList(newBackendInstancePresentation); - }); - - BackendHelper.updateBackendInstances(backends); - } - - /** - * Instantiate backends defined in the given JsonArray. - * - * @param backends The JsonArray containing the backends - * @return An ArrayList of the instantiated backends - */ - private ArrayList<BackendInstance> getBackendsFromJsonArray(JsonArray backends) { - ArrayList<BackendInstance> backendInstances = new ArrayList<>(); - backendInstanceList.getChildren().clear(); - backends.forEach((backend) -> { - BackendInstance newBackendInstance = new BackendInstance(backend.getAsJsonObject()); - backendInstances.add(newBackendInstance); - }); - - return backendInstances; - } - - /** - * Checks a set of paths to the packaged engines, j-Ecdar and Reveaal, and instantiates them - * if one of the related files exists. - * - * @return Backend instances of the packaged engines - */ - private ArrayList<BackendInstance> getPackagedBackends() { - ArrayList<BackendInstance> defaultBackends = new ArrayList<>(); - - // Add Reveaal engine - var reveaal = new BackendInstance(); - reveaal.setName("Reveaal"); - reveaal.setLocal(true); - reveaal.setDefault(true); - - // The engine is thread-safe, a range just adds options for finding an open port - // Only one process will be started - reveaal.setPortStart(5040); - reveaal.setPortEnd(5042); - reveaal.lockInstance(); - reveaal.setIsThreadSafe(true); - - // Load correct Reveaal executable based on OS - List<String> potentialFilesForReveaal = new ArrayList<>(); - if (SystemUtils.IS_OS_WINDOWS) { - potentialFilesForReveaal.add("Reveaal.exe"); - } else { - potentialFilesForReveaal.add("Reveaal"); - } - if (setBackendPathIfFileExists(reveaal, potentialFilesForReveaal)) defaultBackends.add(reveaal); - - // Add jECDAR engine - var jEcdar = new BackendInstance(); - jEcdar.setName("j-Ecdar"); - jEcdar.setLocal(true); - jEcdar.setDefault(false); - jEcdar.setPortStart(5042); - jEcdar.setPortEnd(5050); - jEcdar.lockInstance(); - jEcdar.setIsThreadSafe(false); - - // Load correct j-Ecdar executable based on OS - List<String> potentialFiledForJEcdar = new ArrayList<>(); - if (SystemUtils.IS_OS_WINDOWS) { - potentialFiledForJEcdar.add("j-Ecdar.bat"); - } else { - potentialFiledForJEcdar.add("j-Ecdar"); - } - - if (setBackendPathIfFileExists(jEcdar, potentialFiledForJEcdar)) defaultBackends.add(jEcdar); - - return defaultBackends; - } - - /** - * Sets the path to the backend instance if one of the potential files exists - * - * @param engine The backend instance of the engine to set the path for - * @param potentialFiles List of potential files to use for the engine - * @return True if one of the potentialFiles where found in path, false otherwise. - * This value also signals whether the engine backendLocation is set - */ - private boolean setBackendPathIfFileExists(BackendInstance engine, List<String> potentialFiles) { - engine.setBackendLocation(""); - - try { - // Get directory containing the bin and lib folders for the executing program - String pathToEcdarDirectory = new File(getClass().getProtectionDomain().getCodeSource().getLocation().toURI()).getParentFile().getPath(); - - List<File> files = List.of(Objects.requireNonNull(new File(pathToEcdarDirectory).listFiles())); - for (File f : files) { - if (potentialFiles.contains(f.getName())) { - engine.setBackendLocation(f.getAbsolutePath()); - return true; - } - } - } catch (URISyntaxException e) { - e.printStackTrace(); - Ecdar.showToast("Unable to get URI of parent directory: \"" + getClass().getProtectionDomain().getCodeSource().getLocation() + "\" due to: " + e.getMessage()); - } catch (NullPointerException e) { - e.printStackTrace(); - Ecdar.showToast("Encountered null reference when trying to get path of executing program"); - } - - return !engine.getBackendLocation().equals(""); - } - - /** - * Add the new backend instance presentation to the backend options dialog - * @param newBackendInstancePresentation The presentation of the new backend instance - */ - private void addBackendInstancePresentationToList(BackendInstancePresentation newBackendInstancePresentation) { - backendInstanceList.getChildren().add(newBackendInstancePresentation); - newBackendInstancePresentation.getController().moveBackendInstanceUpRippler.setOnMouseClicked((mouseEvent) -> moveBackendInstance(newBackendInstancePresentation, -1)); - newBackendInstancePresentation.getController().moveBackendInstanceDownRippler.setOnMouseClicked((mouseEvent) -> moveBackendInstance(newBackendInstancePresentation, +1)); - - // Set remove backend action to only fire if the backend is not locked - newBackendInstancePresentation.getController().removeBackendRippler.setOnMouseClicked((mouseEvent) -> { - if (!newBackendInstancePresentation.getController().defaultBackendRadioButton.isSelected()) { - backendInstanceList.getChildren().remove(newBackendInstancePresentation); - } - }); - newBackendInstancePresentation.getController().defaultBackendRadioButton.setToggleGroup(defaultBackendToggleGroup); - } - - /** - * Calculated the location new position of the backend instance, i places further down, in the backend instance list. - * The backend instance presentation is removed at added to the new position. - * Given a negative value, the instance is moved up. This function uses loop-around, meaning that: - * - If the instance is moved down while already at the bottom of the list, it is placed at the top. - * - If the instance is moved up while already at the top of the list, it is placed at the bottom. - * - * @param backendInstancePresentation The backend instance presentation to move - * @param i The number of steps to move the backend instance down - */ - private void moveBackendInstance(BackendInstancePresentation backendInstancePresentation, int i) { - int currentIndex = backendInstanceList.getChildren().indexOf(backendInstancePresentation); - int newIndex = (currentIndex + i) % backendInstanceList.getChildren().size(); - if (newIndex < 0) { - newIndex = backendInstanceList.getChildren().size() - 1; - } - - backendInstanceList.getChildren().remove(backendInstancePresentation); - backendInstanceList.getChildren().add(newIndex, backendInstancePresentation); - } - - /** - * Marks input fields in the backendInstanceList that contains errors and returns whether any errors were found - * - * @return whether any errors were found - */ - private boolean backendInstanceListIsErrorFree() { - boolean error = true; - - for (Node child : backendInstanceList.getChildren()) { - if (child instanceof BackendInstancePresentation) { - BackendInstanceController backendInstanceController = ((BackendInstancePresentation) child).getController(); - error = backendNameIsErrorFree(backendInstanceController) && error; - error = portRangeIsErrorFree(backendInstanceController) && error; - error = backendInstanceLocationIsErrorFree(backendInstanceController) && error; - } - } - - return error; - } - - private boolean backendNameIsErrorFree(BackendInstanceController backendInstanceController) { - String backendName = backendInstanceController.backendName.getText(); - - if (backendName.isBlank()) { - backendInstanceController.backendNameIssue.setText(ValidationErrorMessages.BACKEND_NAME_EMPTY.toString()); - backendInstanceController.backendNameIssue.setVisible(true); - return false; - } - - backendInstanceController.backendNameIssue.setVisible(false); - return true; - } - - private boolean portRangeIsErrorFree(BackendInstanceController backendInstanceController) { - boolean errorFree = true; - int portRangeStart = 0, portRangeEnd = 0; - backendInstanceController.portRangeStartIssue.setText(""); - backendInstanceController.portRangeStartIssue.setVisible(false); - backendInstanceController.portRangeEndIssue.setText(""); - backendInstanceController.portRangeEndIssue.setVisible(false); - backendInstanceController.portRangeIssue.setVisible(false); - - try { - portRangeStart = Integer.parseInt(backendInstanceController.portRangeStart.getText()); - } catch (NumberFormatException numberFormatException) { - backendInstanceController.portRangeStartIssue.setText(ValidationErrorMessages.VALUE_NOT_INTEGER.toString()); - errorFree = false; - } - - try { - portRangeEnd = Integer.parseInt(backendInstanceController.portRangeEnd.getText()); - } catch (NumberFormatException numberFormatException) { - backendInstanceController.portRangeEndIssue.setText(ValidationErrorMessages.VALUE_NOT_INTEGER.toString()); - errorFree = false; - } - - Range<Integer> portRange = Range.between(0, 65535); - - if (!portRange.contains(portRangeStart)) { - if (backendInstanceController.portRangeStartIssue.getText().isBlank()) { - backendInstanceController.portRangeStartIssue.setText(ValidationErrorMessages.PORT_VALUE_NOT_WITHIN_ACCEPTABLE_RANGE.toString()); - } else { - backendInstanceController.portRangeStartIssue.setText(ValidationErrorMessages.PORT_VALUE_NOT_WITHIN_ACCEPTABLE_RANGE_CONCATINATION.toString()); - } - errorFree = false; - } - if (!portRange.contains(portRangeEnd)) { - if (backendInstanceController.portRangeEndIssue.getText().isBlank()) { - backendInstanceController.portRangeEndIssue.setText(ValidationErrorMessages.PORT_VALUE_NOT_WITHIN_ACCEPTABLE_RANGE.toString()); - } else { - backendInstanceController.portRangeEndIssue.setText(ValidationErrorMessages.PORT_VALUE_NOT_WITHIN_ACCEPTABLE_RANGE_CONCATINATION.toString()); - } - errorFree = false; - } - - if (portRangeEnd - portRangeStart < 0) { - backendInstanceController.portRangeIssue.setText(ValidationErrorMessages.PORT_RANGE_MUST_BE_INCREMENTAL.toString()); - errorFree = false; - } - - backendInstanceController.portRangeStartIssue.setVisible(!errorFree); - backendInstanceController.portRangeEndIssue.setVisible(!errorFree); - backendInstanceController.portRangeIssue.setVisible(!errorFree); - - return errorFree; - } - - private boolean backendInstanceLocationIsErrorFree(BackendInstanceController backendInstanceController) { - boolean errorFree = true; - - if (backendInstanceController.isLocal.isSelected()) { - if (backendInstanceController.pathToBackend.getText().isBlank()) { - backendInstanceController.locationIssue.setText(ValidationErrorMessages.FILE_LOCATION_IS_BLANK.toString()); - errorFree = false; - } else { - Path localBackendPath = Paths.get(backendInstanceController.pathToBackend.getText()); - - if (!Files.isExecutable(localBackendPath)) { - backendInstanceController.locationIssue.setText(ValidationErrorMessages.FILE_DOES_NOT_EXIST_OR_NOT_EXECUTABLE.toString()); - errorFree = false; - } - } - } else { - if (backendInstanceController.address.getText().isBlank()) { - backendInstanceController.locationIssue.setText(ValidationErrorMessages.HOST_ADDRESS_IS_BLANK.toString()); - errorFree = false; - } else { - try { - InetAddress address = InetAddress.getByName(backendInstanceController.address.getText()); - boolean reachable = address.isReachable(200); - - if (!reachable) { - backendInstanceController.locationIssue.setText(ValidationErrorMessages.HOST_NOT_REACHABLE.toString()); - errorFree = false; - } - - } catch (UnknownHostException unknownHostException) { - backendInstanceController.locationIssue.setText(ValidationErrorMessages.UNACCEPTABLE_HOST_NAME.toString()); - errorFree = false; - } catch (IOException ioException) { - backendInstanceController.locationIssue.setText(ValidationErrorMessages.IO_EXCEPTION_WITH_HOST.toString()); - errorFree = false; - } - } - } - - backendInstanceController.locationIssue.setVisible(!errorFree); - - return errorFree; - } - - private enum ValidationErrorMessages { - BACKEND_NAME_EMPTY { - @Override - public String toString() { - return "The backend name cannot be empty"; - } - }, - VALUE_NOT_INTEGER { - @Override - public String toString() { - return "Value must be integer"; - } - }, - PORT_RANGE_MUST_BE_INCREMENTAL { - @Override - public String toString() { - return "Start of port range must be greater than end"; - } - }, - PORT_VALUE_NOT_WITHIN_ACCEPTABLE_RANGE { - @Override - public String toString() { - return "Value must be within range 0 - 65535"; - } - }, - PORT_VALUE_NOT_WITHIN_ACCEPTABLE_RANGE_CONCATINATION { - @Override - public String toString() { - return " and within range 0 - 65535"; - } - }, - FILE_LOCATION_IS_BLANK { - @Override - public String toString() { - return "Please specify a file for this backend"; - } - }, - FILE_DOES_NOT_EXIST_OR_NOT_EXECUTABLE { - @Override - public String toString() { - return "The above file does not exists or ECDAR does not have the privileges to execute it"; - } - }, - HOST_ADDRESS_IS_BLANK { - @Override - public String toString() { - return "Please specify an address for the external host"; - } - }, - HOST_NOT_REACHABLE { - @Override - public String toString() { - return "The above address is not reachable. Make sure that the host is correct"; - } - }, - UNACCEPTABLE_HOST_NAME { - @Override - public String toString() { - return "The above address is not an acceptable host name"; - } - }, - IO_EXCEPTION_WITH_HOST { - @Override - public String toString() { - return "An I/O exception was encountered while trying to reach the host"; - } - } - } -} diff --git a/src/main/java/ecdar/controllers/CanvasController.java b/src/main/java/ecdar/controllers/CanvasController.java index 76306a29..7427c70a 100644 --- a/src/main/java/ecdar/controllers/CanvasController.java +++ b/src/main/java/ecdar/controllers/CanvasController.java @@ -2,11 +2,6 @@ import com.jfoenix.controls.JFXRippler; import ecdar.Ecdar; -import ecdar.abstractions.Component; -import ecdar.abstractions.Declarations; -import ecdar.abstractions.HighLevelModelObject; -import ecdar.abstractions.EcdarSystem; -import ecdar.mutation.models.MutationTestPlan; import ecdar.mutation.MutationTestPlanPresentation; import ecdar.presentations.*; import ecdar.utility.helpers.ZoomHelper; @@ -39,8 +34,8 @@ public class CanvasController implements Initializable { public final double DECLARATION_Y_MARGIN = Ecdar.CANVAS_PADDING * 5.5; public ComponentPresentation activeComponentPresentation; - private final ObjectProperty<HighLevelModelObject> activeModel = new SimpleObjectProperty<>(null); - private final HashMap<HighLevelModelObject, Pair<Double, Double>> ModelObjectTranslateMap = new HashMap<>(); + private final ObjectProperty<HighLevelModelPresentation> activeModelPresentation = new SimpleObjectProperty<>(null); + private final HashMap<HighLevelModelPresentation, Pair<Double, Double>> ModelObjectTranslateMap = new HashMap<>(); private DoubleProperty width, height; private BooleanProperty insetShouldShow; @@ -56,21 +51,22 @@ public BooleanProperty getInsetShouldShow() { return insetShouldShow; } - public HighLevelModelObject getActiveModel() { - return activeModel.get(); + public HighLevelModelPresentation getActiveModelPresentation() { + return activeModelPresentation.get(); } /** * Sets the given model as the one to be active, e.g. to be shown on the screen. * @param model the given model */ - public void setActiveModel(final HighLevelModelObject model) { - activeModel.set(model); - Platform.runLater(EcdarController.getActiveCanvasPresentation().getController()::leaveTextAreas); + public void setActiveModelPresentation(final HighLevelModelPresentation model) { + activeModelPresentation.set(model); + Platform.runLater(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController()::leaveTextAreas); + Platform.runLater(zoomHelper::zoomToFit); } - public ObjectProperty<HighLevelModelObject> activeComponentProperty() { - return activeModel; + public ObjectProperty<HighLevelModelPresentation> activeModelProperty() { + return activeModelPresentation; } public void leaveTextAreas() { @@ -101,9 +97,11 @@ public void initialize(final URL location, final ResourceBundle resources) { root.widthProperty().addListener((observable, oldValue, newValue) -> width.setValue(newValue)); root.heightProperty().addListener((observable, oldValue, newValue) -> height.setValue(newValue)); - activeModel.addListener((obs, oldModel, newModel) -> onActiveModelChanged(oldModel, newModel)); + activeModelPresentation.addListener((obs, oldModel, newModel) -> { + onActiveModelChanged(oldModel, newModel); + }); - leaveTextAreas = () -> root.requestFocus(); + Platform.runLater(() -> leaveTextAreas = () -> root.requestFocus()); leaveOnEnterPressed = (keyEvent) -> { if (keyEvent.getCode().equals(KeyCode.ENTER) || keyEvent.getCode().equals(KeyCode.ESCAPE)) { leaveTextAreas(); @@ -130,9 +128,9 @@ public void updateOffset(final Boolean shouldHave) { * @param oldObject old object * @param newObject new object */ - private void onActiveModelChanged(final HighLevelModelObject oldObject, final HighLevelModelObject newObject) { + private void onActiveModelChanged(final HighLevelModelPresentation oldObject, final HighLevelModelPresentation newObject) { // If old object is a component or system, add to map in order to remember coordinate - if (oldObject instanceof Component || oldObject instanceof EcdarSystem) { + if (oldObject instanceof ComponentPresentation || oldObject instanceof SystemPresentation) { ModelObjectTranslateMap.put(oldObject, new Pair<>(modelPane.getTranslateX(), modelPane.getTranslateY())); } @@ -142,23 +140,34 @@ private void onActiveModelChanged(final HighLevelModelObject oldObject, final Hi // Remove old object from view modelPane.getChildren().removeIf(node -> node instanceof HighLevelModelPresentation); - if (newObject instanceof Component) { - activeComponentPresentation = new ComponentPresentation((Component) newObject); + if (newObject instanceof ComponentPresentation) { + activeComponentPresentation = (ComponentPresentation) newObject; modelPane.getChildren().add(activeComponentPresentation); - } else if (newObject instanceof Declarations) { + + // To avoid NullPointerException on initial model + Platform.runLater(zoomHelper::resetZoom); + + } else if (newObject instanceof DeclarationsPresentation) { activeComponentPresentation = null; - modelPane.getChildren().add(new DeclarationPresentation((Declarations) newObject)); - } else if (newObject instanceof EcdarSystem) { + modelPane.getChildren().add(newObject); + + // Bind size of Declaration to size of the model pane to ensure alignment and avoid drag + DeclarationsController declarationsController = (DeclarationsController) newObject.getController(); + declarationsController.root.minWidthProperty().bind(modelPane.minWidthProperty()); + declarationsController.root.maxWidthProperty().bind(modelPane.maxWidthProperty()); + declarationsController.root.minHeightProperty().bind(modelPane.minHeightProperty()); + declarationsController.root.maxHeightProperty().bind(modelPane.maxHeightProperty()); + } else if (newObject instanceof SystemPresentation) { activeComponentPresentation = null; - modelPane.getChildren().add(new SystemPresentation((EcdarSystem) newObject)); - } else if (newObject instanceof MutationTestPlan) { + modelPane.getChildren().add(newObject); + } else if (newObject instanceof MutationTestPlanPresentation) { activeComponentPresentation = null; - modelPane.getChildren().add(new MutationTestPlanPresentation((MutationTestPlan) newObject)); + modelPane.getChildren().add(newObject); } else { throw new IllegalStateException("Type of object is not supported."); } - boolean shouldZoomBeActive = newObject instanceof Component || newObject instanceof EcdarSystem; + boolean shouldZoomBeActive = newObject instanceof ComponentPresentation || newObject instanceof SystemPresentation; setZoomAvailable(shouldZoomBeActive); root.requestFocus(); diff --git a/src/main/java/ecdar/controllers/ComponentController.java b/src/main/java/ecdar/controllers/ComponentController.java index a357c1a6..49fd610f 100644 --- a/src/main/java/ecdar/controllers/ComponentController.java +++ b/src/main/java/ecdar/controllers/ComponentController.java @@ -6,26 +6,32 @@ import ecdar.code_analysis.CodeAnalysis; import ecdar.presentations.*; import ecdar.utility.UndoRedoStack; -import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.*; -import ecdar.utility.mouse.MouseTracker; import com.jfoenix.controls.JFXPopup; import com.jfoenix.controls.JFXRippler; +import ecdar.utility.keyboard.NudgeDirection; import javafx.animation.Interpolator; import javafx.animation.Transition; import javafx.application.Platform; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; -import javafx.beans.value.ChangeListener; import javafx.collections.ListChangeListener; import javafx.fxml.FXML; import javafx.fxml.Initializable; +import javafx.geometry.Insets; +import javafx.geometry.Point2D; +import javafx.geometry.Pos; +import javafx.scene.Cursor; import javafx.scene.layout.*; import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.input.MouseEvent; import javafx.scene.layout.Pane; import javafx.scene.shape.Circle; +import javafx.scene.shape.Path; +import javafx.scene.shape.Rectangle; +import javafx.scene.shape.Shape; import javafx.util.Duration; import org.fxmisc.richtext.LineNumberFactory; import org.fxmisc.richtext.StyleClassedTextArea; @@ -36,15 +42,20 @@ import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static ecdar.presentations.ModelPresentation.*; +import static ecdar.utility.helpers.UnoccupiedSpaceFinder.getUnoccupiedSpace; public class ComponentController extends ModelController implements Initializable { + private final List<Consumer<EnabledColor>> updateColorDelegates = new ArrayList<>(); private static final Map<Component, ListChangeListener<Location>> locationListChangeListenerMap = new HashMap<>(); private static final Map<Component, Boolean> errorsAndWarningsInitialized = new HashMap<>(); - private static Location placingLocation = null; private final ObjectProperty<Component> component = new SimpleObjectProperty<>(null); private final Map<DisplayableEdge, EdgePresentation> edgePresentationMap = new HashMap<>(); private final Map<Location, LocationPresentation> locationPresentationMap = new HashMap<>(); + // View elements public StyleClassedTextArea declarationTextArea; public JFXRippler toggleDeclarationButton; public Label x; @@ -56,33 +67,35 @@ public class ComponentController extends ModelController implements Initializabl public VBox outputSignatureContainer; public VBox inputSignatureContainer; - private MouseTracker mouseTracker; private DropDownMenu contextMenu; private DropDownMenu finishEdgeContextMenu; - - public static boolean isPlacingLocation() { - return placingLocation != null; - } - - public static void setPlacingLocation(final Location placingLocation) { - ComponentController.placingLocation = placingLocation; - } + private Point2D dropdownCoordinatesInComponent; @Override public void initialize(final URL location, final ResourceBundle resources) { declarationTextArea.setParagraphGraphicFactory(LineNumberFactory.get(declarationTextArea)); component.addListener((obs, oldComponent, newComponent) -> { + super.initialize(newComponent.getBox()); + + initializeFrame(); + initializeToolbar(); + initializeBackground(); + + // Re-run initialisation on update of width and height property + newComponent.getBox().getWidthProperty().addListener(observable -> initializeFrame()); + newComponent.getBox().getHeightProperty().addListener(observable -> initializeFrame()); + inputSignatureContainer.heightProperty().addListener((change) -> updateMaxHeight()); outputSignatureContainer.heightProperty().addListener((change) -> updateMaxHeight()); - // Bind the declarations of the abstraction the the view + // Bind the declarations of the abstraction to the view declarationTextArea.replaceText(0, declarationTextArea.getLength(), newComponent.getDeclarationsText()); declarationTextArea.textProperty().addListener((observable, oldDeclaration, newDeclaration) -> newComponent.setDeclarationsText(newDeclaration)); // Find the clocks in the decls newComponent.declarationsTextProperty().addListener((observable, oldValue, newValue) -> { - final List<String> clocks = new ArrayList<String>(); + final List<String> clocks = new ArrayList<>(); final String strippedDecls = newValue.replaceAll("[\\r\\n]+", ""); @@ -90,7 +103,7 @@ public void initialize(final URL location, final ResourceBundle resources) { Matcher matcher = pattern.matcher(strippedDecls); while (matcher.find()) { - final String clockStrings[] = matcher.group("CLOCKS").split(","); + final String[] clockStrings = matcher.group("CLOCKS").split(","); for (String clockString : clockStrings) { clocks.add(clockString.replaceAll("\\s", "")); } @@ -99,48 +112,126 @@ public void initialize(final URL location, final ResourceBundle resources) { //TODO this logs the clocks System.out.println(clocks); }); - initializeEdgeHandling(newComponent); - initializeLocationHandling(newComponent); + initializeEdgeHandling(); + initializeLocationHandling(); initializeDeclarations(); - initializeSignature(newComponent); - initializeSignatureListeners(newComponent); - }); + initializeSignature(); + initializeSignatureListeners(); + if (!errorsAndWarningsInitialized.containsKey(newComponent) || !errorsAndWarningsInitialized.get(newComponent)) { + initializeNoIncomingEdgesWarning(); + errorsAndWarningsInitialized.put(newComponent, true); + } + }); - // The root view have been inflated, initialize the mouse tracker on it - mouseTracker = new MouseTracker(root); initializeContextMenu(); - component.addListener((obs, old, component) -> { - if (component == null) return; + declarationTextArea.textProperty().addListener((obs, oldText, newText) -> + declarationTextArea.setStyleSpans(0, UPPAALSyntaxHighlighter.computeHighlighting(newText))); + } - if (!errorsAndWarningsInitialized.containsKey(component) || !errorsAndWarningsInitialized.get(component)) { - initializeNoIncomingEdgesWarning(); - errorsAndWarningsInitialized.put(component, true); - } + private void initializeToolbar() { + final Consumer<EnabledColor> updateColor = (newColor) -> { + // Set the background of the toolbar + toolbar.setBackground(new Background(new BackgroundFill( + newColor.color.getColor(newColor.intensity), + CornerRadii.EMPTY, + Insets.EMPTY + ))); + + // Set the icon color and rippler color of the toggleDeclarationButton + toggleDeclarationButton.setRipplerFill(newColor.getTextColor()); + + toolbar.setPrefHeight(TOOLBAR_HEIGHT); + toggleDeclarationButton.setBackground(Background.EMPTY); + }; + + updateColorDelegates.add(updateColor); + + getComponent().colorProperty().addListener(observable -> updateColor.accept(getComponent().getColor())); + + updateColor.accept(getComponent().getColor()); + + // Set a hover effect for the controller.toggleDeclarationButton + toggleDeclarationButton.setOnMouseEntered(event -> toggleDeclarationButton.setCursor(Cursor.HAND)); + toggleDeclarationButton.setOnMouseExited(event -> toggleDeclarationButton.setCursor(Cursor.DEFAULT)); + toggleDeclarationButton.setOnMousePressed(this::toggleDeclaration); + } + + private void initializeFrame() { + final Shape[] mask = new Shape[1]; + final Rectangle rectangle = new Rectangle(getComponent().getBox().getWidth(), getComponent().getBox().getHeight()); + + final Consumer<EnabledColor> updateColor = (newColor) -> { + // Mask the parent of the frame (will also mask the background) + mask[0] = Path.subtract(rectangle, TOP_LEFT_CORNER); + frame.setClip(mask[0]); + background.setClip(Path.union(mask[0], mask[0])); + background.setOpacity(0.5); + + // Bind the missing lines that we cropped away + topLeftLine.setStartX(CORNER_SIZE); + topLeftLine.setStartY(0); + topLeftLine.setEndX(0); + topLeftLine.setEndY(CORNER_SIZE); + topLeftLine.setStroke(newColor.getStrokeColor()); + topLeftLine.setStrokeWidth(1.25); + StackPane.setAlignment(topLeftLine, Pos.TOP_LEFT); + + // Set the stroke color to two shades darker + frame.setBorder(new Border(new BorderStroke( + newColor.getStrokeColor(), + BorderStrokeStyle.SOLID, + CornerRadii.EMPTY, + new BorderWidths(1), + Insets.EMPTY + ))); + }; + + updateColorDelegates.add(updateColor); + + getComponent().colorProperty().addListener(observable -> { + updateColor.accept(getComponent().getColor()); + }); + + updateColor.accept(getComponent().getColor()); + } + + private void initializeBackground() { + // Bind the background width and height to the values in the model + background.widthProperty().bindBidirectional(getComponent().getBox().getWidthProperty()); + background.heightProperty().bindBidirectional(getComponent().getBox().getHeightProperty()); + + final Consumer<EnabledColor> updateColor = (newColor) -> { + // Set the background color to the lightest possible version of the color and then increase by two + background.setFill(newColor.getLowestIntensity().nextIntensity(2).getPaintColor()); + }; + + updateColorDelegates.add(updateColor); + + getComponent().colorProperty().addListener(observable -> { + updateColor.accept(getComponent().getColor()); }); + + updateColor.accept(getComponent().getColor()); } /*** * Inserts the initial edges of the component to the input/output signature - * @param newComponent The component that should be presented with its signature */ - private void initializeSignature(final Component newComponent) { - newComponent.getOutputStrings().forEach((channel) -> insertSignatureArrow(channel, EdgeStatus.OUTPUT)); - newComponent.getInputStrings().forEach((channel) -> insertSignatureArrow(channel, EdgeStatus.INPUT)); + private void initializeSignature() { + getComponent().getOutputStrings().forEach((channel) -> insertSignatureArrow(channel, EdgeStatus.OUTPUT)); + getComponent().getInputStrings().forEach((channel) -> insertSignatureArrow(channel, EdgeStatus.INPUT)); } - /*** + /** * Initialize the listeners, that listen for changes in the input and output edges of the presented component. * The view is updated whenever an insert (deletions are also a type of insert) is reported - * @param newComponent The component that should be presented with its signature */ - - - private void initializeSignatureListeners(final Component newComponent) { - newComponent.getIsFailingProperty().addListener((observable, oldValue, newValue) -> { - if (newComponent.getIsFailing()) { + private void initializeSignatureListeners() { + getComponent().getIsFailingProperty().addListener((observable, oldValue, newValue) -> { + if (getComponent().getIsFailing()) { for (Node n : inputSignatureContainer.getChildren()) { if (n instanceof SignatureArrow) { for (String label : component.get().getFailingIOStrings()) { @@ -172,7 +263,7 @@ private void initializeSignatureListeners(final Component newComponent) { } } }); - newComponent.getOutputStrings().addListener((ListChangeListener<String>) c -> { + getComponent().getOutputStrings().addListener((ListChangeListener<String>) c -> { // By clearing the container we don't have to fiddle with which elements are removed and added outputSignatureContainer.getChildren().clear(); while (c.next()) { @@ -180,7 +271,7 @@ private void initializeSignatureListeners(final Component newComponent) { } }); - newComponent.getInputStrings().addListener((ListChangeListener<String>) c -> { + getComponent().getInputStrings().addListener((ListChangeListener<String>) c -> { inputSignatureContainer.getChildren().clear(); while (c.next()) { c.getAddedSubList().forEach((channel) -> insertSignatureArrow(channel, EdgeStatus.INPUT)); @@ -188,35 +279,6 @@ private void initializeSignatureListeners(final Component newComponent) { }); } - /*** - * Inserts a new {@link ecdar.presentations.SignatureArrow} in the containers for either input or output signature - * @param channel A String with the channel name that should be shown with the arrow - * @param status An EdgeStatus for the type of arrow to insert - */ - private void insertSignatureArrow(final String channel, final EdgeStatus status) { - SignatureArrow newArrow = new SignatureArrow(channel, status, component.get()); - - if (status == EdgeStatus.INPUT) { - inputSignatureContainer.getChildren().add(newArrow); - } else { - outputSignatureContainer.getChildren().add(newArrow); - } - - } - - /*** - * Updates the component's height to match the input/output signature containers - * if the component is smaller than either of them - */ - private void updateMaxHeight() { - // If input/outputsignature container is taller than the current component height - // we update the component's height to be as tall as the container - double maxHeight = findMaxHeight(); - if (maxHeight > component.get().getBox().getHeight()) { - component.get().getBox().getHeightProperty().set(maxHeight); - } - } - /*** * Finds the max height of the input/output signature container and the component * @return a double of the largest height @@ -283,26 +345,19 @@ private void initializeNoIncomingEdgesWarning() { } }; - final Component component = getComponent(); - checkLocations.accept(component); + checkLocations.accept(getComponent()); // Check location whenever we get new edges - component.getDisplayableEdges().addListener(new ListChangeListener<DisplayableEdge>() { - @Override - public void onChanged(final Change<? extends DisplayableEdge> c) { - while (c.next()) { - checkLocations.accept(component); - } + getComponent().getDisplayableEdges().addListener((ListChangeListener<DisplayableEdge>) c -> { + while (c.next()) { + checkLocations.accept(getComponent()); } }); // Check location whenever we get new locations - component.getLocations().addListener(new ListChangeListener<Location>() { - @Override - public void onChanged(final Change<? extends Location> c) { - while (c.next()) { - checkLocations.accept(component); - } + getComponent().getLocations().addListener((ListChangeListener<Location>) c -> { + while (c.next()) { + checkLocations.accept(getComponent()); } }); } @@ -313,21 +368,12 @@ private void initializeContextMenu() { return; } - contextMenu = new DropDownMenu(root); + contextMenu = new DropDownMenu(modelContainerSubComponent); contextMenu.addClickableListElement("Add Location", event -> { contextMenu.hide(); - final Location newLocation = new Location(); - newLocation.initialize(); - - double x = DropDownMenu.x - LocationPresentation.RADIUS / 2; - newLocation.setX(x); - - double y = DropDownMenu.y - LocationPresentation.RADIUS / 2; - newLocation.setY(y); - newLocation.setColorIntensity(component.getColorIntensity()); - newLocation.setColor(component.getColor()); + final Location newLocation = new Location(component, Location.Type.NORMAL, getComponent().getUniqueLocationId(), dropdownCoordinatesInComponent.getX(), dropdownCoordinatesInComponent.getY()); // Add a new location UndoRedoStack.pushAndPerform(() -> { // Perform @@ -337,13 +383,10 @@ private void initializeContextMenu() { }, "Added location '" + newLocation + "' to component '" + component.getName() + "'", "add-circle"); }); - // Adds the add universal location element to the drop down menu, this element adds an universal location and its required edges + // Adds the add universal location element to the drop-down menu, this element adds a universal location and its required edges contextMenu.addClickableListElement("Add Universal Location", event -> { contextMenu.hide(); - double x = DropDownMenu.x - LocationPresentation.RADIUS / 2; - double y = DropDownMenu.y - LocationPresentation.RADIUS / 2; - - final Location newLocation = new Location(component, Location.Type.UNIVERSAL, x, y); + final Location newLocation = new Location(component, Location.Type.UNIVERSAL, getComponent().getUniqueLocationId(), dropdownCoordinatesInComponent.getX(), dropdownCoordinatesInComponent.getY()); final Edge inputEdge = newLocation.addLeftEdge("*", EdgeStatus.INPUT); inputEdge.setIsLocked(true); @@ -366,10 +409,7 @@ private void initializeContextMenu() { // Adds the add inconsistent location element to the dropdown menu, this element adds an inconsistent location contextMenu.addClickableListElement("Add Inconsistent Location", event -> { contextMenu.hide(); - double x = DropDownMenu.x - LocationPresentation.RADIUS / 2; - double y = DropDownMenu.y - LocationPresentation.RADIUS / 2; - - final Location newLocation = new Location(component, Location.Type.INCONSISTENT, x, y); + final Location newLocation = new Location(component, Location.Type.INCONSISTENT, getComponent().getUniqueLocationId(), dropdownCoordinatesInComponent.getX(), dropdownCoordinatesInComponent.getY()); // Add a new location UndoRedoStack.pushAndPerform(() -> { // Perform @@ -379,24 +419,6 @@ private void initializeContextMenu() { }, "Added inconsistent location '" + newLocation + "' to component '" + component.getName() + "'", "add-circle"); }); - contextMenu.addSpacerElement(); - - contextMenu.addClickableListElement("Contains deadlock?", event -> { - - // Generate the query - final String deadlockQuery = BackendHelper.getExistDeadlockQuery(getComponent()); - - // Add proper comment - final String deadlockComment = "Does " + component.getName() + " contain a deadlock?"; - - // Add new query for this component - final Query query = new Query(deadlockQuery, deadlockComment, QueryState.UNKNOWN); - query.setType(QueryType.REACHABILITY); - Ecdar.getProject().getQueries().add(query); - Ecdar.getQueryExecutor().executeQuery(query); - contextMenu.hide(); - }); - contextMenu.addSpacerElement(); contextMenu.addColorPicker(component, component::dye); }; @@ -405,13 +427,6 @@ private void initializeContextMenu() { initializeDropDownMenu.accept(newComponent); }); - Ecdar.getProject().getComponents().addListener(new ListChangeListener<Component>() { - @Override - public void onChanged(final Change<? extends Component> c) { - initializeDropDownMenu.accept(getComponent()); - } - }); - initializeDropDownMenu.accept(getComponent()); } @@ -429,15 +444,13 @@ private void initializeFinishEdgeContextMenu(final DisplayableEdge unfinishedEdg locationAware.yProperty().set(y); }; - finishEdgeContextMenu = new DropDownMenu(root); + finishEdgeContextMenu = new DropDownMenu(modelContainerSubComponent); finishEdgeContextMenu.addListElement("Finish edge in a:"); finishEdgeContextMenu.addClickableListElement("Location", event -> { finishEdgeContextMenu.hide(); final Location location = new Location(); - location.initialize(); - - location.setColorIntensity(getComponent().getColorIntensity()); + location.initialize(getComponent().getUniqueLocationId()); location.setColor(getComponent().getColor()); if (component.isAnyEdgeWithoutSource()) { @@ -466,10 +479,7 @@ private void initializeFinishEdgeContextMenu(final DisplayableEdge unfinishedEdg finishEdgeContextMenu.addClickableListElement("Universal Location", event -> { finishEdgeContextMenu.hide(); - double x = DropDownMenu.x - LocationPresentation.RADIUS / 2; - double y = DropDownMenu.y - LocationPresentation.RADIUS / 2; - - final Location newLocation = new Location(component, Location.Type.UNIVERSAL, x, y); + final Location newLocation = new Location(component, Location.Type.UNIVERSAL, getComponent().getUniqueLocationId(), dropdownCoordinatesInComponent.getX(), dropdownCoordinatesInComponent.getY()); final Edge inputEdge = newLocation.addLeftEdge("*", EdgeStatus.INPUT); inputEdge.setIsLocked(true); @@ -508,10 +518,7 @@ private void initializeFinishEdgeContextMenu(final DisplayableEdge unfinishedEdg finishEdgeContextMenu.addClickableListElement("Inconsistent Location", event -> { finishEdgeContextMenu.hide(); - double x = DropDownMenu.x - LocationPresentation.RADIUS / 2; - double y = DropDownMenu.y - LocationPresentation.RADIUS / 2; - - final Location newLocation = new Location(component, Location.Type.INCONSISTENT, x, y); + final Location newLocation = new Location(component, Location.Type.INCONSISTENT, getComponent().getUniqueLocationId(), dropdownCoordinatesInComponent.getX(), dropdownCoordinatesInComponent.getY()); if (component.isAnyEdgeWithoutSource()) { unfinishedEdge.setSourceLocation(newLocation); @@ -544,218 +551,15 @@ private void initializeFinishEdgeContextMenu(final DisplayableEdge unfinishedEdg initializeDropDownMenu.accept(getComponent()); } - private void initializeLocationHandling(final Component newComponent) { - final Consumer<Location> handleAddedLocation = (loc) -> { - // Check related to undo/redo stack - if (locationPresentationMap.containsKey(loc)) { - return; - } - - // Create a new presentation, and register it on the map - final LocationPresentation newLocationPresentation = new LocationPresentation(loc, newComponent); - - final ChangeListener<Number> locationPlacementChangedListener = (observable, oldValue, newValue) -> { - final double offset = newLocationPresentation.getController().circle.getRadius() * 2; - boolean hit = false; - ItemDragHelper.DragBounds componentBounds = newLocationPresentation.getController().getDragBounds(); - - //Define the x and y coordinates for the initial and final locations - final double initialLocationX = component.get().getBox().getX() + newLocationPresentation.getController().circle.getRadius() * 2, - initialLocationY = component.get().getBox().getY() + newLocationPresentation.getController().circle.getRadius() * 2, - finalLocationX = component.get().getBox().getX() + component.get().getBox().getWidth() - newLocationPresentation.getController().circle.getRadius() * 2, - finalLocationY = component.get().getBox().getY() + component.get().getBox().getHeight() - newLocationPresentation.getController().circle.getRadius() * 2; - - double latestHitRight = 0, - latestHitDown = 0, - latestHitLeft = 0, - latestHitUp = 0; - - //Check to see if the location is placed on top of the initial location - if (Math.abs(initialLocationX - (newLocationPresentation.getLayoutX())) < offset && - Math.abs(initialLocationY - (newLocationPresentation.getLayoutY())) < offset) { - hit = true; - latestHitRight = initialLocationX; - latestHitDown = initialLocationY; - latestHitLeft = initialLocationX; - latestHitUp = initialLocationY; - } - - //Check to see if the location is placed on top of the final location - else if (Math.abs(finalLocationX - (newLocationPresentation.getLayoutX())) < offset && - Math.abs(finalLocationY - (newLocationPresentation.getLayoutY())) < offset) { - hit = true; - latestHitRight = finalLocationX; - latestHitDown = finalLocationY; - latestHitLeft = finalLocationX; - latestHitUp = finalLocationY; - } - - //Check to see if the location is placed on top of another location - else { - for (Map.Entry<Location, LocationPresentation> entry : locationPresentationMap.entrySet()) { - if (entry.getValue() != newLocationPresentation && - Math.abs(entry.getValue().getLayoutX() - (newLocationPresentation.getLayoutX())) < offset && - Math.abs(entry.getValue().getLayoutY() - (newLocationPresentation.getLayoutY())) < offset) { - hit = true; - latestHitRight = entry.getValue().getLayoutX(); - latestHitDown = entry.getValue().getLayoutY(); - latestHitLeft = entry.getValue().getLayoutX(); - latestHitUp = entry.getValue().getLayoutY(); - break; - } - } - } - - //If the location is not placed on top of any other locations, do not do anything - if (!hit) { - return; - } - hit = false; - - //Find an unoccupied space for the location - for (int i = 1; i < component.get().getBox().getWidth() / offset; i++) { - - //Check to see, if the location can be placed to the right of the existing locations - if (componentBounds.trimX(latestHitRight + offset) == latestHitRight + offset) { - - //Check if the location would be placed on the final location - if (Math.abs(finalLocationX - (latestHitRight + offset)) < offset && - Math.abs(finalLocationY - (newLocationPresentation.getLayoutY())) < offset) { - hit = true; - latestHitRight = finalLocationX; - } else { - for (Map.Entry<Location, LocationPresentation> entry : locationPresentationMap.entrySet()) { - if (entry.getValue() != newLocationPresentation && - Math.abs(entry.getValue().getLayoutX() - (latestHitRight + offset)) < offset && - Math.abs(entry.getValue().getLayoutY() - (newLocationPresentation.getLayoutY())) < offset) { - hit = true; - latestHitRight = entry.getValue().getLayoutX(); - break; - } - } - } - - if (!hit) { - newLocationPresentation.setLayoutX(latestHitRight + offset); - return; - } - } - hit = false; - - //Check to see, if the location can be placed below the existing locations - if (componentBounds.trimY(latestHitDown + offset) == latestHitDown + offset) { - - //Check if the location would be placed on the final location - if (Math.abs(finalLocationX - (newLocationPresentation.getLayoutX())) < offset && - Math.abs(finalLocationY - (latestHitDown + offset)) < offset) { - hit = true; - latestHitDown = finalLocationY; - } else { - for (Map.Entry<Location, LocationPresentation> entry : locationPresentationMap.entrySet()) { - if (entry.getValue() != newLocationPresentation && - Math.abs(entry.getValue().getLayoutX() - (newLocationPresentation.getLayoutX())) < offset && - Math.abs(entry.getValue().getLayoutY() - (latestHitDown + offset)) < offset) { - hit = true; - latestHitDown = entry.getValue().getLayoutY(); - break; - } - } - } - if (!hit) { - newLocationPresentation.setLayoutY(latestHitDown + offset); - return; - } - } - hit = false; - - //Check to see, if the location can be placed to the left of the existing locations - if (componentBounds.trimX(latestHitLeft - offset) == latestHitLeft - offset) { - - //Check if the location would be placed on the initial location - if (Math.abs(initialLocationX - (latestHitLeft - offset)) < offset && - Math.abs(initialLocationY - (newLocationPresentation.getLayoutY())) < offset) { - hit = true; - latestHitLeft = initialLocationX; - } else { - for (Map.Entry<Location, LocationPresentation> entry : locationPresentationMap.entrySet()) { - if (entry.getValue() != newLocationPresentation && - Math.abs(entry.getValue().getLayoutX() - (latestHitLeft - offset)) < offset && - Math.abs(entry.getValue().getLayoutY() - (newLocationPresentation.getLayoutY())) < offset) { - hit = true; - latestHitLeft = entry.getValue().getLayoutX(); - break; - } - } - } - if (!hit) { - newLocationPresentation.setLayoutX(latestHitLeft - offset); - return; - } - } - hit = false; - - //Check to see, if the location can be placed above the existing locations - if (componentBounds.trimY(latestHitUp - offset) == latestHitUp - offset) { - - //Check if the location would be placed on the initial location - if (Math.abs(initialLocationX - (newLocationPresentation.getLayoutX())) < offset && - Math.abs(initialLocationY - (latestHitUp - offset)) < offset) { - hit = true; - latestHitUp = initialLocationY; - } else { - for (Map.Entry<Location, LocationPresentation> entry : locationPresentationMap.entrySet()) { - if (entry.getValue() != newLocationPresentation && - Math.abs(entry.getValue().getLayoutX() - (newLocationPresentation.getLayoutX())) < offset && - Math.abs(entry.getValue().getLayoutY() - (latestHitUp - offset)) < offset) { - hit = true; - latestHitUp = entry.getValue().getLayoutY(); - break; - } - } - } - if (!hit) { - newLocationPresentation.setLayoutY(latestHitUp - offset); - return; - } - } - hit = false; - } - modelContainerLocation.getChildren().remove(newLocationPresentation); - locationPresentationMap.remove(newLocationPresentation.getController().locationProperty().getValue()); - newComponent.getLocations().remove(newLocationPresentation.getController().getLocation()); - newComponent.getDisplayableEdges().removeIf(e -> e.targetLocationProperty().get().equals(newLocationPresentation.getController().getLocation())); - Ecdar.showToast("Please select an empty space for the new location"); - }; - - newLocationPresentation.layoutXProperty().addListener(locationPlacementChangedListener); - newLocationPresentation.layoutYProperty().addListener(locationPlacementChangedListener); - - locationPresentationMap.put(loc, newLocationPresentation); - - // Add it to the view - modelContainerLocation.getChildren().add(newLocationPresentation); - - // Bind the newly created location to the mouse and tell the ui that it is not placed yet - if (loc.getX() == 0) { - newLocationPresentation.setPlaced(false); - BindingHelper.bind(loc, newComponent.getBox().getXProperty(), newComponent.getBox().getYProperty()); - } - }; - + private void initializeLocationHandling() { final ListChangeListener<Location> locationListChangeListener = c -> { if (c.next()) { // Locations are added to the component c.getAddedSubList().forEach((loc) -> { - handleAddedLocation.accept(loc); - - LocationPresentation locationPresentation = locationPresentationMap.get(loc); - - //Ensure that the component is inside the bounds of the component - locationPresentation.setLayoutX(locationPresentation.getController().getDragBounds().trimX(locationPresentation.getLayoutX())); - locationPresentation.setLayoutY(locationPresentation.getController().getDragBounds().trimY(locationPresentation.getLayoutY())); - - //Change the layoutXProperty slightly to invoke listener and ensure distance to existing locations - locationPresentation.setLayoutX(locationPresentation.getLayoutX() + 0.00001); + // Check related to undo/redo stack + if (!locationPresentationMap.containsKey(loc)) { + addLocation(loc); + } }); // Locations are removed from the component @@ -766,18 +570,19 @@ else if (Math.abs(finalLocationX - (newLocationPresentation.getLayoutX())) < off }); } }; - newComponent.getLocations().addListener(locationListChangeListener); - if (!locationListChangeListenerMap.containsKey(newComponent)) { - locationListChangeListenerMap.put(newComponent, locationListChangeListener); + getComponent().getLocations().addListener(locationListChangeListener); + + if (!locationListChangeListenerMap.containsKey(getComponent())) { + locationListChangeListenerMap.put(getComponent(), locationListChangeListener); } - newComponent.getLocations().forEach(handleAddedLocation); + getComponent().getLocations().forEach(this::addLocation); } - private void initializeEdgeHandling(final Component newComponent) { + private void initializeEdgeHandling() { final Consumer<DisplayableEdge> handleAddedEdge = edge -> { - final EdgePresentation edgePresentation = new EdgePresentation(edge, newComponent); + final EdgePresentation edgePresentation = new EdgePresentation(edge, getComponent()); edgePresentationMap.put(edge, edgePresentation); modelContainerEdge.getChildren().add(edgePresentation); @@ -790,28 +595,26 @@ private void initializeEdgeHandling(final Component newComponent) { }; // React on addition of edges to the component - newComponent.getDisplayableEdges().addListener(new ListChangeListener<DisplayableEdge>() { - @Override - public void onChanged(final Change<? extends DisplayableEdge> c) { - if (c.next()) { - // Edges are added to the component - c.getAddedSubList().forEach(handleAddedEdge); - - // Edges are removed from the component - c.getRemoved().forEach(edge -> { - final EdgePresentation edgePresentation = edgePresentationMap.get(edge); - modelContainerEdge.getChildren().remove(edgePresentation); - edgePresentationMap.remove(edge); - }); - } + getComponent().getDisplayableEdges().addListener((ListChangeListener<DisplayableEdge>) c -> { + if (c.next()) { + // Edges are added to the component + c.getAddedSubList().forEach(handleAddedEdge); + + // Edges are removed from the component + c.getRemoved().forEach(edge -> { + final EdgePresentation edgePresentation = edgePresentationMap.get(edge); + modelContainerEdge.getChildren().remove(edgePresentation); + edgePresentationMap.remove(edge); + }); } }); - newComponent.getDisplayableEdges().forEach(handleAddedEdge); + + getComponent().getDisplayableEdges().forEach(handleAddedEdge); } private void initializeDeclarations() { // Initially style the declarations - declarationTextArea.setStyleSpans(0, ComponentPresentation.computeHighlighting(getComponent().getDeclarationsText())); + declarationTextArea.setStyleSpans(0, UPPAALSyntaxHighlighter.computeHighlighting(getComponent().getDeclarationsText())); declarationTextArea.getStyleClass().add("component-declaration"); final Circle circle = new Circle(0); @@ -823,9 +626,81 @@ private void initializeDeclarations() { clip.set(circle); } - public void toggleDeclaration(final MouseEvent mouseEvent) { + /*** + * Inserts a new {@link ecdar.presentations.SignatureArrow} in the containers for either input or output signature + * @param channel A String with the channel name that should be shown with the arrow + * @param status An EdgeStatus for the type of arrow to insert + */ + private void insertSignatureArrow(final String channel, final EdgeStatus status) { + SignatureArrow newArrow = new SignatureArrow(channel, status, getComponent()); + if (status == EdgeStatus.INPUT) { + inputSignatureContainer.getChildren().add(newArrow); + } else { + outputSignatureContainer.getChildren().add(newArrow); + } + } + + /*** + * Handles the addition of a new location + * @param loc The location to add to the component + */ + private void addLocation(Location loc) { + LocationPresentation newLocationPresentation = new LocationPresentation(loc, getComponent()); + Point2D placement = getUnoccupiedSpace(getComponent().getBox(), locationPresentationMap.values().stream().map(l -> new Point2D(l.getLayoutX(), l.getLayoutY())).collect(Collectors.toList()), new Point2D(loc.getX(), loc.getY()), LocationPresentation.RADIUS * 2 + Ecdar.CANVAS_PADDING); + + if (placement == null) { + getComponent().getLocations().remove(loc); + Ecdar.showToast("Please select an empty space for the new location"); + return; + } + + newLocationPresentation.setLayoutX(placement.getX()); + newLocationPresentation.setLayoutY(placement.getY()); + + locationPresentationMap.put(loc, newLocationPresentation); + modelContainerLocation.getChildren().add(newLocationPresentation); + + // Bind the newly created location to the mouse and tell the ui that it is not placed yet + if (loc.getX() == 0) { + newLocationPresentation.setPlaced(false); + BindingHelper.bind(loc, getComponent().getBox().getXProperty(), getComponent().getBox().getYProperty()); + } + } + + /*** + * Updates the component's height to match the input/output signature containers + * if the component is smaller than either of them + */ + private void updateMaxHeight() { + // If input/outputsignature container is taller than the current component height + // we update the component's height to be as tall as the container + double maxHeight = getMaxHeight(); + if (maxHeight > getComponent().getBox().getHeight()) { + getComponent().getBox().getHeightProperty().set(maxHeight); + } + } + + /*** + * Finds the max height of the input/output signature container and the component + * @return a double of the largest height + */ + private double getMaxHeight() { + double inputHeight = inputSignatureContainer.getHeight(); + double outputHeight = outputSignatureContainer.getHeight(); + double componentHeight = getComponent().getBox().getHeight(); + + double maxSignatureHeight = Math.max(outputHeight, inputHeight); + + return Math.max(maxSignatureHeight, componentHeight); + } + + /*** + * Toggle the declaration of the component with a ripple effect originating from the MouseEvent + * @param mouseEvent to use for the origin of the ripple effect + */ + private void toggleDeclaration(final MouseEvent mouseEvent) { final Circle circle = new Circle(0); - circle.setCenterX(component.get().getBox().getWidth() - (toggleDeclarationButton.getWidth() - mouseEvent.getX())); + circle.setCenterX(getComponent().getBox().getWidth() - (toggleDeclarationButton.getWidth() - mouseEvent.getX())); circle.setCenterY(-1 * mouseEvent.getY()); final ObjectProperty<Node> clip = new SimpleObjectProperty<>(circle); @@ -855,6 +730,20 @@ protected void interpolate(final double fraction) { getComponent().declarationOpenProperty().set(!getComponent().isDeclarationOpen()); } + /*** + * Mark the component as selected in the view + */ + public void componentSelected() { + updateColorDelegates.forEach(colorConsumer -> colorConsumer.accept(new EnabledColor(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL))); + } + + /*** + * Mark the component as not selected in the view + */ + public void componentDeselected() { + updateColorDelegates.forEach(colorConsumer -> colorConsumer.accept(getComponent().getColor())); + } + public Component getComponent() { return component.get(); } @@ -867,19 +756,56 @@ public ObjectProperty<Component> componentProperty() { return component; } + /** + * Moves all nodes left. + */ + public void moveAllNodesLeft() { + getComponent().getLocations().forEach(loc -> loc.setX(loc.getX() + NudgeDirection.LEFT.getXOffset())); + getComponent().getDisplayableEdges().forEach(edge -> edge.getNails().forEach(nail -> nail.setX(nail.getX() + NudgeDirection.LEFT.getXOffset()))); + } + + /** + * Moves all nodes right. + */ + public void moveAllNodesRight() { + getComponent().getLocations().forEach(loc -> loc.setX(loc.getX() + NudgeDirection.RIGHT.getXOffset())); + getComponent().getDisplayableEdges().forEach(edge -> edge.getNails().forEach(nail -> nail.setX(nail.getX() + NudgeDirection.RIGHT.getXOffset()))); + } + + /** + * Moves all nodes down. + */ + public void moveAllNodesDown() { + getComponent().getLocations().forEach(loc -> loc.setY(loc.getY() + NudgeDirection.DOWN.getYOffset())); + getComponent().getDisplayableEdges().forEach(edge -> edge.getNails().forEach(nail -> nail.setY(nail.getY() + NudgeDirection.DOWN.getYOffset()))); + } + + /** + * Moves all nodes up. + */ + public void moveAllNodesUp() { + getComponent().getLocations().forEach(loc -> loc.setY(loc.getY() + NudgeDirection.UP.getYOffset())); + getComponent().getDisplayableEdges().forEach(edge -> edge.getNails().forEach(nail -> nail.setY(nail.getY() + NudgeDirection.UP.getYOffset()))); + } + + /*** + * Handle the component being pressed based on the mouse button and hotkeys + * @param event to use for handling the action + */ @FXML private void modelContainerPressed(final MouseEvent event) { - EcdarController.getActiveCanvasPresentation().getController().leaveTextAreas(); + Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().leaveTextAreas(); final DisplayableEdge unfinishedEdge = getComponent().getUnfinishedEdge(); + dropdownCoordinatesInComponent = new Point2D(event.getX(), event.getY()); + if ((event.isShiftDown() && event.isPrimaryButtonDown()) || event.isMiddleButtonDown()) { final Location location = new Location(); - location.initialize(); + location.initialize(getComponent().getUniqueLocationId()); location.setX(event.getX()); location.setY(event.getY()); - location.setColorIntensity(getComponent().getColorIntensity()); location.setColor(getComponent().getColor()); if (unfinishedEdge != null) { @@ -918,10 +844,10 @@ private void modelContainerPressed(final MouseEvent event) { }); } else if (event.isSecondaryButtonDown()) { if (unfinishedEdge == null) { - contextMenu.show(JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX() * EcdarController.getActiveCanvasZoomFactor().get(), event.getY() * EcdarController.getActiveCanvasZoomFactor().get()); + contextMenu.show(JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX(), event.getY()); } else { initializeFinishEdgeContextMenu(unfinishedEdge); - finishEdgeContextMenu.show(JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX() * EcdarController.getActiveCanvasZoomFactor().get(), event.getY() * EcdarController.getActiveCanvasZoomFactor().get()); + finishEdgeContextMenu.show(JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX(), event.getY()); } } else if (event.isPrimaryButtonDown()) { // We are drawing an edge @@ -950,12 +876,58 @@ private void modelContainerDragged() { contextMenu.hide(); } - public MouseTracker getMouseTracker() { - return mouseTracker; + @Override + public HighLevelModel getModel() { + return getComponent(); } + /** + * Gets the minimum possible height when dragging the anchor. + * The height is based on the y coordinate of locations, nails and the signature arrows + * + * @return the minimum possible height. + */ @Override - public HighLevelModelObject getModel() { - return getComponent(); + double getDragAnchorMinWidth() { + double minWidth = 10 * Ecdar.CANVAS_PADDING; + + for (final Location location : getComponent().getLocations()) { + minWidth = Math.max(minWidth, location.getX() + Ecdar.CANVAS_PADDING * 2); + } + + for (final Edge edge : getComponent().getEdges()) { + for (final Nail nail : edge.getNails()) { + minWidth = Math.max(minWidth, nail.getX() + Ecdar.CANVAS_PADDING); + } + } + + return minWidth; + } + + /** + * Gets the minimum possible height when dragging the anchor. + * The height is based on the y coordinate of locations, nails and the signature arrows + * + * @return the minimum possible height. + */ + @Override + double getDragAnchorMinHeight() { + double minHeight = 10 * Ecdar.CANVAS_PADDING; + + for (final Location location : getComponent().getLocations()) { + minHeight = Math.max(minHeight, location.getY() + Ecdar.CANVAS_PADDING * 2); + } + + for (final Edge edge : getComponent().getEdges()) { + for (final Nail nail : edge.getNails()) { + minHeight = Math.max(minHeight, nail.getY() + Ecdar.CANVAS_PADDING); + } + } + + //Component should not get smaller than the height of the input/output signature containers + minHeight = Math.max(inputSignatureContainer.getHeight(), minHeight); + minHeight = Math.max(outputSignatureContainer.getHeight(), minHeight); + + return minHeight; } } diff --git a/src/main/java/ecdar/controllers/ComponentInstanceController.java b/src/main/java/ecdar/controllers/ComponentInstanceController.java index a706eb95..547ffd78 100644 --- a/src/main/java/ecdar/controllers/ComponentInstanceController.java +++ b/src/main/java/ecdar/controllers/ComponentInstanceController.java @@ -96,7 +96,7 @@ private void initializeDropDownMenu(final EcdarSystem system) { * Listens to an edge to update whether the root has an edge. * @param edge the edge to update with */ - private void handleHasEdge(final EcdarSystemEdge edge) { + private void handleHasEdge(final SystemEdge edge) { edge.getTempNodeProperty().addListener((observable -> updateHasEdge(edge))); edge.getChildProperty().addListener((observable -> updateHasEdge(edge))); edge.getParentProperty().addListener((observable -> updateHasEdge(edge))); @@ -106,7 +106,7 @@ private void handleHasEdge(final EcdarSystemEdge edge) { * Update has edge property to whether the instance is in a given edge. * @param edge the given edge */ - private void updateHasEdge(final EcdarSystemEdge edge) { + private void updateHasEdge(final SystemEdge edge) { hasEdge.set(edge.isInEdge(getInstance())); } @@ -114,7 +114,7 @@ private void updateHasEdge(final EcdarSystemEdge edge) { private void onMouseClicked(final MouseEvent event) { event.consume(); - final EcdarSystemEdge unfinishedEdge = getSystem().getUnfinishedEdge(); + final SystemEdge unfinishedEdge = getSystem().getUnfinishedEdge(); if ((event.isShiftDown() && event.getButton().equals(MouseButton.PRIMARY)) || event.getButton().equals(MouseButton.MIDDLE)) { // If shift click or middle click a component instance, create a new edge @@ -147,11 +147,11 @@ private void onMouseClicked(final MouseEvent event) { } /*** - * Helper method to create a new EcdarSystemEdge and add it to the current system and component instance - * @return The newly created EcdarSystemEdge + * Helper method to create a new SystemEdge and add it to the current system and component instance + * @return The newly created SystemEdge */ - private EcdarSystemEdge createNewSystemEdge() { - final EcdarSystemEdge edge = new EcdarSystemEdge(getInstance()); + private SystemEdge createNewSystemEdge() { + final SystemEdge edge = new SystemEdge(getInstance()); getSystem().addEdge(edge); hasEdge.set(true); diff --git a/src/main/java/ecdar/controllers/ComponentOperatorController.java b/src/main/java/ecdar/controllers/ComponentOperatorController.java index 7da44f1c..f0abc60f 100644 --- a/src/main/java/ecdar/controllers/ComponentOperatorController.java +++ b/src/main/java/ecdar/controllers/ComponentOperatorController.java @@ -1,7 +1,7 @@ package ecdar.controllers; import ecdar.abstractions.ComponentOperator; import ecdar.abstractions.EcdarSystem; -import ecdar.abstractions.EcdarSystemEdge; +import ecdar.abstractions.SystemEdge; import ecdar.presentations.DropDownMenu; import ecdar.presentations.MenuElement; import com.jfoenix.controls.JFXPopup; @@ -83,7 +83,7 @@ private void showContextMenu(MouseEvent mouseEvent) { contextMenu.addMenuElement(new MenuElement("Draw Edge") .setClickable(() -> { - final EcdarSystemEdge edge = new EcdarSystemEdge(operator); + final SystemEdge edge = new SystemEdge(operator); getSystem().addEdge(edge); contextMenu.hide(); @@ -104,7 +104,7 @@ private void showContextMenu(MouseEvent mouseEvent) { * Listens to an edge to update whether the operator has a parent edge. * @param parentEdge the edge to update with */ - private void updateHasParent(final EcdarSystemEdge parentEdge) { + private void updateHasParent(final SystemEdge parentEdge) { // The operator has a parent iff the supposed parent edge has the operator as a child parentEdge.getChildProperty().addListener(((observable, oldValue, newValue) -> hasParent.set(getOperator().equals(newValue)))); } @@ -117,7 +117,7 @@ private void updateHasParent(final EcdarSystemEdge parentEdge) { private void onMouseClicked(final MouseEvent event) { event.consume(); - final EcdarSystemEdge unfinishedEdge = getSystem().getUnfinishedEdge(); + final SystemEdge unfinishedEdge = getSystem().getUnfinishedEdge(); // if primary clicked and there is an unfinished edge, finish it with the system root as target if (unfinishedEdge != null && event.getButton().equals(MouseButton.PRIMARY)) { diff --git a/src/main/java/ecdar/controllers/DeclarationsController.java b/src/main/java/ecdar/controllers/DeclarationsController.java index b4ab05e0..ee474f86 100644 --- a/src/main/java/ecdar/controllers/DeclarationsController.java +++ b/src/main/java/ecdar/controllers/DeclarationsController.java @@ -1,8 +1,8 @@ package ecdar.controllers; -import ecdar.Ecdar; import ecdar.abstractions.Declarations; -import ecdar.presentations.ComponentPresentation; +import ecdar.abstractions.HighLevelModel; +import ecdar.utility.helpers.UPPAALSyntaxHighlighter; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.fxml.Initializable; @@ -17,10 +17,10 @@ /** * Controller for overall declarations. */ -public class DeclarationsController implements Initializable { - public StyleClassedTextArea textArea; +public class DeclarationsController extends HighLevelModelController implements Initializable { public StackPane root; public BorderPane frame; + public StyleClassedTextArea textArea; private final ObjectProperty<Declarations> declarations; @@ -34,21 +34,9 @@ public void setDeclarations(final Declarations declarations) { @Override public void initialize(final URL location, final ResourceBundle resources) { - initializeWidthAndHeight(); initializeText(); } - /** - * Initializes width and height of the text editor field, such that it fills up the whole canvas - */ - private void initializeWidthAndHeight() { - // Fetch width and height of canvas and update - root.minWidthProperty().bind(Ecdar.getPresentation().getController().getEditorPresentation().getController().canvasPane.minWidthProperty()); - root.maxWidthProperty().bind(Ecdar.getPresentation().getController().getEditorPresentation().getController().canvasPane.maxWidthProperty()); - root.minHeightProperty().bind(Ecdar.getPresentation().getController().getEditorPresentation().getController().canvasPane.minHeightProperty()); - root.maxHeightProperty().bind(Ecdar.getPresentation().getController().getEditorPresentation().getController().canvasPane.maxHeightProperty()); - } - /** * Sets up the linenumbers and binds the text in the text area to the declaration object */ @@ -71,6 +59,11 @@ private void initializeText() { * Updates highlighting of the text in the text area. */ public void updateHighlighting() { - textArea.setStyleSpans(0, ComponentPresentation.computeHighlighting(declarations.get().getDeclarationsText())); + textArea.setStyleSpans(0, UPPAALSyntaxHighlighter.computeHighlighting(declarations.get().getDeclarationsText())); + } + + @Override + public HighLevelModel getModel() { + return declarations.get(); } } diff --git a/src/main/java/ecdar/controllers/EcdarController.java b/src/main/java/ecdar/controllers/EcdarController.java index 76f49469..0a135025 100644 --- a/src/main/java/ecdar/controllers/EcdarController.java +++ b/src/main/java/ecdar/controllers/EcdarController.java @@ -6,9 +6,9 @@ import ecdar.Ecdar; import ecdar.abstractions.*; import ecdar.backend.BackendHelper; -import ecdar.backend.BackendInstance; -import ecdar.backend.SimulationHandler; +import ecdar.backend.Engine; import ecdar.code_analysis.CodeAnalysis; +import ecdar.mutation.MutationTestPlanPresentation; import ecdar.mutation.models.MutationTestPlan; import ecdar.presentations.*; import ecdar.utility.UndoRedoStack; @@ -48,7 +48,6 @@ import java.util.stream.Collectors; public class EcdarController implements Initializable { - private SimulationHandler simulationHandler; // View stuff public StackPane root; public BorderPane borderPane; @@ -59,8 +58,6 @@ public class EcdarController implements Initializable { public StackPane modellingHelpDialogContainer; public JFXDialog modellingHelpDialog; public StackPane modalBar; - public JFXTextField queryTextField; - public JFXTextField commentTextField; public ImageView helpInitialImage; public StackPane helpInitialPane; @@ -109,7 +106,7 @@ public class EcdarController implements Initializable { public MenuItem menuBarFileExportAsPngNoBorder; public MenuItem menuBarOptionsCache; public MenuItem menuBarOptionsBackgroundQueries; - public MenuItem menuBarOptionsBackendOptions; + public MenuItem menuBarOptionsEngineOptions; public MenuItem menuBarHelpHelp; public MenuItem menuBarHelpAbout; public MenuItem menuBarHelpTest; @@ -126,8 +123,8 @@ public class EcdarController implements Initializable { public Text queryTextResult; public Text queryTextQuery; - public StackPane backendOptionsDialogContainer; - public BackendOptionsDialogPresentation backendOptionsDialog; + public StackPane engineOptionsDialogContainer; + public EngineOptionsDialogPresentation engineOptionsDialog; public StackPane simulationInitializationDialogContainer; public SimulationInitializationDialogPresentation simulationInitializationDialog; @@ -153,12 +150,7 @@ public enum Mode { public static final ObjectProperty<Mode> currentMode = new SimpleObjectProperty<>(Mode.Editor); private static final EditorPresentation editorPresentation = new EditorPresentation(); - public final ProjectPanePresentation projectPane = new ProjectPanePresentation(); - public final QueryPanePresentation queryPane = new QueryPanePresentation(); - private static final SimulatorPresentation simulatorPresentation = new SimulatorPresentation(); - public final LeftSimPanePresentation leftSimPane = new LeftSimPanePresentation(); - public final RightSimPanePresentation rightSimPane = new RightSimPanePresentation(); @Override public void initialize(final URL location, final ResourceBundle resources) { @@ -167,18 +159,7 @@ public void initialize(final URL location, final ResourceBundle resources) { initializeStatusBar(); initializeMenuBar(); - // Update file coloring when active model changes - editorPresentation.getController().getActiveCanvasPresentation().getController().activeComponentProperty().addListener(observable -> projectPane.getController().updateColorsOnFilePresentations()); - editorPresentation.getController().activeCanvasPresentationProperty().addListener(observable -> projectPane.getController().updateColorsOnFilePresentations()); - - leftSimPane.minWidthProperty().bind(projectPane.minWidthProperty()); - leftSimPane.maxWidthProperty().bind(projectPane.maxWidthProperty()); - rightSimPane.minWidthProperty().bind(queryPane.minWidthProperty()); - rightSimPane.maxWidthProperty().bind(queryPane.maxWidthProperty()); - enterEditorMode(); - - simulationHandler = Ecdar.getSimulationHandler(); } public StackPane getCenter() { @@ -201,22 +182,20 @@ public SimulatorPresentation getSimulatorPresentation() { return simulatorPresentation; } - public static CanvasPresentation getActiveCanvasPresentation() { - return editorPresentation.getController().getActiveCanvasPresentation(); - } - - public static DoubleProperty getActiveCanvasZoomFactor() { - return getActiveCanvasPresentation().getController().zoomHelper.currentZoomFactor; - } - - public static void setActiveCanvasPresentation(CanvasPresentation newActiveCanvasPresentation) { - getActiveCanvasPresentation().setOpacity(0.75); - newActiveCanvasPresentation.setOpacity(1); - editorPresentation.getController().setActiveCanvasPresentation(newActiveCanvasPresentation); + public StackPane getLeftModePane() { + if (currentMode.get().equals(Mode.Editor)) { + return editorPresentation.getController().getLeftPane(); + } else { + return simulatorPresentation.getController().getLeftPane(); + } } - public static void setActiveModelForActiveCanvas(HighLevelModelObject newActiveModel) { - getActiveCanvasPresentation().getController().setActiveModel(newActiveModel); + public StackPane getRightModePane() { + if (currentMode.get().equals(Mode.Editor)) { + return editorPresentation.getController().getRightPane(); + } else { + return simulatorPresentation.getController().getRightPane(); + } } public static void setTemporaryComponentWatermarkVisibility(boolean visibility) { @@ -265,7 +244,29 @@ private void initializeDialogs() { _queryTextQuery = queryTextQuery; initializeDialog(queryDialog, queryDialogContainer); - initializeBackendOptionsDialog(); + initializeDialog(engineOptionsDialog, engineOptionsDialogContainer); + + engineOptionsDialog.getController().resetEnginesButton.setOnMouseClicked(event -> { + engineOptionsDialog.getController().resetEnginesToDefault(); + }); + + engineOptionsDialog.getController().closeButton.setOnMouseClicked(event -> { + engineOptionsDialog.getController().cancelEngineOptionsChanges(); + engineOptionsDialog.close(); + }); + + engineOptionsDialog.getController().saveButton.setOnMouseClicked(event -> { + if (engineOptionsDialog.getController().saveChangesToEngineOptions()) { + engineOptionsDialog.close(); + } + }); + + if (BackendHelper.getEngines().size() < 1) { + Ecdar.showToast("No engines were found. Download j-Ecdar or Reveaal, or add another engine to fix this. No queries can be executed without engines."); + } else { + Engine defaultBackend = BackendHelper.getEngines().stream().filter(Engine::isDefault).findFirst().orElse(BackendHelper.getEngines().get(0)); + BackendHelper.setDefaultEngine(defaultBackend); + } initializeDialog(simulationInitializationDialog, simulationInitializationDialogContainer); @@ -277,37 +278,12 @@ private void initializeDialogs() { simulationInitializationDialog.getController().startButton.setOnMouseClicked(event -> { // ToDo NIELS: Start simulation of selected query - simulationHandler.setComposition(simulationInitializationDialog.getController().simulationComboBox.getSelectionModel().getSelectedItem()); + SimulatorController.simulationHandler.setComposition(simulationInitializationDialog.getController().simulationComboBox.getSelectionModel().getSelectedItem()); currentMode.setValue(Mode.Simulator); simulationInitializationDialog.close(); }); } - private void initializeBackendOptionsDialog() { - initializeDialog(backendOptionsDialog, backendOptionsDialogContainer); - backendOptionsDialog.getController().resetBackendsButton.setOnMouseClicked(event -> { - backendOptionsDialog.getController().resetBackendsToDefault(); - }); - - backendOptionsDialog.getController().closeButton.setOnMouseClicked(event -> { - backendOptionsDialog.getController().cancelBackendOptionsChanges(); - backendOptionsDialog.close(); - }); - - backendOptionsDialog.getController().saveButton.setOnMouseClicked(event -> { - if (backendOptionsDialog.getController().saveChangesToBackendOptions()) { - backendOptionsDialog.close(); - } - }); - - if (BackendHelper.getBackendInstances().size() < 1) { - Ecdar.showToast("No engines were found. Download j-Ecdar or Reveaal, or add another engine to fix this. No queries can be executed without engines."); - } else { - BackendInstance defaultBackend = BackendHelper.getBackendInstances().stream().filter(BackendInstance::isDefault).findFirst().orElse(BackendHelper.getBackendInstances().get(0)); - BackendHelper.setDefaultBackendInstance(defaultBackend); - } - } - private void initializeDialog(JFXDialog dialog, StackPane dialogContainer) { dialog.setDialogContainer(dialogContainer); dialogContainer.opacityProperty().bind(dialog.getChildren().get(0).scaleXProperty()); @@ -321,9 +297,6 @@ private void initializeDialog(JFXDialog dialog, StackPane dialogContainer) { dialogContainer.setMouseTransparent(false); }); - projectPane.getStyleClass().add("responsive-pane-sizing"); - queryPane.getStyleClass().add("responsive-pane-sizing"); - initializeKeybindings(); initializeStatusBar(); } @@ -349,19 +322,6 @@ private void intitializeTemporaryComponentWatermark() { * - Colors */ private void initializeKeybindings() { - //Press ctrl+N or cmd+N to create a new component. The canvas changes to this new component - KeyCodeCombination combination = new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN); - Keybind binding = new Keybind(combination, (event) -> { - final Component newComponent = new Component(true); - UndoRedoStack.pushAndPerform(() -> { // Perform - Ecdar.getProject().getComponents().add(newComponent); - setActiveModelForActiveCanvas(newComponent); - }, () -> { // Undo - Ecdar.getProject().getComponents().remove(newComponent); - }, "Created new component: " + newComponent.getName(), "add-circle"); - }); - KeyboardTracker.registerKeybind(KeyboardTracker.CREATE_COMPONENT, binding); - // Keybind for nudging the selected elements KeyboardTracker.registerKeybind(KeyboardTracker.NUDGE_UP, new Keybind(new KeyCodeCombination(KeyCode.UP), (event) -> { event.consume(); @@ -469,10 +429,10 @@ private void initializeOptionsMenu() { menuBarOptionsCache.getGraphic().opacityProperty().bind(new When(isCached).then(1).otherwise(0)); }); - menuBarOptionsBackendOptions.setOnAction(event -> { - backendOptionsDialogContainer.setVisible(true); - backendOptionsDialog.show(backendOptionsDialogContainer); - backendOptionsDialog.setMouseTransparent(false); + menuBarOptionsEngineOptions.setOnAction(event -> { + engineOptionsDialogContainer.setVisible(true); + engineOptionsDialog.show(engineOptionsDialogContainer); + engineOptionsDialog.setMouseTransparent(false); }); menuBarOptionsBackgroundQueries.setOnAction(event -> { @@ -487,33 +447,33 @@ private void initializeOptionsMenu() { private void initializeEditMenu() { menuEditMoveLeft.setAccelerator(new KeyCodeCombination(KeyCode.LEFT, KeyCombination.CONTROL_DOWN)); menuEditMoveLeft.setOnAction(event -> { - final HighLevelModelObject activeModel = getActiveCanvasPresentation().getController().getActiveModel(); - - if (activeModel instanceof Component) ((Component) activeModel).moveAllNodesLeft(); + final HighLevelModelController activeModelController = editorPresentation.getController().getActiveCanvasPresentation().getController().getActiveModelPresentation().getController(); + if (activeModelController instanceof ComponentController) + ((ComponentController) activeModelController).moveAllNodesLeft(); else Ecdar.showToast("This can only be performed on components."); }); menuEditMoveRight.setAccelerator(new KeyCodeCombination(KeyCode.RIGHT, KeyCombination.CONTROL_DOWN)); menuEditMoveRight.setOnAction(event -> { - final HighLevelModelObject activeModel = getActiveCanvasPresentation().getController().getActiveModel(); - - if (activeModel instanceof Component) ((Component) activeModel).moveAllNodesRight(); + final HighLevelModelController activeModelController = editorPresentation.getController().getActiveCanvasPresentation().getController().getActiveModelPresentation().getController(); + if (activeModelController instanceof ComponentController) + ((ComponentController) activeModelController).moveAllNodesRight(); else Ecdar.showToast("This can only be performed on components."); }); menuEditMoveUp.setAccelerator(new KeyCodeCombination(KeyCode.UP, KeyCombination.CONTROL_DOWN)); menuEditMoveUp.setOnAction(event -> { - final HighLevelModelObject activeModel = getActiveCanvasPresentation().getController().getActiveModel(); - - if (activeModel instanceof Component) ((Component) activeModel).moveAllNodesUp(); + final HighLevelModelController activeModelController = editorPresentation.getController().getActiveCanvasPresentation().getController().getActiveModelPresentation().getController(); + if (activeModelController instanceof ComponentController) + ((ComponentController) activeModelController).moveAllNodesUp(); else Ecdar.showToast("This can only be performed on components."); }); menuEditMoveDown.setAccelerator(new KeyCodeCombination(KeyCode.DOWN, KeyCombination.CONTROL_DOWN)); menuEditMoveDown.setOnAction(event -> { - final HighLevelModelObject activeModel = getActiveCanvasPresentation().getController().getActiveModel(); - - if (activeModel instanceof Component) ((Component) activeModel).moveAllNodesDown(); + final HighLevelModelController activeModelController = editorPresentation.getController().getActiveCanvasPresentation().getController().getActiveModelPresentation().getController(); + if (activeModelController instanceof ComponentController) + ((ComponentController) activeModelController).moveAllNodesDown(); else Ecdar.showToast("This can only be performed on components."); }); } @@ -550,13 +510,14 @@ private void initializeViewMenu() { menuBarViewCanvasSplit.getGraphic().setOpacity(1); menuBarViewCanvasSplit.setOnAction(event -> { - final BooleanProperty isSplit = Ecdar.toggleCanvasSplit(); - if (isSplit.get()) { - Platform.runLater(() -> editorPresentation.getController().setCanvasModeToSingular()); - menuBarViewCanvasSplit.setText("Split canvas"); + Ecdar.toggleCanvasSplit(); + }); + + Ecdar.isSplitProperty().addListener((observable, oldValue, newValue) -> { + if (newValue) { + Platform.runLater(editorPresentation.getController()::setCanvasModeToSplit); } else { - Platform.runLater(() -> editorPresentation.getController().setCanvasModeToSplit()); - menuBarViewCanvasSplit.setText("Merge canvases"); + Platform.runLater(editorPresentation.getController()::setCanvasModeToSingular); } }); @@ -568,7 +529,7 @@ private void initializeViewMenu() { return; } - if (!simulationHandler.isSimulationRunning()) { + if (!SimulatorController.getSimulationHandler().isSimulationRunning()) { ArrayList<String> queryOptions = Ecdar.getProject().getQueries().stream().map(Query::getQuery).collect(Collectors.toCollection(ArrayList::new)); if (!simulationInitializationDialog.getController().simulationComboBox.getItems().equals(queryOptions)) { simulationInitializationDialog.getController().simulationComboBox.getItems().setAll(queryOptions); @@ -623,7 +584,7 @@ private void updateScaling(double newScale) { // Text do not scale on the canvas to avoid ugly elements, // this zooms in on the component in order to get the "same font size" - EcdarController.getActiveCanvasPresentation().getController().zoomHelper.setZoomLevel(newCalculatedScale / 13); + editorPresentation.getController().getActiveCanvasPresentation().getController().zoomHelper.setZoomLevel(newCalculatedScale / 13); Ecdar.preferences.put("scale", String.valueOf(newScale)); scaleIcons(root, newCalculatedScale); @@ -655,8 +616,7 @@ private void initializeOpenProjectMenuItem() { final File file = projectPicker.showDialog(root.getScene().getWindow()); if (file != null) { try { - Ecdar.projectDirectory.set(file.getAbsolutePath()); - Ecdar.initializeProjectFolder(); + setProjectDirectory(file.getAbsolutePath()); UndoRedoStack.clear(); addProjectToRecentProjects(file.getAbsolutePath()); } catch (final IOException e) { @@ -676,8 +636,7 @@ private void initializeRecentProjectsMenu() { item.setOnAction(event -> { try { - Ecdar.projectDirectory.set(path); - Ecdar.initializeProjectFolder(); + setProjectDirectory(path); } catch (IOException ex) { Ecdar.showToast("Unable to load project: \"" + path + "\""); } @@ -703,9 +662,15 @@ private void initializeRecentProjectsMenu() { menuBarFileRecentProjects.getItems().add(item); } + private static void setProjectDirectory(String path) throws IOException { + Ecdar.projectDirectory.set(path); + Ecdar.initializeProjectFolder(); + Ecdar.isSplitProperty().set(false); + } + /** * Saves the project to the {@link Ecdar#projectDirectory} path. - * This include making directories, converting project files (components and queries) + * This includes making directories, converting project files (components and queries) * into Json formatted files. */ public void save() { @@ -818,7 +783,7 @@ private void initializeNewMutationTestObjectMenuItem() { UndoRedoStack.pushAndPerform(() -> { // Perform Ecdar.getProject().getTestPlans().add(newPlan); - setActiveModelForActiveCanvas(newPlan); + editorPresentation.getController().setActiveModelPresentationForActiveCanvas(new MutationTestPlanPresentation(newPlan)); }, () -> { // Undo Ecdar.getProject().getTestPlans().remove(newPlan); }, "Created new mutation test plan", ""); @@ -828,15 +793,18 @@ private void initializeNewMutationTestObjectMenuItem() { /** * Creates a new project. */ - private static void createNewProject() { + private void createNewProject() { CodeAnalysis.disable(); CodeAnalysis.clearErrorsAndWarnings(); - Ecdar.projectDirectory.set(null); + try { + setProjectDirectory(null); + } catch (IOException ex) { + Ecdar.showToast("Unable to initialize new project directory"); + } - Ecdar.getProject().reset(); - setActiveModelForActiveCanvas(Ecdar.getProject().getComponents().get(0)); + editorPresentation.getController().setActiveModelPresentationForActiveCanvas(editorPresentation.getController().projectPane.getController().getComponentPresentations().get(0)); UndoRedoStack.clear(); @@ -850,12 +818,12 @@ private void initializeFileExportAsPng() { menuBarFileExportAsPng.setAccelerator(new KeyCodeCombination(KeyCode.L, KeyCombination.SHORTCUT_DOWN)); menuBarFileExportAsPng.setOnAction(event -> { // If there is no active component or system - if (!(getActiveCanvasPresentation().getController().getActiveModel() instanceof Component || getActiveCanvasPresentation().getController().getActiveModel() instanceof EcdarSystem)) { + if (!(editorPresentation.getController().getActiveCanvasPresentation().getController().getActiveModelPresentation() instanceof ComponentPresentation || editorPresentation.getController().getActiveCanvasPresentation().getController().getActiveModelPresentation() instanceof SystemPresentation)) { Ecdar.showToast("No component or system to export."); return; } - CanvasPresentation canvas = getActiveCanvasPresentation(); + CanvasPresentation canvas = editorPresentation.getController().getActiveCanvasPresentation(); // If there was an active component, hide button for toggling declarations final ComponentPresentation presentation = canvas.getController().getActiveComponentPresentation(); @@ -874,7 +842,7 @@ private void initializeFileExportAsPng() { }); menuBarFileExportAsPngNoBorder.setOnAction(event -> { - CanvasPresentation canvas = getActiveCanvasPresentation(); + CanvasPresentation canvas = editorPresentation.getController().getActiveCanvasPresentation(); final ComponentPresentation presentation = canvas.getController().getActiveComponentPresentation(); @@ -898,10 +866,7 @@ private void initializeFileExportAsPng() { */ private void enterEditorMode() { borderPane.setCenter(editorPresentation); - leftPane.getChildren().clear(); - leftPane.getChildren().add(projectPane); - rightPane.getChildren().clear(); - rightPane.getChildren().add(queryPane); + updateSidePanes(editorPresentation.getController()); } /** @@ -910,12 +875,17 @@ private void enterEditorMode() { */ private void enterSimulatorMode() { simulatorPresentation.getController().willShow(); - borderPane.setCenter(simulatorPresentation); + updateSidePanes(simulatorPresentation.getController()); + } + + private void updateSidePanes(ModeController modeController) { leftPane.getChildren().clear(); - leftPane.getChildren().add(leftSimPane); + leftPane.getChildren().add(modeController.getLeftPane()); rightPane.getChildren().clear(); - rightPane.getChildren().add(rightSimPane); + rightPane.getChildren().add(modeController.getRightPane()); + + } /** @@ -953,7 +923,7 @@ private WritableImage scaleAndTakeSnapshot(CanvasPresentation canvas) { * @param image the image */ private void CropAndExportImage(final WritableImage image) { - final String name = getActiveCanvasPresentation().getController().getActiveModel().getName(); + final String name = editorPresentation.getController().getActiveCanvasPresentation().getController().getActiveModelPresentation().getController().getModel().getName(); final FileChooser filePicker = new FileChooser(); filePicker.setTitle("Export png"); @@ -1075,7 +1045,6 @@ private static int getAutoCropRightX(final BufferedImage image) { throw new IllegalArgumentException("Image is all white"); } - private void nudgeSelected(final NudgeDirection direction) { final List<SelectHelper.ItemSelectable> selectedElements = SelectHelper.getSelectedElements(); diff --git a/src/main/java/ecdar/controllers/EdgeController.java b/src/main/java/ecdar/controllers/EdgeController.java index 42bde140..3c65cb0d 100644 --- a/src/main/java/ecdar/controllers/EdgeController.java +++ b/src/main/java/ecdar/controllers/EdgeController.java @@ -8,6 +8,7 @@ import ecdar.utility.Highlightable; import ecdar.utility.UndoRedoStack; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.*; import ecdar.utility.keyboard.Keybind; import ecdar.utility.keyboard.KeyboardTracker; @@ -442,11 +443,11 @@ public void onChanged(final Change<? extends Link> c) { dropDownMenu.hide(); }); - dropDownMenu.show(JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX() * EcdarController.getActiveCanvasZoomFactor().get(), event.getY() * EcdarController.getActiveCanvasZoomFactor().get()); + dropDownMenu.show(JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX() * Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get(), event.getY() * Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get()); } else if ((event.isShiftDown() && event.isPrimaryButtonDown()) || event.isMiddleButtonDown()) { - final double nailX = EcdarController.getActiveCanvasPresentation().mouseTracker.xProperty().subtract(getComponent().getBox().getXProperty()).doubleValue(); - final double nailY = EcdarController.getActiveCanvasPresentation().mouseTracker.yProperty().subtract(getComponent().getBox().getYProperty()).doubleValue(); + final double nailX = Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().mouseTracker.xProperty().subtract(getComponent().getBox().getXProperty()).doubleValue(); + final double nailY = Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().mouseTracker.yProperty().subtract(getComponent().getBox().getYProperty()).doubleValue(); final Nail newNail = new Nail(nailX, nailY); @@ -593,8 +594,8 @@ private void addEdgePropertyRow(final DropDownMenu dropDownMenu, final String ro } private Nail getNewNailBasedOnDropdownPosition() { - final double nailX = DropDownMenu.x / EcdarController.getActiveCanvasZoomFactor().get(); - final double nailY = DropDownMenu.y / EcdarController.getActiveCanvasZoomFactor().get(); + final double nailX = DropDownMenu.x / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get(); + final double nailY = DropDownMenu.y / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get(); return new Nail(nailX, nailY); } @@ -722,24 +723,18 @@ public void edgeDragged(final MouseEvent event) { } @Override - public void color(final Color color, final Color.Intensity intensity) { + public void color(final EnabledColor color) { final DisplayableEdge edge = getEdge(); // Set the color of the edge - edge.setColorIntensity(intensity); edge.setColor(color); } @Override - public Color getColor() { + public EnabledColor getColor() { return getEdge().getColor(); } - @Override - public Color.Intensity getColorIntensity() { - return getEdge().getColorIntensity(); - } - @Override public ItemDragHelper.DragBounds getDragBounds() { return ItemDragHelper.DragBounds.generateLooseDragBounds(); diff --git a/src/main/java/ecdar/controllers/EditorController.java b/src/main/java/ecdar/controllers/EditorController.java index 15557dea..cf12107d 100644 --- a/src/main/java/ecdar/controllers/EditorController.java +++ b/src/main/java/ecdar/controllers/EditorController.java @@ -7,8 +7,7 @@ import ecdar.abstractions.Component; import ecdar.abstractions.DisplayableEdge; import ecdar.abstractions.EdgeStatus; -import ecdar.abstractions.HighLevelModelObject; -import ecdar.presentations.CanvasPresentation; +import ecdar.presentations.*; import ecdar.utility.UndoRedoStack; import ecdar.utility.colors.Color; import ecdar.utility.colors.EnabledColor; @@ -16,7 +15,9 @@ import ecdar.utility.keyboard.Keybind; import ecdar.utility.keyboard.KeyboardTracker; import javafx.application.Platform; +import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.ObservableList; import javafx.fxml.FXML; @@ -30,10 +31,11 @@ import java.net.URL; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.ResourceBundle; -public class EditorController implements Initializable { +public class EditorController implements ModeController, Initializable { public VBox root; public HBox toolbar; public JFXRippler colorSelected; @@ -45,6 +47,9 @@ public class EditorController implements Initializable { public JFXToggleButton switchEdgeStatusButton; public StackPane canvasPane; + public final ProjectPanePresentation projectPane = new ProjectPanePresentation(); + public final QueryPanePresentation queryPane = new QueryPanePresentation(); + private final ObjectProperty<EdgeStatus> globalEdgeStatus = new SimpleObjectProperty<>(EdgeStatus.INPUT); private final ObjectProperty<CanvasPresentation> activeCanvasPresentation = new SimpleObjectProperty<>(new CanvasPresentation()); @@ -53,6 +58,35 @@ public void initialize(final URL location, final ResourceBundle resources) { initializeCanvasPane(); initializeEdgeStatusHandling(); initializeKeybindings(); + Platform.runLater(this::initializeSidePanes); + + // Update file coloring when active model changes + getActiveCanvasPresentation().getController().activeModelProperty().addListener((observable, oldModel, newModel) -> projectPane.getController().swapHighlightBetweenTwoModelFiles(oldModel, newModel)); + } + + private void initializeSidePanes() { + final DoubleProperty prevX = new SimpleDoubleProperty(); + final DoubleProperty prevWidth = new SimpleDoubleProperty(); + + queryPane.getController().resizeAnchor.setOnMousePressed(event -> { + event.consume(); + + prevX.set(event.getScreenX()); + prevWidth.set(queryPane.getWidth()); + }); + + queryPane.getController().resizeAnchor.setOnMouseDragged(event -> { + double diff = prevX.get() - event.getScreenX(); + + // Set bounds for resizing to be between 280px and half the screen width + final double newWidth = Math.min(Math.max(prevWidth.get() + diff, 280), Ecdar.getPresentation().getWidth() / 2); + + queryPane.setMinWidth(newWidth); + queryPane.setMaxWidth(newWidth); + }); + + projectPane.getStyleClass().add("responsive-pane-sizing"); + queryPane.getStyleClass().add("responsive-pane-sizing"); } public EdgeStatus getGlobalEdgeStatus() { @@ -73,11 +107,15 @@ public void setActiveCanvasPresentation(CanvasPresentation newActiveCanvasPresen activeCanvasPresentation.set(newActiveCanvasPresentation); } - public void setActiveModelForActiveCanvas(HighLevelModelObject newActiveModel) { - EcdarController.setActiveModelForActiveCanvas(newActiveModel); + public void setActiveModelPresentationForActiveCanvas(HighLevelModelPresentation newActiveModelPresentation) { + getActiveCanvasPresentation().getController().setActiveModelPresentation(newActiveModelPresentation); // Change zoom level to fit new active model - Platform.runLater(() -> EcdarController.getActiveCanvasPresentation().getController().zoomHelper.zoomToFit()); + Platform.runLater(() -> getActiveCanvasPresentation().getController().zoomHelper.zoomToFit()); + } + + public DoubleProperty getActiveCanvasZoomFactor() { + return getActiveCanvasPresentation().getController().zoomHelper.currentZoomFactor; } private void initializeCanvasPane() { @@ -92,7 +130,7 @@ private void initializeKeybindings() { final List<Pair<SelectHelper.ItemSelectable, EnabledColor>> previousColor = new ArrayList<>(); SelectHelper.getSelectedElements().forEach(selectable -> { - previousColor.add(new Pair<>(selectable, new EnabledColor(selectable.getColor(), selectable.getColorIntensity()))); + previousColor.add(new Pair<>(selectable, selectable.getColor())); }); changeColorOnSelectedElements(enabledColor, previousColor); SelectHelper.clearSelectedElements(); @@ -159,9 +197,9 @@ public void changeColorOnSelectedElements(final EnabledColor enabledColor, final List<Pair<SelectHelper.ItemSelectable, EnabledColor>> previousColor) { UndoRedoStack.pushAndPerform(() -> { // Perform SelectHelper.getSelectedElements() - .forEach(selectable -> selectable.color(enabledColor.color, enabledColor.intensity)); + .forEach(selectable -> selectable.color(enabledColor)); }, () -> { // Undo - previousColor.forEach(selectableEnabledColorPair -> selectableEnabledColorPair.getKey().color(selectableEnabledColorPair.getValue().color, selectableEnabledColorPair.getValue().intensity)); + previousColor.forEach(selectableEnabledColorPair -> selectableEnabledColorPair.getKey().color(selectableEnabledColorPair.getValue())); }, String.format("Changed the color of %d elements to %s", previousColor.size(), enabledColor.color.name()), "color-lens"); } @@ -171,28 +209,26 @@ public void changeColorOnSelectedElements(final EnabledColor enabledColor, void setCanvasModeToSingular() { canvasPane.getChildren().clear(); CanvasPresentation canvasPresentation = new CanvasPresentation(); - HighLevelModelObject model = activeCanvasPresentation.get().getController().getActiveModel(); - if (model != null) { - canvasPresentation.getController().setActiveModel(activeCanvasPresentation.get().getController().getActiveModel()); - } else { - // If no components where found, the project has not been initialized. The active model will be updated when the project is initialized - canvasPresentation.getController().setActiveModel(Ecdar.getProject().getComponents().stream().findFirst().orElse(null)); + if (activeCanvasPresentation.get().getController().getActiveModelPresentation() != null) { + canvasPresentation.getController().setActiveModelPresentation(activeCanvasPresentation.get().getController().getActiveModelPresentation()); } canvasPane.getChildren().add(canvasPresentation); - activeCanvasPresentation.set(canvasPresentation); + setActiveCanvasPresentation(canvasPresentation); Rectangle clip = new Rectangle(); clip.setArcWidth(1); clip.setArcHeight(1); clip.widthProperty().bind(canvasPane.widthProperty()); clip.heightProperty().bind(canvasPane.heightProperty()); - canvasPresentation.getController().zoomablePane.setClip(clip); + canvasPresentation.getController().zoomablePane.minWidthProperty().bind(canvasPane.widthProperty()); canvasPresentation.getController().zoomablePane.maxWidthProperty().bind(canvasPane.widthProperty()); canvasPresentation.getController().zoomablePane.minHeightProperty().bind(canvasPane.heightProperty()); canvasPresentation.getController().zoomablePane.maxHeightProperty().bind(canvasPane.heightProperty()); + + Platform.runLater(() -> projectPane.getController().setHighlightedForModelFiles(Collections.singletonList(getActiveCanvasPresentation().getController().getActiveModelPresentation()))); } /** @@ -220,12 +256,12 @@ void setCanvasModeToSplit() { canvasGrid.getRowConstraints().add(row1); canvasGrid.getRowConstraints().add(row1); - ObservableList<Component> components = Ecdar.getProject().getComponents(); + ObservableList<ComponentPresentation> components = projectPane.getController().getComponentPresentations(); int currentCompNum = 0, numComponents = components.size(); // Add the canvasPresentation at the top-left CanvasPresentation canvasPresentation = initializeNewCanvasPresentation(); - canvasPresentation.getController().setActiveModel(getActiveCanvasPresentation().getController().getActiveModel()); + canvasPresentation.getController().setActiveModelPresentation(getActiveCanvasPresentation().getController().getActiveModelPresentation()); canvasGrid.add(canvasPresentation, 0, 0); setActiveCanvasPresentation(canvasPresentation); @@ -236,7 +272,7 @@ void setCanvasModeToSplit() { // Update the startIndex for the next canvasPresentation for (int i = 0; i < numComponents; i++) { - if (canvasPresentation.getController().getActiveModel() != null && canvasPresentation.getController().getActiveModel().equals(components.get(i))) { + if (canvasPresentation.getController().getActiveModelPresentation() != null && canvasPresentation.getController().getActiveModelPresentation().getController().getModel().equals(components.get(i))) { currentCompNum = i + 1; } } @@ -248,7 +284,7 @@ void setCanvasModeToSplit() { // Update the startIndex for the next canvasPresentation for (int i = 0; i < numComponents; i++) - if (canvasPresentation.getController().getActiveModel() != null && canvasPresentation.getController().getActiveModel().equals(components.get(i))) { + if (canvasPresentation.getController().getActiveModelPresentation() != null && canvasPresentation.getController().getActiveModelPresentation().getController().getModel().equals(components.get(i))) { currentCompNum = i + 1; } @@ -263,18 +299,18 @@ void setCanvasModeToSplit() { /** * Initialize a new CanvasShellPresentation and set its active component to the next component encountered from the startIndex and return it * - * @param components the list of components for assigning active component of the CanvasPresentation + * @param componentPresentations the list of componentPresentations for assigning active component of the CanvasPresentation * @param startIndex the index to start at when trying to find the component to set as active * @return new CanvasShellPresentation */ - private CanvasPresentation initializeNewCanvasPresentationWithActiveComponent(ObservableList<Component> components, int startIndex) { + private CanvasPresentation initializeNewCanvasPresentationWithActiveComponent(ObservableList<ComponentPresentation> componentPresentations, int startIndex) { CanvasPresentation canvasPresentation = initializeNewCanvasPresentation(); - int numComponents = components.size(); - canvasPresentation.getController().setActiveModel(null); + int numComponents = componentPresentations.size(); + canvasPresentation.getController().setActiveModelPresentation(null); for (int currentCompNum = startIndex; currentCompNum < numComponents; currentCompNum++) { - if (getActiveCanvasPresentation().getController().getActiveModel() != components.get(currentCompNum)) { - canvasPresentation.getController().setActiveModel(components.get(currentCompNum)); + if (getActiveCanvasPresentation().getController().getActiveModelPresentation() != componentPresentations.get(currentCompNum)) { + canvasPresentation.getController().setActiveModelPresentation(componentPresentations.get(currentCompNum)); break; } } @@ -377,4 +413,14 @@ private void deleteSelectedClicked() { SelectHelper.clearSelectedElements(); } + + @Override + public StackPane getLeftPane() { + return projectPane; + } + + @Override + public StackPane getRightPane() { + return queryPane; + } } diff --git a/src/main/java/ecdar/controllers/BackendInstanceController.java b/src/main/java/ecdar/controllers/EngineInstanceController.java similarity index 52% rename from src/main/java/ecdar/controllers/BackendInstanceController.java rename to src/main/java/ecdar/controllers/EngineInstanceController.java index c4f5337b..8daf2668 100644 --- a/src/main/java/ecdar/controllers/BackendInstanceController.java +++ b/src/main/java/ecdar/controllers/EngineInstanceController.java @@ -3,7 +3,7 @@ import com.jfoenix.controls.JFXCheckBox; import com.jfoenix.controls.JFXRippler; import com.jfoenix.controls.JFXTextField; -import ecdar.backend.BackendInstance; +import ecdar.backend.Engine; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.fxml.FXML; @@ -22,22 +22,22 @@ import java.net.URL; import java.util.ResourceBundle; -public class BackendInstanceController implements Initializable { - private BackendInstance backendInstance = new BackendInstance(); +public class EngineInstanceController implements Initializable { + private Engine engine = new Engine(); /* Design elements */ - public Label backendNameIssue; - public JFXRippler removeBackendRippler; - public FontIcon removeBackendIcon; + public Label engineNameIssue; + public JFXRippler removeEngineRippler; + public FontIcon removeEngineIcon; public FontIcon expansionIcon; public StackPane content; public JFXCheckBox isLocal; public HBox addressSection; - public HBox pathToBackendSection; - public JFXRippler pickPathToBackend; - public FontIcon pickPathToBackendIcon; - public StackPane moveBackendInstanceUpRippler; - public StackPane moveBackendInstanceDownRippler; + public HBox pathToEngineSection; + public JFXRippler pickPathToEngine; + public FontIcon pickPathToEngineIcon; + public StackPane moveEngineInstanceUpRippler; + public StackPane moveEngineInstanceDownRippler; // Labels for showing potential issues public Label locationIssue; @@ -46,25 +46,24 @@ public class BackendInstanceController implements Initializable { public Label portRangeIssue; /* Input fields */ - public JFXTextField backendName; + public JFXTextField engineName; public JFXTextField address; - public JFXTextField pathToBackend; + public JFXTextField pathToEngine; public JFXTextField portRangeStart; public JFXTextField portRangeEnd; - public RadioButton defaultBackendRadioButton; - public JFXCheckBox threadSafeBackendCheckBox; + public RadioButton defaultEngineRadioButton; + public JFXCheckBox threadSafeEngineCheckBox; @Override public void initialize(URL location, ResourceBundle resources) { Platform.runLater(() -> { this.handleLocalPropertyChanged(); - moveBackendInstanceUpRippler.setCursor(Cursor.HAND); - moveBackendInstanceDownRippler.setCursor(Cursor.HAND); + moveEngineInstanceUpRippler.setCursor(Cursor.HAND); + moveEngineInstanceDownRippler.setCursor(Cursor.HAND); setHGrow(); - colorIconAsDisabledBasedOnProperty(removeBackendIcon, defaultBackendRadioButton.selectedProperty()); - colorIconAsDisabledBasedOnProperty(removeBackendIcon, threadSafeBackendCheckBox.selectedProperty()); - colorIconAsDisabledBasedOnProperty(pickPathToBackendIcon, backendInstance.getLockedProperty()); + colorIconAsDisabledBasedOnProperty(removeEngineIcon, defaultEngineRadioButton.selectedProperty()); + colorIconAsDisabledBasedOnProperty(pickPathToEngineIcon, engine.getLockedProperty()); }); } @@ -74,7 +73,7 @@ public void initialize(URL location, ResourceBundle resources) { * @param property The property to bind to */ private void colorIconAsDisabledBasedOnProperty(FontIcon icon, BooleanProperty property) { - // Disallow the user to pick new backend file location for locked backends + // Disallow the user to pick new engine file location for locked engines property.addListener((observable, oldValue, newValue) -> { if (newValue) { icon.setFill(Color.GREY); @@ -86,22 +85,22 @@ private void colorIconAsDisabledBasedOnProperty(FontIcon icon, BooleanProperty p } /*** - * Sets the backend instance and overrides the current values of the input fields in the GUI. - * @param instance the new BackendInstance + * Sets the engine instance and overrides the current values of the input fields in the GUI. + * @param instance the new Engine */ - public void setBackendInstance(BackendInstance instance) { - this.backendInstance = instance; + public void setEngine(Engine instance) { + this.engine = instance; - this.backendName.setText(instance.getName()); + this.engineName.setText(instance.getName()); this.isLocal.setSelected(instance.isLocal()); - this.defaultBackendRadioButton.setSelected(instance.isDefault()); - this.threadSafeBackendCheckBox.setSelected(instance.isThreadSafe()); + this.defaultEngineRadioButton.setSelected(instance.isDefault()); + this.threadSafeEngineCheckBox.setSelected(instance.isThreadSafe()); // Check if the path or the address should be used if (isLocal.isSelected()) { - this.pathToBackend.setText(instance.getBackendLocation()); + this.pathToEngine.setText(instance.getEngineLocation()); } else { - this.address.setText(instance.getBackendLocation()); + this.address.setText(instance.getIpAddress()); } this.portRangeStart.setText(String.valueOf(instance.getPortStart())); @@ -109,30 +108,30 @@ public void setBackendInstance(BackendInstance instance) { } /** - * Updates the values of the backend instance to the values from the input fields. - * @return The updated backend instance + * Updates the values of the engine instance to the values from the input fields. + * @return The updated engine instance */ - public BackendInstance updateBackendInstance() { - backendInstance.setName(backendName.getText()); - backendInstance.setLocal(isLocal.isSelected()); - backendInstance.setDefault(defaultBackendRadioButton.isSelected()); - backendInstance.setIsThreadSafe(threadSafeBackendCheckBox.isSelected()); - backendInstance.setBackendLocation(isLocal.isSelected() ? pathToBackend.getText() : address.getText()); - backendInstance.setPortStart(Integer.parseInt(portRangeStart.getText())); - backendInstance.setPortEnd(Integer.parseInt(portRangeEnd.getText())); - - return backendInstance; + public Engine updateEngineInstance() { + engine.setName(engineName.getText()); + engine.setLocal(isLocal.isSelected()); + engine.setDefault(defaultEngineRadioButton.isSelected()); + engine.setIsThreadSafe(threadSafeEngineCheckBox.isSelected()); + engine.setEngineLocation(isLocal.isSelected() ? pathToEngine.getText() : address.getText()); + engine.setPortStart(Integer.parseInt(portRangeStart.getText())); + engine.setPortEnd(Integer.parseInt(portRangeEnd.getText())); + + return engine; } private void setHGrow() { - HBox.setHgrow(backendName.getParent().getParent().getParent(), Priority.ALWAYS); - HBox.setHgrow(backendName.getParent(), Priority.ALWAYS); - HBox.setHgrow(backendName, Priority.ALWAYS); + HBox.setHgrow(engineName.getParent().getParent().getParent(), Priority.ALWAYS); + HBox.setHgrow(engineName.getParent(), Priority.ALWAYS); + HBox.setHgrow(engineName, Priority.ALWAYS); HBox.setHgrow(content, Priority.ALWAYS); HBox.setHgrow(addressSection, Priority.ALWAYS); HBox.setHgrow(address, Priority.ALWAYS); - HBox.setHgrow(pathToBackendSection, Priority.ALWAYS); - HBox.setHgrow(pathToBackend, Priority.ALWAYS); + HBox.setHgrow(pathToEngineSection, Priority.ALWAYS); + HBox.setHgrow(pathToEngine, Priority.ALWAYS); HBox.setHgrow(portRangeStart, Priority.ALWAYS); HBox.setHgrow(portRangeEnd, Priority.ALWAYS); } @@ -142,14 +141,14 @@ private void handleLocalPropertyChanged() { address.setDisable(true); addressSection.setVisible(false); addressSection.setManaged(false); - pathToBackendSection.setVisible(true); - pathToBackendSection.setManaged(true); + pathToEngineSection.setVisible(true); + pathToEngineSection.setManaged(true); } else { address.setDisable(false); addressSection.setVisible(true); addressSection.setManaged(true); - pathToBackendSection.setVisible(false); - pathToBackendSection.setManaged(false); + pathToEngineSection.setVisible(false); + pathToEngineSection.setManaged(false); } } @@ -172,23 +171,23 @@ private void expansionClicked() { } @FXML - private void openPathToBackendDialog() { + private void openPathToEngineDialog() { // Dialog title - final FileChooser backendPicker = new FileChooser(); - backendPicker.setTitle("Choose backend"); + final FileChooser enginePicker = new FileChooser(); + enginePicker.setTitle("Choose Engine"); // The initial location for the file choosing dialog - final File jarDir = new File(pathToBackend.getText()).getAbsoluteFile().getParentFile(); + final File jarDir = new File(pathToEngine.getText()).getAbsoluteFile().getParentFile(); // If the file does not exist, we must be running it from a development environment, use a default location if(jarDir.exists()) { - backendPicker.setInitialDirectory(jarDir); + enginePicker.setInitialDirectory(jarDir); } // Prompt the user to find a file (will halt the UI thread) - final File file = backendPicker.showOpenDialog(null); + final File file = enginePicker.showOpenDialog(null); if(file != null) { - pathToBackend.setText(file.getAbsolutePath()); + pathToEngine.setText(file.getAbsolutePath()); } } } diff --git a/src/main/java/ecdar/controllers/EngineOptionsDialogController.java b/src/main/java/ecdar/controllers/EngineOptionsDialogController.java new file mode 100644 index 00000000..5f078ad3 --- /dev/null +++ b/src/main/java/ecdar/controllers/EngineOptionsDialogController.java @@ -0,0 +1,494 @@ +package ecdar.controllers; + +import com.google.gson.JsonArray; +import com.google.gson.JsonParser; +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXRippler; +import ecdar.Ecdar; +import ecdar.backend.Engine; +import ecdar.backend.BackendException; +import ecdar.backend.BackendHelper; +import ecdar.presentations.EnginePresentation; +import javafx.fxml.Initializable; +import javafx.scene.Node; +import javafx.scene.control.ToggleGroup; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import org.apache.commons.lang3.Range; +import org.apache.commons.lang3.SystemUtils; + +import java.io.File; +import java.io.IOException; +import java.net.InetAddress; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.UnknownHostException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.*; + +public class EngineOptionsDialogController implements Initializable { + public VBox engineInstanceList; + public JFXRippler addEngineButton; + public JFXButton closeButton; + public ToggleGroup defaultEngineToggleGroup = new ToggleGroup(); + public JFXButton saveButton; + public JFXButton resetEnginesButton; + + @Override + public void initialize(URL location, ResourceBundle resources) { + initializeEngineInstanceList(); + } + + /** + * Reverts any changes made to the engine options by reloading the options specified in the preference file, + * or to the default, if no engines are present in the preferences file. + */ + public void cancelEngineOptionsChanges() { + initializeEngineInstanceList(); + } + + /** + * Saves the changes made to the engine options to the preferences file and returns true + * if no errors where found in the engine instance definitions, otherwise false. + * + * @return whether the changes could be saved, + * meaning that no errors where found in the changes made to the engine options + */ + public boolean saveChangesToEngineOptions() { + if (this.engineInstanceListIsErrorFree()) { + ArrayList<Engine> engines = new ArrayList<>(); + for (Node engine : engineInstanceList.getChildren()) { + if (engine instanceof EnginePresentation) { + engines.add(((EnginePresentation) engine).getController().updateEngineInstance()); + } + } + + if (engines.size() < 1) { + Ecdar.showToast("Please add an engine instance or press: \"" + resetEnginesButton.getText() + "\""); + return false; + } + + // Close all engine connections to avoid dangling engine connections when port range is changed + try { + BackendHelper.clearEngineConnections(); + } catch (BackendException e) { + e.printStackTrace(); + } + + BackendHelper.updateEngineInstances(engines); + + JsonArray jsonArray = new JsonArray(); + for (Engine engine : engines) { + jsonArray.add(engine.serialize()); + } + + Ecdar.preferences.put("engines", jsonArray.toString()); + + Engine defaultEngine = engines.stream().filter(Engine::isDefault).findFirst().orElse(engines.get(0)); + BackendHelper.setDefaultEngine(defaultEngine); + + String defaultEngineName = (defaultEngine.getName()); + Ecdar.preferences.put("default_engine", defaultEngineName); + + return true; + } else { + return false; + } + } + + /** + * Resets the engines to the default engines present in the 'default_engines.json' file. + */ + public void resetEnginesToDefault() { + updateEnginesInGUI(getPackagedEngines()); + } + + private void initializeEngineInstanceList() { + ArrayList<Engine> engines; + + // Load engines from preferences or get default + var savedEngines = Ecdar.preferences.get("engines", null); + if (savedEngines != null) { + engines = getEnginesFromJsonArray( + JsonParser.parseString(savedEngines).getAsJsonArray()); + } else { + engines = getPackagedEngines(); + } + + // Style add engine button and handle click event + HBox.setHgrow(addEngineButton, Priority.ALWAYS); + addEngineButton.setMaxWidth(Double.MAX_VALUE); + addEngineButton.setOnMouseClicked((event) -> { + EnginePresentation newEnginePresentation = new EnginePresentation(); + addEnginePresentationToList(newEnginePresentation); + }); + + updateEnginesInGUI(engines); + } + + /** + * Clear the engine list and add the newly defined engines to it. + * + * @param engines The new list of engines + */ + private void updateEnginesInGUI(ArrayList<Engine> engines) { + engineInstanceList.getChildren().clear(); + + engines.forEach((engine) -> { + EnginePresentation newEnginePresentation = new EnginePresentation(engine); + + // Bind input fields that should not be changed for packaged engines to the locked property of the engine instance + newEnginePresentation.getController().engineName.disableProperty().bind(engine.getLockedProperty()); + newEnginePresentation.getController().pathToEngine.disableProperty().bind(engine.getLockedProperty()); + newEnginePresentation.getController().pickPathToEngine.disableProperty().bind(engine.getLockedProperty()); + newEnginePresentation.getController().isLocal.disableProperty().bind(engine.getLockedProperty()); + addEnginePresentationToList(newEnginePresentation); + }); + + BackendHelper.updateEngineInstances(engines); + } + + /** + * Instantiate enginesArray defined in the given JsonArray. + * + * @param enginesArray The JsonArray containing the enginesArray + * @return An ArrayList of the instantiated enginesArray + */ + private ArrayList<Engine> getEnginesFromJsonArray(JsonArray enginesArray) { + ArrayList<Engine> engines = new ArrayList<>(); + engineInstanceList.getChildren().clear(); + enginesArray.forEach((engine) -> { + Engine newEngine = new Engine(engine.getAsJsonObject()); + engines.add(newEngine); + }); + + return engines; + } + + /** + * Checks a set of paths to the packaged engines, j-Ecdar and Reveaal, and instantiates them + * if one of the related files exists. + * + * @return The packaged engines + */ + private ArrayList<Engine> getPackagedEngines() { + ArrayList<Engine> defaultEngines = new ArrayList<>(); + + // Add Reveaal engine + var reveaal = new Engine(); + reveaal.setName("Reveaal"); + reveaal.setLocal(true); + reveaal.setDefault(true); + reveaal.setPortStart(5040); + reveaal.setPortEnd(5042); + reveaal.lockInstance(); + reveaal.setIsThreadSafe(true); + + // Load correct Reveaal executable based on OS + List<String> potentialFilesForReveaal = new ArrayList<>(); + if (SystemUtils.IS_OS_WINDOWS) { + potentialFilesForReveaal.add("Reveaal.exe"); + } else { + potentialFilesForReveaal.add("Reveaal"); + } + if (setEnginePathIfFileExists(reveaal, potentialFilesForReveaal)) defaultEngines.add(reveaal); + + // Add jECDAR engine + var jEcdar = new Engine(); + jEcdar.setName("j-Ecdar"); + jEcdar.setLocal(true); + jEcdar.setDefault(false); + jEcdar.setPortStart(5042); + jEcdar.setPortEnd(5050); + jEcdar.lockInstance(); + jEcdar.setIsThreadSafe(false); + + // Load correct j-Ecdar executable based on OS + List<String> potentialFiledForJEcdar = new ArrayList<>(); + if (SystemUtils.IS_OS_WINDOWS) { + potentialFiledForJEcdar.add("j-Ecdar.bat"); + } else { + potentialFiledForJEcdar.add("j-Ecdar"); + } + + if (setEnginePathIfFileExists(jEcdar, potentialFiledForJEcdar)) defaultEngines.add(jEcdar); + + return defaultEngines; + } + + /** + * Sets the path to the engine if one of the potential files exists + * + * @param engine The engine to set the path for + * @param potentialFiles List of potential files to use for the engine + * @return True if one of the potentialFiles where found in path, false otherwise. + * This value also signals whether the engine engineLocation is set + */ + private boolean setEnginePathIfFileExists(Engine engine, List<String> potentialFiles) { + engine.setEngineLocation(""); + + try { + // Get directory containing the bin and lib folders for the executing program + String pathToEcdarDirectory = new File(getClass().getProtectionDomain().getCodeSource().getLocation().toURI()).getParentFile().getPath(); + + List<File> files = List.of(Objects.requireNonNull(new File(pathToEcdarDirectory).listFiles())); + for (File f : files) { + if (potentialFiles.contains(f.getName())) { + engine.setEngineLocation(f.getAbsolutePath()); + return true; + } + } + } catch (URISyntaxException e) { + e.printStackTrace(); + Ecdar.showToast("Unable to get URI of parent directory: \"" + getClass().getProtectionDomain().getCodeSource().getLocation() + "\" due to: " + e.getMessage()); + } catch (NullPointerException e) { + e.printStackTrace(); + Ecdar.showToast("Encountered null reference when trying to get path of executing program"); + } + + return !engine.getEngineLocation().equals(""); + } + + /** + * Add the new engine instance presentation to the engine options dialog + * @param newEnginePresentation The presentation of the new engine instance + */ + private void addEnginePresentationToList(EnginePresentation newEnginePresentation) { + newEnginePresentation.getController().defaultEngineRadioButton.setSelected(engineInstanceList.getChildren().isEmpty()); + engineInstanceList.getChildren().add(newEnginePresentation); + newEnginePresentation.getController().moveEngineInstanceUpRippler.setOnMouseClicked((mouseEvent) -> moveEngineInstance(newEnginePresentation, -1)); + newEnginePresentation.getController().moveEngineInstanceDownRippler.setOnMouseClicked((mouseEvent) -> moveEngineInstance(newEnginePresentation, +1)); + + // Set remove engine action to only fire if the engine is not locked + newEnginePresentation.getController().removeEngineRippler.setOnMouseClicked((mouseEvent) -> { + if (!newEnginePresentation.getController().defaultEngineRadioButton.isSelected()) { + engineInstanceList.getChildren().remove(newEnginePresentation); + } + }); + newEnginePresentation.getController().defaultEngineRadioButton.setToggleGroup(defaultEngineToggleGroup); + } + + /** + * Calculated the new position of the engine, 'i' places further down, in the engine list. + * The engine presentation is removed and added to the new position. + * Given a negative value, the instance is moved up. This function uses loop-around, meaning that: + * - If the instance is moved down while already at the bottom of the list, it is placed at the top. + * - If the instance is moved up while already at the top of the list, it is placed at the bottom. + * + * @param enginePresentation The engine presentation to move + * @param i The number of steps to move the engine down + */ + private void moveEngineInstance(EnginePresentation enginePresentation, int i) { + int currentIndex = engineInstanceList.getChildren().indexOf(enginePresentation); + int newIndex = (currentIndex + i) % engineInstanceList.getChildren().size(); + if (newIndex < 0) { + newIndex = engineInstanceList.getChildren().size() - 1; + } + + engineInstanceList.getChildren().remove(enginePresentation); + engineInstanceList.getChildren().add(newIndex, enginePresentation); + } + + /** + * Marks input fields in the engineList that contains errors and returns whether any errors were found + * + * @return whether any errors were found + */ + private boolean engineInstanceListIsErrorFree() { + boolean error = true; + + for (Node child : engineInstanceList.getChildren()) { + if (child instanceof EnginePresentation) { + EngineInstanceController engineInstanceController = ((EnginePresentation) child).getController(); + error = engineNameIsErrorFree(engineInstanceController) && error; + error = portRangeIsErrorFree(engineInstanceController) && error; + error = engineInstanceLocationIsErrorFree(engineInstanceController) && error; + } + } + + return error; + } + + private boolean engineNameIsErrorFree(EngineInstanceController engineInstanceController) { + String engineName = engineInstanceController.engineName.getText(); + + if (engineName.isBlank()) { + engineInstanceController.engineNameIssue.setText(ValidationErrorMessages.ENGINE_NAME_EMPTY.toString()); + engineInstanceController.engineNameIssue.setVisible(true); + return false; + } + + engineInstanceController.engineNameIssue.setVisible(false); + return true; + } + + private boolean portRangeIsErrorFree(EngineInstanceController engineInstanceController) { + boolean errorFree = true; + int portRangeStart = 0, portRangeEnd = 0; + engineInstanceController.portRangeStartIssue.setText(""); + engineInstanceController.portRangeStartIssue.setVisible(false); + engineInstanceController.portRangeEndIssue.setText(""); + engineInstanceController.portRangeEndIssue.setVisible(false); + engineInstanceController.portRangeIssue.setVisible(false); + + try { + portRangeStart = Integer.parseInt(engineInstanceController.portRangeStart.getText()); + } catch (NumberFormatException numberFormatException) { + engineInstanceController.portRangeStartIssue.setText(ValidationErrorMessages.VALUE_NOT_INTEGER.toString()); + errorFree = false; + } + + try { + portRangeEnd = Integer.parseInt(engineInstanceController.portRangeEnd.getText()); + } catch (NumberFormatException numberFormatException) { + engineInstanceController.portRangeEndIssue.setText(ValidationErrorMessages.VALUE_NOT_INTEGER.toString()); + errorFree = false; + } + + Range<Integer> portRange = Range.between(0, 65535); + + if (!portRange.contains(portRangeStart)) { + if (engineInstanceController.portRangeStartIssue.getText().isBlank()) { + engineInstanceController.portRangeStartIssue.setText(ValidationErrorMessages.PORT_VALUE_NOT_WITHIN_ACCEPTABLE_RANGE.toString()); + } else { + engineInstanceController.portRangeStartIssue.setText(ValidationErrorMessages.PORT_VALUE_NOT_WITHIN_ACCEPTABLE_RANGE_CONCATINATION.toString()); + } + errorFree = false; + } + if (!portRange.contains(portRangeEnd)) { + if (engineInstanceController.portRangeEndIssue.getText().isBlank()) { + engineInstanceController.portRangeEndIssue.setText(ValidationErrorMessages.PORT_VALUE_NOT_WITHIN_ACCEPTABLE_RANGE.toString()); + } else { + engineInstanceController.portRangeEndIssue.setText(ValidationErrorMessages.PORT_VALUE_NOT_WITHIN_ACCEPTABLE_RANGE_CONCATINATION.toString()); + } + errorFree = false; + } + + if (portRangeEnd - portRangeStart < 0) { + engineInstanceController.portRangeIssue.setText(ValidationErrorMessages.PORT_RANGE_MUST_BE_INCREMENTAL.toString()); + errorFree = false; + } + + engineInstanceController.portRangeStartIssue.setVisible(!errorFree); + engineInstanceController.portRangeEndIssue.setVisible(!errorFree); + engineInstanceController.portRangeIssue.setVisible(!errorFree); + + return errorFree; + } + + private boolean engineInstanceLocationIsErrorFree(EngineInstanceController engineInstanceController) { + boolean errorFree = true; + + if (engineInstanceController.isLocal.isSelected()) { + if (engineInstanceController.pathToEngine.getText().isBlank()) { + engineInstanceController.locationIssue.setText(ValidationErrorMessages.FILE_LOCATION_IS_BLANK.toString()); + errorFree = false; + } else { + Path localEnginePath = Paths.get(engineInstanceController.pathToEngine.getText()); + + if (!Files.isExecutable(localEnginePath)) { + engineInstanceController.locationIssue.setText(ValidationErrorMessages.FILE_DOES_NOT_EXIST_OR_NOT_EXECUTABLE.toString()); + errorFree = false; + } + } + } else { + if (engineInstanceController.address.getText().isBlank()) { + engineInstanceController.locationIssue.setText(ValidationErrorMessages.HOST_ADDRESS_IS_BLANK.toString()); + errorFree = false; + } else { + try { + InetAddress address = InetAddress.getByName(engineInstanceController.address.getText()); + boolean reachable = address.isReachable(200); + + if (!reachable) { + engineInstanceController.locationIssue.setText(ValidationErrorMessages.HOST_NOT_REACHABLE.toString()); + errorFree = false; + } + + } catch (UnknownHostException unknownHostException) { + engineInstanceController.locationIssue.setText(ValidationErrorMessages.UNACCEPTABLE_HOST_NAME.toString()); + errorFree = false; + } catch (IOException ioException) { + engineInstanceController.locationIssue.setText(ValidationErrorMessages.IO_EXCEPTION_WITH_HOST.toString()); + errorFree = false; + } + } + } + + engineInstanceController.locationIssue.setVisible(!errorFree); + + return errorFree; + } + + private enum ValidationErrorMessages { + ENGINE_NAME_EMPTY { + @Override + public String toString() { + return "The engine name cannot be empty"; + } + }, + VALUE_NOT_INTEGER { + @Override + public String toString() { + return "Value must be integer"; + } + }, + PORT_RANGE_MUST_BE_INCREMENTAL { + @Override + public String toString() { + return "Start of port range must be greater than end"; + } + }, + PORT_VALUE_NOT_WITHIN_ACCEPTABLE_RANGE { + @Override + public String toString() { + return "Value must be within range 0 - 65535"; + } + }, + PORT_VALUE_NOT_WITHIN_ACCEPTABLE_RANGE_CONCATINATION { + @Override + public String toString() { + return " and within range 0 - 65535"; + } + }, + FILE_LOCATION_IS_BLANK { + @Override + public String toString() { + return "Please specify a file for this engine"; + } + }, + FILE_DOES_NOT_EXIST_OR_NOT_EXECUTABLE { + @Override + public String toString() { + return "The above file does not exists or ECDAR does not have the privileges to execute it"; + } + }, + HOST_ADDRESS_IS_BLANK { + @Override + public String toString() { + return "Please specify an address for the external host"; + } + }, + HOST_NOT_REACHABLE { + @Override + public String toString() { + return "The above address is not reachable. Make sure that the host is correct"; + } + }, + UNACCEPTABLE_HOST_NAME { + @Override + public String toString() { + return "The above address is not an acceptable host name"; + } + }, + IO_EXCEPTION_WITH_HOST { + @Override + public String toString() { + return "An I/O exception was encountered while trying to reach the host"; + } + } + } +} diff --git a/src/main/java/ecdar/controllers/FileController.java b/src/main/java/ecdar/controllers/FileController.java index 3c76d1b2..6d51f07e 100644 --- a/src/main/java/ecdar/controllers/FileController.java +++ b/src/main/java/ecdar/controllers/FileController.java @@ -1,9 +1,27 @@ package ecdar.controllers; import com.jfoenix.controls.JFXRippler; +import ecdar.Ecdar; +import ecdar.abstractions.Component; +import ecdar.abstractions.EcdarSystem; +import ecdar.abstractions.HighLevelModel; +import ecdar.mutation.models.MutationTestPlan; +import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; +import ecdar.utility.helpers.ImageScaler; +import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; import javafx.fxml.Initializable; +import javafx.geometry.Insets; +import javafx.scene.Cursor; +import javafx.scene.control.Label; +import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.scene.layout.StackPane; +import javafx.scene.layout.*; +import javafx.scene.shape.Circle; +import org.kordamp.ikonli.javafx.FontIcon; import java.net.URL; import java.util.ResourceBundle; @@ -12,12 +30,132 @@ * Controller for a file in the project pane. */ public class FileController implements Initializable { + public AnchorPane root; + public JFXRippler rippler; + public Circle iconBackground; public JFXRippler moreInformation; + public FontIcon moreInformationIcon; + public Label fileName; public ImageView fileImage; public StackPane fileImageStackPane; + private final SimpleObjectProperty<HighLevelModel> model = new SimpleObjectProperty<>(null); + private final BooleanProperty isActive = new SimpleBooleanProperty(false); + private final EnabledColor color = EnabledColor.getDefault().getLowestIntensity(); + @Override public void initialize(URL location, ResourceBundle resources) { + Platform.runLater(() -> { + initializeIcon(); + initializeFileIcons(); + initializeFileName(); + initializeColors(); + initializeRippler(); + initializeMoreInformationButton(); + }); + } + + private void initializeMoreInformationButton() { + if (getModel() instanceof Component || getModel() instanceof EcdarSystem || getModel() instanceof MutationTestPlan) { + moreInformation.setVisible(true); + moreInformation.setMaskType(JFXRippler.RipplerMask.CIRCLE); + moreInformation.setPosition(JFXRippler.RipplerPos.BACK); + moreInformation.setRipplerFill(Color.GREY_BLUE.getColor(Color.Intensity.I500)); + } + } + + private void initializeRippler() { + final Color color = Color.GREY_BLUE; + final Color.Intensity colorIntensity = Color.Intensity.I400; + + rippler.setMaskType(JFXRippler.RipplerMask.RECT); + rippler.setRipplerFill(color.getColor(colorIntensity)); + rippler.setPosition(JFXRippler.RipplerPos.BACK); + } + + private void initializeFileName() { + model.get().nameProperty().addListener((obs, oldName, newName) -> fileName.setText(newName)); + fileName.setText(model.get().getName()); + } + + private void initializeIcon() { + model.get().colorProperty().addListener((obs, oldColor, newColor) -> iconBackground.setFill(newColor.getPaintColor())); + iconBackground.setFill(model.get().getColor().getPaintColor()); + } + + private void initializeColors() { + // Update the background when hovered + root.setOnMouseEntered(event -> { + if (isActive.get()) { + setBackground(color.nextIntensity(2)); + } else { + setBackground(color.nextIntensity()); + } + root.setCursor(Cursor.HAND); + }); + + root.setOnMouseExited(event -> { + if (isActive.get()) { + setBackground(color.nextIntensity()); + } else { + setBackground(color); + } + root.setCursor(Cursor.DEFAULT); + }); + + // Update the background initially + setBackground(color); + } + + /** + * Initialises the icons for the three different file types in Ecdar: Component, System and Declarations + */ + private void initializeFileIcons() { + if (model.get() instanceof Component) { + fileImage.setImage(new Image(Ecdar.class.getResource("component_frame.png").toExternalForm())); + } else if (model.get() instanceof EcdarSystem) { + fileImage.setImage(new Image(Ecdar.class.getResource("system_frame.png").toExternalForm())); + } else if (model.get() instanceof MutationTestPlan) { + fileImage.setImage(new Image(Ecdar.class.getResource("test_frame.png").toExternalForm())); + } else { + fileImage.setImage(new Image(Ecdar.class.getResource("description_frame.png").toExternalForm())); + } + + ImageScaler.fitImageToPane(fileImage, fileImageStackPane); + } + + private void setBackground(EnabledColor newColor) { + root.setBackground(new Background(new BackgroundFill( + newColor.getPaintColor(), + CornerRadii.EMPTY, + Insets.EMPTY + ))); + + root.setBorder(new Border(new BorderStroke( + newColor.getStrokeColor(), + BorderStrokeStyle.SOLID, + CornerRadii.EMPTY, + new BorderWidths(0, 0, 1, 0) + ))); + + moreInformationIcon.setFill(newColor.nextIntensity(5).getPaintColor()); + } + + public void setModel(HighLevelModel newModel) { + model.set(newModel); + } + + public HighLevelModel getModel() { + return model.get(); + } + + public void setIsActive(boolean active) { + isActive.set(active); + if (active) { + setBackground(color.nextIntensity()); + } else { + setBackground(color); + } } } diff --git a/src/main/java/ecdar/controllers/HighLevelModelController.java b/src/main/java/ecdar/controllers/HighLevelModelController.java new file mode 100644 index 00000000..c346a268 --- /dev/null +++ b/src/main/java/ecdar/controllers/HighLevelModelController.java @@ -0,0 +1,7 @@ +package ecdar.controllers; + +import ecdar.abstractions.HighLevelModel; + +abstract public class HighLevelModelController { + public abstract HighLevelModel getModel(); +} diff --git a/src/main/java/ecdar/controllers/LocationController.java b/src/main/java/ecdar/controllers/LocationController.java index 34b62838..d6392b4f 100644 --- a/src/main/java/ecdar/controllers/LocationController.java +++ b/src/main/java/ecdar/controllers/LocationController.java @@ -8,6 +8,7 @@ import ecdar.presentations.*; import ecdar.utility.UndoRedoStack; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.ItemDragHelper; import ecdar.utility.helpers.SelectHelper; import ecdar.utility.keyboard.Keybind; @@ -36,8 +37,9 @@ import java.util.*; import java.util.function.Consumer; -public class LocationController implements Initializable, SelectHelper.ItemSelectable, Nudgeable { +import static ecdar.presentations.ModelPresentation.TOOLBAR_HEIGHT; +public class LocationController implements Initializable, SelectHelper.ItemSelectable, Nudgeable { private static final Map<Location, Boolean> invalidNameError = new HashMap<>(); private final ObjectProperty<Location> location = new SimpleObjectProperty<>(); @@ -64,7 +66,6 @@ public class LocationController implements Initializable, SelectHelper.ItemSelec public Line prohibitedLocStrikeThrough; private DropDownMenu dropDownMenu; - private boolean dropDownMenuInitialized = false; @Override public void initialize(final URL location, final ResourceBundle resources) { @@ -84,18 +85,15 @@ public void initialize(final URL location, final ResourceBundle resources) { } private void initializeSelectListener() { - SelectHelper.elementsToBeSelected.addListener(new ListChangeListener<Nearable>() { - @Override - public void onChanged(final Change<? extends Nearable> c) { - while (c.next()) { - if (c.getAddedSize() == 0) return; - - for (final Nearable nearable : SelectHelper.elementsToBeSelected) { - if (nearable instanceof Location) { - if (nearable.equals(getLocation())) { - SelectHelper.addToSelection(LocationController.this); - break; - } + SelectHelper.elementsToBeSelected.addListener((ListChangeListener<Nearable>) c -> { + while (c.next()) { + if (c.getAddedSize() == 0) return; + + for (final Nearable nearable : SelectHelper.elementsToBeSelected) { + if (nearable instanceof Location) { + if (nearable.equals(getLocation())) { + SelectHelper.addToSelection(LocationController.this); + break; } } } @@ -108,15 +106,15 @@ public void initializeDropDownMenu() { dropDownMenu.addClickableAndDisableableListElement("Draw Edge", getLocation().getIsLocked(), (event) -> { - final Edge newEdge = new Edge(getLocation(), EcdarController.getGlobalEdgeStatus()); + final Edge newEdge = new Edge(getLocation(), EcdarController.getGlobalEdgeStatus()); - KeyboardTracker.registerKeybind(KeyboardTracker.ABANDON_EDGE, new Keybind(new KeyCodeCombination(KeyCode.ESCAPE), () -> { - getComponent().removeEdge(newEdge); - })); - getComponent().addEdge(newEdge); - dropDownMenu.hide(); - } - ); + KeyboardTracker.registerKeybind(KeyboardTracker.ABANDON_EDGE, new Keybind(new KeyCodeCombination(KeyCode.ESCAPE), () -> { + getComponent().removeEdge(newEdge); + })); + getComponent().addEdge(newEdge); + dropDownMenu.hide(); + } + ); dropDownMenu.addClickableAndDisableableListElement("Add Nickname", getLocation().nicknameProperty().isNotEmpty().or(nicknameTag.textFieldFocusProperty()), @@ -191,8 +189,25 @@ public void initializeDropDownMenu() { dropDownMenu.addSpacerElement(); - dropDownMenu.addColorPicker(getLocation(), (color, intensity) -> { - getLocation().setColorIntensity(intensity); + dropDownMenu.addClickableListElement("Is " + getLocation().getId() + " reachable?", event -> { + dropDownMenu.hide(); + // Generate the query from the engine + final String reachabilityQuery = getComponent().getName() + "." + getLocation().getId(); + + // Add proper comment + final String reachabilityComment = "Is " + getLocation().getMostDescriptiveIdentifier() + " reachable?"; + + // Add new query for this location + final Query query = new Query(reachabilityQuery, reachabilityComment, QueryState.UNKNOWN); + query.setType(QueryType.REACHABILITY); + Ecdar.getProject().getQueries().add(query); + query.execute(); + dropDownMenu.hide(); + }); + + dropDownMenu.addSpacerElement(); + + dropDownMenu.addColorPicker(getLocation(), (color) -> { getLocation().setColor(color); }); @@ -262,17 +277,11 @@ public Location getLocation() { public void setLocation(final Location location) { this.location.set(location); - - if (ComponentController.isPlacingLocation()) { - root.layoutXProperty().bindBidirectional(location.xProperty()); - root.layoutYProperty().bindBidirectional(location.yProperty()); - } else { - root.setLayoutX(location.getX()); - root.setLayoutY(location.getY()); - location.xProperty().bindBidirectional(root.layoutXProperty()); - location.yProperty().bindBidirectional(root.layoutYProperty()); - root.setPlaced(true); - } + root.setLayoutX(location.getX()); + root.setLayoutY(location.getY()); + location.xProperty().bindBidirectional(root.layoutXProperty()); + location.yProperty().bindBidirectional(root.layoutYProperty()); + root.setPlaced(true); } public ObjectProperty<Location> locationProperty() { @@ -303,7 +312,7 @@ private void locationExited() { @FXML private void mouseEntered() { - if(!this.root.isInteractable()) return; + if (!this.root.isInteractable()) return; circle.setCursor(Cursor.HAND); this.root.animateHoverEntered(); @@ -330,7 +339,7 @@ private void mouseEntered() { @FXML private void mouseExited() { final LocationPresentation locationPresentation = this.root; - if(!locationPresentation.isInteractable()) return; + if (!locationPresentation.isInteractable()) return; circle.setCursor(Cursor.DEFAULT); locationPresentation.animateHoverExited(); @@ -409,7 +418,7 @@ private void initializeMouseControls() { } // Otherwise, select the location else { - if(root.isInteractable()) { + if (root.isInteractable()) { if (event.isShortcutDown()) { if (SelectHelper.getSelectedElements().contains(this)) { SelectHelper.deselect(this); @@ -429,7 +438,7 @@ private void initializeMouseControls() { final double minY = Ecdar.CANVAS_PADDING * 4; final double maxY = getComponent().getBox().getHeight() - Ecdar.CANVAS_PADDING * 2; - if(root.getLayoutX() >= minX && root.getLayoutX() <= maxX && root.getLayoutY() >= minY && root.getLayoutY() <= maxY) { + if (root.getLayoutX() >= minX && root.getLayoutX() <= maxX && root.getLayoutY() >= minY && root.getLayoutY() <= maxY) { // Unbind presentation root x and y coordinates (bind the view properly to enable dragging) root.layoutXProperty().unbind(); root.layoutYProperty().unbind(); @@ -440,7 +449,6 @@ private void initializeMouseControls() { // Notify that the location was placed root.setPlaced(true); - ComponentController.setPlacingLocation(null); KeyboardTracker.unregisterKeybind(KeyboardTracker.ABANDON_LOCATION); } else { root.shake(); @@ -449,37 +457,31 @@ private void initializeMouseControls() { }; locationProperty().addListener((obs, oldLocation, newLocation) -> { - if(newLocation == null) return; + if (newLocation == null) return; root.addEventHandler(MouseEvent.MOUSE_CLICKED, mouseClicked::accept); ItemDragHelper.makeDraggable(root, this::getDragBounds); }); } @Override - public void color(final Color color, final Color.Intensity intensity) { + public void color(final EnabledColor color) { final Location location = getLocation(); // Set the color of the location - location.setColorIntensity(intensity); location.setColor(color); } @Override - public Color getColor() { + public EnabledColor getColor() { return getLocation().getColor(); } - @Override - public Color.Intensity getColorIntensity() { - return getLocation().getColorIntensity(); - } - @Override public ItemDragHelper.DragBounds getDragBounds() { final int PADDING = 5; final ObservableDoubleValue minX = new SimpleDoubleProperty(location.get().getRadius() + PADDING); final ObservableDoubleValue maxX = getComponent().getBox().getWidthProperty().subtract(location.get().getRadius() + PADDING); - final ObservableDoubleValue minY = new SimpleDoubleProperty(location.get().getRadius() + PADDING); + final ObservableDoubleValue minY = new SimpleDoubleProperty(location.get().getRadius() + TOOLBAR_HEIGHT + PADDING); final ObservableDoubleValue maxY = getComponent().getBox().getHeightProperty().subtract(location.get().getRadius() + PADDING); return new ItemDragHelper.DragBounds(minX, maxX, minY, maxY); } @@ -507,14 +509,15 @@ public boolean nudge(final NudgeDirection direction) { return oldX != newX || oldY != newY; } - /**' + /** * Method which has the logical expression for the shortcut of adding a new edge, * checks whether the location is locked and the correct buttons are held down + * * @param event the mouse event * @return the result of the boolean expression, false if the location is locked or the incorrect buttons are held down, * true if the correct buttons are down */ - private boolean canCreateEdgeShortcut(MouseEvent event){ + private boolean canCreateEdgeShortcut(MouseEvent event) { return (!getLocation().getIsLocked().get() && ((event.isShiftDown() && event.getButton().equals(MouseButton.PRIMARY)) || event.getButton().equals(MouseButton.MIDDLE))); } diff --git a/src/main/java/ecdar/controllers/MessageCollectionController.java b/src/main/java/ecdar/controllers/MessageCollectionController.java new file mode 100644 index 00000000..c8269bc2 --- /dev/null +++ b/src/main/java/ecdar/controllers/MessageCollectionController.java @@ -0,0 +1,123 @@ +package ecdar.controllers; + +import ecdar.Ecdar; +import ecdar.abstractions.Component; +import ecdar.code_analysis.CodeAnalysis; +import ecdar.presentations.MessagePresentation; +import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; +import javafx.beans.InvalidationListener; +import javafx.beans.property.SimpleListProperty; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.event.EventHandler; +import javafx.fxml.Initializable; +import javafx.scene.Cursor; +import javafx.scene.control.Label; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.VBox; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Line; + +import java.net.URL; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.function.Consumer; + +public class MessageCollectionController implements Initializable { + public VBox root; + public Circle indicator; + public Label headline; + public Line line; + public VBox messageBox; + + private Component component; + private final ObservableList<CodeAnalysis.Message> messages = new SimpleListProperty<>(); + private Map<CodeAnalysis.Message, MessagePresentation> messageMessagePresentationMap; + + @Override + public void initialize(URL location, ResourceBundle resources) { + initializeHeadline(component); + initializeLine(); + initializeErrorsListener(); + } + + private void initializeErrorsListener() { + messageMessagePresentationMap = new HashMap<>(); + + final Consumer<CodeAnalysis.Message> addMessage = (message) -> { + final MessagePresentation messagePresentation = new MessagePresentation(message); + messageMessagePresentationMap.put(message, messagePresentation); + messageBox.getChildren().add(messagePresentation); + }; + + messages.forEach(addMessage); + messages.addListener((ListChangeListener<CodeAnalysis.Message>) c -> { + while (c.next()) { + c.getAddedSubList().forEach(addMessage); + + c.getRemoved().forEach(message -> { + messageBox.getChildren().remove(messageMessagePresentationMap.get(message)); + messageMessagePresentationMap.remove(message); + }); + } + }); + } + + private void initializeLine() { + messageBox.getChildren().addListener((InvalidationListener) observable -> line.setEndY(messageBox.getChildren().size() * 23 + 8)); + } + + private void initializeHeadline(final Component component) { + line.setStroke(Color.GREY.getColor(Color.Intensity.I400)); + + // This is an project wide message that is not specific to a component + if (component == null) { + headline.setText("Project"); + return; + } + + headline.setText(component.getName()); + headline.textProperty().bind(component.nameProperty()); + + final EventHandler<MouseEvent> onMouseEntered = event -> { + root.setCursor(Cursor.HAND); + headline.setStyle("-fx-underline: true;"); + }; + + final EventHandler<MouseEvent> onMouseExited = event -> { + root.setCursor(Cursor.DEFAULT); + headline.setStyle("-fx-underline: false;"); + }; + + final EventHandler<MouseEvent> onMousePressed = event -> Ecdar.getPresentation().getController().getEditorPresentation().getController().setActiveModelPresentationForActiveCanvas( + Ecdar.getPresentation().getController().getEditorPresentation().getController(). + projectPane.getController().getComponentPresentations(). + stream().filter(componentPresentation -> componentPresentation.getController().getComponent().equals(component)) + .findFirst().orElse(null)); + + headline.setOnMouseEntered(onMouseEntered); + headline.setOnMouseExited(onMouseExited); + headline.setOnMousePressed(onMousePressed); + indicator.setOnMouseEntered(onMouseEntered); + indicator.setOnMouseExited(onMouseExited); + indicator.setOnMousePressed(onMousePressed); + + final Consumer<EnabledColor> updateColor = (color) -> { + indicator.setFill(color.getPaintColor()); + }; + + updateColor.accept(component.getColor()); + component.colorProperty().addListener((observable, oldColor, newColor) -> updateColor.accept(newColor)); + } + + public void setComponent(Component newComponent) { + component = newComponent; + } + + public void setMessages(List<CodeAnalysis.Message> newMessages) { + if (!newMessages.isEmpty()) messages.setAll(newMessages); + } +} diff --git a/src/main/java/ecdar/controllers/ModeController.java b/src/main/java/ecdar/controllers/ModeController.java new file mode 100644 index 00000000..d1238118 --- /dev/null +++ b/src/main/java/ecdar/controllers/ModeController.java @@ -0,0 +1,8 @@ +package ecdar.controllers; + +import javafx.scene.layout.StackPane; + +interface ModeController { + StackPane getLeftPane(); + StackPane getRightPane(); +} diff --git a/src/main/java/ecdar/controllers/ModelController.java b/src/main/java/ecdar/controllers/ModelController.java index f0b91496..6e378f4a 100644 --- a/src/main/java/ecdar/controllers/ModelController.java +++ b/src/main/java/ecdar/controllers/ModelController.java @@ -1,16 +1,31 @@ package ecdar.controllers; -import ecdar.abstractions.HighLevelModelObject; +import ecdar.Ecdar; +import ecdar.abstractions.Box; +import ecdar.abstractions.HighLevelModel; import com.jfoenix.controls.JFXTextField; +import ecdar.utility.UndoRedoStack; +import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; +import ecdar.utility.helpers.StringValidator; +import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.geometry.Insets; +import javafx.scene.Cursor; import javafx.scene.layout.BorderPane; import javafx.scene.layout.StackPane; import javafx.scene.shape.Line; import javafx.scene.shape.Rectangle; +import static ecdar.presentations.ModelPresentation.CORNER_SIZE; + /** * Controller for a high level model, such as a component or a system. */ -public abstract class ModelController { +public abstract class ModelController extends HighLevelModelController { public StackPane root; public Rectangle background; public BorderPane frame; @@ -21,8 +36,6 @@ public abstract class ModelController { public BorderPane toolbar; public JFXTextField name; - public abstract HighLevelModelObject getModel(); - /** * Hides the border and background. */ @@ -40,4 +53,261 @@ void showBorderAndBackground() { topLeftLine.setVisible(true); background.setVisible(true); } + + abstract double getDragAnchorMinWidth(); + abstract double getDragAnchorMinHeight(); + + /** + * Initializes this. + * @param box the box of the model + */ + void initialize(final Box box) { + initializeName(); + initializeDimensions(box); + initializesBottomDragAnchor(box); + initializesRightDragAnchor(box); + initializesCornerDragAnchor(box); + } + + /** + * Initializes handling of name. + */ + private void initializeName() { + final HighLevelModel model = getModel(); + + final BooleanProperty initialized = new SimpleBooleanProperty(false); + + name.focusedProperty().addListener((observable, oldValue, newValue) -> { + if (newValue && !initialized.get()) { + root.requestFocus(); + initialized.setValue(true); + } + }); + + // Set the text field to the name in the model, and bind the model to the text field + name.setText(model.getName()); + name.textProperty().addListener((obs, oldName, newName) -> { + if (StringValidator.validateComponentName(newName)) { + model.nameProperty().unbind(); + model.setName(newName); + } else { + name.setText(model.getName()); + Ecdar.showToast("Component names cannot contain '.'"); + } + }); + + final Runnable updateColor = () -> { + final EnabledColor color = model.getColor(); + + // Set the text color for the label + name.setStyle("-fx-text-fill: " + color.color.getTextColorRgbaString(color.intensity) + ";"); + name.setFocusColor(color.getTextColor()); + name.setUnFocusColor(javafx.scene.paint.Color.TRANSPARENT); + }; + + model.colorProperty().addListener(observable -> updateColor.run()); + updateColor.run(); + + Platform.runLater(() -> { + // Center the text vertically and add a left padding of CORNER_SIZE + name.setPadding(new Insets(2, 0, 0, CORNER_SIZE)); + name.setOnKeyPressed(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler()); + }); + } + + /** + * Sets the width and the height of the view to the values in the abstraction. + * @param box The dimensions to set + */ + void initializeDimensions(final Box box) { + // Ensure that the component snaps to the grid + if (box.getX() == 0 && box.getY() == 0) { + box.setX(Ecdar.CANVAS_PADDING * 0.5); + box.setY(Ecdar.CANVAS_PADDING * 0.5); + } + + // Bind the position of the abstraction to the values in the view + root.layoutXProperty().set(box.getX()); + root.layoutYProperty().set(box.getY()); + box.getXProperty().bindBidirectional(root.layoutXProperty()); + box.getYProperty().bindBidirectional(root.layoutYProperty()); + + root.setMinWidth(box.getWidth()); + root.setMaxWidth(box.getWidth()); + root.setMinHeight(box.getHeight()); + root.setMaxHeight(box.getHeight()); + root.minHeightProperty().bindBidirectional(box.getHeightProperty()); + root.maxHeightProperty().bindBidirectional(box.getHeightProperty()); + root.minWidthProperty().bindBidirectional(box.getWidthProperty()); + root.maxWidthProperty().bindBidirectional(box.getWidthProperty()); + } + + /** + * Initializes the right drag anchor. + * @param box the box of the model + */ + private void initializesRightDragAnchor(final Box box) { + final BooleanProperty wasResized = new SimpleBooleanProperty(false); + rightAnchor.setCursor(Cursor.E_RESIZE); + + // Bind the place and size of bottom anchor + rightAnchor.setWidth(5); + rightAnchor.heightProperty().bind(box.getHeightProperty()); + + final DoubleProperty prevX = new SimpleDoubleProperty(); + final DoubleProperty prevWidth = new SimpleDoubleProperty(); + + rightAnchor.setOnMousePressed(event -> { + event.consume(); + + prevX.set(event.getScreenX()); + prevWidth.set(box.getWidth()); + }); + + rightAnchor.setOnMouseDragged(event -> { + double diff = event.getScreenX() - prevX.get(); + diff -= diff % Ecdar.CANVAS_PADDING; + + final double newWidth = prevWidth.get() + diff; + final double minWidth = getDragAnchorMinWidth(); + + // Move the model left or right to account for new height (needed because model is centered in parent) + root.setTranslateX(root.getTranslateX() + (Math.max(newWidth, minWidth) - box.getWidth()) / 2); + box.setWidth(Math.max(newWidth, minWidth)); + wasResized.set(true); + }); + + rightAnchor.setOnMouseReleased(event -> { + if (!wasResized.get()) return; + final double previousWidth = prevWidth.doubleValue(); + final double currentWidth = box.getWidth(); + + // If no difference do not save change + if (previousWidth == currentWidth) return; + + UndoRedoStack.pushAndPerform(() -> { // Perform + box.setWidth(currentWidth); + }, () -> { // Undo + box.setWidth(previousWidth); + }, + "Component width resized", + "settings-overscan" + ); + + wasResized.set(false); + }); + } + + /** + * Initializes the bottom drag anchor. + * @param box the box of the model + */ + private void initializesBottomDragAnchor(final Box box) { + final BooleanProperty wasResized = new SimpleBooleanProperty(false); + bottomAnchor.setCursor(Cursor.S_RESIZE); + + // Bind the place and size of bottom anchor + bottomAnchor.widthProperty().bind(box.getWidthProperty()); + bottomAnchor.setHeight(5); + + final DoubleProperty prevY = new SimpleDoubleProperty(); + final DoubleProperty prevHeight = new SimpleDoubleProperty(); + + bottomAnchor.setOnMousePressed(event -> { + event.consume(); + + prevY.set(event.getScreenY()); + prevHeight.set(box.getHeight()); + }); + + bottomAnchor.setOnMouseDragged(event -> { + double diff = event.getScreenY() - prevY.get(); + final double newHeight = prevHeight.get() + diff; + final double minHeight = getDragAnchorMinHeight(); + + // Move the model up or down to account for new height (needed because model is centered in parent) + root.setTranslateY(root.getTranslateY() + (Math.max(newHeight, minHeight) - box.getHeight()) / 2); + box.setHeight(Math.max(newHeight, minHeight)); + wasResized.set(true); + }); + + bottomAnchor.setOnMouseReleased(event -> { + if (!wasResized.get()) return; + final double previousHeight = prevHeight.doubleValue(); + final double currentHeight = box.getHeight(); + + // If no difference do not save change + if (previousHeight == currentHeight) return; + + UndoRedoStack.pushAndPerform(() -> { // Perform + box.setHeight(currentHeight); + }, () -> { // Undo + box.setHeight(previousHeight); + }, + "Component height resized", + "settings-overscan" + ); + + wasResized.set(false); + }); + } + + private void initializesCornerDragAnchor(final Box box) { + final BooleanProperty wasResized = new SimpleBooleanProperty(false); + cornerAnchor.setCursor(Cursor.SE_RESIZE); + + // Bind the place and size of bottom anchor + cornerAnchor.setWidth(10); + cornerAnchor.setHeight(10); + + final DoubleProperty prevX = new SimpleDoubleProperty(); + final DoubleProperty prevY = new SimpleDoubleProperty(); + final DoubleProperty prevWidth = new SimpleDoubleProperty(); + final DoubleProperty prevHeight = new SimpleDoubleProperty(); + + cornerAnchor.setOnMousePressed(event -> { + event.consume(); + + prevX.set(event.getScreenX()); + prevWidth.set(box.getWidth()); + prevY.set(event.getScreenY()); + prevHeight.set(box.getHeight()); + }); + + cornerAnchor.setOnMouseDragged(event -> { + double xDiff = (event.getScreenX() - prevX.get()) / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get(); // ToDo NIELS: Fix dependency + final double newWidth = Math.max(prevWidth.get() + xDiff, getDragAnchorMinWidth()); + box.setWidth(newWidth); + + double yDiff = (event.getScreenY() - prevY.get()) / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get(); + final double newHeight = Math.max(prevHeight.get() + yDiff, getDragAnchorMinHeight()); + box.setHeight(newHeight); + + wasResized.set(true); + }); + + cornerAnchor.setOnMouseReleased(event -> { + if (!wasResized.get()) return; + final double previousWidth = prevWidth.doubleValue(); + final double currentWidth = box.getWidth(); + final double previousHeight = prevHeight.doubleValue(); + final double currentHeight = box.getHeight(); + + // If no difference do not save change + if (previousWidth == currentWidth && previousHeight == currentHeight) return; + + UndoRedoStack.pushAndPerform(() -> { // Perform + box.setWidth(currentWidth); + box.setHeight(currentHeight); + }, () -> { // Undo + box.setWidth(previousWidth); + box.setHeight(previousHeight); + }, + "Component resized", + "settings-overscan" + ); + + wasResized.set(false); + }); + } } diff --git a/src/main/java/ecdar/controllers/MultiSyncTagController.java b/src/main/java/ecdar/controllers/MultiSyncTagController.java index fd2d5bae..6a7bfb8a 100644 --- a/src/main/java/ecdar/controllers/MultiSyncTagController.java +++ b/src/main/java/ecdar/controllers/MultiSyncTagController.java @@ -14,7 +14,6 @@ import java.util.ResourceBundle; public class MultiSyncTagController implements Initializable { - public VBox syncList; public BorderPane topbar; public BorderPane frame; diff --git a/src/main/java/ecdar/controllers/NailController.java b/src/main/java/ecdar/controllers/NailController.java index e3222c61..3f28556a 100644 --- a/src/main/java/ecdar/controllers/NailController.java +++ b/src/main/java/ecdar/controllers/NailController.java @@ -6,6 +6,7 @@ import ecdar.presentations.*; import ecdar.utility.UndoRedoStack; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.ItemDragHelper; import ecdar.utility.helpers.SelectHelper; import ecdar.utility.keyboard.NudgeDirection; @@ -244,20 +245,15 @@ public ObjectProperty<DisplayableEdge> edgeProperty() { } @Override - public void color(final Color color, final Color.Intensity intensity) { + public void color(final EnabledColor color) { // Do nothing. A nail cannot be colored, but can be colored as selected } @Override - public Color getColor() { + public EnabledColor getColor() { return getComponent().getColor(); } - @Override - public Color.Intensity getColorIntensity() { - return getComponent().getColorIntensity(); - } - @Override public ItemDragHelper.DragBounds getDragBounds() { final int PADDING = 5; diff --git a/src/main/java/ecdar/controllers/ProcessController.java b/src/main/java/ecdar/controllers/ProcessController.java index dc94965c..aa79a1b1 100755 --- a/src/main/java/ecdar/controllers/ProcessController.java +++ b/src/main/java/ecdar/controllers/ProcessController.java @@ -1,10 +1,13 @@ package ecdar.controllers; import com.jfoenix.controls.JFXRippler; +import ecdar.Ecdar; import ecdar.abstractions.*; import ecdar.presentations.ComponentPresentation; +import ecdar.presentations.ModelPresentation; import ecdar.presentations.SimEdgePresentation; import ecdar.presentations.SimLocationPresentation; +import ecdar.utility.helpers.UPPAALSyntaxHighlighter; import javafx.animation.Interpolator; import javafx.animation.Transition; import javafx.beans.property.ObjectProperty; @@ -18,6 +21,7 @@ import javafx.scene.shape.Circle; import javafx.util.Duration; +import org.checkerframework.checker.units.qual.K; import org.fxmisc.richtext.LineNumberFactory; import org.fxmisc.richtext.StyleClassedTextArea; import org.kordamp.ikonli.javafx.FontIcon; @@ -50,7 +54,7 @@ public class ProcessController extends ModelController implements Initializable @Override public void initialize(final URL location, final ResourceBundle resources) { - component = new SimpleObjectProperty<>(new Component(true)); + component = new SimpleObjectProperty<>(new Component()); // add line numbers to the declaration text area declarationTextArea.setParagraphGraphicFactory(LineNumberFactory.get(declarationTextArea)); initializeValues(); @@ -205,7 +209,7 @@ public void setComponent(final Component component){ }); declarationTextArea.appendText(component.getDeclarationsText()); - declarationTextArea.setStyleSpans(0, ComponentPresentation.computeHighlighting(getComponent().getDeclarationsText())); + declarationTextArea.setStyleSpans(0, UPPAALSyntaxHighlighter.computeHighlighting(getComponent().getDeclarationsText())); declarationTextArea.getStyleClass().add("component-declaration"); } @@ -249,7 +253,7 @@ public Component getComponent(){ } @Override - public HighLevelModelObject getModel() { + public HighLevelModel getModel() { return component.get(); } @@ -260,4 +264,49 @@ public ObservableMap<String, BigDecimal> getVariables() { public ObservableMap<String, BigDecimal> getClocks() { return clocks; } + + /** + * Gets the minimum possible width when dragging the anchor. + * The width is based on the x coordinate of locations, nails and the signature arrows. + * @return the minimum possible width. + */ + @Override + double getDragAnchorMinWidth() { + final Component component = getComponent(); + double minWidth = Ecdar.CANVAS_PADDING * 10; + + for (final Location location : component.getLocations()) { + minWidth = Math.max(minWidth, location.getX() + Ecdar.CANVAS_PADDING * 2); + } + + for (final Edge edge : component.getEdges()) { + for (final Nail nail : edge.getNails()) { + minWidth = Math.max(minWidth, nail.getX() + Ecdar.CANVAS_PADDING); + } + } + return minWidth; + } + + /** + * Gets the minimum possible height when dragging the anchor. + * The height is based on the y coordinate of locations, nails and the signature arrows. + * @return the minimum possible height. + */ + @Override + double getDragAnchorMinHeight() { + final Component component = getComponent(); + double minHeight = Ecdar.CANVAS_PADDING * 10; + + for (final Location location : component.getLocations()) { + minHeight = Math.max(minHeight, location.getY() + Ecdar.CANVAS_PADDING * 2); + } + + for (final Edge edge : component.getEdges()) { + for (final Nail nail : edge.getNails()) { + minHeight = Math.max(minHeight, nail.getY() + Ecdar.CANVAS_PADDING); + } + } + + return minHeight; + } } diff --git a/src/main/java/ecdar/controllers/ProjectPaneController.java b/src/main/java/ecdar/controllers/ProjectPaneController.java index b2e3d4e6..c040b5c9 100644 --- a/src/main/java/ecdar/controllers/ProjectPaneController.java +++ b/src/main/java/ecdar/controllers/ProjectPaneController.java @@ -4,23 +4,31 @@ import ecdar.Ecdar; import ecdar.abstractions.Component; import ecdar.abstractions.EcdarSystem; -import ecdar.abstractions.HighLevelModelObject; +import ecdar.abstractions.HighLevelModel; +import ecdar.abstractions.Project; +import ecdar.mutation.MutationTestPlanPresentation; import ecdar.mutation.models.MutationTestPlan; -import ecdar.presentations.DropDownMenu; -import ecdar.presentations.FilePresentation; +import ecdar.presentations.*; import ecdar.utility.UndoRedoStack; import com.jfoenix.controls.JFXPopup; import com.jfoenix.controls.JFXRippler; import com.jfoenix.controls.JFXTextArea; +import ecdar.utility.colors.EnabledColor; +import ecdar.utility.keyboard.Keybind; +import ecdar.utility.keyboard.KeyboardTracker; import javafx.application.Platform; +import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.geometry.Insets; -import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; import javafx.scene.image.ImageView; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; @@ -28,10 +36,7 @@ import org.kordamp.ikonli.material.Material; import java.net.URL; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashMap; -import java.util.ResourceBundle; +import java.util.*; public class ProjectPaneController implements Initializable { public StackPane root; @@ -52,65 +57,71 @@ public class ProjectPaneController implements Initializable { public ImageView createSystemImage; public StackPane createSystemPane; - private final HashMap<HighLevelModelObject, FilePresentation> modelPresentationMap = new HashMap<>(); + + public final Project project = new Project(); + private final HashMap<HighLevelModelPresentation, FilePresentation> modelPresentationMap = new HashMap<>(); + private final ObservableList<ComponentPresentation> componentPresentations = FXCollections.observableArrayList(); @Override public void initialize(final URL location, final ResourceBundle resources) { - // Bind global declarations and add mouse event - final FilePresentation globalDclPresentation = new FilePresentation(Ecdar.getProject().getGlobalDeclarations()); - globalDclPresentation.setOnMousePressed(event -> { - event.consume(); - EcdarController.setActiveModelForActiveCanvas(Ecdar.getProject().getGlobalDeclarations()); - updateColorsOnFilePresentations(); + Platform.runLater(() -> { + // Bind global declarations and add mouse event + final DeclarationsPresentation globalDeclarationsPresentation = new DeclarationsPresentation(project.getGlobalDeclarations()); + final FilePresentation globalDclPresentation = new FilePresentation(project.getGlobalDeclarations()); + modelPresentationMap.put(globalDeclarationsPresentation, globalDclPresentation); + globalDclPresentation.setOnMousePressed(event -> { + Ecdar.getPresentation().getController().getEditorPresentation().getController().setActiveModelPresentationForActiveCanvas(globalDeclarationsPresentation); + }); + + filesList.getChildren().add(globalDclPresentation); }); - filesList.getChildren().add(globalDclPresentation); - Ecdar.getProject().getComponents().addListener(new ListChangeListener<Component>() { - @Override - public void onChanged(final Change<? extends Component> c) { - while (c.next()) { - c.getAddedSubList().forEach(o -> handleAddedModel(o)); - c.getRemoved().forEach(o -> handleRemovedModel(o)); + initializeComponentHandling(); + initializeSystemHandling(); + initializeMutationTestPlanHandling(); + initializeCreateComponentKeybinding(); + resetProject(); - // Sort the children alphabetically - sortPresentations(); - } - } + Platform.runLater(() -> { + final var initializedModelPresentation = modelPresentationMap.keySet().stream().filter(mp -> mp instanceof ComponentPresentation).findFirst().orElse(null); + Ecdar.getPresentation().getController().getEditorPresentation().getController().setActiveModelPresentationForActiveCanvas(initializedModelPresentation); }); + } - Ecdar.getProject().getTempComponents().addListener((ListChangeListener<Component>) c -> { - while (c.next()) { - c.getAddedSubList().forEach(this::handleAddedModel); - c.getRemoved().forEach(this::handleRemovedModel); - - generatedComponentsDivider.setVisible(!tempFilesList.getChildren().isEmpty()); + private void initializeSystemHandling() { + project.getSystems().addListener((ListChangeListener<EcdarSystem>) change -> { + while (change.next()) { + change.getAddedSubList().forEach(o -> handleAddedModelPresentation(new SystemPresentation(o))); + change.getRemoved().forEach(o -> handleRemovedModelPresentation(modelPresentationMap.keySet().stream().filter(modelPresentation -> modelPresentation.getController().getModel().equals(o)).findFirst().orElse(null))); // Sort the children alphabetically sortPresentations(); } }); + } - Ecdar.getProject().getComponents().forEach(this::handleAddedModel); + private void initializeComponentHandling() { + project.getComponents().addListener(getComponentListChangeListener()); + project.getTempComponents().addListener(getComponentListChangeListener()); + } - // Listen to added and removed systems - Ecdar.getProject().getSystemsProperty().addListener((ListChangeListener<EcdarSystem>) change -> { - while (change.next()) { - change.getAddedSubList().forEach(this::handleAddedModel); - change.getRemoved().forEach(this::handleRemovedModel); + private ListChangeListener<Component> getComponentListChangeListener() { + return c -> { + while (c.next()) { + c.getAddedSubList().forEach(o -> handleAddedModelPresentation(new ComponentPresentation(o))); + c.getRemoved().forEach(o -> handleRemovedModelPresentation(modelPresentationMap.keySet().stream().filter(modelPresentation -> modelPresentation.getController().getModel().equals(o)).findFirst().orElse(null))); // Sort the children alphabetically sortPresentations(); } - }); - - initializeMutationTestPlanHandling(); + }; } private void initializeMutationTestPlanHandling() { - Ecdar.getProject().getTestPlans().addListener((ListChangeListener<MutationTestPlan>) change -> { + project.getTestPlans().addListener((ListChangeListener<MutationTestPlan>) change -> { while (change.next()) { - change.getAddedSubList().forEach(this::handleAddedModel); - change.getRemoved().forEach(this::handleRemovedModel); + change.getAddedSubList().forEach(o -> handleAddedModelPresentation(new MutationTestPlanPresentation(o))); + change.getRemoved().forEach(o -> handleRemovedModelPresentation(new MutationTestPlanPresentation(o))); // Sort the children alphabetically sortPresentations(); @@ -119,15 +130,20 @@ private void initializeMutationTestPlanHandling() { } private void sortPresentations() { - final ArrayList<HighLevelModelObject> sortedComponentList = new ArrayList<>(modelPresentationMap.keySet()); - sortedComponentList.sort(Comparator.comparing(HighLevelModelObject::getName)); - sortedComponentList.forEach(component -> modelPresentationMap.get(component).toFront()); + Platform.runLater(() -> { + final ArrayList<HighLevelModelPresentation> sortedComponentList = new ArrayList<>(modelPresentationMap.keySet()); + sortedComponentList.sort(Comparator.comparing(o -> o.getController().getModel().getName())); + sortedComponentList.forEach(component -> modelPresentationMap.get(component).toFront()); + + var globalDec = modelPresentationMap.keySet().stream().filter(hp -> hp instanceof DeclarationsPresentation).findFirst().orElse(null); + modelPresentationMap.get(globalDec).toBack(); + }); } private void initializeMoreInformationDropDown(final FilePresentation filePresentation) { final JFXRippler moreInformation = (JFXRippler) filePresentation.lookup("#moreInformation"); final DropDownMenu moreInformationDropDown = new DropDownMenu(moreInformation); - final HighLevelModelObject model = filePresentation.getModel(); + final HighLevelModel model = filePresentation.getController().getModel(); // If component, added toggle for periodic check if (model instanceof Component) { @@ -166,13 +182,13 @@ private void initializeMoreInformationDropDown(final FilePresentation filePresen // Add color picker if (model instanceof Component) { moreInformationDropDown.addColorPicker( - filePresentation.getModel(), - ((Component) filePresentation.getModel())::dye + model, + ((Component) model)::dye ); } else if (model instanceof EcdarSystem) { moreInformationDropDown.addColorPicker( - filePresentation.getModel(), - ((EcdarSystem) filePresentation.getModel())::dye + model, + ((EcdarSystem) model)::dye ); } @@ -180,54 +196,52 @@ private void initializeMoreInformationDropDown(final FilePresentation filePresen if (model instanceof Component) { moreInformationDropDown.addSpacerElement(); - if (!filePresentation.getModel().isTemporary()) { + if (!filePresentation.getController().getModel().isTemporary()) { moreInformationDropDown.addClickableListElement("Delete", event -> { UndoRedoStack.pushAndPerform(() -> { // Perform - Ecdar.getProject().getComponents().remove(model); + project.getComponents().remove(model); }, () -> { // Undo - Ecdar.getProject().getComponents().add((Component) model); + project.addComponent((Component) model); }, "Deleted component " + model.getName(), "delete"); moreInformationDropDown.hide(); }); } else { moreInformationDropDown.addClickableListElement("Delete", event -> { UndoRedoStack.pushAndPerform(() -> { // Perform - Ecdar.getProject().getTempComponents().remove(model); + project.getTempComponents().remove(model); }, () -> { // Undo - Ecdar.getProject().getTempComponents().add((Component) model); + project.getTempComponents().add((Component) model); }, "Deleted component " + model.getName(), "delete"); moreInformationDropDown.hide(); }); - + moreInformationDropDown.addClickableListElement("Add as component", event -> { - if(Ecdar.getProject().getComponents().stream().noneMatch(component -> component.getName().equals(model.getName()))) { + if (project.getComponents().stream().noneMatch(component -> component.getName().equals(model.getName()))) { UndoRedoStack.pushAndPerform(() -> { // Perform - Ecdar.getProject().getTempComponents().remove(model); + project.getTempComponents().remove(model); model.setTemporary(false); - Ecdar.getProject().getComponents().add((Component) model); - EcdarController.setActiveModelForActiveCanvas(model); + project.addComponent((Component) model); }, () -> { // Undo - Ecdar.getProject().getComponents().remove(model); + project.getComponents().remove(model); model.setTemporary(true); - Ecdar.getProject().getTempComponents().add((Component) model); - EcdarController.setActiveModelForActiveCanvas(model); + project.getTempComponents().add((Component) model); }, "Add component " + model.getName(), "add"); moreInformationDropDown.hide(); } else { String originalModelName = model.getName(); + // Get new model number starting from 2 to symbolize second version for (int i = 2; i < 100; i++) { final String newName = originalModelName + " #" + i; - if(Ecdar.getProject().getComponents().stream().noneMatch(component -> component.getName().equals(newName))) { + if (project.getComponents().stream().noneMatch(component -> component.getName().equals(newName))) { UndoRedoStack.pushAndPerform(() -> { // Perform - Ecdar.getProject().getTempComponents().remove(model); + project.getTempComponents().remove(model); model.setTemporary(false); - Ecdar.getProject().getComponents().add((Component) model); - EcdarController.setActiveModelForActiveCanvas(model); + project.addComponent((Component) model); model.setName(newName); }, () -> { // Undo - Ecdar.getProject().getComponents().remove(model); + project.getComponents().remove(model); model.setTemporary(true); - Ecdar.getProject().getTempComponents().add((Component) model); + project.getTempComponents().add((Component) model); model.setName(originalModelName); }, "Add component " + model.getName(), "add"); moreInformationDropDown.hide(); @@ -244,9 +258,9 @@ private void initializeMoreInformationDropDown(final FilePresentation filePresen moreInformationDropDown.addSpacerElement(); moreInformationDropDown.addClickableListElement("Delete", event -> { UndoRedoStack.pushAndPerform(() -> { // Perform - Ecdar.getProject().getSystemsProperty().remove(model); + project.getSystems().remove(model); }, () -> { // Undo - Ecdar.getProject().getSystemsProperty().add((EcdarSystem) model); + project.getSystems().add((EcdarSystem) model); }, "Deleted system " + model.getName(), "delete"); moreInformationDropDown.hide(); }); @@ -265,9 +279,9 @@ private void initializeMoreInformationDropDown(final FilePresentation filePresen // Delete button for test plan moreInformationDropDown.addClickableListElement("Delete", event -> { UndoRedoStack.pushAndPerform(() -> { // Perform - Ecdar.getProject().getTestPlans().remove(model); + project.getTestPlans().remove(model); }, () -> { // Undo - Ecdar.getProject().getTestPlans().add((MutationTestPlan) model); + project.getTestPlans().add((MutationTestPlan) model); }, "Deleted test plan " + model.getName(), "delete"); moreInformationDropDown.hide(); }); @@ -292,83 +306,183 @@ private void initializeTogglePeriodicCheck(DropDownMenu moreInformationDropDown, }); } - private void handleAddedModel(final HighLevelModelObject model) { - final FilePresentation filePresentation = new FilePresentation(model); + private void initializeCreateComponentKeybinding() { + //Press ctrl+N or cmd+N to create a new component. The canvas changes to this new component + KeyCodeCombination combination = new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN); + Keybind binding = new Keybind(combination, (event) -> { + final Component newComponent = new Component(getAvailableColor(), getUniqueComponentName()); + UndoRedoStack.pushAndPerform(() -> { // Perform + project.addComponent(newComponent); + }, () -> { // Undo + project.getComponents().remove(newComponent); + }, "Created new component: " + newComponent.getName(), "add-circle"); + + Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().setActiveModelPresentation(getComponentPresentations().stream().filter(componentPresentation -> componentPresentation.getController().getComponent().equals(newComponent)).findFirst().orElse(null)); + }); + KeyboardTracker.registerKeybind(KeyboardTracker.CREATE_COMPONENT, binding); + } + + private void handleAddedModelPresentation(final HighLevelModelPresentation modelPresentation) { + final FilePresentation filePresentation = new FilePresentation(modelPresentation.getController().getModel()); initializeMoreInformationDropDown(filePresentation); - // Add the file presentation related to the model to the project pane - if (model.isTemporary()) { + // Add the file presentation related to the modelPresentation to the project pane + if (modelPresentation.getController().getModel().isTemporary()) { tempFilesList.getChildren().add(filePresentation); } else { filesList.getChildren().add(filePresentation); } - modelPresentationMap.put(model, filePresentation); - // Open the component if the presentation is pressed + modelPresentationMap.put(modelPresentation, filePresentation);// ToDo NIELS: Bind these two + if (modelPresentation instanceof ComponentPresentation) { + componentPresentations.add((ComponentPresentation) modelPresentation); + } + + // Open the component if the file is pressed filePresentation.setOnMousePressed(event -> { - event.consume(); - EcdarController.setActiveModelForActiveCanvas(model); - updateColorsOnFilePresentations(); + final var previouslyActiveFile = modelPresentationMap.get(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation() + .getController() + .getActiveModelPresentation()); + if (previouslyActiveFile != null) previouslyActiveFile.getController().setIsActive(false); + + Ecdar.getPresentation().getController().getEditorPresentation().getController().setActiveModelPresentationForActiveCanvas(modelPresentation); + Platform.runLater(() -> { + filePresentation.getController().setIsActive(true); + }); }); - model.nameProperty().addListener(obs -> sortPresentations()); + modelPresentation.getController().getModel().nameProperty().addListener(obs -> sortPresentations()); + filePresentation.getController().setIsActive(true); + Platform.runLater(() -> Ecdar.getPresentation().getController().getEditorPresentation().getController().setActiveModelPresentationForActiveCanvas(modelPresentation)); } - private void handleRemovedModel(final HighLevelModelObject model) { - // If we remove the model active on the canvas - if (EcdarController.getActiveCanvasPresentation().getController().getActiveModel() == model) { - if (Ecdar.getProject().getComponents().size() > 0) { + private void handleRemovedModelPresentation(final HighLevelModelPresentation modelPresentation) { + // If we remove the modelPresentation active on the canvas + if (Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getActiveModelPresentation() == modelPresentation) { + if (project.getComponents().size() > 0) { // Find the first available component and show it instead of the removed one - final Component component = Ecdar.getProject().getComponents().get(0); - EcdarController.setActiveModelForActiveCanvas(component); - updateColorsOnFilePresentations(); + final HighLevelModelPresentation newActiveModelPresentation = modelPresentationMap.keySet().iterator().next(); + Ecdar.getPresentation().getController().getEditorPresentation().getController().setActiveModelPresentationForActiveCanvas(newActiveModelPresentation); + modelPresentationMap.get(newActiveModelPresentation).getController().setIsActive(true); } else { // Show no components (since there are none in the project) - EcdarController.setActiveModelForActiveCanvas(null); + Ecdar.getPresentation().getController().getEditorPresentation().getController().setActiveModelPresentationForActiveCanvas(null); } } // Remove the file presentation related to the model from the project pane - if (model.isTemporary()) { - tempFilesList.getChildren().remove(modelPresentationMap.get(model)); + if (modelPresentation.getController().getModel().isTemporary()) { + tempFilesList.getChildren().removeIf(n -> n == modelPresentationMap.get(modelPresentation)); } else { - filesList.getChildren().remove(modelPresentationMap.get(model)); + filesList.getChildren().removeIf(n -> n == modelPresentationMap.get(modelPresentation)); } - modelPresentationMap.remove(model); + + modelPresentationMap.remove(modelPresentation); } /** - * Update the color of all FilePresentations to display currently active components + * Resets components. + * After this, there is only one component. + * Be sure to disable code analysis before call and enable after call. */ - public void updateColorsOnFilePresentations() { - for (Node child : filesList.getChildren()) { - if (child instanceof FilePresentation) { - Platform.runLater(() -> ((FilePresentation) child).updateColors()); + public void resetProject() { + project.clean(); + project.addComponent(new Component(getAvailableColor(), getUniqueComponentName())); + } + + public EnabledColor getAvailableColor() { + ArrayList<EnabledColor> availableColors = new ArrayList<>(EnabledColor.enabledColors); + for (Component comp : project.getComponents()) { + availableColors.removeIf(c -> comp.getColor().equals(c)); + } + + if (availableColors.isEmpty()) { + return EnabledColor.enabledColors.get(new Random().nextInt(EnabledColor.enabledColors.size())); + } + + return availableColors.get(0); + } + + /** + * Gets the name of all components in the project and inserts it into a set + * + * @return the set of all component names + */ + private HashSet<String> getComponentNames() { + final HashSet<String> names = new HashSet<>(); + + for (final Component component : project.getComponents()) { + names.add(component.getName()); + } + + return names; + } + + /** + * Generate a unique name for the component + * + * @return A project unique name + */ + public String getUniqueComponentName() { + for (int counter = 1; ; counter++) { + final String name = Project.COMPONENT + counter; + if (!getComponentNames().contains(name)) { + return name; } } + } - for (Node child : tempFilesList.getChildren()) { - if (child instanceof FilePresentation) { - ((FilePresentation) child).updateColors(); + public String getUniqueSystemName() { + for (int counter = 1; ; counter++) { + final String name = Project.SYSTEM + counter; + if (!getSystemNames().contains(name)) { + return name; } } } + private HashSet<String> getSystemNames() { + final HashSet<String> names = new HashSet<>(); + + for (final EcdarSystem component : project.getSystems()) { + names.add(component.getName()); + } + + return names; + } + + public ObservableList<ComponentPresentation> getComponentPresentations() { + return componentPresentations; + } + + public void setHighlightedForModelFiles(List<HighLevelModelPresentation> currentlyActiveModelPresentations) { + modelPresentationMap.values().forEach(fp -> fp.getController().setIsActive(false)); + + for (HighLevelModelPresentation modelPresentation : currentlyActiveModelPresentations) { + if (modelPresentationMap.containsKey(modelPresentation)) modelPresentationMap.get(modelPresentation).getController().setIsActive(true); + } + } + + public void swapHighlightBetweenTwoModelFiles(final HighLevelModelPresentation oldActive, final HighLevelModelPresentation newActive) { + if (modelPresentationMap.containsKey(oldActive)) modelPresentationMap.get(oldActive) + .getController() + .setIsActive(false); + + if (modelPresentationMap.containsKey(newActive)) modelPresentationMap.get(newActive).getController().setIsActive(true); // newActive is not in the map when opening an existing project + } + /** * Method for creating a new component */ @FXML private void createComponentClicked() { - final Component newComponent = new Component(true); + final Component newComponent = new Component(getAvailableColor(), getUniqueComponentName()); UndoRedoStack.pushAndPerform(() -> { // Perform - Ecdar.getProject().getComponents().add(newComponent); - EcdarController.setActiveModelForActiveCanvas(newComponent); + project.addComponent(newComponent); }, () -> { // Undo - Ecdar.getProject().getComponents().remove(newComponent); + project.getComponents().remove(newComponent); }, "Created new component: " + newComponent.getName(), "add-circle"); - - updateColorsOnFilePresentations(); } /** @@ -376,13 +490,12 @@ private void createComponentClicked() { */ @FXML private void createSystemClicked() { - final EcdarSystem newSystem = new EcdarSystem(); + final EcdarSystem newSystem = new EcdarSystem(getAvailableColor(), getUniqueSystemName()); UndoRedoStack.pushAndPerform(() -> { // Perform - Ecdar.getProject().getSystemsProperty().add(newSystem); - EcdarController.setActiveModelForActiveCanvas(newSystem); + project.getSystems().add(newSystem); }, () -> { // Undo - Ecdar.getProject().getSystemsProperty().remove(newSystem); + project.getSystems().remove(newSystem); }, "Created new system: " + newSystem.getName(), "add-circle"); } diff --git a/src/main/java/ecdar/controllers/QueryController.java b/src/main/java/ecdar/controllers/QueryController.java index 53ec4f68..7602378e 100644 --- a/src/main/java/ecdar/controllers/QueryController.java +++ b/src/main/java/ecdar/controllers/QueryController.java @@ -2,8 +2,8 @@ import com.jfoenix.controls.JFXComboBox; import com.jfoenix.controls.JFXRippler; +import ecdar.backend.Engine; import com.jfoenix.controls.JFXTextField; -import ecdar.backend.BackendInstance; import ecdar.abstractions.Query; import ecdar.abstractions.QueryType; import ecdar.backend.BackendHelper; @@ -27,7 +27,7 @@ public class QueryController implements Initializable { public JFXRippler actionButton; public JFXRippler queryTypeExpand; public Text queryTypeSymbol; - public JFXComboBox<BackendInstance> backendsDropdown; + public JFXComboBox<Engine> enginesDropdown; private Query query; private final Map<QueryType, SimpleBooleanProperty> queryTypeListElementsSelectedState = new HashMap<>(); private final Tooltip noQueryTypeSetTooltip = new Tooltip("Please select a query type beneath the status icon"); @@ -38,12 +38,7 @@ public class QueryController implements Initializable { public void initialize(URL location, ResourceBundle resources) { initializeActionButton(); - queryText.textProperty().addListener(new ChangeListener<String>() { - @Override - public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) { - queryText.setText(StringHelper.ConvertSymbolsToUnicode(newValue)); - } - }); + queryText.textProperty().addListener((observable, oldValue, newValue) -> queryText.setText(StringHelper.ConvertSymbolsToUnicode(newValue))); } public void setQuery(Query query) { @@ -64,29 +59,29 @@ public void setQuery(Query query) { } })); - if (BackendHelper.getBackendInstances().contains(query.getBackend())) { - backendsDropdown.setValue(query.getBackend()); + if (BackendHelper.getEngines().contains(query.getEngine())) { + enginesDropdown.setValue(query.getEngine()); } else { - backendsDropdown.setValue(BackendHelper.getDefaultBackendInstance()); + enginesDropdown.setValue(BackendHelper.getDefaultEngine()); } - backendsDropdown.valueProperty().addListener((observable, oldValue, newValue) -> { + enginesDropdown.valueProperty().addListener((observable, oldValue, newValue) -> { if (newValue != null) { - query.setBackend(newValue); + query.setEngine(newValue); } else { - backendsDropdown.setValue(BackendHelper.getDefaultBackendInstance()); + enginesDropdown.setValue(BackendHelper.getDefaultEngine()); } }); - BackendHelper.addBackendInstanceListener(() -> { + BackendHelper.addEngineInstanceListener(() -> { Platform.runLater(() -> { // The value must be set before the items (https://stackoverflow.com/a/29483445) - if (BackendHelper.getBackendInstances().contains(query.getBackend())) { - backendsDropdown.setValue(query.getBackend()); + if (BackendHelper.getEngines().contains(query.getEngine())) { + enginesDropdown.setValue(query.getEngine()); } else { - backendsDropdown.setValue(BackendHelper.getDefaultBackendInstance()); + enginesDropdown.setValue(BackendHelper.getDefaultEngine()); } - backendsDropdown.setItems(BackendHelper.getBackendInstances()); + enginesDropdown.setItems(BackendHelper.getEngines()); }); }); } diff --git a/src/main/java/ecdar/controllers/QueryPaneController.java b/src/main/java/ecdar/controllers/QueryPaneController.java index 690aad5a..a718b2df 100644 --- a/src/main/java/ecdar/controllers/QueryPaneController.java +++ b/src/main/java/ecdar/controllers/QueryPaneController.java @@ -6,6 +6,7 @@ import ecdar.presentations.QueryPresentation; import com.jfoenix.controls.JFXRippler; import ecdar.utility.colors.Color; +import javafx.application.Platform; import javafx.collections.ListChangeListener; import javafx.fxml.FXML; import javafx.fxml.Initializable; @@ -36,25 +37,26 @@ public class QueryPaneController implements Initializable { @Override public void initialize(final URL location, final ResourceBundle resources) { - Ecdar.getProject().getQueries().addListener((ListChangeListener<Query>) change -> { - while (change.next()) { - for (final Query removeQuery : change.getRemoved()) { - queriesList.getChildren().remove(queryPresentationMap.get(removeQuery)); - queryPresentationMap.remove(removeQuery); - } + Platform.runLater(() -> { + Ecdar.getProject().getQueries().addListener((ListChangeListener<Query>) change -> { + while (change.next()) { + for (final Query removeQuery : change.getRemoved()) { + queriesList.getChildren().remove(queryPresentationMap.get(removeQuery)); + queryPresentationMap.remove(removeQuery); + } - for (final Query newQuery : change.getAddedSubList()) { - final QueryPresentation newQueryPresentation = new QueryPresentation(newQuery); - queryPresentationMap.put(newQuery, newQueryPresentation); - queriesList.getChildren().add(newQueryPresentation); + for (final Query newQuery : change.getAddedSubList()) { + final QueryPresentation newQueryPresentation = new QueryPresentation(newQuery); + queryPresentationMap.put(newQuery, newQueryPresentation); + queriesList.getChildren().add(newQueryPresentation); + } } + }); + for (final Query newQuery : Ecdar.getProject().getQueries()) { + queriesList.getChildren().add(new QueryPresentation(newQuery)); } }); - for (final Query newQuery : Ecdar.getProject().getQueries()) { - queriesList.getChildren().add(new QueryPresentation(newQuery)); - } - initializeResizeAnchor(); } @@ -78,7 +80,7 @@ private void runAllQueriesButtonClicked() { Ecdar.getProject().getQueries().forEach(query -> { if (query.getType() == null) return; query.cancel(); - Ecdar.getQueryExecutor().executeQuery(query); + query.execute(); }); } diff --git a/src/main/java/ecdar/controllers/SimEdgeController.java b/src/main/java/ecdar/controllers/SimEdgeController.java index ee83c513..d2e35a8e 100755 --- a/src/main/java/ecdar/controllers/SimEdgeController.java +++ b/src/main/java/ecdar/controllers/SimEdgeController.java @@ -10,6 +10,7 @@ import ecdar.presentations.SimNailPresentation; import ecdar.utility.Highlightable; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.BindingHelper; import ecdar.utility.helpers.Circular; import ecdar.utility.helpers.ItemDragHelper; @@ -371,24 +372,18 @@ public ObjectProperty<Component> componentProperty() { * Colors the edge model * * @param color the new color of the edge - * @param intensity the intensity of the edge */ - public void color(final Color color, final Color.Intensity intensity) { + public void color(final EnabledColor color) { final Edge edge = getEdge(); // Set the color of the edge - edge.setColorIntensity(intensity); edge.setColor(color); } - public Color getColor() { + public EnabledColor getColor() { return getEdge().getColor(); } - public Color.Intensity getColorIntensity() { - return getEdge().getColorIntensity(); - } - public ItemDragHelper.DragBounds getDragBounds() { return ItemDragHelper.DragBounds.generateLooseDragBounds(); } diff --git a/src/main/java/ecdar/controllers/SimLocationController.java b/src/main/java/ecdar/controllers/SimLocationController.java index 9921a057..1bd0d1d8 100755 --- a/src/main/java/ecdar/controllers/SimLocationController.java +++ b/src/main/java/ecdar/controllers/SimLocationController.java @@ -1,14 +1,13 @@ package ecdar.controllers; import com.jfoenix.controls.JFXPopup; -import ecdar.Ecdar; import ecdar.abstractions.*; -import ecdar.backend.BackendHelper; import ecdar.backend.SimulationHandler; import ecdar.presentations.DropDownMenu; import ecdar.presentations.SimLocationPresentation; import ecdar.presentations.SimTagPresentation; -import ecdar.utility.colors.Color; +import ecdar.simulation.SimulationState; +import ecdar.utility.colors.EnabledColor; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; @@ -46,6 +45,109 @@ public class SimLocationController implements Initializable { private DropDownMenu dropDownMenu; private SimulationHandler simulationHandler; + public static String getSimLocationReachableQuery(final Location endLocation, final Component component, final String query) { + return getSimLocationReachableQuery(endLocation, component, query, null); + } + + /** + * Generates a reachability query based on the given location and component + * + * @param endLocation The location which should be checked for reachability + * @return A reachability query string + */ + public static String getSimLocationReachableQuery(final Location endLocation, final Component component, final String query, final SimulationState state) { + var stringBuilder = new StringBuilder(); + + // append simulation query + stringBuilder.append(query); + + // append arrow + stringBuilder.append(" -> "); + + // ToDo: append start location here + if (state != null){ + stringBuilder.append(getStartStateString(state)); + stringBuilder.append(";"); + } + + // append end state + stringBuilder.append(getEndStateString(component.getName(), endLocation.getId())); + + // return example: m1||M2->[L1,L4](y<3);[L2, L7](y<2) + System.out.println(stringBuilder); + return stringBuilder.toString(); + } + + private static String getStartStateString(SimulationState state) { + var stringBuilder = new StringBuilder(); + + // append locations + var locations = state.getLocations(); + stringBuilder.append("["); + var appendLocationWithSeparator = false; + for(var componentName : SimulatorController.getSimulationHandler().getComponentsInSimulation()){ + var locationFound = false; + + for(var location:locations){ + if (location.getKey().equals(componentName)){ + if (appendLocationWithSeparator){ + stringBuilder.append("," + location.getValue()); + } + else{ + stringBuilder.append(location.getValue()); + } + locationFound = true; + } + if (locationFound){ + // don't go through more locations, when a location is found for the specific component that we're looking at + break; + } + } + appendLocationWithSeparator = true; + } + stringBuilder.append("]"); + + // append clock values + var clocks = state.getSimulationClocks(); + stringBuilder.append("()"); + + return stringBuilder.toString(); + } + + private static String getEndStateString(String componentName, String endLocationId) { + var stringBuilder = new StringBuilder(); + + stringBuilder.append("["); + var appendLocationWithSeparator = false; + + for (var component : SimulatorController.getSimulationHandler().getComponentsInSimulation()) + { + if (component.equals(componentName)){ + if (appendLocationWithSeparator){ + stringBuilder.append("," + endLocationId); + } + else{ + stringBuilder.append(endLocationId); + } + } + else{ // add underscore to indicate, that we don't care about the end locations in the other components + if (appendLocationWithSeparator){ + stringBuilder.append(",_"); + } + else{ + stringBuilder.append("_"); + } + } + if (!appendLocationWithSeparator) { + appendLocationWithSeparator = true; + } + } + stringBuilder.append("]"); + stringBuilder.append("()"); + + return stringBuilder.toString(); + } + @Override public void initialize(final URL location, final ResourceBundle resources) { this.location.addListener((obsLocation, oldLocation, newLocation) -> { @@ -60,7 +162,7 @@ public void initialize(final URL location, final ResourceBundle resources) { scaleContent.scaleYProperty().bind(scaleContent.scaleXProperty()); initializeMouseControls(); - simulationHandler = Ecdar.getSimulationHandler(); + simulationHandler = SimulatorController.getSimulationHandler(); } private void initializeMouseControls() { @@ -88,7 +190,7 @@ public void initializeDropDownMenu(){ dropDownMenu.addClickableListElement("Is " + getLocation().getId() + " reachable from initial state?", event -> { // Generate the query from the backend - final String reachabilityQuery = BackendHelper.getLocationReachableQuery(getLocation(), getComponent(), simulationHandler.getSimulationQuery()); + final String reachabilityQuery = getSimLocationReachableQuery(getLocation(), getComponent(), simulationHandler.getSimulationQuery()); // Add proper comment final String reachabilityComment = "Is " + getLocation().getMostDescriptiveIdentifier() + " reachable from initial state?"; @@ -98,14 +200,14 @@ public void initializeDropDownMenu(){ query.setType(QueryType.REACHABILITY); // execute query - Ecdar.getQueryExecutor().executeQuery(query); + query.execute(); dropDownMenu.hide(); }); dropDownMenu.addClickableListElement("Is " + getLocation().getId() + " reachable from current locations?", event -> { // Generate the query from the backend - final String reachabilityQuery = BackendHelper.getLocationReachableQuery(getLocation(), getComponent(), simulationHandler.getSimulationQuery(), simulationHandler.getCurrentState()); + final String reachabilityQuery = getSimLocationReachableQuery(getLocation(), getComponent(), simulationHandler.getSimulationQuery(), simulationHandler.getCurrentState()); // Add proper comment final String reachabilityComment = "Is " + getLocation().getMostDescriptiveIdentifier() + " reachable from current locations?"; @@ -115,7 +217,7 @@ public void initializeDropDownMenu(){ query.setType(QueryType.REACHABILITY); // execute query - Ecdar.getQueryExecutor().executeQuery(query); + query.execute(); dropDownMenu.hide(); }); @@ -157,24 +259,18 @@ public ObjectProperty<Component> componentProperty() { /** * Colors the location model * @param color the new color of the location - * @param intensity the intensity of the color */ - public void color(final Color color, final Color.Intensity intensity) { + public void color(final EnabledColor color) { final Location location = getLocation(); // Set the color of the location - location.setColorIntensity(intensity); location.setColor(color); } - public Color getColor() { + public EnabledColor getColor() { return getLocation().getColor(); } - public Color.Intensity getColorIntensity() { - return getLocation().getColorIntensity(); - } - public DoubleProperty xProperty() { return root.layoutXProperty(); } diff --git a/src/main/java/ecdar/controllers/SimNailController.java b/src/main/java/ecdar/controllers/SimNailController.java index 053456d3..ca4546cc 100755 --- a/src/main/java/ecdar/controllers/SimNailController.java +++ b/src/main/java/ecdar/controllers/SimNailController.java @@ -7,6 +7,7 @@ import ecdar.presentations.SimNailPresentation; import ecdar.presentations.SimTagPresentation; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; @@ -100,13 +101,10 @@ public ObjectProperty<Edge> edgeProperty() { return edge; } - public Color getColor() { + public EnabledColor getColor() { return getComponent().getColor(); } - public Color.Intensity getColorIntensity() { - return getComponent().getColorIntensity(); - } public DoubleProperty xProperty() { return root.layoutXProperty(); diff --git a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java index 6d5c017b..4aa189f6 100644 --- a/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java +++ b/src/main/java/ecdar/controllers/SimulationInitializationDialogController.java @@ -42,6 +42,6 @@ public void setSimulationData(){ } public void initialize(URL location, ResourceBundle resources) { - simulationHandler = Ecdar.getSimulationHandler(); + simulationHandler = SimulatorController.getSimulationHandler(); } } diff --git a/src/main/java/ecdar/controllers/SimulatorController.java b/src/main/java/ecdar/controllers/SimulatorController.java index f2689c0b..231ab789 100644 --- a/src/main/java/ecdar/controllers/SimulatorController.java +++ b/src/main/java/ecdar/controllers/SimulatorController.java @@ -3,6 +3,8 @@ import ecdar.Ecdar; import ecdar.abstractions.*; import ecdar.backend.SimulationHandler; +import ecdar.presentations.LeftSimPanePresentation; +import ecdar.presentations.RightSimPanePresentation; import ecdar.presentations.SimulatorOverviewPresentation; import ecdar.simulation.SimulationState; import javafx.beans.property.DoubleProperty; @@ -17,12 +19,14 @@ import java.util.List; import java.util.ResourceBundle; -public class SimulatorController implements Initializable { +public class SimulatorController implements ModeController, Initializable { public StackPane root; - private SimulationHandler simulationHandler; + public static SimulationHandler simulationHandler = new SimulationHandler(); public SimulatorOverviewPresentation overviewPresentation; public StackPane toolbar; + public final LeftSimPanePresentation leftSimPane = new LeftSimPanePresentation(); + public final RightSimPanePresentation rightSimPane = new RightSimPanePresentation(); private boolean firstTimeInSimulator; private final static DoubleProperty width = new SimpleDoubleProperty(), height = new SimpleDoubleProperty(); @@ -33,7 +37,12 @@ public void initialize(URL location, ResourceBundle resources) { root.widthProperty().addListener((observable, oldValue, newValue) -> width.setValue(newValue)); root.heightProperty().addListener((observable, oldValue, newValue) -> height.setValue(newValue)); firstTimeInSimulator = true; - simulationHandler = Ecdar.getSimulationHandler(); + } + + public static SimulationHandler getSimulationHandler() { return simulationHandler; } + + public static void setSimulationHandler(SimulationHandler simHandler) { + simulationHandler = simHandler; } /** @@ -128,4 +137,14 @@ public static DoubleProperty getHeightProperty() { public static void setSelectedState(SimulationState selectedState) { SimulatorController.selectedState.set(selectedState); } + + @Override + public StackPane getLeftPane() { + return leftSimPane; + } + + @Override + public StackPane getRightPane() { + return rightSimPane; + } } diff --git a/src/main/java/ecdar/controllers/SimulatorOverviewController.java b/src/main/java/ecdar/controllers/SimulatorOverviewController.java index bb3678b7..0f2bf7f8 100644 --- a/src/main/java/ecdar/controllers/SimulatorOverviewController.java +++ b/src/main/java/ecdar/controllers/SimulatorOverviewController.java @@ -1,6 +1,5 @@ package ecdar.controllers; -import ecdar.Ecdar; import ecdar.abstractions.*; import ecdar.backend.SimulationHandler; import ecdar.presentations.ProcessPresentation; @@ -67,7 +66,7 @@ public class SimulatorOverviewController implements Initializable { @Override public void initialize(final URL location, final ResourceBundle resources) { - simulationHandler = Ecdar.getSimulationHandler(); + simulationHandler = SimulatorController.getSimulationHandler(); groupContainer = new Group(); processContainer = new FlowPane(); diff --git a/src/main/java/ecdar/controllers/SystemController.java b/src/main/java/ecdar/controllers/SystemController.java index 5720d06c..49a0f596 100644 --- a/src/main/java/ecdar/controllers/SystemController.java +++ b/src/main/java/ecdar/controllers/SystemController.java @@ -4,6 +4,8 @@ import ecdar.abstractions.*; import ecdar.presentations.*; import ecdar.utility.UndoRedoStack; +import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.SelectHelper; import com.jfoenix.controls.JFXPopup; import javafx.beans.property.ObjectProperty; @@ -11,15 +13,21 @@ import javafx.collections.ListChangeListener; import javafx.fxml.FXML; import javafx.fxml.Initializable; +import javafx.geometry.Insets; +import javafx.geometry.Pos; import javafx.scene.input.MouseEvent; -import javafx.scene.layout.Pane; -import javafx.scene.shape.Circle; -import javafx.scene.shape.Line; +import javafx.scene.layout.*; +import javafx.scene.shape.*; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.ResourceBundle; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +import static ecdar.Ecdar.CANVAS_PADDING; +import static ecdar.presentations.ModelPresentation.*; /** * Controller for a system. @@ -34,21 +42,13 @@ public class SystemController extends ModelController implements Initializable { private final Map<ComponentInstance, ComponentInstancePresentation> componentInstancePresentationMap = new HashMap<>(); private final Map<ComponentOperator, ComponentOperatorPresentation> componentOperatorPresentationMap = new HashMap<>(); - private final Map<EcdarSystemEdge, SystemEdgePresentation> edgePresentationMap = new HashMap<>(); + private final Map<SystemEdge, SystemEdgePresentation> edgePresentationMap = new HashMap<>(); private final ObjectProperty<EcdarSystem> system = new SimpleObjectProperty<>(); private Circle dropDownMenuHelperCircle; private DropDownMenu contextMenu; - public EcdarSystem getSystem() { - return system.get(); - } - - public void setSystem(final EcdarSystem system) { - this.system.setValue(system); - } - @Override public void initialize(final URL location, final ResourceBundle resources) { // Initialize when system is added @@ -60,20 +60,136 @@ public void initialize(final URL location, final ResourceBundle resources) { initializeComponentInstanceHandling(newValue); initializeOperatorHandling(newValue); initializeEdgeHandling(newValue); + + super.initialize(newValue.getBox()); + initializeDimensions(newValue.getBox()); + + // Initialize methods that are sensitive to width and height + final Runnable onUpdateSize = () -> { + initializeToolbar(); + initializeFrame(); + initializeBackground(); + }; + + onUpdateSize.run(); + + // Re-run initialisation on update of width and height property + newValue.getBox().getWidthProperty().addListener(obs -> onUpdateSize.run()); + newValue.getBox().getHeightProperty().addListener(obs -> onUpdateSize.run()); }); } + public void setSystem(final EcdarSystem system) { + this.system.setValue(system); + } + + public EcdarSystem getSystem() { + return system.get(); + } + private void initializeSystemRoot(final EcdarSystem system) { systemRootContainer.getChildren().add(new SystemRootPresentation(system)); } + /** + * Initializes the toolbar. + */ + private void initializeToolbar() { + final Consumer<EnabledColor> updateColor = (newColor) -> { + // Set the background of the toolbar + toolbar.setBackground(new Background(new BackgroundFill( + newColor.getPaintColor(), + CornerRadii.EMPTY, + Insets.EMPTY + ))); + + toolbar.setPrefHeight(TOOLBAR_HEIGHT); + }; + + getSystem().colorProperty().addListener(observable -> updateColor.accept(getSystem().getColor())); + + updateColor.accept(getSystem().getColor()); + } + + /** + * Initializes the frame and handling of it. + * The frame is a rectangle minus two cutouts. + */ + private void initializeFrame() { + final Shape[] mask = new Shape[1]; + final Rectangle rectangle = new Rectangle(getSystem().getBox().getWidth(), getSystem().getBox().getHeight()); + + // Generate top right corner (to subtract) + final Polygon topRightCorner = new Polygon( + getSystem().getBox().getWidth(), 0, + getSystem().getBox().getWidth() - (CORNER_SIZE + 2), 0, + getSystem().getBox().getWidth(), CORNER_SIZE + 2 + ); + + final Consumer<EnabledColor> updateColor = (newColor) -> { + // Mask the parent of the frame (will also mask the background) + mask[0] = Path.subtract(rectangle, TOP_LEFT_CORNER); + mask[0] = Path.subtract(mask[0], topRightCorner); + frame.setClip(mask[0]); + background.setClip(Path.union(mask[0], mask[0])); + background.setOpacity(0.5); + + // Bind the missing lines that we cropped away + topLeftLine.setStartX(CORNER_SIZE); + topLeftLine.setStartY(0); + topLeftLine.setEndX(0); + topLeftLine.setEndY(CORNER_SIZE); + topLeftLine.setStroke(newColor.getStrokeColor()); + topLeftLine.setStrokeWidth(1.25); + StackPane.setAlignment(topLeftLine, Pos.TOP_LEFT); + + topRightLine.setStartX(0); + topRightLine.setStartY(0); + topRightLine.setEndX(CORNER_SIZE); + topRightLine.setEndY(CORNER_SIZE); + topRightLine.setStroke(newColor.getStrokeColor()); + topRightLine.setStrokeWidth(1.25); + StackPane.setAlignment(topRightLine, Pos.TOP_RIGHT); + + // Set the stroke color to two shades darker + frame.setBorder(new Border(new BorderStroke( + newColor.getStrokeColor(), + BorderStrokeStyle.SOLID, + CornerRadii.EMPTY, + new BorderWidths(1), + Insets.EMPTY + ))); + }; + + // Update now, and update on color change + updateColor.accept(getSystem().getColor()); + getSystem().colorProperty().addListener(observable -> updateColor.accept(getSystem().getColor())); + } + + /** + * Initializes the background + */ + private void initializeBackground() { + // Bind the background width and height to the values in the model + background.widthProperty().bindBidirectional(getSystem().getBox().getWidthProperty()); + background.heightProperty().bindBidirectional(getSystem().getBox().getHeightProperty()); + + final Consumer<EnabledColor> updateColor = (newColor) -> { + // Set the background color to the lightest possible version of the color + background.setFill(newColor.setIntensity(2).getPaintColor()); + }; + + getSystem().colorProperty().addListener(observable -> updateColor.accept(getSystem().getColor())); + updateColor.accept(getSystem().getColor()); + } + /** * Handles when tapping on the background of the system view. * @param event mouse event */ @FXML private void modelContainerPressed(final MouseEvent event) { - EcdarController.getActiveCanvasPresentation().getController().leaveTextAreas(); + Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().leaveTextAreas(); SelectHelper.clearSelectedElements(); if (event.isSecondaryButtonDown()) { @@ -87,7 +203,7 @@ private void modelContainerPressed(final MouseEvent event) { } @Override - public HighLevelModelObject getModel() { + public HighLevelModel getModel() { return getSystem(); } @@ -260,7 +376,7 @@ private void handleRemovedComponentOperator(final ComponentOperator operator) { */ private void initializeEdgeHandling(final EcdarSystem system) { system.getEdges().forEach(this::handleAddedEdge); - system.getEdges().addListener((ListChangeListener<EcdarSystemEdge>) change -> { + system.getEdges().addListener((ListChangeListener<SystemEdge>) change -> { if (change.next()) { change.getAddedSubList().forEach(this::handleAddedEdge); change.getRemoved().forEach(this::handleRemovedEdge); @@ -272,7 +388,7 @@ private void initializeEdgeHandling(final EcdarSystem system) { * Handles an added edge. * @param edge the edge */ - private void handleAddedEdge(final EcdarSystemEdge edge) { + private void handleAddedEdge(final SystemEdge edge) { final SystemEdgePresentation presentation = new SystemEdgePresentation(edge, getSystem()); edgePresentationMap.put(edge, presentation); edgeContainer.getChildren().add(presentation); @@ -282,7 +398,7 @@ private void handleAddedEdge(final EcdarSystemEdge edge) { * Handles a removed component instance. * @param edge the edge */ - private void handleRemovedEdge(final EcdarSystemEdge edge) { + private void handleRemovedEdge(final SystemEdge edge) { edgeContainer.getChildren().remove(edgePresentationMap.get(edge)); edgePresentationMap.remove(edge); @@ -309,4 +425,44 @@ void showBorderAndBackground() { super.showBorderAndBackground(); topRightLine.setVisible(true); } + + /** + * Gets the minimum allowed width when dragging the anchor. + * It is determined by the position and size of the system nodes. + * @return the minimum allowed width + */ + @Override + double getDragAnchorMinWidth() { + double minWidth = getSystem().getSystemRoot().getX() + SystemRoot.WIDTH + 2 * CANVAS_PADDING; + + for (final ComponentInstance instance : getSystem().getComponentInstances()) { + minWidth = Math.max(minWidth, instance.getBox().getX() + instance.getBox().getWidth() + CANVAS_PADDING); + } + + for (final ComponentOperator operator : getSystem().getComponentOperators()) { + minWidth = Math.max(minWidth, operator.getBox().getX() + operator.getBox().getWidth() + CANVAS_PADDING); + } + + return minWidth; + } + + /** + * Gets the minimum allowed height when dragging the anchor. + * It is determined by the position and size of the system nodes. + * @return the minimum allowed height + */ + @Override + double getDragAnchorMinHeight() { + double minHeight = 10 * CANVAS_PADDING; + + for (final ComponentInstance instance : getSystem().getComponentInstances()) { + minHeight = Math.max(minHeight, instance.getBox().getY() + instance.getBox().getHeight() + CANVAS_PADDING); + } + + for (final ComponentOperator operator : getSystem().getComponentOperators()) { + minHeight = Math.max(minHeight, operator.getBox().getY() + operator.getBox().getHeight() + CANVAS_PADDING); + } + + return minHeight; + } } diff --git a/src/main/java/ecdar/controllers/SystemEdgeController.java b/src/main/java/ecdar/controllers/SystemEdgeController.java index de846446..5901b169 100644 --- a/src/main/java/ecdar/controllers/SystemEdgeController.java +++ b/src/main/java/ecdar/controllers/SystemEdgeController.java @@ -1,7 +1,7 @@ package ecdar.controllers; import ecdar.abstractions.EcdarSystem; -import ecdar.abstractions.EcdarSystemEdge; +import ecdar.abstractions.SystemEdge; import ecdar.presentations.DropDownMenu; import ecdar.utility.helpers.SelectHelper; import ecdar.utility.keyboard.Keybind; @@ -27,7 +27,7 @@ public class SystemEdgeController implements Initializable { public Group root; - private EcdarSystemEdge edge; + private SystemEdge edge; private final ObjectProperty<EcdarSystem> system = new SimpleObjectProperty<>(); private SelectHelper.ItemSelectable selectable; @@ -48,11 +48,11 @@ public void initialize(final URL location, final ResourceBundle resources) { }); } - public EcdarSystemEdge getEdge() { + public SystemEdge getEdge() { return edge; } - public void setEdge(final EcdarSystemEdge edge) { + public void setEdge(final SystemEdge edge) { this.edge = edge; } diff --git a/src/main/java/ecdar/controllers/SystemRootController.java b/src/main/java/ecdar/controllers/SystemRootController.java index c3a81562..38c40053 100644 --- a/src/main/java/ecdar/controllers/SystemRootController.java +++ b/src/main/java/ecdar/controllers/SystemRootController.java @@ -1,6 +1,6 @@ package ecdar.controllers; -import ecdar.abstractions.EcdarSystemEdge; +import ecdar.abstractions.SystemEdge; import ecdar.abstractions.EcdarSystem; import ecdar.abstractions.SystemRoot; import ecdar.presentations.DropDownMenu; @@ -85,7 +85,7 @@ private void initializeDropDownMenu(final EcdarSystem system) { * Listens to an edge to update whether the root has an edge. * @param edge the edge to update with */ - private void handleHasEdge(final EcdarSystemEdge edge) { + private void handleHasEdge(final SystemEdge edge) { edge.getTempNodeProperty().addListener((observable -> updateHasEdge(edge))); edge.getChildProperty().addListener((observable -> updateHasEdge(edge))); edge.getParentProperty().addListener((observable -> updateHasEdge(edge))); @@ -95,7 +95,7 @@ private void handleHasEdge(final EcdarSystemEdge edge) { * Update has edge property to whether the root is in a given edge. * @param edge the given edge */ - private void updateHasEdge(final EcdarSystemEdge edge) { + private void updateHasEdge(final SystemEdge edge) { hasEdge.set(edge.isInEdge(getSystemRoot())); } @@ -103,7 +103,7 @@ private void updateHasEdge(final EcdarSystemEdge edge) { private void onMouseClicked(final MouseEvent event) { event.consume(); - final EcdarSystemEdge unfinishedEdge = getSystem().getUnfinishedEdge(); + final SystemEdge unfinishedEdge = getSystem().getUnfinishedEdge(); if ((event.isShiftDown() && event.getButton().equals(MouseButton.PRIMARY)) || event.getButton().equals(MouseButton.MIDDLE)) { // If shift click or middle click a component instance, create a new edge @@ -131,11 +131,11 @@ private void onMouseClicked(final MouseEvent event) { } /*** - * Helper method to create a new EcdarSystemEdge and add it to the current system and system root - * @return The newly created EcdarSystemEdge + * Helper method to create a new SystemEdge and add it to the current system and system root + * @return The newly created SystemEdge */ - private EcdarSystemEdge createNewSystemEdge() { - final EcdarSystemEdge edge = new EcdarSystemEdge(systemRoot); + private SystemEdge createNewSystemEdge() { + final SystemEdge edge = new SystemEdge(systemRoot); getSystem().addEdge(edge); hasEdge.set(true); handleHasEdge(edge); diff --git a/src/main/java/ecdar/controllers/TracePaneElementController.java b/src/main/java/ecdar/controllers/TracePaneElementController.java index ce5cea76..505a58b7 100755 --- a/src/main/java/ecdar/controllers/TracePaneElementController.java +++ b/src/main/java/ecdar/controllers/TracePaneElementController.java @@ -42,7 +42,7 @@ public class TracePaneElementController implements Initializable { @Override public void initialize(URL location, ResourceBundle resources) { - simulationHandler = Ecdar.getSimulationHandler(); + simulationHandler = SimulatorController.getSimulationHandler(); simulationHandler.getTraceLog().addListener((ListChangeListener<SimulationState>) c -> { while (c.next()) { @@ -64,7 +64,7 @@ public void initialize(URL location, ResourceBundle resources) { /** * Initializes the expand functionality that allows the user to show or hide the trace. - * By default the trace is shown. + * By default, the trace is shown. */ private void initializeTraceExpand() { isTraceExpanded.addListener((obs, oldVal, newVal) -> { diff --git a/src/main/java/ecdar/controllers/TransitionPaneElementController.java b/src/main/java/ecdar/controllers/TransitionPaneElementController.java index 3772f46b..a0872b92 100755 --- a/src/main/java/ecdar/controllers/TransitionPaneElementController.java +++ b/src/main/java/ecdar/controllers/TransitionPaneElementController.java @@ -45,7 +45,7 @@ public class TransitionPaneElementController implements Initializable { @Override public void initialize(URL location, ResourceBundle resources) { - simulationHandler = Ecdar.getSimulationHandler(); + simulationHandler = SimulatorController.getSimulationHandler(); initializeTransitionExpand(); initializeDelayChooser(); } diff --git a/src/main/java/ecdar/issues/ExitStatusCodes.java b/src/main/java/ecdar/issues/ExitStatusCodes.java new file mode 100644 index 00000000..5fc46972 --- /dev/null +++ b/src/main/java/ecdar/issues/ExitStatusCodes.java @@ -0,0 +1,17 @@ +package ecdar.issues; + +/** + * Enum for representing the status of a requested exit + */ +public enum ExitStatusCodes { + SHUTDOWN_SUCCESSFUL(0), + GRACEFUL_SHUTDOWN_FAILED(-1), + CLOSE_ENGINE_CONNECTIONS_FAILED(-2); + + private final int statusCode; + ExitStatusCodes(int statusCode) { this.statusCode = statusCode; } + + public int getStatusCode() { + return statusCode; + } +} \ No newline at end of file diff --git a/src/main/java/ecdar/mutation/ComponentVerificationTransformer.java b/src/main/java/ecdar/mutation/ComponentVerificationTransformer.java new file mode 100644 index 00000000..cba15670 --- /dev/null +++ b/src/main/java/ecdar/mutation/ComponentVerificationTransformer.java @@ -0,0 +1,245 @@ +package ecdar.mutation; + +import com.bpodgursky.jbool_expressions.*; +import com.bpodgursky.jbool_expressions.rules.RuleSet; +import ecdar.abstractions.*; +import ecdar.utility.ExpressionHelper; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class ComponentVerificationTransformer { + /** + * Applies demonic completion on this component. + */ + public static void applyDemonicCompletionToComponent(final Component component) { + // Make a universal location + final Location uniLocation = new Location(component, Location.Type.UNIVERSAL, component.getUniqueLocationId(), 0, 0); + component.addLocation(uniLocation); + + final Edge inputEdge = uniLocation.addLeftEdge("*", EdgeStatus.INPUT); + inputEdge.setIsLocked(true); + component.addEdge(inputEdge); + + final Edge outputEdge = uniLocation.addRightEdge("*", EdgeStatus.OUTPUT); + outputEdge.setIsLocked(true); + component.addEdge(outputEdge); + + // Cache input signature, since it could be updated when added edges + final List<String> inputStrings = new ArrayList<>(component.getInputStrings()); + + component.getLocations().forEach(location -> inputStrings.forEach(input -> { + final List<Edge> matchingEdges = getOutgoingInputEdgesFromLocationWithSync(component, location, input); + if (matchingEdges.isEmpty()) return; + + // Extract expression for which edges to create. + // The expression is in DNF + // We create edges to Universal for each child expression in the disjunction. + createDemonicEdgesForComponent(component, location, uniLocation, input, getNegatedEdgeExpressionForComponent(component, matchingEdges)); + })); + } + + /** + * Applies angelic completion on this component. + */ + public static void applyAngelicCompletionForComponent(final Component component) { + // Cache input signature, since it could be updated when added edges + final List<String> inputStrings = new ArrayList<>(component.getInputStrings()); + + component.getLocations().forEach(location -> inputStrings.forEach(input -> { + final List<Edge> matchingEdges = getOutgoingInputEdgesFromLocationWithSync(component, location, input); + if (matchingEdges.isEmpty()) return; + + // Extract expression for which edges to create. + // The expression is in DNF + // We create self loops for each child expression in the disjunction. + createAngelicSelfLoopsForComponent(component, location, input, getNegatedEdgeExpressionForComponent(component, matchingEdges)); + })); + } + + /** + * Creates a clone of another component. + * Copy objects used for verification (e.g. locations, edges and the declarations). + * Does not copy UI elements (sizes and positions). + * Its locations are cloned from the original component. Their ids are the same. + * Does not initialize io listeners, but copies the input and output strings. + * Reachability analysis binding is not initialized. + * @return the clone + */ + public static Component cloneForVerification(final Component component) { + final Component clone = new Component(); + addVerificationObjects(component, clone); + clone.setIncludeInPeriodicCheck(false); + clone.getInputStrings().addAll(component.getInputStrings()); + clone.getOutputStrings().addAll(component.getOutputStrings()); + clone.setName(component.getName()); + + return clone; + } + + /** + * Get the input edges starting from the location with the specified sync. + * If no edges match, this method adds a self loop on the location with the sync. + * @param component containing component + * @param location source location to check outgoing edges + * @param sync desired sync to check for and possibly create self-loop with + * @return list of matching edges (if empty, the self-loop might have been created) + */ + private static List<Edge> getOutgoingInputEdgesFromLocationWithSync(Component component, Location location, String sync) { + // Get outgoing input edges that has the chosen sync + final List<Edge> matchingEdges = component.getListOfEdgesFromDisplayableEdges(component.getOutgoingEdges(location)).stream().filter( + edge -> edge.getStatus().equals(EdgeStatus.INPUT) && + edge.getSync().equals(sync)).collect(Collectors.toList() + ); + + // If no such edges, add a self loop without a guard + if (matchingEdges.isEmpty()) { + final Edge edge = new Edge(location, EdgeStatus.INPUT); + edge.setTargetLocation(location); + edge.addSyncNail(sync); + component.addEdge(edge); + return matchingEdges; + } + + // If an edge has no guard and its target has no invariants, ignore. + // Component is already input-enabled with respect to this location and input. + if (matchingEdges.stream().anyMatch(edge -> edge.getGuard().isEmpty() && + edge.getTargetLocation().getInvariant().isEmpty())) return matchingEdges; + + return matchingEdges; + } + + /** + * Creates edges to a specified Universal location from a location + * in order to finish missing inputs with a demonic completion. + * + * @param location the location to create self loops on + * @param universal the Universal location to create edge to + * @param input the input action to use in the synchronization properties + * @param guardExpression the expression that represents the guards of the edges to create + */ + private static void createDemonicEdgesForComponent(final Component component, final Location location, final Location universal, final String input, final Expression<String> guardExpression) { + final Edge edge; + + switch (guardExpression.getExprType()) { + case Literal.EXPR_TYPE: + // If false, do not create any edges + if (!((Literal<String>) guardExpression).getValue()) break; + + // It should never be true, since that should be handled before calling this method + throw new RuntimeException("Type of expression " + guardExpression + " not accepted"); + case Variable.EXPR_TYPE: + edge = new Edge(location, EdgeStatus.INPUT); + edge.setTargetLocation(universal); + edge.addSyncNail(input); + edge.addGuardNail(((Variable<String>) guardExpression).getValue()); + component.addEdge(edge); + break; + case And.EXPR_TYPE: + edge = new Edge(location, EdgeStatus.INPUT); + edge.setTargetLocation(universal); + edge.addSyncNail(input); + edge.addGuardNail(guardExpression.getChildren().stream() + .map(child -> ((Variable<String>) child).getValue()) + .collect(Collectors.joining("&&"))); + component.addEdge(edge); + break; + case Or.EXPR_TYPE: + guardExpression.getChildren().forEach(child -> createDemonicEdgesForComponent(component, location, universal, input, child)); + break; + default: + throw new RuntimeException("Type of expression " + guardExpression + " not accepted"); + } + } + + /** + * Extracts an expression that represents the negation of a list of edges. + * Multiple edges are resolved as disjunctions. + * There can be conjunction in guards. + * We translate each edge to the conjunction of its guard and the invariant of its target location + * (since it should also be satisfied). + * The result is converted to disjunctive normal form. + * + * @param edges the edges to extract from + * @return the expression that represents the negation + */ + private static Expression<String> getNegatedEdgeExpressionForComponent(final Component component, List<Edge> edges) { + final List<String> clocks = component.getClocks(); + return ExpressionHelper.simplifyNegatedSimpleExpressions( + RuleSet.toDNF(RuleSet.simplify(Not.of(Or.of(edges.stream() + .map(edge -> { + final List<String> clocksToReset = ExpressionHelper.getUpdateSides(edge.getUpdate()) + .keySet().stream().filter(clocks::contains).collect(Collectors.toList()); + return And.of( + ExpressionHelper.parseGuard(edge.getGuard()), + ExpressionHelper.parseInvariantButIgnore(edge.getTargetLocation().getInvariant(), clocksToReset) + ); + } + ).collect(Collectors.toList())) + )))); + } + + /** + * Creates self loops on a location to finish missing inputs with an angelic completion. + * The guard expression should be in DNF without negations. + * + * @param location the location to create self loops on + * @param input the input action to use in the synchronization properties + * @param guardExpression the expression that represents the guards of the self loops + */ + private static void createAngelicSelfLoopsForComponent(final Component component, Location location, final String input, final Expression<String> guardExpression) { + final Edge edge; + + switch (guardExpression.getExprType()) { + case Literal.EXPR_TYPE: + // If false, do not create any loops + if (!((Literal<String>) guardExpression).getValue()) break; + + // It should never be true, since that should be handled before calling this method + throw new RuntimeException("Type of expression " + guardExpression + " not accepted"); + case Variable.EXPR_TYPE: + edge = new Edge(location, EdgeStatus.INPUT); + edge.setTargetLocation(location); + edge.addSyncNail(input); + edge.addGuardNail(((Variable<String>) guardExpression).getValue()); + component.addEdge(edge); + break; + case And.EXPR_TYPE: + edge = new Edge(location, EdgeStatus.INPUT); + edge.setTargetLocation(location); + edge.addSyncNail(input); + edge.addGuardNail(guardExpression.getChildren().stream() + .map(child -> { + if (!child.getExprType().equals(Variable.EXPR_TYPE)) + throw new RuntimeException("Child " + child + " of type " + + child.getExprType() + " in and expression " + + guardExpression + " should be a variable"); + + return ((Variable<String>) child).getValue(); + }) + .collect(Collectors.joining("&&"))); + component.addEdge(edge); + break; + case Or.EXPR_TYPE: + guardExpression.getChildren().forEach(child -> createAngelicSelfLoopsForComponent(component, location, input, child)); + break; + default: + throw new RuntimeException("Type of expression " + guardExpression + " not accepted"); + } + } + + /** + * Adds objects used for verifications from original to clone. + * @param original the component to add from + * @param clone the component to add to + */ + private static void addVerificationObjects(final Component original, final Component clone) { + for (final Location originalLoc : original.getLocations()) { + clone.addLocation(originalLoc.cloneForVerification()); + } + + clone.getListOfEdgesFromDisplayableEdges(original.getDisplayableEdges()).forEach(edge -> clone.addEdge((edge).cloneForVerification(original))); + clone.setDeclarationsText(original.getDeclarationsText()); + } +} diff --git a/src/main/java/ecdar/mutation/ExportHandler.java b/src/main/java/ecdar/mutation/ExportHandler.java index d75dcdfc..9f943a89 100644 --- a/src/main/java/ecdar/mutation/ExportHandler.java +++ b/src/main/java/ecdar/mutation/ExportHandler.java @@ -79,7 +79,7 @@ void start() { // Apply angelic completion if selected if (getPlan().isAngelicWhenExport()) - cases.stream().map(MutationTestCase::getMutant).forEach(Component::applyAngelicCompletion); + cases.stream().map(MutationTestCase::getMutant).forEach(ComponentVerificationTransformer::applyAngelicCompletionForComponent); getPlan().setMutantsText("Mutants: " + cases.size() + " - Execution time: " + MutationTestPlanPresentation.readableFormat(Duration.between(start, Instant.now()))); diff --git a/src/main/java/ecdar/mutation/MutationHandler.java b/src/main/java/ecdar/mutation/MutationHandler.java index 44f091e3..f4a3db16 100644 --- a/src/main/java/ecdar/mutation/MutationHandler.java +++ b/src/main/java/ecdar/mutation/MutationHandler.java @@ -36,9 +36,6 @@ public MutationHandler(final Component testModel, final MutationTestPlan plan, f this.consumer = consumer; } - - /* Properties */ - private Component getTestModel() { return testModel; } @@ -51,9 +48,6 @@ private Consumer<List<MutationTestCase>> getConsumer() { return consumer; } - - /* Other */ - /** * Starts. */ @@ -85,7 +79,7 @@ public void start() { return; } - cases.forEach(testCase -> testCase.getMutant().applyAngelicCompletion()); + cases.forEach(testCase -> ComponentVerificationTransformer.applyAngelicCompletionForComponent(testCase.getMutant())); Platform.runLater(() -> getPlan().setMutantsText("Mutants: " + cases.size() + " - Mutation time: " + MutationTestPlanPresentation.readableFormat(Duration.between(start, Instant.now()))) @@ -94,7 +88,7 @@ public void start() { testModel.setName(MutationTestPlanController.SPEC_NAME); // If chosen, apply demonic completion - if (getPlan().isDemonic()) testModel.applyDemonicCompletion(); + if (getPlan().isDemonic()) ComponentVerificationTransformer.applyDemonicCompletionToComponent(testModel); //Rename universal and inconsistent locations testModel.getLocations().forEach(location -> { @@ -116,4 +110,5 @@ public void start() { Platform.runLater(() -> getConsumer().accept(cases)); }).start(); } + } diff --git a/src/main/java/ecdar/mutation/MutationTestPlanController.java b/src/main/java/ecdar/mutation/MutationTestPlanController.java index b98a5b82..888d8a31 100644 --- a/src/main/java/ecdar/mutation/MutationTestPlanController.java +++ b/src/main/java/ecdar/mutation/MutationTestPlanController.java @@ -6,6 +6,8 @@ import com.jfoenix.controls.JFXTextField; import ecdar.Ecdar; import ecdar.abstractions.Component; +import ecdar.abstractions.HighLevelModel; +import ecdar.controllers.HighLevelModelController; import ecdar.mutation.models.MutationTestCase; import ecdar.mutation.models.MutationTestPlan; import ecdar.mutation.models.TestResult; @@ -30,7 +32,7 @@ /** * Controller for a test plan with model-based mutation testing. */ -public class MutationTestPlanController { +public class MutationTestPlanController extends HighLevelModelController { public final static String SPEC_NAME = "S"; public final static String MUTANT_NAME = "M"; @@ -105,16 +107,10 @@ public class MutationTestPlanController { public Text failedNumber; public Text primaryFailedNumber; - - /* Mutation fields */ - private MutationTestPlan plan; private TestingHandler testingHandler; public final ObservableList<TestResult> resultsToShow = new SimpleListProperty<>(FXCollections.observableArrayList()); - - /* Properties */ - public MutationTestPlan getPlan() { return plan; } @@ -128,8 +124,6 @@ public TestingHandler getTestingHandler() { return testingHandler; } - /* Other methods */ - /** * Triggered when pressed the test button. * Conducts the test. @@ -139,7 +133,7 @@ public void onTestButtonPressed() { // Find test model from test model picker // Clone it, because we want to change its name - final Component testModel = Ecdar.getProject().findComponent(modelPicker.getValue().getText()).cloneForVerification(); + final Component testModel = ComponentVerificationTransformer.cloneForVerification(plan.getTestModel()); new MutationHandler(testModel, getPlan(), cases -> startGeneration(testModel, cases)).start(); } @@ -234,4 +228,9 @@ public void onExportButtonPressed() { getPlan().clearResults(); new ExportHandler(getPlan(), Ecdar.getProject().findComponent(modelPicker.getValue().getText())).start(); } + + @Override + public HighLevelModel getModel() { + return plan; + } } \ No newline at end of file diff --git a/src/main/java/ecdar/mutation/MutationTestPlanPresentation.java b/src/main/java/ecdar/mutation/MutationTestPlanPresentation.java index a50c6f4e..8f299be8 100644 --- a/src/main/java/ecdar/mutation/MutationTestPlanPresentation.java +++ b/src/main/java/ecdar/mutation/MutationTestPlanPresentation.java @@ -6,6 +6,7 @@ import ecdar.Ecdar; import ecdar.abstractions.Component; import ecdar.controllers.EcdarController; +import ecdar.controllers.HighLevelModelController; import ecdar.mutation.models.ExpandableContent; import ecdar.mutation.models.MutationTestPlan; import ecdar.mutation.models.TestResult; @@ -550,19 +551,19 @@ private void initializeModelPicker() { * Initializes width and height of the text editor field, such that it fills up the whole canvas. */ private void initializeWidthAndHeight() { - controller.scrollPane.setPrefWidth(EcdarController.getActiveCanvasPresentation().getController().getWidthProperty().doubleValue()); - EcdarController.getActiveCanvasPresentation().getController().getWidthProperty().addListener((observable, oldValue, newValue) -> + controller.scrollPane.setPrefWidth(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getWidthProperty().doubleValue()); + Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getWidthProperty().addListener((observable, oldValue, newValue) -> controller.scrollPane.setPrefWidth(newValue.doubleValue())); - updateOffset(EcdarController.getActiveCanvasPresentation().getController().getInsetShouldShow().get()); - EcdarController.getActiveCanvasPresentation().getController().getInsetShouldShow().addListener((observable, oldValue, newValue) -> { + updateOffset(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getInsetShouldShow().get()); + Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getInsetShouldShow().addListener((observable, oldValue, newValue) -> { updateOffset(newValue); updateHeight(); }); - canvasHeight = EcdarController.getActiveCanvasPresentation().getController().getHeightProperty().doubleValue(); + canvasHeight = Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getHeightProperty().doubleValue(); updateHeight(); - EcdarController.getActiveCanvasPresentation().getController().getHeightProperty().addListener((observable, oldValue, newValue) -> { + Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getHeightProperty().addListener((observable, oldValue, newValue) -> { canvasHeight = newValue.doubleValue(); updateHeight(); }); @@ -589,7 +590,7 @@ private void updateOffset(final boolean shouldHave) { * Updates the height of the view. */ private void updateHeight() { - controller.scrollPane.setPrefHeight(canvasHeight - EcdarController.getActiveCanvasPresentation().getController().DECLARATION_Y_MARGIN - offSet); + controller.scrollPane.setPrefHeight(canvasHeight - Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().DECLARATION_Y_MARGIN - offSet); } /** @@ -699,4 +700,8 @@ private void initializeFormatPicker() { getPlan().setFormat(newValue.getText()))); } + @Override + public HighLevelModelController getController() { + return controller; + } } diff --git a/src/main/java/ecdar/mutation/TextFlowBuilder.java b/src/main/java/ecdar/mutation/TextFlowBuilder.java index 74e55aa9..291f1641 100644 --- a/src/main/java/ecdar/mutation/TextFlowBuilder.java +++ b/src/main/java/ecdar/mutation/TextFlowBuilder.java @@ -4,7 +4,6 @@ import ecdar.abstractions.Component; import ecdar.abstractions.Edge; import ecdar.abstractions.Location; -import ecdar.controllers.EcdarController; import ecdar.utility.helpers.SelectHelper; import javafx.application.Platform; import javafx.collections.FXCollections; @@ -94,7 +93,7 @@ public static Text createLocationLink(final String locationId, final String comp } SelectHelper.elementsToBeSelected = FXCollections.observableArrayList(); - EcdarController.setActiveModelForActiveCanvas(component); + Ecdar.getPresentation().getController().getEditorPresentation().getController().setActiveModelPresentationForActiveCanvas(Ecdar.getComponentPresentationOfComponent(component)); // Use a list, since there could be multiple locations (e.i. Universal locations) final List<Location> locations = component.getLocations().filtered(loc -> loc.getId().equals(locationId)); diff --git a/src/main/java/ecdar/mutation/models/MutationTestPlan.java b/src/main/java/ecdar/mutation/models/MutationTestPlan.java index add18dce..96448519 100644 --- a/src/main/java/ecdar/mutation/models/MutationTestPlan.java +++ b/src/main/java/ecdar/mutation/models/MutationTestPlan.java @@ -4,7 +4,7 @@ import com.google.gson.JsonPrimitive; import ecdar.Ecdar; import ecdar.abstractions.Component; -import ecdar.abstractions.HighLevelModelObject; +import ecdar.abstractions.HighLevelModel; import ecdar.mutation.VisibilityHelper; import ecdar.mutation.operators.MutationOperator; import javafx.beans.property.*; @@ -18,7 +18,7 @@ /** * A test plan for conducting model-based mutation testing on a component. */ -public class MutationTestPlan extends HighLevelModelObject { +public class MutationTestPlan extends HighLevelModel { /** * The status of the test plan. * STOPPING: Stopped by the user diff --git a/src/main/java/ecdar/mutation/operators/ChangeActionOperator.java b/src/main/java/ecdar/mutation/operators/ChangeActionOperator.java index 3ab7bf2c..a807a0b3 100644 --- a/src/main/java/ecdar/mutation/operators/ChangeActionOperator.java +++ b/src/main/java/ecdar/mutation/operators/ChangeActionOperator.java @@ -3,6 +3,7 @@ import ecdar.abstractions.Component; import ecdar.abstractions.Edge; import ecdar.abstractions.EdgeStatus; +import ecdar.mutation.ComponentVerificationTransformer; import ecdar.mutation.TextFlowBuilder; import ecdar.mutation.models.MutationTestCase; @@ -25,7 +26,7 @@ MutationTestCase generateTestCase(final Component original, final int edgeIndex, // If action is the action of the original edge, ignore if (originalEdge.getStatus().equals(status) && originalEdge.getSync().equals(sync)) return null; - final Component mutant = original.cloneForVerification(); + final Component mutant = ComponentVerificationTransformer.cloneForVerification(original); // Mutate final Edge mutantEdge = mutant.getEdges().get(edgeIndex); diff --git a/src/main/java/ecdar/mutation/operators/ChangeGuardConstantOperator.java b/src/main/java/ecdar/mutation/operators/ChangeGuardConstantOperator.java index 5c2ec5a6..7d991aec 100644 --- a/src/main/java/ecdar/mutation/operators/ChangeGuardConstantOperator.java +++ b/src/main/java/ecdar/mutation/operators/ChangeGuardConstantOperator.java @@ -2,6 +2,7 @@ import ecdar.abstractions.Component; import ecdar.abstractions.Edge; +import ecdar.mutation.ComponentVerificationTransformer; import ecdar.mutation.TextFlowBuilder; import ecdar.mutation.models.MutationTestCase; @@ -41,7 +42,7 @@ public List<MutationTestCase> generateTestCases(final Component original) { int index = 0; while (matcher.find()) { { - final Component mutant = original.cloneForVerification(); + final Component mutant = ComponentVerificationTransformer.cloneForVerification(original); final Edge mutantEdge = mutant.getEdges().get(edgeIndex); final int newNumber = Integer.parseInt(matcher.group(1)) + 1; @@ -54,7 +55,7 @@ public List<MutationTestCase> generateTestCases(final Component original) { .text(" to ").boldText(mutantEdge.getGuard()).build() )); } { - final Component mutant = original.cloneForVerification(); + final Component mutant = ComponentVerificationTransformer.cloneForVerification(original); final Edge mutantEdge = mutant.getEdges().get(edgeIndex); final int newNumber = Integer.parseInt(matcher.group(1)) -1; diff --git a/src/main/java/ecdar/mutation/operators/ChangeGuardOpOperator.java b/src/main/java/ecdar/mutation/operators/ChangeGuardOpOperator.java index ce12beb4..f2645493 100644 --- a/src/main/java/ecdar/mutation/operators/ChangeGuardOpOperator.java +++ b/src/main/java/ecdar/mutation/operators/ChangeGuardOpOperator.java @@ -2,6 +2,7 @@ import ecdar.abstractions.Component; import ecdar.abstractions.Edge; +import ecdar.mutation.ComponentVerificationTransformer; import ecdar.mutation.MutationTestingException; import ecdar.mutation.TextFlowBuilder; import ecdar.mutation.models.MutationTestCase; @@ -108,7 +109,7 @@ private static boolean containsVar(final String expr, final List<String> vars) { */ private static Component createMutant(final Component original, final String[] originalSimpleGuards, final String newSimpleGuard, final int simpleGuardIndex, final int edgeIndex) { - final Component mutant = original.cloneForVerification(); + final Component mutant = ComponentVerificationTransformer.cloneForVerification(original); final String[] newSimpleGuards = originalSimpleGuards.clone(); newSimpleGuards[simpleGuardIndex] = newSimpleGuard; diff --git a/src/main/java/ecdar/mutation/operators/ChangeInvariantOperator.java b/src/main/java/ecdar/mutation/operators/ChangeInvariantOperator.java index 64038a54..878d192f 100644 --- a/src/main/java/ecdar/mutation/operators/ChangeInvariantOperator.java +++ b/src/main/java/ecdar/mutation/operators/ChangeInvariantOperator.java @@ -2,6 +2,7 @@ import ecdar.abstractions.Component; import ecdar.abstractions.Location; +import ecdar.mutation.ComponentVerificationTransformer; import ecdar.mutation.TextFlowBuilder; import ecdar.mutation.models.MutationTestCase; @@ -39,7 +40,7 @@ public List<MutationTestCase> generateTestCases(final Component original) { final List<String> invariantParts = Arrays.stream(originalLocation.getInvariant() .split("&&")).map(String::trim).collect(Collectors.toList()); for (int partIndex = 0; partIndex < invariantParts.size(); partIndex++) { - final Component mutant = original.cloneForVerification(); + final Component mutant = ComponentVerificationTransformer.cloneForVerification(original); final List<String> newParts = new ArrayList<>(invariantParts); newParts.set(partIndex, newParts.get(partIndex) + " + 1"); diff --git a/src/main/java/ecdar/mutation/operators/ChangeSourceOperator.java b/src/main/java/ecdar/mutation/operators/ChangeSourceOperator.java index cdbac5e4..72d908fc 100644 --- a/src/main/java/ecdar/mutation/operators/ChangeSourceOperator.java +++ b/src/main/java/ecdar/mutation/operators/ChangeSourceOperator.java @@ -3,6 +3,7 @@ import ecdar.abstractions.Component; import ecdar.abstractions.Edge; import ecdar.abstractions.Location; +import ecdar.mutation.ComponentVerificationTransformer; import ecdar.mutation.TextFlowBuilder; import ecdar.mutation.models.MutationTestCase; @@ -46,7 +47,7 @@ public List<MutationTestCase> generateTestCases(final Component original) { if (originalLocation.getType().equals(Location.Type.INCONSISTENT) || originalLocation.getType().equals(Location.Type.UNIVERSAL)) continue; - final Component mutant = original.cloneForVerification(); + final Component mutant = ComponentVerificationTransformer.cloneForVerification(original); // Mutate final Edge mutantEdge = mutant.getEdges().get(edgeIndex); diff --git a/src/main/java/ecdar/mutation/operators/ChangeTargetOperator.java b/src/main/java/ecdar/mutation/operators/ChangeTargetOperator.java index 2b0e3e45..56ddee6a 100644 --- a/src/main/java/ecdar/mutation/operators/ChangeTargetOperator.java +++ b/src/main/java/ecdar/mutation/operators/ChangeTargetOperator.java @@ -3,6 +3,7 @@ import ecdar.abstractions.Component; import ecdar.abstractions.Edge; import ecdar.abstractions.Location; +import ecdar.mutation.ComponentVerificationTransformer; import ecdar.mutation.TextFlowBuilder; import ecdar.mutation.models.MutationTestCase; @@ -39,7 +40,7 @@ public List<MutationTestCase> generateTestCases(final Component original) { // Ignore if location is target in original edge if (originalEdge.getTargetLocation() == originalLocation) continue; - final Component mutant = original.cloneForVerification(); + final Component mutant = ComponentVerificationTransformer.cloneForVerification(original); // Mutate final Edge mutantEdge = mutant.getEdges().get(edgeIndex); diff --git a/src/main/java/ecdar/mutation/operators/ChangeVarUpdateOperator.java b/src/main/java/ecdar/mutation/operators/ChangeVarUpdateOperator.java index 93980f73..80b6707d 100644 --- a/src/main/java/ecdar/mutation/operators/ChangeVarUpdateOperator.java +++ b/src/main/java/ecdar/mutation/operators/ChangeVarUpdateOperator.java @@ -1,15 +1,17 @@ package ecdar.mutation.operators; +import ecdar.Ecdar; import ecdar.abstractions.Component; import ecdar.abstractions.Edge; +import ecdar.mutation.ComponentVerificationTransformer; import ecdar.mutation.TextFlowBuilder; import ecdar.mutation.models.MutationTestCase; import ecdar.utility.ExpressionHelper; import org.apache.commons.lang3.tuple.Triple; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Mutation operator that changes the assignment of local variables. @@ -27,7 +29,7 @@ public String getCodeName() { @Override public List<MutationTestCase> generateTestCases(final Component original) { - final List<Triple<String, Integer, Integer>> locals = original.getLocalVariablesWithBounds(); + final List<Triple<String, Integer, Integer>> variablesWithBounds = getLocalComponentVariablesWithBounds(original); final List<MutationTestCase> cases = new ArrayList<>(); @@ -43,12 +45,12 @@ public List<MutationTestCase> generateTestCases(final Component original) { // For each variable final int finalEdgeIndex = edgeIndex; - locals.forEach(local -> { + variablesWithBounds.forEach(local -> { // If variable is not assigned, add it if (sides.get(local.getLeft()) == null) { // For each possible assignment of that variable for (int value = local.getMiddle(); value <= local.getRight(); value++) { - final Component mutant = original.cloneForVerification(); + final Component mutant = ComponentVerificationTransformer.cloneForVerification(original); final Edge mutantEdge = mutant.getEdges().get(finalEdgeIndex); final List<String> newSimpleUpdates = new ArrayList<>(); @@ -73,7 +75,7 @@ public List<MutationTestCase> generateTestCases(final Component original) { // If this is already the original assignment, ignore if (sides.get(local.getLeft()).equals(String.valueOf(value))) continue; - final Component mutant = original.cloneForVerification(); + final Component mutant = ComponentVerificationTransformer.cloneForVerification(original); final Edge mutantEdge = mutant.getEdges().get(finalEdgeIndex); final List<String> newSimpleUpdates = new ArrayList<>(); @@ -102,6 +104,31 @@ public List<MutationTestCase> generateTestCases(final Component original) { return cases; } + /** + * Gets the local variables defined in the declarations text. + * Also gets the lower and upper bounds for these variables. + * + * @return Triples containing (left) name of the variable, (middle) lower bound, (right) upper bound + */ + private List<Triple<String, Integer, Integer>> getLocalComponentVariablesWithBounds(Component component) { + final List<Triple<String, Integer, Integer>> typedefs = Ecdar.getProject().getGlobalDeclarations().getTypedefs(); + + final List<Triple<String, Integer, Integer>> locals = new ArrayList<>(); + + Arrays.stream(component.getDeclarationsText().split(";")).forEach(statement -> { + final Matcher matcher = Pattern.compile("^\\s*(\\w+)\\s+(\\w+)(\\W|$)").matcher(statement); + if (!matcher.find()) return; + + final Optional<Triple<String, Integer, Integer>> typedef = typedefs.stream() + .filter(def -> def.getLeft().equals(matcher.group(1))).findAny(); + if (typedef.isEmpty()) return; + + locals.add(Triple.of(matcher.group(2), typedef.get().getMiddle(), typedef.get().getRight())); + }); + + return locals; + } + @Override public String getDescription() { return "Changes the assignment (or adds, if the variable is not assigned in corresponding edge) of a local variable " + @@ -114,10 +141,9 @@ public String getDescription() { @Override public int getUpperLimit(final Component original) { // Get the sum of valuations of each variable - final int varValueCount = original.getLocalVariablesWithBounds().stream().mapToInt(local -> local.getRight() - local.getMiddle() + 1).sum(); + final int varValueCount = getLocalComponentVariablesWithBounds(original).stream().mapToInt(local -> local.getRight() - local.getMiddle() + 1).sum(); return original.getDisplayableEdges().size() * varValueCount; - } @Override diff --git a/src/main/java/ecdar/mutation/operators/InvertResetOperator.java b/src/main/java/ecdar/mutation/operators/InvertResetOperator.java index 0ecae0a6..c3aecc7f 100644 --- a/src/main/java/ecdar/mutation/operators/InvertResetOperator.java +++ b/src/main/java/ecdar/mutation/operators/InvertResetOperator.java @@ -3,6 +3,7 @@ import com.google.common.collect.Lists; import ecdar.abstractions.Component; import ecdar.abstractions.Edge; +import ecdar.mutation.ComponentVerificationTransformer; import ecdar.mutation.TextFlowBuilder; import ecdar.mutation.models.MutationTestCase; @@ -39,7 +40,7 @@ public List<MutationTestCase> generateTestCases(final Component original) { // For each clock final int finalEdgeIndex = edgeIndex; clocks.forEach(clock -> { - final Component mutant = original.cloneForVerification(); + final Component mutant = ComponentVerificationTransformer.cloneForVerification(original); final Edge mutantEdge = mutant.getEdges().get(finalEdgeIndex); // Mutate diff --git a/src/main/java/ecdar/mutation/operators/SinkLocationOperator.java b/src/main/java/ecdar/mutation/operators/SinkLocationOperator.java index b0862c42..721d2329 100644 --- a/src/main/java/ecdar/mutation/operators/SinkLocationOperator.java +++ b/src/main/java/ecdar/mutation/operators/SinkLocationOperator.java @@ -4,6 +4,7 @@ import ecdar.abstractions.Edge; import ecdar.abstractions.EdgeStatus; import ecdar.abstractions.Location; +import ecdar.mutation.ComponentVerificationTransformer; import ecdar.mutation.TextFlowBuilder; import ecdar.mutation.models.MutationTestCase; @@ -36,7 +37,7 @@ public List<MutationTestCase> generateTestCases(final Component original) { // Ignore if locked (e.g. if edge on the Inconsistent or Universal locations) if (originalEdge.getIsLockedProperty().get()) continue; - final Component mutant = original.cloneForVerification(); + final Component mutant = ComponentVerificationTransformer.cloneForVerification(original); // Mutate final Edge mutantEdge = mutant.getEdges().get(edgeIndex); diff --git a/src/main/java/ecdar/presentations/BackendInstancePresentation.java b/src/main/java/ecdar/presentations/BackendInstancePresentation.java deleted file mode 100644 index 67801874..00000000 --- a/src/main/java/ecdar/presentations/BackendInstancePresentation.java +++ /dev/null @@ -1,34 +0,0 @@ -package ecdar.presentations; - -import com.jfoenix.controls.JFXRippler; -import ecdar.Ecdar; -import ecdar.backend.BackendInstance; -import ecdar.controllers.BackendInstanceController; -import ecdar.utility.colors.Color; -import javafx.application.Platform; -import javafx.scene.Cursor; -import javafx.scene.layout.StackPane; - -public class BackendInstancePresentation extends StackPane { - private final BackendInstanceController controller; - - public BackendInstancePresentation(BackendInstance backendInstance) { - this(); - controller.setBackendInstance(backendInstance); - - // Ensure that the icons are scaled to current font scale - Platform.runLater(() -> Ecdar.getPresentation().getController().scaleIcons(this)); - } - - public BackendInstancePresentation() { - controller = new EcdarFXMLLoader().loadAndGetController("BackendInstancePresentation.fxml", this); - - controller.pickPathToBackend.setCursor(Cursor.HAND); - controller.pickPathToBackend.setRipplerFill(Color.GREY.getColor(Color.Intensity.I500)); - controller.pickPathToBackend.setMaskType(JFXRippler.RipplerMask.CIRCLE); - } - - public BackendInstanceController getController() { - return controller; - } -} diff --git a/src/main/java/ecdar/presentations/BackendOptionsDialogPresentation.java b/src/main/java/ecdar/presentations/BackendOptionsDialogPresentation.java deleted file mode 100644 index c3949083..00000000 --- a/src/main/java/ecdar/presentations/BackendOptionsDialogPresentation.java +++ /dev/null @@ -1,16 +0,0 @@ -package ecdar.presentations; - -import com.jfoenix.controls.JFXDialog; -import ecdar.controllers.BackendOptionsDialogController; - -public class BackendOptionsDialogPresentation extends JFXDialog { - private final BackendOptionsDialogController controller; - - public BackendOptionsDialogPresentation() { - controller = new EcdarFXMLLoader().loadAndGetController("BackendOptionsDialogPresentation.fxml", this); - } - - public BackendOptionsDialogController getController() { - return controller; - } -} diff --git a/src/main/java/ecdar/presentations/CanvasPresentation.java b/src/main/java/ecdar/presentations/CanvasPresentation.java index c8fa7f56..726e85a3 100644 --- a/src/main/java/ecdar/presentations/CanvasPresentation.java +++ b/src/main/java/ecdar/presentations/CanvasPresentation.java @@ -2,10 +2,11 @@ import com.jfoenix.controls.JFXRippler; import com.jfoenix.skins.ValidationPane; +import ecdar.Ecdar; import ecdar.controllers.CanvasController; import ecdar.controllers.EcdarController; import ecdar.utility.colors.Color; -import ecdar.utility.helpers.MouseTrackable; +import ecdar.utility.helpers.LocationAware; import ecdar.utility.helpers.SelectHelper; import ecdar.utility.mouse.MouseTracker; import javafx.application.Platform; @@ -23,7 +24,7 @@ import javafx.scene.layout.*; import javafx.scene.shape.Rectangle; -public class CanvasPresentation extends StackPane implements MouseTrackable { +public class CanvasPresentation extends StackPane implements LocationAware { public MouseTracker mouseTracker; private final DoubleProperty x = new SimpleDoubleProperty(0); @@ -32,11 +33,11 @@ public class CanvasPresentation extends StackPane implements MouseTrackable { private final CanvasController controller; public CanvasPresentation() { - mouseTracker = new MouseTracker(this); controller = new EcdarFXMLLoader().loadAndGetController("CanvasPresentation.fxml", this); + mouseTracker = new MouseTracker(this); mouseTracker.registerOnMousePressedEventHandler(this::startDragSelect); - getController().root.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> EcdarController.setActiveCanvasPresentation(this)); + getController().root.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> Ecdar.getPresentation().getController().getEditorPresentation().getController().setActiveCanvasPresentation(this)); initializeModelDrag(); initializeToolbar(); @@ -121,14 +122,6 @@ private void initializeZoomHelper() { controller.zoomHelper.setCanvas(this); } - /** - * Updates if views should show an inset behind the error view. - * @param shouldShow true iff views should show an inset - */ - public static void showBottomInset(final Boolean shouldShow) { - EcdarController.getActiveCanvasPresentation().getController().updateOffset(shouldShow); - } - @Override public DoubleProperty xProperty() { return x; @@ -149,15 +142,14 @@ public double getY() { return yProperty().get(); } - @Override - public MouseTracker getMouseTracker() { - return mouseTracker; - } - public CanvasController getController() { return controller; } + /*** + * Start drawing selection rectangle for area selection + * @param event used for the origin of the selection rectangle + */ private void startDragSelect(final MouseEvent event) { if(event.isPrimaryButtonDown()) { SelectHelper.clearSelectedElements(); @@ -200,6 +192,12 @@ private void startDragSelect(final MouseEvent event) { } } + /*** + * Initialize the rectangle to use for selection + * @param mouseDownX X-coordinate for the initial mouse press + * @param mouseDownY Y-coordinate for the initial mouse press + * @return the initialized rectangle + */ private Rectangle initializeRectangleForSelectionBox(double mouseDownX, double mouseDownY) { Rectangle selectionRectangle = new Rectangle(); selectionRectangle.setStroke(SelectHelper.SELECT_COLOR.getColor(SelectHelper.SELECT_COLOR_INTENSITY_BORDER)); @@ -216,6 +214,11 @@ private Rectangle initializeRectangleForSelectionBox(double mouseDownX, double m return selectionRectangle; } + /*** + * Traverse the node graph recursively to update the set of nodes that should be selected + * @param currentNode the current 'root' node to traverse + * @param selectionRectangle the rectangle representing the selection area + */ private void updateSelection(Parent currentNode, Rectangle selectionRectangle) { // None of these nodes contain ItemSelectable nodes, so avoiding traversing these sub-trees improves performance if (currentNode instanceof VBox || currentNode instanceof ValidationPane || currentNode instanceof JFXRippler || currentNode instanceof BorderPane) { @@ -245,7 +248,7 @@ private void updateSelection(Parent currentNode, Rectangle selectionRectangle) { }); } - /** + /*** * Returns whether the item is within the selection box. * @param item the node to potentially be selected. * @param itemSelectable the ItemSelectable object related to the item (in order to get width and height for the checks). diff --git a/src/main/java/ecdar/presentations/ComponentInstancePresentation.java b/src/main/java/ecdar/presentations/ComponentInstancePresentation.java index a3a5c509..e15df2d7 100644 --- a/src/main/java/ecdar/presentations/ComponentInstancePresentation.java +++ b/src/main/java/ecdar/presentations/ComponentInstancePresentation.java @@ -5,6 +5,7 @@ import ecdar.controllers.ComponentInstanceController; import ecdar.controllers.EcdarController; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.ItemDragHelper; import ecdar.utility.helpers.SelectHelper; import javafx.beans.property.BooleanProperty; @@ -24,13 +25,14 @@ import java.util.ArrayList; import java.util.List; import java.util.function.BiConsumer; +import java.util.function.Consumer; /** * Presentation for a component instance. */ public class ComponentInstancePresentation extends StackPane implements SelectHelper.ItemSelectable { private final ComponentInstanceController controller; - private final List<BiConsumer<Color, Color.Intensity>> updateColorDelegates = new ArrayList<>(); + private final List<Consumer<EnabledColor>> updateColorDelegates = new ArrayList<>(); public ComponentInstancePresentation(final ComponentInstance instance, final EcdarSystem system) { controller = new EcdarFXMLLoader().loadAndGetController("ComponentInstancePresentation.fxml", this); @@ -64,28 +66,21 @@ public ComponentInstancePresentation(final ComponentInstance instance, final Ecd */ private void initializeNails() { final Component component = controller.getInstance().getComponent(); - final BiConsumer<Color, Color.Intensity> updateNailColor = (newColor, newIntensity) -> + final Consumer<EnabledColor> updateNailColor = (newColor) -> { - final Color color = newColor; - final Color.Intensity colorIntensity = newIntensity; + controller.inputNailCircle.setFill(newColor.getPaintColor()); + controller.inputNailCircle.setStroke(newColor.getStrokeColor()); - controller.inputNailCircle.setFill(color.getColor(colorIntensity)); - controller.inputNailCircle.setStroke(color.getColor(colorIntensity.next(2))); - - controller.outputNailCircle.setFill(color.getColor(colorIntensity)); - controller.outputNailCircle.setStroke(color.getColor(colorIntensity.next(2))); + controller.outputNailCircle.setFill(newColor.getPaintColor()); + controller.outputNailCircle.setStroke(newColor.getStrokeColor()); }; // When the color of the component updates, update the nail indicator as well controller.getInstance().getComponent().colorProperty().addListener( - (observable) -> updateNailColor.accept(component.getColor(), component.getColorIntensity())); - - // When the color intensity of the component updates, update the nail indicator - controller.getInstance().getComponent().colorIntensityProperty().addListener( - (observable) -> updateNailColor.accept(component.getColor(), component.getColorIntensity())); + (observable) -> updateNailColor.accept(component.getColor())); // Initialize the color of the nail with the current color - updateNailColor.accept(component.getColor(), component.getColorIntensity()); + updateNailColor.accept(component.getColor()); updateColorDelegates.add(updateNailColor); } @@ -107,15 +102,14 @@ private void initializeName() { controller.identifier.textProperty().bindBidirectional(instance.getInstanceIdProperty()); final Runnable updateColor = () -> { - final Color color = instance.getComponent().getColor(); - final Color.Intensity colorIntensity = instance.getComponent().getColorIntensity(); + final EnabledColor color = instance.getComponent().getColor(); // Set the text color for the label - controller.identifier.setStyle("-fx-text-fill: " + color.getTextColorRgbaString(colorIntensity) + ";"); - controller.identifier.setFocusColor(color.getTextColor(colorIntensity)); + controller.identifier.setStyle("-fx-text-fill: " + color.getTextColorRgbaString() + ";"); + controller.identifier.setFocusColor(color.getTextColor()); controller.identifier.setUnFocusColor(javafx.scene.paint.Color.TRANSPARENT); - controller.originalComponentLabel.setStyle("-fx-text-fill: " + color.getTextColorRgbaString(colorIntensity) + ";"); + controller.originalComponentLabel.setStyle("-fx-text-fill: " + color.getTextColorRgbaString() + ";"); }; // Update color and whenever color of the component changes @@ -124,7 +118,7 @@ private void initializeName() { // Center the text vertically and aff a left padding of CORNER_SIZE controller.identifier.setPadding(new Insets(2, 0, 0, Ecdar.CANVAS_PADDING * 4)); - controller.identifier.setOnKeyPressed(EcdarController.getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler()); + controller.identifier.setOnKeyPressed(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler()); controller.originalComponentLabel.setPadding(new Insets(0, 5, 0, 15)); controller.originalComponentLabel.textProperty().bind(instance.getComponent().nameProperty()); @@ -158,10 +152,10 @@ private void initializeDimensions() { private void initializeToolbar() { final Component component = controller.getInstance().getComponent(); - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { + final Consumer<EnabledColor> updateColor = (newColor) -> { // Set the background of the toolbar controller.toolbar.setBackground(new Background(new BackgroundFill( - newColor.getColor(newIntensity), + newColor.getPaintColor(), CornerRadii.EMPTY, Insets.EMPTY ))); @@ -170,8 +164,8 @@ private void initializeToolbar() { }; // Update color now, whenever color of component changes, and when someone uses the color delegates - updateColor.accept(component.getColor(), component.getColorIntensity()); - component.colorProperty().addListener(observable -> updateColor.accept(component.getColor(), component.getColorIntensity())); + updateColor.accept(component.getColor()); + component.colorProperty().addListener(observable -> updateColor.accept(component.getColor())); updateColorDelegates.add(updateColor); } @@ -183,8 +177,8 @@ private void initializeToolbar() { private void initializeSeparator() { final Component component = controller.getInstance().getComponent(); - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { - controller.separatorLine.setStroke(newColor.getColor(newIntensity.next(2))); + final Consumer<EnabledColor> updateColor = (newColor) -> { + controller.separatorLine.setStroke(newColor.getStrokeColor()); }; final Runnable drawLine = () -> { @@ -201,8 +195,8 @@ private void initializeSeparator() { heightProperty().addListener(observable -> drawLine.run()); controller.toolbar.heightProperty().addListener(obs -> drawLine.run()); - updateColor.accept(component.getColor(), component.getColorIntensity()); - component.colorProperty().addListener(observable -> updateColor.accept(component.getColor(), component.getColorIntensity())); + updateColor.accept(component.getColor()); + component.colorProperty().addListener(observable -> updateColor.accept(component.getColor())); updateColorDelegates.add(updateColor); } @@ -222,7 +216,7 @@ private void initializeFrame() { 0, Ecdar.CANVAS_PADDING * 4 + 2 ); - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { + final Consumer<EnabledColor> updateColor = (newColor) -> { // Mask the parent of the frame (will also mask the background) mask[0] = Path.subtract(rectangle, corner1); controller.frame.setClip(mask[0]); @@ -233,13 +227,13 @@ private void initializeFrame() { controller.line1.setStartY(0); controller.line1.setEndX(0); controller.line1.setEndY(Ecdar.CANVAS_PADDING * 4); - controller.line1.setStroke(newColor.getColor(newIntensity.next(2))); + controller.line1.setStroke(newColor.getStrokeColor()); controller.line1.setStrokeWidth(1.25); StackPane.setAlignment(controller.line1, Pos.TOP_LEFT); // Set the stroke color to two shades darker controller.frame.setBorder(new Border(new BorderStroke( - newColor.getColor(newIntensity.next(2)), + newColor.getStrokeColor(), BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(1), @@ -248,8 +242,8 @@ private void initializeFrame() { }; // Update color now, whenever color of component changes, and when someone uses the color delegates - updateColor.accept(component.getColor(), component.getColorIntensity()); - component.colorProperty().addListener(observable -> updateColor.accept(component.getColor(), component.getColorIntensity())); + updateColor.accept(component.getColor()); + component.colorProperty().addListener(observable -> updateColor.accept(component.getColor())); updateColorDelegates.add(updateColor); } @@ -263,14 +257,14 @@ private void initializeBackground() { controller.background.widthProperty().bind(minWidthProperty()); controller.background.heightProperty().bind(minHeightProperty()); - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { + final Consumer<EnabledColor> updateColor = (newColor) -> { // Set the background color to the lightest possible version of the color - controller.background.setFill(newColor.getColor(newIntensity.next(-20))); + controller.background.setFill(newColor.getLowestIntensity().getPaintColor()); }; // Update color now, whenever color of component changes, and when someone uses the color delegates - updateColor.accept(component.getColor(), component.getColorIntensity()); - component.colorProperty().addListener(observable -> updateColor.accept(component.getColor(), component.getColorIntensity())); + updateColor.accept(component.getColor()); + component.colorProperty().addListener(observable -> updateColor.accept(component.getColor())); updateColorDelegates.add(updateColor); } @@ -301,7 +295,7 @@ private void initializeMouseControls() { */ @Override public void select() { - updateColorDelegates.forEach(colorConsumer -> colorConsumer.accept(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL)); + updateColorDelegates.forEach(colorConsumer -> colorConsumer.accept(new EnabledColor(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL))); } /** @@ -312,7 +306,7 @@ public void deselect() { updateColorDelegates.forEach(colorConsumer -> { final Component component = controller.getInstance().getComponent(); - colorConsumer.accept(component.getColor(), component.getColorIntensity()); + colorConsumer.accept(component.getColor()); }); } @@ -321,30 +315,20 @@ public void deselect() { * But since, component instances use the color of the corresponding component, * this method does nothing. * @param color not used - * @param intensity not used */ @Override @Deprecated - public void color(final Color color, final Color.Intensity intensity) { } + public void color(final EnabledColor color) { } /** * Gets the color of the corresponding component. * @return the color of the component. */ @Override - public Color getColor() { + public EnabledColor getColor() { return controller.getInstance().getComponent().getColor(); } - /** - * Gets the color intensity of the corresponding component. - * @return the color of the component - */ - @Override - public Color.Intensity getColorIntensity() { - return controller.getInstance().getComponent().getColorIntensity(); - } - /** * Gets the bound that it is valid to drag the instance within. * @return the bounds diff --git a/src/main/java/ecdar/presentations/ComponentOperatorPresentation.java b/src/main/java/ecdar/presentations/ComponentOperatorPresentation.java index a09f861b..c4f53cc0 100644 --- a/src/main/java/ecdar/presentations/ComponentOperatorPresentation.java +++ b/src/main/java/ecdar/presentations/ComponentOperatorPresentation.java @@ -4,6 +4,7 @@ import ecdar.abstractions.EcdarSystem; import ecdar.controllers.ComponentOperatorController; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.ItemDragHelper; import ecdar.utility.helpers.SelectHelper; import javafx.beans.property.DoubleProperty; @@ -15,13 +16,14 @@ import java.util.ArrayList; import java.util.List; import java.util.function.BiConsumer; +import java.util.function.Consumer; /** * Presentation of a component operator */ public class ComponentOperatorPresentation extends StackPane implements SelectHelper.ItemSelectable { private final ComponentOperatorController controller; - private final List<BiConsumer<Color, Color.Intensity>> updateColorDelegates = new ArrayList<>(); + private final List<Consumer<EnabledColor>> updateColorDelegates = new ArrayList<>(); /** * Constructor for ComponentOperatorPresentation, sets the controller and initializes label, dimensions, frame and mouse controls @@ -53,11 +55,11 @@ private void initializeIdLabel() { idLabel.setTranslateY(-2); // Delegate to style the label based on the color of the location - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> idLabel.setTextFill(newColor.getTextColor(newIntensity)); + final Consumer<EnabledColor> updateColor = (newColor) -> idLabel.setTextFill(newColor.getTextColor()); // Update color now and on color change - updateColor.accept(system.getColor(), system.getColorIntensity()); - system.colorProperty().addListener(observable -> updateColor.accept(system.getColor(), system.getColorIntensity())); + updateColor.accept(system.getColor()); + system.colorProperty().addListener(observable -> updateColor.accept(system.getColor())); updateColorDelegates.add(updateColor); } @@ -96,10 +98,10 @@ private void initializeFrame() { controller.frame.getPoints().addAll(1d * Ecdar.CANVAS_PADDING, 1d * Ecdar.CANVAS_PADDING); controller.frame.getPoints().addAll(2d * Ecdar.CANVAS_PADDING, 0d); - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> controller.frame.setFill(newColor.getColor(newIntensity)); + final Consumer<EnabledColor> updateColor = (newColor) -> controller.frame.setFill(newColor.getPaintColor()); - updateColor.accept(system.getColor(), system.getColorIntensity()); - system.colorProperty().addListener(observable -> updateColor.accept(system.getColor(), system.getColorIntensity())); + updateColor.accept(system.getColor()); + system.colorProperty().addListener(observable -> updateColor.accept(system.getColor())); updateColorDelegates.add(updateColor); } @@ -128,19 +130,14 @@ private void initializeMouseControls() { * Is meant to set the color, but this feature is not available for operators so this method does nothing */ @Override - public void color(final Color color, final Color.Intensity intensity) { + public void color(final EnabledColor color) { } @Override - public Color getColor() { + public EnabledColor getColor() { return controller.getSystem().getColor(); } - @Override - public Color.Intensity getColorIntensity() { - return controller.getSystem().getColorIntensity(); - } - /** * Gets the bound that it is valid to drag the operator within. * @return the bounds @@ -191,7 +188,7 @@ public double getSelectableHeight() { */ @Override public void select() { - updateColorDelegates.forEach(colorConsumer -> colorConsumer.accept(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL)); + updateColorDelegates.forEach(colorConsumer -> colorConsumer.accept(new EnabledColor(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL))); } /** @@ -202,7 +199,7 @@ public void deselect() { updateColorDelegates.forEach(colorConsumer -> { final EcdarSystem system = controller.getSystem(); - colorConsumer.accept(system.getColor(), system.getColorIntensity()); + colorConsumer.accept(system.getColor()); }); } diff --git a/src/main/java/ecdar/presentations/ComponentPresentation.java b/src/main/java/ecdar/presentations/ComponentPresentation.java index bb59f463..ff5515f8 100644 --- a/src/main/java/ecdar/presentations/ComponentPresentation.java +++ b/src/main/java/ecdar/presentations/ComponentPresentation.java @@ -1,180 +1,25 @@ package ecdar.presentations; - -import ecdar.Ecdar; import ecdar.abstractions.Component; -import ecdar.abstractions.Edge; -import ecdar.abstractions.Location; -import ecdar.abstractions.Nail; import ecdar.controllers.ComponentController; -import ecdar.controllers.ModelController; -import ecdar.utility.colors.Color; -import ecdar.utility.helpers.MouseTrackable; +import ecdar.utility.helpers.LocationAware; import ecdar.utility.helpers.SelectHelper; -import ecdar.utility.mouse.MouseTracker; import javafx.beans.property.DoubleProperty; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.Cursor; -import javafx.scene.layout.*; -import javafx.scene.shape.*; -import org.fxmisc.richtext.model.StyleSpans; -import org.fxmisc.richtext.model.StyleSpansBuilder; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.function.BiConsumer; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -public class ComponentPresentation extends ModelPresentation implements MouseTrackable, SelectHelper.Selectable { - private static final String uppaalKeywords = "clock|chan|urgent|broadcast"; - private static final String cKeywords = "auto|bool|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while"; - private static final Pattern UPPAAL = Pattern.compile("" - + "(" + uppaalKeywords + ")" - + "|(" + cKeywords + ")" - + "|(//.*|(\"(?:\\\\[^\"]|\\\\\"|.)*?\")|(?s)/\\*.*?\\*/)"); +public class ComponentPresentation extends ModelPresentation implements LocationAware, SelectHelper.Selectable { private final ComponentController controller; - private final List<BiConsumer<Color, Color.Intensity>> updateColorDelegates = new ArrayList<>(); public ComponentPresentation(final Component component) { controller = new EcdarFXMLLoader().loadAndGetController("ComponentPresentation.fxml", this); controller.setComponent(component); - super.initialize(component.getBox()); - - // Initialize methods that is sensitive to width and height - final Runnable onUpdateSize = () -> { - initializeToolbar(); - initializeFrame(); - initializeBackground(); - }; - - onUpdateSize.run(); - - // Re run initialisation on update of width and height property - component.getBox().getWidthProperty().addListener(observable -> onUpdateSize.run()); - component.getBox().getHeightProperty().addListener(observable -> onUpdateSize.run()); - - controller.declarationTextArea.textProperty().addListener((obs, oldText, newText) -> - controller.declarationTextArea.setStyleSpans(0, computeHighlighting(newText))); - } - - public static StyleSpans<Collection<String>> computeHighlighting(final String text) { - final Matcher matcher = UPPAAL.matcher(text); - int lastKwEnd = 0; - final StyleSpansBuilder<Collection<String>> spansBuilder = new StyleSpansBuilder<>(); - while (matcher.find()) { - - spansBuilder.add(Collections.emptyList(), matcher.start() - lastKwEnd); - - if (matcher.group(1) != null) { - spansBuilder.add(Collections.singleton("uppaal-keyword"), matcher.end(1) - matcher.start(1)); - } else if (matcher.group(2) != null) { - spansBuilder.add(Collections.singleton("c-keyword"), matcher.end(2) - matcher.start(2)); - } else if (matcher.group(3) != null) { - spansBuilder.add(Collections.singleton("comment"), matcher.end(3) - matcher.start(3)); - } - - lastKwEnd = matcher.end(); - } - - spansBuilder.add(Collections.emptyList(), text.length() - lastKwEnd); - return spansBuilder.create(); - } - - private void initializeToolbar() { - final Component component = controller.getComponent(); - - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { - // Set the background of the toolbar - controller.toolbar.setBackground(new Background(new BackgroundFill( - newColor.getColor(newIntensity), - CornerRadii.EMPTY, - Insets.EMPTY - ))); - - // Set the icon color and rippler color of the toggleDeclarationButton - controller.toggleDeclarationButton.setRipplerFill(newColor.getTextColor(newIntensity)); - - controller.toolbar.setPrefHeight(Ecdar.CANVAS_PADDING * 2); - controller.toggleDeclarationButton.setBackground(Background.EMPTY); - }; - - updateColorDelegates.add(updateColor); - - controller.getComponent().colorProperty().addListener(observable -> updateColor.accept(component.getColor(), component.getColorIntensity())); - - updateColor.accept(component.getColor(), component.getColorIntensity()); - - // Set a hover effect for the controller.toggleDeclarationButton - controller.toggleDeclarationButton.setOnMouseEntered(event -> controller.toggleDeclarationButton.setCursor(Cursor.HAND)); - controller.toggleDeclarationButton.setOnMouseExited(event -> controller.toggleDeclarationButton.setCursor(Cursor.DEFAULT)); - controller.toggleDeclarationButton.setOnMousePressed(controller::toggleDeclaration); - - } - - private void initializeFrame() { - final Component component = controller.getComponent(); - - final Shape[] mask = new Shape[1]; - final Rectangle rectangle = new Rectangle(component.getBox().getWidth(), component.getBox().getHeight()); - - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { - // Mask the parent of the frame (will also mask the background) - mask[0] = Path.subtract(rectangle, TOP_LEFT_CORNER); - controller.frame.setClip(mask[0]); - controller.background.setClip(Path.union(mask[0], mask[0])); - controller.background.setOpacity(0.5); - - // Bind the missing lines that we cropped away - controller.topLeftLine.setStartX(Ecdar.CANVAS_PADDING * 4); - controller.topLeftLine.setStartY(0); - controller.topLeftLine.setEndX(0); - controller.topLeftLine.setEndY(Ecdar.CANVAS_PADDING * 4); - controller.topLeftLine.setStroke(newColor.getColor(newIntensity.next(2))); - controller.topLeftLine.setStrokeWidth(1.25); - StackPane.setAlignment(controller.topLeftLine, Pos.TOP_LEFT); - - // Set the stroke color to two shades darker - controller.frame.setBorder(new Border(new BorderStroke( - newColor.getColor(newIntensity.next(2)), - BorderStrokeStyle.SOLID, - CornerRadii.EMPTY, - new BorderWidths(1), - Insets.EMPTY - ))); - }; - - updateColorDelegates.add(updateColor); - - component.colorProperty().addListener(observable -> { - updateColor.accept(component.getColor(), component.getColorIntensity()); - }); - - updateColor.accept(component.getColor(), component.getColorIntensity()); + minHeightProperty().bindBidirectional(component.getBox().getHeightProperty()); + maxHeightProperty().bindBidirectional(component.getBox().getHeightProperty()); + minWidthProperty().bindBidirectional(component.getBox().getWidthProperty()); + maxWidthProperty().bindBidirectional(component.getBox().getWidthProperty()); } - private void initializeBackground() { - final Component component = controller.getComponent(); - - // Bind the background width and height to the values in the model - controller.background.widthProperty().bindBidirectional(component.getBox().getWidthProperty()); - controller.background.heightProperty().bindBidirectional(component.getBox().getHeightProperty()); - - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { - // Set the background color to the lightest possible version of the color - controller.background.setFill(newColor.getColor(newIntensity.next(-10).next(2))); - }; - - updateColorDelegates.add(updateColor); - - component.colorProperty().addListener(observable -> { - updateColor.accept(component.getColor(), component.getColorIntensity()); - }); - - updateColor.accept(component.getColor(), component.getColorIntensity()); + public ComponentController getController() { + return controller; } @Override @@ -197,76 +42,13 @@ public double getY() { return yProperty().get(); } - public ComponentController getController() { - return controller; - } - - @Override - public MouseTracker getMouseTracker() { - return controller.getMouseTracker(); - } - @Override public void select() { - updateColorDelegates.forEach(colorConsumer -> colorConsumer.accept(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL)); + controller.componentSelected(); } @Override public void deselect() { - updateColorDelegates.forEach(colorConsumer -> { - final Component component = controller.getComponent(); - - colorConsumer.accept(component.getColor(), component.getColorIntensity()); - }); - } - - @Override - ModelController getModelController() { - return getController(); - } - - @Override - double getDragAnchorMinWidth() { - final Component component = controller.getComponent(); - double minWidth = Ecdar.CANVAS_PADDING * 10; - - for (final Location location : component.getLocations()) { - minWidth = Math.max(minWidth, location.getX() + Ecdar.CANVAS_PADDING * 2); - } - - for (final Edge edge : component.getEdges()) { - for (final Nail nail : edge.getNails()) { - minWidth = Math.max(minWidth, nail.getX() + Ecdar.CANVAS_PADDING); - } - } - - return minWidth; - } - - /** - * Gets the minimum possible height when dragging the anchor. - * The height is based on the y coordinate of locations, nails and the signature arrows - * @return the minimum possible height. - */ - @Override - double getDragAnchorMinHeight() { - final Component component = controller.getComponent(); - double minHeight = 100; - - for (final Location location : component.getLocations()) { - minHeight = Math.max(minHeight, location.getY() + 20); - } - - for (final Edge edge : component.getEdges()) { - for (final Nail nail : edge.getNails()) { - minHeight = Math.max(minHeight, nail.getY() + 10); - } - } - - //Component should not get smaller than the height of the input/output signature containers - minHeight = Math.max(controller.inputSignatureContainer.getHeight(), minHeight); - minHeight = Math.max(controller.outputSignatureContainer.getHeight(), minHeight); - - return minHeight; + controller.componentDeselected(); } } diff --git a/src/main/java/ecdar/presentations/DeclarationPresentation.java b/src/main/java/ecdar/presentations/DeclarationsPresentation.java similarity index 60% rename from src/main/java/ecdar/presentations/DeclarationPresentation.java rename to src/main/java/ecdar/presentations/DeclarationsPresentation.java index 60740c4a..99eff0de 100644 --- a/src/main/java/ecdar/presentations/DeclarationPresentation.java +++ b/src/main/java/ecdar/presentations/DeclarationsPresentation.java @@ -2,19 +2,25 @@ import ecdar.abstractions.Declarations; import ecdar.controllers.DeclarationsController; +import ecdar.controllers.HighLevelModelController; /** * Presentation for overall declarations. */ -public class DeclarationPresentation extends HighLevelModelPresentation { +public class DeclarationsPresentation extends HighLevelModelPresentation { private final DeclarationsController controller; - public DeclarationPresentation(final Declarations declarations) { - controller = new EcdarFXMLLoader().loadAndGetController("DeclarationPresentation.fxml", this); + public DeclarationsPresentation(final Declarations declarations) { + controller = new EcdarFXMLLoader().loadAndGetController("DeclarationsPresentation.fxml", this); controller.setDeclarations(declarations); // Listen to changes and controller.textArea.textProperty().addListener((obs, oldText, newText) -> controller.updateHighlighting()); } + + @Override + public HighLevelModelController getController() { + return controller; + } } diff --git a/src/main/java/ecdar/presentations/DropDownMenu.java b/src/main/java/ecdar/presentations/DropDownMenu.java index ce2ebdab..3a389499 100644 --- a/src/main/java/ecdar/presentations/DropDownMenu.java +++ b/src/main/java/ecdar/presentations/DropDownMenu.java @@ -31,7 +31,6 @@ /** * DropDownMenu is a {@link JFXPopup} which is used as the right-click menu and options menu on for instance - * {@link QueryPresentation#actionButton}. * The DropDownMenu includes methods for adding elements to the menu itself. * * Batteries included @@ -266,15 +265,15 @@ public void addSmallSpacerElement() { * @param hasColor The current color * @param consumer A consumer for the color property */ - public void addColorPicker(final HasColor hasColor, final BiConsumer<Color, Color.Intensity> consumer) { + public void addColorPicker(final HasColor hasColor, final Consumer<EnabledColor> consumer) { addListElement("Color"); final FlowPane flowPane = new FlowPane(); flowPane.setStyle("-fx-padding: 0 8 0 8"); for (final EnabledColor color : enabledColors) { - final Circle circle = new Circle(16, color.color.getColor(color.intensity)); - circle.setStroke(color.color.getColor(color.intensity.next(2))); + final Circle circle = new Circle(16, color.getPaintColor()); + circle.setStroke(color.getStrokeColor()); circle.setStrokeWidth(1); final FontIcon icon = new FontIcon(); @@ -310,7 +309,7 @@ public void addColorPicker(final HasColor hasColor, final BiConsumer<Color, Colo // Only color the subject if the user chooses a new color if (hasColor.colorProperty().get().equals(color.color)) return; - consumer.accept(color.color, color.intensity); + consumer.accept(color); }); flowPane.getChildren().add(child); @@ -501,8 +500,6 @@ private void makeSpacerElement(final int height) { } public interface HasColor { - ObjectProperty<Color> colorProperty(); - - ObjectProperty<Color.Intensity> colorIntensityProperty(); + ObjectProperty<EnabledColor> colorProperty(); } } \ No newline at end of file diff --git a/src/main/java/ecdar/presentations/EcdarPresentation.java b/src/main/java/ecdar/presentations/EcdarPresentation.java index b6085699..f69c2213 100644 --- a/src/main/java/ecdar/presentations/EcdarPresentation.java +++ b/src/main/java/ecdar/presentations/EcdarPresentation.java @@ -2,24 +2,24 @@ import com.jfoenix.controls.JFXSnackbarLayout; import ecdar.Ecdar; +import ecdar.abstractions.Component; import ecdar.abstractions.Query; import ecdar.abstractions.Snackbar; import ecdar.controllers.EcdarController; import ecdar.utility.UndoRedoStack; import ecdar.utility.colors.Color; +import ecdar.utility.helpers.ImageScaler; import com.jfoenix.controls.JFXSnackbar; import ecdar.utility.keyboard.Keybind; import ecdar.utility.keyboard.KeyboardTracker; import javafx.animation.*; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; -import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.collections.ListChangeListener; import javafx.geometry.Insets; import javafx.scene.image.Image; -import javafx.scene.image.ImageView; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCombination; @@ -29,9 +29,9 @@ public class EcdarPresentation extends StackPane { private final EcdarController controller; - private final BooleanProperty leftPaneOpen = new SimpleBooleanProperty(false); + private final BooleanProperty leftPaneOpen = new SimpleBooleanProperty(true); private final SimpleDoubleProperty leftPaneAnimationProperty = new SimpleDoubleProperty(0); - private final BooleanProperty rightPaneOpen = new SimpleBooleanProperty(false); + private final BooleanProperty rightPaneOpen = new SimpleBooleanProperty(true); private final SimpleDoubleProperty rightPaneAnimationProperty = new SimpleDoubleProperty(0); private Timeline openLeftPaneAnimation; private Timeline closeLeftPaneAnimation; @@ -66,8 +66,10 @@ public EcdarPresentation() { controller.topPane.minHeightProperty().bind(controller.menuBar.heightProperty()); controller.topPane.maxHeightProperty().bind(controller.menuBar.heightProperty()); - toggleLeftPane(); - toggleRightPane(); + EcdarController.currentMode.addListener(observable -> { + initializeToggleLeftPaneFunctionality(); + initializeToggleRightPaneFunctionality(); + }); Ecdar.getPresentation().controller.scalingProperty.addListener((observable, oldValue, newValue) -> { // If the scaling has changed trigger animations for open panes to update width @@ -80,17 +82,15 @@ public EcdarPresentation() { } }); }); + + // Trigger closing followed by opening of the left pane to ensure correct placement + closeLeftPaneAnimation.setOnFinished((e) -> openLeftPaneAnimation.play()); + closeLeftPaneAnimation.play(); }); initializeHelpImages(); - KeyboardTracker.registerKeybind(KeyboardTracker.ZOOM_IN, new Keybind(new KeyCodeCombination(KeyCode.PLUS, KeyCombination.SHORTCUT_DOWN), () -> EcdarController.getActiveCanvasPresentation().getController().zoomHelper.zoomIn())); - KeyboardTracker.registerKeybind(KeyboardTracker.ZOOM_OUT, new Keybind(new KeyCodeCombination(KeyCode.MINUS, KeyCombination.SHORTCUT_DOWN), () -> EcdarController.getActiveCanvasPresentation().getController().zoomHelper.zoomOut())); - KeyboardTracker.registerKeybind(KeyboardTracker.ZOOM_TO_FIT, new Keybind(new KeyCodeCombination(KeyCode.EQUALS, KeyCombination.SHORTCUT_DOWN), () -> EcdarController.getActiveCanvasPresentation().getController().zoomHelper.zoomToFit())); - KeyboardTracker.registerKeybind(KeyboardTracker.RESET_ZOOM, new Keybind(new KeyCodeCombination(KeyCode.DIGIT0, KeyCombination.SHORTCUT_DOWN), () -> EcdarController.getActiveCanvasPresentation().getController().zoomHelper.resetZoom())); KeyboardTracker.registerKeybind(KeyboardTracker.UNDO, new Keybind(new KeyCodeCombination(KeyCode.Z, KeyCombination.SHORTCUT_DOWN), UndoRedoStack::undo)); KeyboardTracker.registerKeybind(KeyboardTracker.REDO, new Keybind(new KeyCodeCombination(KeyCode.Z, KeyCombination.SHORTCUT_DOWN, KeyCombination.SHIFT_DOWN), UndoRedoStack::redo)); - - initializeResizeQueryPane(); } private void initializeSnackbar() { @@ -116,11 +116,10 @@ private void initializeToggleLeftPaneFunctionality() { initializeCloseLeftPaneAnimation(); // Translate the x coordinate to create the open/close animations - controller.projectPane.translateXProperty().bind(leftPaneAnimationProperty.subtract(controller.projectPane.widthProperty())); - controller.leftSimPane.translateXProperty().bind(leftPaneAnimationProperty.subtract(controller.leftSimPane.widthProperty())); + controller.getLeftModePane().translateXProperty().bind(leftPaneAnimationProperty.subtract(controller.getLeftModePane().widthProperty())); // Whenever the width of the file pane is updated, update the animations - controller.projectPane.widthProperty().addListener((observable) -> { + controller.getLeftModePane().widthProperty().addListener((observable) -> { initializeOpenLeftPaneAnimation(); initializeCloseLeftPaneAnimation(); }); @@ -130,6 +129,7 @@ private void initializeToggleLeftPaneFunctionality() { initializeOpenLeftPaneAnimation(); initializeCloseLeftPaneAnimation(); }); + } private void initializeCloseLeftPaneAnimation() { @@ -137,7 +137,7 @@ private void initializeCloseLeftPaneAnimation() { closeLeftPaneAnimation = new Timeline(); - final KeyValue open = new KeyValue(leftPaneAnimationProperty, controller.projectPane.getWidth(), interpolator); + final KeyValue open = new KeyValue(leftPaneAnimationProperty, controller.getLeftModePane().getWidth(), interpolator); final KeyValue closed = new KeyValue(leftPaneAnimationProperty, 0, interpolator); final KeyFrame kf1 = new KeyFrame(Duration.millis(0), open); @@ -152,7 +152,7 @@ private void initializeOpenLeftPaneAnimation() { openLeftPaneAnimation = new Timeline(); final KeyValue closed = new KeyValue(leftPaneAnimationProperty, 0, interpolator); - final KeyValue open = new KeyValue(leftPaneAnimationProperty, controller.projectPane.getWidth(), interpolator); + final KeyValue open = new KeyValue(leftPaneAnimationProperty, controller.getLeftModePane().getWidth(), interpolator); final KeyFrame kf1 = new KeyFrame(Duration.millis(0), closed); final KeyFrame kf2 = new KeyFrame(Duration.millis(200), open); @@ -165,31 +165,34 @@ private void initializeToggleRightPaneFunctionality() { initializeCloseRightPaneAnimation(); // Translate the x coordinate to create the open/close animations - controller.queryPane.translateXProperty().bind(rightPaneAnimationProperty.multiply(-1).add(controller.queryPane.widthProperty())); - controller.rightSimPane.translateXProperty().bind(rightPaneAnimationProperty.multiply(-1).add(controller.rightSimPane.widthProperty())); + controller.getRightModePane().translateXProperty().bind(rightPaneAnimationProperty.multiply(-1).add(controller.getRightModePane().widthProperty())); // Whenever the width of the query pane is updated, update the animations - controller.queryPane.widthProperty().addListener((observable) -> { + controller.getRightModePane().widthProperty().addListener((observable, oldWidth, newWidth) -> { + Platform.runLater(() -> rightPaneAnimationProperty.set(controller.getRightModePane().getWidth())); + initializeOpenRightPaneAnimation(); initializeCloseRightPaneAnimation(); }); - // When new queries are added, make sure that the query pane is open - Ecdar.getProject().getQueries().addListener((ListChangeListener<Query>) c -> { - if (closeRightPaneAnimation == null) - return; // The query pane is not yet initialized - - while (c.next()) { - c.getAddedSubList().forEach(o -> { - if (!rightPaneOpen.get()) { - // Open the pane - openRightPaneAnimation.play(); - - // Toggle the open state - rightPaneOpen.set(rightPaneOpen.not().get()); - } - }); - } + Platform.runLater(() -> { + // When new queries are added, make sure that the query pane is open + Ecdar.getProject().getQueries().addListener((ListChangeListener<Query>) c -> { + if (closeRightPaneAnimation == null) + return; // The query pane is not yet initialized + + while (c.next()) { + c.getAddedSubList().forEach(o -> { + if (!rightPaneOpen.get()) { + // Open the pane + openRightPaneAnimation.play(); + + // Toggle the open state + rightPaneOpen.set(true); + } + }); + } + }); }); } @@ -198,7 +201,7 @@ private void initializeCloseRightPaneAnimation() { closeRightPaneAnimation = new Timeline(); - final KeyValue open = new KeyValue(rightPaneAnimationProperty, controller.queryPane.getWidth(), interpolator); + final KeyValue open = new KeyValue(rightPaneAnimationProperty, controller.getRightModePane().getWidth(), interpolator); final KeyValue closed = new KeyValue(rightPaneAnimationProperty, 0, interpolator); final KeyFrame kf1 = new KeyFrame(Duration.millis(0), open); @@ -213,7 +216,7 @@ private void initializeOpenRightPaneAnimation() { openRightPaneAnimation = new Timeline(); final KeyValue closed = new KeyValue(rightPaneAnimationProperty, 0, interpolator); - final KeyValue open = new KeyValue(rightPaneAnimationProperty, controller.queryPane.getWidth(), interpolator); + final KeyValue open = new KeyValue(rightPaneAnimationProperty, controller.getRightModePane().getWidth(), interpolator); final KeyFrame kf1 = new KeyFrame(Duration.millis(0), closed); final KeyFrame kf2 = new KeyFrame(Duration.millis(200), open); @@ -246,39 +249,16 @@ private void initializeTopBar() { */ private void initializeHelpImages() { controller.helpInitialImage.setImage(new Image(Ecdar.class.getResource("ic_help_initial.png").toExternalForm())); - fitSizeWhenAvailable(controller.helpInitialImage, controller.helpInitialPane); + ImageScaler.fitImageToPane(controller.helpInitialImage, controller.helpInitialPane); controller.helpUrgentImage.setImage(new Image(Ecdar.class.getResource("ic_help_urgent.png").toExternalForm())); - fitSizeWhenAvailable(controller.helpUrgentImage, controller.helpUrgentPane); + ImageScaler.fitImageToPane(controller.helpUrgentImage, controller.helpUrgentPane); controller.helpInputImage.setImage(new Image(Ecdar.class.getResource("ic_help_input.png").toExternalForm())); - fitSizeWhenAvailable(controller.helpInputImage, controller.helpInputPane); + ImageScaler.fitImageToPane(controller.helpInputImage, controller.helpInputPane); controller.helpOutputImage.setImage(new Image(Ecdar.class.getResource("ic_help_output.png").toExternalForm())); - fitSizeWhenAvailable(controller.helpOutputImage, controller.helpOutputPane); - } - - private void initializeResizeQueryPane() { - final DoubleProperty prevX = new SimpleDoubleProperty(); - final DoubleProperty prevWidth = new SimpleDoubleProperty(); - - controller.queryPane.getController().resizeAnchor.setOnMousePressed(event -> { - event.consume(); - - prevX.set(event.getScreenX()); - prevWidth.set(controller.queryPane.getWidth()); - }); - - controller.queryPane.getController().resizeAnchor.setOnMouseDragged(event -> { - double diff = prevX.get() - event.getScreenX(); - - // Set bounds for resizing to be between 280px and half the screen width - final double newWidth = Math.min(Math.max(prevWidth.get() + diff, 280), controller.root.getWidth() / 2); - - rightPaneAnimationProperty.set(newWidth); - controller.queryPane.setMaxWidth(newWidth); - controller.queryPane.setMinWidth(newWidth); - }); + ImageScaler.fitImageToPane(controller.helpOutputImage, controller.helpOutputPane); } public BooleanProperty toggleLeftPane() { @@ -307,13 +287,6 @@ public BooleanProperty toggleRightPane() { return rightPaneOpen; } - public static void fitSizeWhenAvailable(final ImageView imageView, final StackPane pane) { - pane.widthProperty().addListener((observable, oldValue, newValue) -> - imageView.setFitWidth(pane.getWidth())); - pane.heightProperty().addListener((observable, oldValue, newValue) -> - imageView.setFitHeight(pane.getHeight())); - } - public void showSnackbarMessage(final String message) { JFXSnackbarLayout content = new JFXSnackbarLayout(message); controller.snackbar.enqueue(new JFXSnackbar.SnackbarEvent(content, new Duration(5000))); diff --git a/src/main/java/ecdar/presentations/EditorPresentation.java b/src/main/java/ecdar/presentations/EditorPresentation.java index d17a4ab0..e3abc899 100644 --- a/src/main/java/ecdar/presentations/EditorPresentation.java +++ b/src/main/java/ecdar/presentations/EditorPresentation.java @@ -163,7 +163,7 @@ private void initializeColorSelector() { final List<Pair<SelectHelper.ItemSelectable, EnabledColor>> previousColor = new ArrayList<>(); SelectHelper.getSelectedElements().forEach(selectable -> { - previousColor.add(new Pair<>(selectable, new EnabledColor(selectable.getColor(), selectable.getColorIntensity()))); + previousColor.add(new Pair<>(selectable, selectable.getColor())); }); controller.changeColorOnSelectedElements(color, previousColor); diff --git a/src/main/java/ecdar/presentations/EngineOptionsDialogPresentation.java b/src/main/java/ecdar/presentations/EngineOptionsDialogPresentation.java new file mode 100644 index 00000000..e08de3e4 --- /dev/null +++ b/src/main/java/ecdar/presentations/EngineOptionsDialogPresentation.java @@ -0,0 +1,16 @@ +package ecdar.presentations; + +import com.jfoenix.controls.JFXDialog; +import ecdar.controllers.EngineOptionsDialogController; + +public class EngineOptionsDialogPresentation extends JFXDialog { + private final EngineOptionsDialogController controller; + + public EngineOptionsDialogPresentation() { + controller = new EcdarFXMLLoader().loadAndGetController("EngineOptionsDialogPresentation.fxml", this); + } + + public EngineOptionsDialogController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/presentations/EnginePresentation.java b/src/main/java/ecdar/presentations/EnginePresentation.java new file mode 100644 index 00000000..4923221c --- /dev/null +++ b/src/main/java/ecdar/presentations/EnginePresentation.java @@ -0,0 +1,34 @@ +package ecdar.presentations; + +import com.jfoenix.controls.JFXRippler; +import ecdar.Ecdar; +import ecdar.backend.Engine; +import ecdar.controllers.EngineInstanceController; +import ecdar.utility.colors.Color; +import javafx.application.Platform; +import javafx.scene.Cursor; +import javafx.scene.layout.StackPane; + +public class EnginePresentation extends StackPane { + private final EngineInstanceController controller; + + public EnginePresentation(Engine engine) { + this(); + controller.setEngine(engine); + + // Ensure that the icons are scaled to current font scale + Platform.runLater(() -> Ecdar.getPresentation().getController().scaleIcons(this)); + } + + public EnginePresentation() { + controller = new EcdarFXMLLoader().loadAndGetController("EnginePresentation.fxml", this); + + controller.pickPathToEngine.setCursor(Cursor.HAND); + controller.pickPathToEngine.setRipplerFill(Color.GREY.getColor(Color.Intensity.I500)); + controller.pickPathToEngine.setMaskType(JFXRippler.RipplerMask.CIRCLE); + } + + public EngineInstanceController getController() { + return controller; + } +} diff --git a/src/main/java/ecdar/presentations/FilePresentation.java b/src/main/java/ecdar/presentations/FilePresentation.java index 700b61b8..de61861f 100644 --- a/src/main/java/ecdar/presentations/FilePresentation.java +++ b/src/main/java/ecdar/presentations/FilePresentation.java @@ -1,206 +1,23 @@ package ecdar.presentations; import ecdar.Ecdar; -import ecdar.abstractions.Component; -import ecdar.abstractions.EcdarSystem; -import ecdar.abstractions.HighLevelModelObject; -import ecdar.controllers.EcdarController; +import ecdar.abstractions.HighLevelModel; import ecdar.controllers.FileController; -import ecdar.mutation.models.MutationTestPlan; -import ecdar.utility.colors.Color; -import com.jfoenix.controls.JFXRippler; import javafx.application.Platform; -import javafx.beans.property.SimpleObjectProperty; -import javafx.geometry.Insets; -import javafx.scene.Cursor; -import javafx.scene.Node; -import javafx.scene.control.Label; -import javafx.scene.image.Image; import javafx.scene.layout.*; -import javafx.scene.shape.Circle; -import org.kordamp.ikonli.javafx.FontIcon; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.BiConsumer; public class FilePresentation extends AnchorPane { - private final SimpleObjectProperty<HighLevelModelObject> model = new SimpleObjectProperty<>(null); - private final FileController controller; - private boolean isActive = false; - public FilePresentation(final HighLevelModelObject model) { + public FilePresentation(final HighLevelModel model) { controller = new EcdarFXMLLoader().loadAndGetController("FilePresentation.fxml", this); - - this.model.set(model); - - initializeIcon(); - initializeFileIcons(); - initializeFileName(); - initializeColors(); - initializeRippler(); - initializeMoreInformationButton(); + controller.setModel(model); // Ensure that the icons are scaled to current font scale Platform.runLater(() -> Ecdar.getPresentation().getController().scaleIcons(this)); } - private void initializeMoreInformationButton() { - if (getModel() instanceof Component || getModel() instanceof EcdarSystem || getModel() instanceof MutationTestPlan) { - controller.moreInformation.setVisible(true); - controller.moreInformation.setMaskType(JFXRippler.RipplerMask.CIRCLE); - controller.moreInformation.setPosition(JFXRippler.RipplerPos.BACK); - controller.moreInformation.setRipplerFill(Color.GREY_BLUE.getColor(Color.Intensity.I500)); - } - } - - private void initializeRippler() { - final JFXRippler rippler = (JFXRippler) lookup("#rippler"); - - final Color color = Color.GREY_BLUE; - final Color.Intensity colorIntensity = Color.Intensity.I400; - - rippler.setMaskType(JFXRippler.RipplerMask.RECT); - rippler.setRipplerFill(color.getColor(colorIntensity)); - rippler.setPosition(JFXRippler.RipplerPos.BACK); - } - - private void initializeFileName() { - final Label label = (Label) lookup("#fileName"); - - model.get().nameProperty().addListener((obs, oldName, newName) -> label.setText(newName)); - label.setText(model.get().getName()); - } - - private void initializeIcon() { - final Circle circle = (Circle) lookup("#iconBackground"); - - model.get().colorProperty().addListener((obs, oldColor, newColor) -> { - circle.setFill(newColor.getColor(model.get().getColorIntensity())); - }); - - circle.setFill(model.get().getColor().getColor(model.get().getColorIntensity())); - } - - private void initializeColors() { - final FontIcon moreInformationIcon = (FontIcon) lookup("#moreInformationIcon"); - - final Color color = Color.GREY_BLUE; - final Color.Intensity colorIntensity = Color.Intensity.I50; - - final BiConsumer<Color, Color.Intensity> setBackground = (newColor, newIntensity) -> { - setBackground(new Background(new BackgroundFill( - newColor.getColor(newIntensity), - CornerRadii.EMPTY, - Insets.EMPTY - ))); - - setBorder(new Border(new BorderStroke( - newColor.getColor(newIntensity.next(2)), - BorderStrokeStyle.SOLID, - CornerRadii.EMPTY, - new BorderWidths(0, 0, 1, 0) - ))); - - moreInformationIcon.setFill(newColor.getColor(newIntensity.next(5))); - }; - - // Update the background when hovered - setOnMouseEntered(event -> { - if(isActive) { - setBackground.accept(color, colorIntensity.next(2)); - } else { - setBackground.accept(color, colorIntensity.next()); - } - setCursor(Cursor.HAND); - }); - - setOnMouseExited(event -> { - if(isActive) { - setBackground.accept(color, colorIntensity.next(1)); - } else { - setBackground.accept(color, colorIntensity); - } - setCursor(Cursor.DEFAULT); - }); - - // Update the background initially - setBackground.accept(color, colorIntensity); - } - - private ArrayList<HighLevelModelObject> getActiveComponents() { - ArrayList<HighLevelModelObject> activeComponents = new ArrayList<>(); - - Node canvasPaneFirstChild = Ecdar.getPresentation().getController().getEditorPresentation().getController().canvasPane.getChildren().get(0); - if(canvasPaneFirstChild instanceof GridPane) { - for (Node child : ((GridPane) canvasPaneFirstChild).getChildren()) { - activeComponents.add(((CanvasPresentation) child).getController().getActiveModel()); - } - } else { - activeComponents.add(EcdarController.getActiveCanvasPresentation().getController().getActiveModel()); - } - - return activeComponents; - } - - /** - * Initialises the icons for the three different file types in Ecdar: Component, System and Declarations - */ - private void initializeFileIcons() { - if(model.get() instanceof Component){ - controller.fileImage.setImage(new Image(Ecdar.class.getResource("component_frame.png").toExternalForm())); - } else if(model.get() instanceof EcdarSystem){ - controller.fileImage.setImage(new Image(Ecdar.class.getResource("system_frame.png").toExternalForm())); - } else if(model.get() instanceof MutationTestPlan){ - controller.fileImage.setImage(new Image(Ecdar.class.getResource("test_frame.png").toExternalForm())); - } else { - controller.fileImage.setImage(new Image(Ecdar.class.getResource("description_frame.png").toExternalForm())); - } - EcdarPresentation.fitSizeWhenAvailable(controller.fileImage, controller.fileImageStackPane); - } - - public HighLevelModelObject getModel() { - return model.get(); - } - - /** - * Updates the color of the FilePresentation based on whether the component is aactive or not - */ - public void updateColors() { - final FontIcon moreInformationIcon = (FontIcon) lookup("#moreInformationIcon"); - - final Color color = Color.GREY_BLUE; - final Color.Intensity colorIntensity = Color.Intensity.I50; - - final BiConsumer<Color, Color.Intensity> setBackground = (newColor, newIntensity) -> { - setBackground(new Background(new BackgroundFill( - newColor.getColor(newIntensity), - CornerRadii.EMPTY, - Insets.EMPTY - ))); - - setBorder(new Border(new BorderStroke( - newColor.getColor(newIntensity.next(2)), - BorderStrokeStyle.SOLID, - CornerRadii.EMPTY, - new BorderWidths(0, 0, 1, 0) - ))); - - moreInformationIcon.setFill(newColor.getColor(newIntensity.next(5))); - }; - - final List<HighLevelModelObject> activeComponents = getActiveComponents(); - setActive(activeComponents.contains(model.get())); - - if (isActive) { - setBackground.accept(color, colorIntensity.next(1)); - } else { - setBackground.accept(color, colorIntensity); - } - } - - public void setActive(boolean active) { - isActive = active; + public FileController getController() { + return controller; } } diff --git a/src/main/java/ecdar/presentations/HighLevelModelPresentation.java b/src/main/java/ecdar/presentations/HighLevelModelPresentation.java index d3e0d5f7..ede058cb 100644 --- a/src/main/java/ecdar/presentations/HighLevelModelPresentation.java +++ b/src/main/java/ecdar/presentations/HighLevelModelPresentation.java @@ -1,9 +1,11 @@ package ecdar.presentations; +import ecdar.controllers.HighLevelModelController; import javafx.scene.layout.StackPane; /** * */ -public class HighLevelModelPresentation extends StackPane { +public abstract class HighLevelModelPresentation extends StackPane { + abstract public HighLevelModelController getController(); } diff --git a/src/main/java/ecdar/presentations/LocationPresentation.java b/src/main/java/ecdar/presentations/LocationPresentation.java index d7cfea10..5fdc833a 100644 --- a/src/main/java/ecdar/presentations/LocationPresentation.java +++ b/src/main/java/ecdar/presentations/LocationPresentation.java @@ -1,13 +1,15 @@ package ecdar.presentations; +import ecdar.Ecdar; import ecdar.abstractions.Component; import ecdar.abstractions.Location; -import ecdar.controllers.EcdarController; import ecdar.controllers.LocationController; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.BindingHelper; import ecdar.utility.helpers.SelectHelper; import javafx.animation.*; +import javafx.application.Platform; import javafx.beans.binding.DoubleBinding; import javafx.beans.property.*; import javafx.scene.Group; @@ -20,7 +22,6 @@ import java.util.List; import java.util.function.BiConsumer; import java.util.function.Consumer; -import java.util.function.Function; import static javafx.util.Duration.millis; @@ -39,7 +40,7 @@ public class LocationPresentation extends Group implements SelectHelper.Selectab private final Timeline hiddenAreaAnimationExited = new Timeline(); private final Timeline scaleShakeIndicatorBackgroundAnimation = new Timeline(); private final Timeline shakeContentAnimation = new Timeline(); - private final List<BiConsumer<Color, Color.Intensity>> updateColorDelegates = new ArrayList<>(); + private final List<Consumer<EnabledColor>> updateColorDelegates = new ArrayList<>(); private final DoubleProperty animation = new SimpleDoubleProperty(0); private final DoubleBinding reverseAnimation = new SimpleDoubleProperty(1).subtract(animation); private final boolean interactable; @@ -90,33 +91,29 @@ private void initializeIdLabel() { idLabel.widthProperty().addListener((obsWidth, oldWidth, newWidth) -> idLabel.translateXProperty().set(newWidth.doubleValue() / -2)); idLabel.heightProperty().addListener((obsHeight, oldHeight, newHeight) -> idLabel.translateYProperty().set(newHeight.doubleValue() / -2)); - final ObjectProperty<Color> color = location.colorProperty(); - final ObjectProperty<Color.Intensity> colorIntensity = location.colorIntensityProperty(); - final BooleanProperty failing = location.failingProperty(); - // Delegate to style the label based on the color of the location - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { + final Consumer<EnabledColor> updateColor = (newColor) -> { if (location.getFailing()) { - idLabel.setTextFill(Color.RED.getTextColor(newIntensity)); - ds.setColor(Color.RED.getColor(newIntensity)); + idLabel.setTextFill(Color.RED.getTextColor(Color.Intensity.I700)); + ds.setColor(Color.RED.getColor(Color.Intensity.I700)); } else { - idLabel.setTextFill(newColor.getTextColor(newIntensity)); - ds.setColor(newColor.getColor(newIntensity)); + idLabel.setTextFill(newColor.getTextColor()); + ds.setColor(newColor.getPaintColor()); } }; updateColorDelegates.add(updateColor); // Set the initial color - updateColor.accept(color.get(), colorIntensity.get()); + updateColor.accept(location.getColor()); // Update the color of the circle when the color of the location is updated - color.addListener((obs, old, newColor) -> updateColor.accept(newColor, colorIntensity.get())); - failing.addListener((obs, old, newFailing) -> updateColor.accept(color.get(), colorIntensity.get() )); + location.colorProperty().addListener((obs, old, newColor) -> updateColor.accept(newColor)); + location.failingProperty().addListener((obs, old, newFailing) -> updateColor.accept(location.getColor())); // RED if failing, provided color otherwise } private void initializeTags() { - if(!interactable) { + if (!interactable) { controller.nicknameTag.setVisible(false); controller.invariantTag.setVisible(false); return; @@ -129,12 +126,12 @@ private void initializeTags() { // Set the layout from the model (if they are not both 0) final Location loc = controller.getLocation(); - if((loc.getNicknameX() != 0) && (loc.getNicknameY() != 0)) { + if ((loc.getNicknameX() != 0) && (loc.getNicknameY() != 0)) { controller.nicknameTag.setTranslateX(loc.getNicknameX()); controller.nicknameTag.setTranslateY(loc.getNicknameY()); } - if((loc.getInvariantX() != 0) && (loc.getInvariantY() != 0)) { + if ((loc.getInvariantX() != 0) && (loc.getInvariantY() != 0)) { controller.invariantTag.setTranslateX(loc.getInvariantX()); controller.invariantTag.setTranslateY(loc.getInvariantY()); } @@ -152,8 +149,8 @@ private void initializeTags() { final Consumer<Location> updateTags = location -> { // Update the color - controller.nicknameTag.bindToColor(location.colorProperty(), location.colorIntensityProperty(), true); - controller.invariantTag.bindToColor(location.colorProperty(), location.colorIntensityProperty(), false); + controller.nicknameTag.bindToColor(location.colorProperty(), true); + controller.invariantTag.bindToColor(location.colorProperty(), false); // Update the invariant controller.nicknameTag.setAndBindString(location.nicknameProperty()); @@ -167,7 +164,8 @@ private void initializeTags() { final Consumer<String> updateVisibilityFromNickName = (nickname) -> { if (nickname.equals("") && !controller.nicknameTag.textFieldFocusProperty().get()) { controller.nicknameTag.setOpacity(0); - } else {controller.nicknameTag.setOpacity(1); + } else { + controller.nicknameTag.setOpacity(1); } }; @@ -207,8 +205,10 @@ private void initializeTags() { BindingHelper.bind(controller.invariantTagLine, controller.invariantTag); }; - controller.nicknameTag.setOnKeyPressed(EcdarController.getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler()); - controller.invariantTag.setOnKeyPressed(EcdarController.getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler()); + Platform.runLater(() -> { + controller.nicknameTag.setOnKeyPressed(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler()); + controller.invariantTag.setOnKeyPressed(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler()); + }); // Update the tags when the loc updates controller.locationProperty().addListener(observable -> updateTags.accept(loc)); @@ -260,23 +260,21 @@ private void initializeCircle() { final Circle circle = controller.circle; circle.setRadius(RADIUS); - final ObjectProperty<Color> color = location.colorProperty(); - final ObjectProperty<Color.Intensity> colorIntensity = location.colorIntensityProperty(); + final ObjectProperty<EnabledColor> color = location.colorProperty(); // Delegate to style the label based on the color of the location - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { - circle.setFill(newColor.getColor(newIntensity)); - circle.setStroke(newColor.getColor(newIntensity.next(2))); + final Consumer<EnabledColor> updateColor = (newColor) -> { + circle.setFill(newColor.getPaintColor()); + circle.setStroke(newColor.getStrokeColor()); }; updateColorDelegates.add(updateColor); // Set the initial color - updateColor.accept(color.get(), colorIntensity.get()); + updateColor.accept(color.get()); // Update the color of the circle when the color of the location is updated - color.addListener((obs, old, newColor) -> updateColor.accept(newColor, colorIntensity.get())); - colorIntensity.addListener((obs, old, newIntensity) -> updateColor.accept(color.get(), newIntensity)); + color.addListener((obs, old, newColor) -> updateColor.accept(newColor)); } private void initializeLocationShapes() { @@ -314,19 +312,19 @@ protected void interpolate(final double frac) { @Override protected void interpolate(final double frac) { - animation.set(1-frac); + animation.set(1 - frac); } }; boolean isNormalOrProhibited = newUrgency.equals(Location.Urgency.NORMAL) || newUrgency.equals(Location.Urgency.PROHIBITED); - if(!oldUrgency.equals(Location.Urgency.URGENT) && !isNormalOrProhibited) { + if (!oldUrgency.equals(Location.Urgency.URGENT) && !isNormalOrProhibited) { toUrgent.play(); - } else if(isNormalOrProhibited && oldUrgency.equals(Location.Urgency.URGENT)) { + } else if (isNormalOrProhibited && oldUrgency.equals(Location.Urgency.URGENT)) { toNormal.play(); } - if(newUrgency.equals(Location.Urgency.COMMITTED)) { + if (newUrgency.equals(Location.Urgency.COMMITTED)) { committedShape.setVisible(true); notCommittedShape.setVisible(false); } else { @@ -334,13 +332,13 @@ protected void interpolate(final double frac) { notCommittedShape.setVisible(true); } - if(newUrgency.equals(Location.Urgency.PROHIBITED)) { + if (newUrgency.equals(Location.Urgency.PROHIBITED)) { notCommittedShape.setStrokeWidth(4); notCommittedShape.setStroke(Color.RED.getColor(Color.Intensity.A700)); controller.prohibitedLocStrikeThrough.setVisible(true); } else { notCommittedShape.setStrokeWidth(1); - notCommittedShape.setStroke(location.getColor().getColor(location.getColorIntensity().next(2))); + notCommittedShape.setStroke(location.getColor().getStrokeColor()); controller.prohibitedLocStrikeThrough.setVisible(false); } }; @@ -351,48 +349,33 @@ protected void interpolate(final double frac) { updateUrgencies.accept(Location.Urgency.NORMAL, location.getUrgency()); - // Update the colors - final ObjectProperty<Color> color = location.colorProperty(); - final ObjectProperty<Color.Intensity> colorIntensity = location.colorIntensityProperty(); - final BooleanProperty failing = location.failingProperty(); - // Delegate to style the label based on the color of the location - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { - + final Consumer<EnabledColor> updateColor = (newColor) -> { // ToDo NIELS: Fix this consumer, it seems a bit weird if (!location.getUrgency().equals(Location.Urgency.PROHIBITED)) { if (location.getFailing()) { - notCommittedShape.setStroke(Color.RED.getColor(newIntensity)); + notCommittedShape.setFill(Color.RED.getColor(Color.Intensity.I700)); } else { - notCommittedShape.setStroke(newColor.getColor(newIntensity.next(2))); + notCommittedShape.setFill(newColor.getStrokeColor()); } - } - if (location.getFailing()) { - notCommittedShape.setFill(Color.RED.getColor(newIntensity)); - committedShape.setFill(Color.RED.getColor(newIntensity)); - committedShape.setStroke(Color.RED.getColor(newIntensity.next(2))); - } else { - notCommittedShape.setFill(newColor.getColor(newIntensity)); - committedShape.setFill(newColor.getColor(newIntensity)); - committedShape.setStroke(newColor.getColor(newIntensity.next(2))); - } - }; - - final Consumer<Boolean> handleFailingUpdate = (isFailing) -> { - if(isFailing) { - updateColor.accept(Color.RED, colorIntensity.get()); + } else if (location.getFailing()) { + notCommittedShape.setFill(Color.RED.getColor(Color.Intensity.I700)); + committedShape.setFill(Color.RED.getColor(Color.Intensity.I700)); + committedShape.setStroke(Color.RED.getColor(Color.Intensity.I700.next(2))); } else { - updateColor.accept(location.getColor(), colorIntensity.get()); + notCommittedShape.setFill(newColor.getPaintColor()); + committedShape.setFill(newColor.getPaintColor()); + committedShape.setStroke(newColor.getStrokeColor()); } }; updateColorDelegates.add(updateColor); // Set the initial color - updateColor.accept(color.get(), colorIntensity.get()); + updateColor.accept(location.getColor()); // Update the color of the circle when the color of the location is updated - color.addListener((obs, old, newColor) -> updateColor.accept(newColor, colorIntensity.get())); - failing.addListener((obs, old, newFailing) -> updateColor.accept(color.get(), colorIntensity.get())); + location.colorProperty().addListener((obs, old, newColor) -> updateColor.accept(newColor)); + location.failingProperty().addListener(obs -> updateColor.accept(location.getColor())); } private void initializeTypeGraphics() { @@ -550,7 +533,7 @@ public void animateShakeWarning(final boolean start) { @Override public void select() { - updateColorDelegates.forEach(colorConsumer -> colorConsumer.accept(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL)); + updateColorDelegates.forEach(colorConsumer -> colorConsumer.accept(new EnabledColor(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL))); } @Override @@ -558,7 +541,7 @@ public void deselect() { updateColorDelegates.forEach(colorConsumer -> { final Location location = controller.getLocation(); - colorConsumer.accept(location.getColor(), location.getColorIntensity()); + colorConsumer.accept(location.getColor()); }); } diff --git a/src/main/java/ecdar/presentations/MessageCollectionPresentation.java b/src/main/java/ecdar/presentations/MessageCollectionPresentation.java index 9f69aae2..c9d9eddf 100644 --- a/src/main/java/ecdar/presentations/MessageCollectionPresentation.java +++ b/src/main/java/ecdar/presentations/MessageCollectionPresentation.java @@ -2,121 +2,20 @@ import ecdar.abstractions.Component; import ecdar.code_analysis.CodeAnalysis; -import ecdar.controllers.EcdarController; -import ecdar.utility.colors.Color; -import javafx.beans.InvalidationListener; -import javafx.beans.Observable; -import javafx.collections.ListChangeListener; +import ecdar.controllers.MessageCollectionController; import javafx.collections.ObservableList; -import javafx.event.EventHandler; -import javafx.scene.Cursor; -import javafx.scene.control.Label; -import javafx.scene.input.MouseEvent; import javafx.scene.layout.VBox; -import javafx.scene.shape.Circle; -import javafx.scene.shape.Line; - -import java.util.HashMap; -import java.util.Map; -import java.util.function.BiConsumer; -import java.util.function.Consumer; public class MessageCollectionPresentation extends VBox { - - private final ObservableList<CodeAnalysis.Message> messages; + private final MessageCollectionController controller; public MessageCollectionPresentation(final Component component, final ObservableList<CodeAnalysis.Message> messages) { - this.messages = messages; - - new EcdarFXMLLoader().loadAndGetController("MessageCollectionPresentation.fxml", this); - - initializeHeadline(component); - initializeLine(); - initializeErrorsListener(); - } - - private void initializeErrorsListener() { - final VBox children = (VBox) lookup("#children"); - - final Map<CodeAnalysis.Message, MessagePresentation> messageMessagePresentationMap = new HashMap<>(); - - final Consumer<CodeAnalysis.Message> addMessage = (message) -> { - final MessagePresentation messagePresentation = new MessagePresentation(message); - messageMessagePresentationMap.put(message, messagePresentation); - children.getChildren().add(messagePresentation); - }; - - messages.forEach(addMessage); - messages.addListener(new ListChangeListener<CodeAnalysis.Message>() { - @Override - public void onChanged(final Change<? extends CodeAnalysis.Message> c) { - while (c.next()) { - c.getAddedSubList().forEach(addMessage::accept); - - c.getRemoved().forEach(message -> { - children.getChildren().remove(messageMessagePresentationMap.get(message)); - messageMessagePresentationMap.remove(message); - }); - } - } - }); + controller = new EcdarFXMLLoader().loadAndGetController("MessageCollectionPresentation.fxml", this); + controller.setComponent(component); + controller.setMessages(messages); } - private void initializeLine() { - final VBox children = (VBox) lookup("#children"); - final Line line = (Line) lookup("#line"); - - children.getChildren().addListener(new InvalidationListener() { - @Override - public void invalidated(final Observable observable) { - line.setEndY(children.getChildren().size() * 23 + 8); - } - }); + public MessageCollectionController getController() { + return controller; } - - private void initializeHeadline(final Component component) { - final Label headline = (Label) lookup("#headline"); - final Circle indicator = (Circle) lookup("#indicator"); - final Line line = (Line) lookup("#line"); - - line.setStroke(Color.GREY.getColor(Color.Intensity.I400)); - - // This is an project wide message that is not specific to a component - if(component == null) { - headline.setText("Project"); - return; - } - - headline.setText(component.getName()); - headline.textProperty().bind(component.nameProperty()); - - final EventHandler<MouseEvent> onMouseEntered = event -> { - setCursor(Cursor.HAND); - headline.setStyle("-fx-underline: true;"); - }; - - final EventHandler<MouseEvent> onMouseExited = event -> { - setCursor(Cursor.DEFAULT); - headline.setStyle("-fx-underline: false;"); - }; - - final EventHandler<MouseEvent> onMousePressed = event -> { - EcdarController.setActiveModelForActiveCanvas(component); - }; - - headline.setOnMouseEntered(onMouseEntered); - headline.setOnMouseExited(onMouseExited); - headline.setOnMousePressed(onMousePressed); - indicator.setOnMouseEntered(onMouseEntered); - indicator.setOnMouseExited(onMouseExited); - indicator.setOnMousePressed(onMousePressed); - - final BiConsumer<Color, Color.Intensity> updateColor = (color, intensity) -> { - indicator.setFill(color.getColor(component.getColorIntensity())); - }; - - updateColor.accept(component.getColor(), component.getColorIntensity()); - component.colorProperty().addListener((observable, oldColor, newColor) -> updateColor.accept(newColor, component.getColorIntensity())); - } - } diff --git a/src/main/java/ecdar/presentations/MessagePresentation.java b/src/main/java/ecdar/presentations/MessagePresentation.java index fefd7a2a..6dd49d82 100644 --- a/src/main/java/ecdar/presentations/MessagePresentation.java +++ b/src/main/java/ecdar/presentations/MessagePresentation.java @@ -21,8 +21,7 @@ public class MessagePresentation extends HBox { public MessagePresentation(final CodeAnalysis.Message message) { this.message = message; - - new EcdarFXMLLoader().loadAndGetController("MessagePresentation.fxml", this); + new EcdarFXMLLoader().loadAndGetController("MessagePresentation.fxml", this); // Initialize initializeMessage(); @@ -81,9 +80,9 @@ private void initializeNearLabel() { } if (openComponent[0] != null) { - if (!EcdarController.getActiveCanvasPresentation().getController().getActiveModel().equals(openComponent[0])) { + if (!Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getActiveModelPresentation().getController().getModel().equals(openComponent[0])) { SelectHelper.elementsToBeSelected = FXCollections.observableArrayList(); - EcdarController.setActiveModelForActiveCanvas(openComponent[0]); + Ecdar.getPresentation().getController().getEditorPresentation().getController().setActiveModelPresentationForActiveCanvas(Ecdar.getComponentPresentationOfComponent(openComponent[0])); } SelectHelper.clearSelectedElements(); diff --git a/src/main/java/ecdar/presentations/ModelPresentation.java b/src/main/java/ecdar/presentations/ModelPresentation.java index b487e1b9..4f52014e 100644 --- a/src/main/java/ecdar/presentations/ModelPresentation.java +++ b/src/main/java/ecdar/presentations/ModelPresentation.java @@ -1,21 +1,6 @@ package ecdar.presentations; -import ecdar.Ecdar; -import ecdar.abstractions.Box; -import ecdar.abstractions.HighLevelModelObject; -import ecdar.controllers.EcdarController; -import ecdar.controllers.ModelController; -import ecdar.utility.UndoRedoStack; -import ecdar.utility.colors.Color; -import ecdar.utility.helpers.StringValidator; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.DoubleProperty; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleDoubleProperty; -import javafx.geometry.Insets; -import javafx.scene.Cursor; import javafx.scene.shape.Polygon; -import javafx.scene.shape.Rectangle; /** * Presentation for high level graphical models such as systems and components @@ -23,269 +8,9 @@ public abstract class ModelPresentation extends HighLevelModelPresentation { public static final int CORNER_SIZE = 40; public static final int TOOLBAR_HEIGHT = CORNER_SIZE / 2; - - static final Polygon TOP_LEFT_CORNER = new Polygon( + public static final Polygon TOP_LEFT_CORNER = new Polygon( 0, 0, CORNER_SIZE + 2, 0, 0, CORNER_SIZE + 2 ); - - abstract ModelController getModelController(); - abstract double getDragAnchorMinWidth(); - abstract double getDragAnchorMinHeight(); - - /** - * Initializes this. - * @param box the box of the model - */ - void initialize(final Box box) { - initializeName(); - initializeDimensions(box); - initializesBottomDragAnchor(box); - initializesRightDragAnchor(box); - initializesCornerDragAnchor(box); - } - - /** - * Initializes handling of name. - */ - private void initializeName() { - final ModelController controller = getModelController(); - final HighLevelModelObject model = controller.getModel(); - - final BooleanProperty initialized = new SimpleBooleanProperty(false); - - controller.name.focusedProperty().addListener((observable, oldValue, newValue) -> { - if (newValue && !initialized.get()) { - controller.root.requestFocus(); - initialized.setValue(true); - } - }); - - // Set the text field to the name in the model, and bind the model to the text field - controller.name.setText(model.getName()); - controller.name.textProperty().addListener((obs, oldName, newName) -> { - if (StringValidator.validateComponentName(newName)) { - model.nameProperty().unbind(); - model.setName(newName); - } else { - controller.name.setText(model.getName()); - Ecdar.showToast("Component names cannot contain '.'"); - } - }); - - final Runnable updateColor = () -> { - final Color color = model.getColor(); - final Color.Intensity colorIntensity = model.getColorIntensity(); - - // Set the text color for the label - controller.name.setStyle("-fx-text-fill: " + color.getTextColorRgbaString(colorIntensity) + ";"); - controller.name.setFocusColor(color.getTextColor(colorIntensity)); - controller.name.setUnFocusColor(javafx.scene.paint.Color.TRANSPARENT); - }; - - model.colorProperty().addListener(observable -> updateColor.run()); - updateColor.run(); - - // Center the text vertically and add a left padding - controller.name.setPadding(new Insets(2, 0, 0, CORNER_SIZE)); - controller.name.setOnKeyPressed(EcdarController.getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler()); - } - - /** - * Sets the width and the height of the view to the values in the abstraction. - * @param box The dimensions to set - */ - void initializeDimensions(final Box box) { - // Bind the position of the abstraction to the values in the view - layoutXProperty().set(box.getX()); - layoutYProperty().set(box.getY()); - box.getXProperty().bindBidirectional(layoutXProperty()); - box.getYProperty().bindBidirectional(layoutYProperty()); - - setMinWidth(box.getWidth()); - setMaxWidth(box.getWidth()); - setMinHeight(box.getHeight()); - setMaxHeight(box.getHeight()); - minHeightProperty().bindBidirectional(box.getHeightProperty()); - maxHeightProperty().bindBidirectional(box.getHeightProperty()); - minWidthProperty().bindBidirectional(box.getWidthProperty()); - maxWidthProperty().bindBidirectional(box.getWidthProperty()); - } - - /** - * Initializes the right drag anchor. - * @param box the box of the model - */ - private void initializesRightDragAnchor(final Box box) { - final BooleanProperty wasResized = new SimpleBooleanProperty(false); - - // Right anchor - final Rectangle rightAnchor = getModelController().rightAnchor; - - rightAnchor.setCursor(Cursor.E_RESIZE); - - // Bind the place and size of bottom anchor - rightAnchor.setWidth(5); - rightAnchor.heightProperty().bind(box.getHeightProperty()); - - final DoubleProperty prevX = new SimpleDoubleProperty(); - final DoubleProperty prevWidth = new SimpleDoubleProperty(); - - rightAnchor.setOnMousePressed(event -> { - event.consume(); - - prevX.set(event.getScreenX()); - prevWidth.set(box.getWidth()); - }); - - rightAnchor.setOnMouseDragged(event -> { - double diff = event.getScreenX() - prevX.get(); - final double newWidth = prevWidth.get() + diff; - final double minWidth = getDragAnchorMinWidth(); - - // Move the model left or right to account for new height (needed because model is centered in parent) - setTranslateX(getTranslateX() + (Math.max(newWidth, minWidth) - box.getWidth()) / 2); - box.setWidth(Math.max(newWidth, minWidth)); - wasResized.set(true); - }); - - rightAnchor.setOnMouseReleased(event -> { - if (!wasResized.get()) return; - final double previousWidth = prevWidth.doubleValue(); - final double currentWidth = box.getWidth(); - - // If no difference do not save change - if (previousWidth == currentWidth) return; - - UndoRedoStack.pushAndPerform(() -> { // Perform - box.setWidth(currentWidth); - }, () -> { // Undo - box.setWidth(previousWidth); - }, - "Component width resized", - "settings-overscan" - ); - - wasResized.set(false); - }); - } - - /** - * Initializes the bottom drag anchor. - * @param box the box of the model - */ - private void initializesBottomDragAnchor(final Box box) { - final BooleanProperty wasResized = new SimpleBooleanProperty(false); - // Bottom anchor - final Rectangle bottomAnchor = getModelController().bottomAnchor; - - bottomAnchor.setCursor(Cursor.S_RESIZE); - - // Bind the place and size of bottom anchor - bottomAnchor.widthProperty().bind(box.getWidthProperty()); - bottomAnchor.setHeight(5); - - final DoubleProperty prevY = new SimpleDoubleProperty(); - final DoubleProperty prevHeight = new SimpleDoubleProperty(); - - bottomAnchor.setOnMousePressed(event -> { - event.consume(); - - prevY.set(event.getScreenY()); - prevHeight.set(box.getHeight()); - }); - - bottomAnchor.setOnMouseDragged(event -> { - double diff = event.getScreenY() - prevY.get(); - final double newHeight = prevHeight.get() + diff; - final double minHeight = getDragAnchorMinHeight(); - - // Move the model up or down to account for new height (needed because model is centered in parent) - setTranslateY(getTranslateY() + (Math.max(newHeight, minHeight) - box.getHeight()) / 2); - box.setHeight(Math.max(newHeight, minHeight)); - wasResized.set(true); - }); - - bottomAnchor.setOnMouseReleased(event -> { - if (!wasResized.get()) return; - final double previousHeight = prevHeight.doubleValue(); - final double currentHeight = box.getHeight(); - - // If no difference do not save change - if (previousHeight == currentHeight) return; - - UndoRedoStack.pushAndPerform(() -> { // Perform - box.setHeight(currentHeight); - }, () -> { // Undo - box.setHeight(previousHeight); - }, - "Component height resized", - "settings-overscan" - ); - - wasResized.set(false); - }); - } - - private void initializesCornerDragAnchor(final Box box) { - final BooleanProperty wasResized = new SimpleBooleanProperty(false); - - final Rectangle cornerAnchor = getModelController().cornerAnchor; - cornerAnchor.setCursor(Cursor.SE_RESIZE); - - // Bind the place and size of bottom anchor - cornerAnchor.setWidth(10); - cornerAnchor.setHeight(10); - - final DoubleProperty prevX = new SimpleDoubleProperty(); - final DoubleProperty prevY = new SimpleDoubleProperty(); - final DoubleProperty prevWidth = new SimpleDoubleProperty(); - final DoubleProperty prevHeight = new SimpleDoubleProperty(); - - cornerAnchor.setOnMousePressed(event -> { - event.consume(); - - prevX.set(event.getScreenX()); - prevWidth.set(box.getWidth()); - prevY.set(event.getScreenY()); - prevHeight.set(box.getHeight()); - }); - - cornerAnchor.setOnMouseDragged(event -> { - double xDiff = (event.getScreenX() - prevX.get()) / EcdarController.getActiveCanvasZoomFactor().get(); - final double newWidth = Math.max(prevWidth.get() + xDiff, getDragAnchorMinWidth()); - box.setWidth(newWidth); - - double yDiff = (event.getScreenY() - prevY.get()) / EcdarController.getActiveCanvasZoomFactor().get(); - final double newHeight = Math.max(prevHeight.get() + yDiff, getDragAnchorMinHeight()); - box.setHeight(newHeight); - - wasResized.set(true); - }); - - cornerAnchor.setOnMouseReleased(event -> { - if (!wasResized.get()) return; - final double previousWidth = prevWidth.doubleValue(); - final double currentWidth = box.getWidth(); - final double previousHeight = prevHeight.doubleValue(); - final double currentHeight = box.getHeight(); - - // If no difference do not save change - if (previousWidth == currentWidth && previousHeight == currentHeight) return; - - UndoRedoStack.pushAndPerform(() -> { // Perform - box.setWidth(currentWidth); - box.setHeight(currentHeight); - }, () -> { // Undo - box.setWidth(previousWidth); - box.setHeight(previousHeight); - }, - "Component resized", - "settings-overscan" - ); - - wasResized.set(false); - }); - } } diff --git a/src/main/java/ecdar/presentations/MultiSyncTagPresentation.java b/src/main/java/ecdar/presentations/MultiSyncTagPresentation.java index a2d1fb8d..85b0790b 100644 --- a/src/main/java/ecdar/presentations/MultiSyncTagPresentation.java +++ b/src/main/java/ecdar/presentations/MultiSyncTagPresentation.java @@ -46,7 +46,7 @@ public MultiSyncTagPresentation(GroupedEdge edge, Runnable updateIOStatusOfSubEd Platform.runLater(() -> { // Added to avoid NullPointer exception for location aware and component in getDragBounds method edge.getNails().stream().filter(n -> n.getPropertyType().equals(DisplayableEdge.PropertyType.SYNCHRONIZATION)).findFirst().ifPresent(this::setLocationAware); - setComponent(EcdarController.getActiveCanvasPresentation().getController().activeComponentPresentation.getController().getComponent()); + setComponent(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().activeComponentPresentation.getController().getComponent()); updateTopBorder(); initializeMouseTransparency(); @@ -165,7 +165,7 @@ private void updateTopBorder() { for (Node child : controller.syncList.getChildren()) { ((SyncTextFieldPresentation) child).getController().textField - .setOnKeyPressed(EcdarController.getActiveCanvasPresentation().getController() + .setOnKeyPressed(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController() .getLeaveTextAreaKeyHandler((keyEvent) -> { if (keyEvent.getCode().equals(KeyCode.ENTER)) { @@ -181,13 +181,11 @@ private void updateTopBorder() { } private void updateColorAndMouseShape() { - EcdarController.getActiveCanvasPresentation().getController().activeComponentPresentation.getController().getComponent().colorProperty().addListener((observable, oldValue, newValue) -> { - controller.frame.setBackground(new Background(new BackgroundFill(newValue.getColor( - EcdarController.getActiveCanvasPresentation().getController().activeComponentPresentation.getController().getComponent().getColorIntensity()), new CornerRadii(0), Insets.EMPTY))); + Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().activeComponentPresentation.getController().getComponent().colorProperty().addListener((observable, oldValue, newValue) -> { + controller.frame.setBackground(new Background(new BackgroundFill(newValue.getPaintColor(), new CornerRadii(0), Insets.EMPTY))); }); - Color color = EcdarController.getActiveCanvasPresentation().getController().activeComponentPresentation.getController().getComponent().getColor().getColor( - EcdarController.getActiveCanvasPresentation().getController().activeComponentPresentation.getController().getComponent().getColorIntensity()); + Color color = Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().activeComponentPresentation.getController().getComponent().getColor().getPaintColor(); controller.frame.setBackground(new Background(new BackgroundFill(color, new CornerRadii(0), Insets.EMPTY))); controller.frame.setCursor(Cursor.OPEN_HAND); @@ -216,8 +214,8 @@ private void initializeDragging() { event.consume(); Platform.runLater(() -> { - final double dragDistanceX = (event.getSceneX() - dragOffsetX.get()) / EcdarController.getActiveCanvasZoomFactor().get(); - final double dragDistanceY = (event.getSceneY() - dragOffsetY.get()) / EcdarController.getActiveCanvasZoomFactor().get(); + final double dragDistanceX = (event.getSceneX() - dragOffsetX.get()) / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get(); + final double dragDistanceY = (event.getSceneY() - dragOffsetY.get()) / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get(); double draggableNewX = getDragBounds().trimX(draggablePreviousX.get() + dragDistanceX); double draggableNewY = getDragBounds().trimY(draggablePreviousY.get() + dragDistanceY); diff --git a/src/main/java/ecdar/presentations/NailPresentation.java b/src/main/java/ecdar/presentations/NailPresentation.java index 2aa8ea18..f6ee250e 100644 --- a/src/main/java/ecdar/presentations/NailPresentation.java +++ b/src/main/java/ecdar/presentations/NailPresentation.java @@ -4,6 +4,7 @@ import ecdar.controllers.EdgeController; import ecdar.controllers.NailController; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.BindingHelper; import ecdar.utility.Highlightable; import ecdar.utility.helpers.SelectHelper; @@ -165,7 +166,7 @@ private void setPropertyTagComponentAndLocationAware(TagPresentation propertyTag BindingHelper.bind(propertyTagLine, propertyTag); // Bind the color of the tag to the color of the component - propertyTag.bindToColor(controller.getComponent().colorProperty(), controller.getComponent().colorIntensityProperty()); + propertyTag.bindToColor(controller.getComponent().colorProperty()); } /** @@ -190,8 +191,6 @@ private void initializeNailCircleColor() { // When the color of the component updates, update the nail indicator as well controller.getComponent().colorProperty().addListener((observable) -> updateNailColor()); - // When the color intensity of the component updates, update the nail indicator - controller.getComponent().colorIntensityProperty().addListener((observable) -> updateNailColor()); // Initialize the color of the nail updateNailColor(); } @@ -201,10 +200,8 @@ private void initializeNailCircleColor() { */ public void onFailingUpdate(boolean isFailing) { final Runnable updateNailColorOnFailingUpdate = () -> { - final Color color = controller.getComponent().getColor(); - final Color.Intensity colorIntensity = controller.getComponent().getColorIntensity(); - controller.nailCircle.setFill(Color.RED.getColor(colorIntensity)); - controller.nailCircle.setStroke(Color.RED.getColor(colorIntensity.next(2))); + controller.nailCircle.setFill(Color.RED.getColor(Color.Intensity.I700)); + controller.nailCircle.setStroke(Color.RED.getColor(Color.Intensity.I700.next(2))); }; if (isFailing) { updateNailColorOnFailingUpdate.run(); @@ -215,22 +212,26 @@ public void onFailingUpdate(boolean isFailing) { private void updateNailColor() { final Runnable updateNailColor = () -> { - final Color color = controller.getComponent().getColor(); - final Color.Intensity colorIntensity = controller.getComponent().getColorIntensity(); - //If edge is failing and is a SYNC + final EnabledColor color = controller.getComponent().getColor(); + if (controller.getEdge().getFailing() && controller.getNail().getPropertyType().equals(Edge.PropertyType.SYNCHRONIZATION)) { - controller.nailCircle.setFill(Color.RED.getColor(colorIntensity)); - controller.nailCircle.setStroke(Color.RED.getColor(colorIntensity.next(2))); + controller.nailCircle.setFill(Color.RED.getColor(Color.Intensity.I700)); + controller.nailCircle.setStroke(Color.RED.getColor(Color.Intensity.I700.next(2))); } //If edge is not NONE - else if (!controller.getNail().getPropertyType().equals(Edge.PropertyType.NONE)) { - controller.nailCircle.setFill(color.getColor(colorIntensity)); - controller.nailCircle.setStroke(color.getColor(colorIntensity.next(2))); + else if(!controller.getNail().getPropertyType().equals(Edge.PropertyType.NONE)) { + controller.nailCircle.setFill(color.getPaintColor()); + controller.nailCircle.setStroke(color.getStrokeColor()); } else { controller.nailCircle.setFill(Color.GREY_BLUE.getColor(Color.Intensity.I800)); controller.nailCircle.setStroke(Color.GREY_BLUE.getColor(Color.Intensity.I900)); } }; + + // When the color of the component updates, update the nail indicator as well + controller.getComponent().colorProperty().addListener((observable) -> updateNailColor.run()); + + // Initialize the color of the nail updateNailColor.run(); } diff --git a/src/main/java/ecdar/presentations/ProcessPresentation.java b/src/main/java/ecdar/presentations/ProcessPresentation.java index 92b4b11d..f4db4c46 100755 --- a/src/main/java/ecdar/presentations/ProcessPresentation.java +++ b/src/main/java/ecdar/presentations/ProcessPresentation.java @@ -8,6 +8,7 @@ import ecdar.controllers.ModelController; import ecdar.controllers.ProcessController; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import javafx.beans.InvalidationListener; import javafx.geometry.Insets; import javafx.geometry.Pos; @@ -24,6 +25,7 @@ import java.math.BigDecimal; import java.util.List; import java.util.function.BiConsumer; +import java.util.function.Consumer; /** * The presenter of a Process which is shown in {@link SimulatorOverviewPresentation}. <br /> @@ -40,7 +42,7 @@ public class ProcessPresentation extends ModelPresentation { public ProcessPresentation(final Component component){ controller = new EcdarFXMLLoader().loadAndGetController("ProcessPresentation.fxml", this); controller.setComponent(component); - super.initialize(component.getBox()); + // Initialize methods that is sensitive to width and height final Runnable onUpdateSize = () -> { initializeToolbar(); @@ -122,7 +124,7 @@ private void initializeFrame() { final Shape[] mask = new Shape[1]; final Rectangle rectangle = new Rectangle(component.getBox().getWidth(), component.getBox().getHeight()); - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { + final Consumer<EnabledColor> updateColor = (newColor) -> { // Mask the parent of the frame (will also mask the background) mask[0] = Path.subtract(rectangle, TOP_LEFT_CORNER); controller.frame.setClip(mask[0]); @@ -134,13 +136,13 @@ private void initializeFrame() { controller.topLeftLine.setStartY(0); controller.topLeftLine.setEndX(0); controller.topLeftLine.setEndY(CORNER_SIZE); - controller.topLeftLine.setStroke(newColor.getColor(newIntensity.next(2))); + controller.topLeftLine.setStroke(newColor.getStrokeColor()); controller.topLeftLine.setStrokeWidth(1.25); StackPane.setAlignment(controller.topLeftLine, Pos.TOP_LEFT); // Set the stroke color to two shades darker controller.frame.setBorder(new Border(new BorderStroke( - newColor.getColor(newIntensity.next(2)), + newColor.getStrokeColor(), BorderStrokeStyle.SOLID, CornerRadii.EMPTY, new BorderWidths(1), @@ -148,9 +150,9 @@ private void initializeFrame() { ))); }; component.colorProperty().addListener(observable -> { - updateColor.accept(component.getColor(), component.getColorIntensity()); + updateColor.accept(component.getColor()); }); - updateColor.accept(component.getColor(), component.getColorIntensity()); + updateColor.accept(component.getColor()); } /** @@ -163,16 +165,16 @@ private void initializeBackground() { // Bind the background width and height to the values in the model controller.background.widthProperty().bind(component.getBox().getWidthProperty()); controller.background.heightProperty().bind(component.getBox().getHeightProperty()); - controller.background.setFill(component.getColor().getColor(component.getColorIntensity().next(-10).next(2))); - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { + controller.background.setFill(component.getColor().setIntensity(2).getPaintColor()); + final Consumer<EnabledColor> updateColor = (newColor) -> { // Set the background color to the lightest possible version of the color - controller.background.setFill(newColor.getColor(newIntensity.next(-10).next(2))); + controller.background.setFill(newColor.setIntensity(2).getPaintColor()); }; component.colorProperty().addListener(observable -> { - updateColor.accept(component.getColor(), component.getColorIntensity()); + updateColor.accept(component.getColor()); }); - updateColor.accept(component.getColor(), component.getColorIntensity()); + updateColor.accept(component.getColor()); } /** @@ -182,23 +184,23 @@ private void initializeBackground() { private void initializeToolbar() { final Component component = controller.getComponent(); - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { + final Consumer<EnabledColor> updateColor = (newColor) -> { // Set the background of the toolbar controller.toolbar.setBackground(new Background(new BackgroundFill( - newColor.getColor(newIntensity), + newColor.getPaintColor(), CornerRadii.EMPTY, Insets.EMPTY ))); // Set the icon color and rippler color of the toggleDeclarationButton - controller.toggleValuesButton.setRipplerFill(newColor.getTextColor(newIntensity)); + controller.toggleValuesButton.setRipplerFill(newColor.getTextColor()); controller.toolbar.setPrefHeight(TOOLBAR_HEIGHT); controller.toggleValuesButton.setBackground(Background.EMPTY); }; - controller.getComponent().colorProperty().addListener(observable -> updateColor.accept(component.getColor(), component.getColorIntensity())); + controller.getComponent().colorProperty().addListener(observable -> updateColor.accept(component.getColor())); - updateColor.accept(component.getColor(), component.getColorIntensity()); + updateColor.accept(component.getColor()); // Set a hover effect for the controller.toggleDeclarationButton controller.toggleValuesButton.setOnMouseEntered(event -> controller.toggleValuesButton.setCursor(Cursor.HAND)); @@ -222,59 +224,6 @@ public void showActive() { } @Override - ModelController getModelController() { - return controller; - } - - /** - * Gets the minimum possible width when dragging the anchor. - * The width is based on the x coordinate of locations, nails and the signature arrows. <br /> - * This should be removed from {@link ModelPresentation} and made into an interface of its own - * @return the minimum possible width. - */ - @Override - @Deprecated - double getDragAnchorMinWidth() { - final Component component = controller.getComponent(); - double minWidth = Ecdar.CANVAS_PADDING *10; - - for (final Location location : component.getLocations()) { - minWidth = Math.max(minWidth, location.getX() + Ecdar.CANVAS_PADDING * 2); - } - - for (final Edge edge : component.getEdges()) { - for (final Nail nail : edge.getNails()) { - minWidth = Math.max(minWidth, nail.getX() + Ecdar.CANVAS_PADDING); - } - } - return minWidth; - } - - /** - * Gets the minimum possible height when dragging the anchor. - * The height is based on the y coordinate of locations, nails and the signature arrows <br /> - * This should be removed from {@link ModelPresentation} and made into an interface of its own - * @return the minimum possible height. - */ - @Override - @Deprecated - double getDragAnchorMinHeight() { - final Component component = controller.getComponent(); - double minHeight = Ecdar.CANVAS_PADDING * 10; - - for (final Location location : component.getLocations()) { - minHeight = Math.max(minHeight, location.getY() + Ecdar.CANVAS_PADDING * 2); - } - - for (final Edge edge : component.getEdges()) { - for (final Nail nail : edge.getNails()) { - minHeight = Math.max(minHeight, nail.getY() + Ecdar.CANVAS_PADDING); - } - } - - return minHeight; - } - public ProcessController getController() { return controller; } diff --git a/src/main/java/ecdar/presentations/ProjectPanePresentation.java b/src/main/java/ecdar/presentations/ProjectPanePresentation.java index 9d2e8ccc..d152fcaa 100644 --- a/src/main/java/ecdar/presentations/ProjectPanePresentation.java +++ b/src/main/java/ecdar/presentations/ProjectPanePresentation.java @@ -5,13 +5,13 @@ import ecdar.utility.colors.Color; import ecdar.utility.helpers.DropShadowHelper; import com.jfoenix.controls.JFXRippler; +import ecdar.utility.helpers.ImageScaler; import javafx.geometry.Insets; import javafx.scene.control.Tooltip; import javafx.scene.image.Image; import javafx.scene.layout.*; public class ProjectPanePresentation extends StackPane { - private final ProjectPaneController controller; public ProjectPanePresentation() { @@ -101,9 +101,9 @@ private void initializeToolbarButton(final JFXRippler button) { */ private void initializeAddModelIcons() { controller.createComponentImage.setImage(new Image(Ecdar.class.getResource("add_component_frame.png").toExternalForm())); - EcdarPresentation.fitSizeWhenAvailable(controller.createComponentImage, controller.createComponentPane); + ImageScaler.fitImageToPane(controller.createComponentImage, controller.createComponentPane); controller.createSystemImage.setImage(new Image(Ecdar.class.getResource("add_system_frame.png").toExternalForm())); - EcdarPresentation.fitSizeWhenAvailable(controller.createSystemImage, controller.createSystemPane); + ImageScaler.fitImageToPane(controller.createSystemImage, controller.createSystemPane); } /** diff --git a/src/main/java/ecdar/presentations/QueryPresentation.java b/src/main/java/ecdar/presentations/QueryPresentation.java index 852b1704..9c7bd48a 100644 --- a/src/main/java/ecdar/presentations/QueryPresentation.java +++ b/src/main/java/ecdar/presentations/QueryPresentation.java @@ -30,7 +30,6 @@ public class QueryPresentation extends HBox { private final Tooltip tooltip = new Tooltip(); - private Tooltip backendDropdownTooltip; private final QueryController controller; public QueryPresentation(final Query query) { @@ -43,18 +42,19 @@ public QueryPresentation(final Query query) { initializeDetailsButton(); initializeTextFields(); initializeMoreInformationButtonAndQueryTypeSymbol(); - initializeBackendsDropdown(); + initializeEnginesDropdown(); // Ensure that the icons are scaled to current font scale Platform.runLater(() -> Ecdar.getPresentation().getController().scaleIcons(this)); } - private void initializeBackendsDropdown() { - controller.backendsDropdown.setItems(BackendHelper.getBackendInstances()); - backendDropdownTooltip = new Tooltip(); - backendDropdownTooltip.setText("Current backend used for the query"); - JFXTooltip.install(controller.backendsDropdown, backendDropdownTooltip); - controller.backendsDropdown.setValue(BackendHelper.getDefaultBackendInstance()); + private void initializeEnginesDropdown() { + controller.enginesDropdown.setItems(BackendHelper.getEngines()); + + Tooltip enginesDropdownTooltip = new Tooltip(); + enginesDropdownTooltip.setText("Current engine used for the query"); + JFXTooltip.install(controller.enginesDropdown, enginesDropdownTooltip); + controller.enginesDropdown.setValue(BackendHelper.getDefaultEngine()); } private void initializeTextFields() { @@ -69,7 +69,7 @@ private void initializeTextFields() { controller.getQuery().commentProperty().bind(commentTextField.textProperty()); - queryTextField.setOnKeyPressed(EcdarController.getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler(keyEvent -> { + queryTextField.setOnKeyPressed(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler(keyEvent -> { Platform.runLater(() -> { if (keyEvent.getCode().equals(KeyCode.ENTER) && controller.getQuery().getType() != null) { runQuery(); @@ -85,7 +85,7 @@ private void initializeTextFields() { } }); - commentTextField.setOnKeyPressed(EcdarController.getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler()); + commentTextField.setOnKeyPressed(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler()); }); } @@ -332,6 +332,6 @@ private void addQueryTypeListElement(final QueryType type, final DropDownMenu dr } private void runQuery() { - Ecdar.getQueryExecutor().executeQuery(this.controller.getQuery()); + this.controller.getQuery().execute(); } } diff --git a/src/main/java/ecdar/presentations/SignatureArrow.java b/src/main/java/ecdar/presentations/SignatureArrow.java index 51db977e..fb2bfdfb 100644 --- a/src/main/java/ecdar/presentations/SignatureArrow.java +++ b/src/main/java/ecdar/presentations/SignatureArrow.java @@ -1,5 +1,6 @@ package ecdar.presentations; +import ecdar.Ecdar; import ecdar.abstractions.Component; import ecdar.abstractions.EdgeStatus; import ecdar.controllers.EcdarController; @@ -128,14 +129,14 @@ public void unhighlight() { // Color matching SignatureArrow red SignatureArrow matchingSignatureArrow; if (controller.getEdgeStatus().equals(EdgeStatus.INPUT)) { - matchingSignatureArrow = (SignatureArrow) EcdarController.getActiveCanvasPresentation().getController() + matchingSignatureArrow = (SignatureArrow) Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController() .activeComponentPresentation.getController() .outputSignatureContainer.getChildren() .stream().filter(node -> node instanceof SignatureArrow && ((SignatureArrow) node).controller.getSyncText().equals(controller.getSyncText())) .findFirst().orElse(null); } else { - matchingSignatureArrow = (SignatureArrow) EcdarController.getActiveCanvasPresentation().getController() + matchingSignatureArrow = (SignatureArrow) Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController() .activeComponentPresentation.getController() .inputSignatureContainer.getChildren() .stream().filter(node -> node instanceof SignatureArrow && ((SignatureArrow) node).controller.getSyncText().equals(controller.getSyncText())) diff --git a/src/main/java/ecdar/presentations/SimEdgePresentation.java b/src/main/java/ecdar/presentations/SimEdgePresentation.java index 1456adc8..8991f145 100644 --- a/src/main/java/ecdar/presentations/SimEdgePresentation.java +++ b/src/main/java/ecdar/presentations/SimEdgePresentation.java @@ -1,9 +1,9 @@ package ecdar.presentations; -import ecdar.Ecdar; import ecdar.abstractions.Component; import ecdar.abstractions.Edge; import ecdar.controllers.SimEdgeController; +import ecdar.controllers.SimulatorController; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.scene.Group; @@ -20,7 +20,7 @@ public class SimEdgePresentation extends Group { public SimEdgePresentation(final Edge edge, final Component component) { controller = new EcdarFXMLLoader().loadAndGetController("SimEdgePresentation.fxml", this); - var simulationHandler = Ecdar.getSimulationHandler(); + var simulationHandler = SimulatorController.getSimulationHandler(); controller.setEdge(edge); this.edge.bind(controller.edgeProperty()); diff --git a/src/main/java/ecdar/presentations/SimLocationPresentation.java b/src/main/java/ecdar/presentations/SimLocationPresentation.java index 082a9bb3..29b51cb1 100755 --- a/src/main/java/ecdar/presentations/SimLocationPresentation.java +++ b/src/main/java/ecdar/presentations/SimLocationPresentation.java @@ -5,6 +5,7 @@ import ecdar.controllers.SimLocationController; import ecdar.utility.Highlightable; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.BindingHelper; import ecdar.utility.helpers.SelectHelper; import javafx.animation.*; @@ -41,7 +42,7 @@ public class SimLocationPresentation extends Group implements Highlightable { private final Timeline hiddenAreaAnimationExited = new Timeline(); private final Timeline scaleShakeIndicatorBackgroundAnimation = new Timeline(); private final Timeline shakeContentAnimation = new Timeline(); - private final List<BiConsumer<Color, Color.Intensity>> updateColorDelegates = new ArrayList<>(); + private final List<Consumer<EnabledColor>> updateColorDelegates = new ArrayList<>(); private final DoubleProperty animation = new SimpleDoubleProperty(0); private final DoubleBinding reverseAnimation = new SimpleDoubleProperty(1).subtract(animation); private BooleanProperty isPlaced = new SimpleBooleanProperty(true); @@ -87,22 +88,21 @@ private void initializeIdLabel() { idLabel.widthProperty().addListener((obsWidth, oldWidth, newWidth) -> idLabel.translateXProperty().set(newWidth.doubleValue() / -2)); idLabel.heightProperty().addListener((obsHeight, oldHeight, newHeight) -> idLabel.translateYProperty().set(newHeight.doubleValue() / -2)); - final ObjectProperty<Color> color = location.colorProperty(); - final ObjectProperty<Color.Intensity> colorIntensity = location.colorIntensityProperty(); + final ObjectProperty<EnabledColor> color = location.colorProperty(); // Delegate to style the label based on the color of the location - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { - idLabel.setTextFill(newColor.getTextColor(newIntensity)); - ds.setColor(newColor.getColor(newIntensity)); + final Consumer<EnabledColor> updateColor = (newColor) -> { + idLabel.setTextFill(newColor.getTextColor()); + ds.setColor(newColor.getPaintColor()); }; updateColorDelegates.add(updateColor); // Set the initial color - updateColor.accept(color.get(), colorIntensity.get()); + updateColor.accept(color.get()); // Update the color of the circle when the color of the location is updated - color.addListener((obs, old, newColor) -> updateColor.accept(newColor, colorIntensity.get())); + color.addListener((obs, old, newColor) -> updateColor.accept(newColor)); } /** @@ -130,8 +130,8 @@ private void initializeTags() { final Consumer<Location> updateTags = location -> { // Update the color - controller.nicknameTag.bindToColor(location.colorProperty(), location.colorIntensityProperty(), true); - controller.invariantTag.bindToColor(location.colorProperty(), location.colorIntensityProperty(), false); + controller.nicknameTag.bindToColor(location.colorProperty(), true); + controller.invariantTag.bindToColor(location.colorProperty(), false); // Update the invariant controller.nicknameTag.setAndBindString(location.nicknameProperty()); @@ -185,23 +185,21 @@ private void initializeCircle() { final Circle circle = controller.circle; circle.setRadius(RADIUS); - final ObjectProperty<Color> color = location.colorProperty(); - final ObjectProperty<Color.Intensity> colorIntensity = location.colorIntensityProperty(); + final ObjectProperty<EnabledColor> color = location.colorProperty(); // Delegate to style the label based on the color of the location - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { - circle.setFill(newColor.getColor(newIntensity)); - circle.setStroke(newColor.getColor(newIntensity.next(2))); + final Consumer<EnabledColor> updateColor = (newColor) -> { + circle.setFill(newColor.getPaintColor()); + circle.setStroke(newColor.getStrokeColor()); }; updateColorDelegates.add(updateColor); // Set the initial color - updateColor.accept(color.get(), colorIntensity.get()); + updateColor.accept(color.get()); // Update the color of the circle when the color of the location is updated - color.addListener((obs, old, newColor) -> updateColor.accept(newColor, colorIntensity.get())); - colorIntensity.addListener((obs, old, newIntensity) -> updateColor.accept(color.get(), newIntensity)); + color.addListener((obs, old, newColor) -> updateColor.accept(newColor)); } /** @@ -253,22 +251,21 @@ protected void interpolate(final double frac) { updateUrgencies.accept(Location.Urgency.NORMAL, location.getUrgency()); // Update the colors - final ObjectProperty<Color> color = location.colorProperty(); - final ObjectProperty<Color.Intensity> colorIntensity = location.colorIntensityProperty(); + final ObjectProperty<EnabledColor> color = location.colorProperty(); // Delegate to style the label based on the color of the location - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { - notCommittedShape.setFill(newColor.getColor(newIntensity)); - notCommittedShape.setStroke(newColor.getColor(newIntensity.next(2))); + final Consumer<EnabledColor> updateColor = (newColor) -> { + notCommittedShape.setFill(newColor.getPaintColor()); + notCommittedShape.setStroke(newColor.getStrokeColor()); }; updateColorDelegates.add(updateColor); // Set the initial color - updateColor.accept(color.get(), colorIntensity.get()); + updateColor.accept(color.get()); // Update the color of the circle when the color of the location is updated - color.addListener((obs, old, newColor) -> updateColor.accept(newColor, colorIntensity.get())); + color.addListener((obs, old, newColor) -> updateColor.accept(newColor)); } /** @@ -491,15 +488,13 @@ private void initializeLocationShapes(final Path locationShape, final double rad @Override public void highlight() { - updateColorDelegates.forEach(colorConsumer -> colorConsumer.accept(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL)); + updateColorDelegates.forEach(colorConsumer -> colorConsumer.accept(new EnabledColor(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL))); } @Override public void unhighlight() { updateColorDelegates.forEach(colorConsumer -> { - final Location location = controller.getLocation(); - - colorConsumer.accept(location.getColor(), location.getColorIntensity()); + colorConsumer.accept(controller.getLocation().getColor()); }); } } \ No newline at end of file diff --git a/src/main/java/ecdar/presentations/SimNailPresentation.java b/src/main/java/ecdar/presentations/SimNailPresentation.java index 1baaf392..d6e7fbca 100755 --- a/src/main/java/ecdar/presentations/SimNailPresentation.java +++ b/src/main/java/ecdar/presentations/SimNailPresentation.java @@ -8,6 +8,7 @@ import ecdar.controllers.SimNailController; import ecdar.utility.Highlightable; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.BindingHelper; import javafx.animation.Interpolator; import javafx.animation.KeyFrame; @@ -88,7 +89,7 @@ private void initializePropertyTag() { BindingHelper.bind(propertyTagLine, propertyTag); // Bind the color of the tag to the color of the component - propertyTag.bindToColor(controller.getComponent().colorProperty(), controller.getComponent().colorIntensityProperty()); + propertyTag.bindToColor(controller.getComponent().colorProperty()); // Updates visibility and placeholder of the tag depending on the type of nail final Consumer<Edge.PropertyType> updatePropertyType = (propertyType) -> { @@ -175,12 +176,11 @@ private void updateSyncLabel() { */ private void initializeNailCircleColor() { final Runnable updateNailColor = () -> { - final Color color = controller.getComponent().getColor(); - final Color.Intensity colorIntensity = controller.getComponent().getColorIntensity(); + final EnabledColor color = controller.getComponent().getColor(); if(!controller.getNail().getPropertyType().equals(Edge.PropertyType.NONE)) { - controller.nailCircle.setFill(color.getColor(colorIntensity)); - controller.nailCircle.setStroke(color.getColor(colorIntensity.next(2))); + controller.nailCircle.setFill(color.getPaintColor()); + controller.nailCircle.setStroke(color.getStrokeColor()); } else { controller.nailCircle.setFill(Color.GREY_BLUE.getColor(Color.Intensity.I800)); controller.nailCircle.setStroke(Color.GREY_BLUE.getColor(Color.Intensity.I900)); @@ -190,9 +190,6 @@ private void initializeNailCircleColor() { // When the color of the component updates, update the nail indicator as well controller.getComponent().colorProperty().addListener((observable) -> updateNailColor.run()); - // When the color intensity of the component updates, update the nail indicator - controller.getComponent().colorIntensityProperty().addListener((observable) -> updateNailColor.run()); - // Initialize the color of the nail updateNailColor.run(); } @@ -253,16 +250,14 @@ public void highlightPurple() { */ @Override public void unhighlight() { - Color color = Color.GREY_BLUE; - Color.Intensity intensity = Color.Intensity.I800; + EnabledColor color = new EnabledColor(Color.GREY_BLUE, Color.Intensity.I800); // Set the color if(!controller.getNail().getPropertyType().equals(Edge.PropertyType.NONE)) { color = controller.getComponent().getColor(); - intensity = controller.getComponent().getColorIntensity(); } - controller.nailCircle.setFill(color.getColor(intensity)); - controller.nailCircle.setStroke(color.getColor(intensity.next(2))); + controller.nailCircle.setFill(color.getPaintColor()); + controller.nailCircle.setStroke(color.getStrokeColor()); } } diff --git a/src/main/java/ecdar/presentations/SimTagPresentation.java b/src/main/java/ecdar/presentations/SimTagPresentation.java index 47fc7d1e..6e322076 100755 --- a/src/main/java/ecdar/presentations/SimTagPresentation.java +++ b/src/main/java/ecdar/presentations/SimTagPresentation.java @@ -3,6 +3,7 @@ import ecdar.Ecdar; import ecdar.abstractions.Component; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.LocationAware; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; @@ -15,6 +16,7 @@ import javafx.scene.shape.Path; import java.util.function.BiConsumer; +import java.util.function.Consumer; /** * The presentation for the tag shown on a {@link SimEdgePresentation} in the {@link SimulatorOverviewPresentation}<br /> @@ -112,22 +114,21 @@ private void initializeShape() { shape.setStroke(backgroundColor.getColor(backgroundColorIntensity.next(4))); } - public void bindToColor(final ObjectProperty<Color> color, final ObjectProperty<Color.Intensity> intensity) { - bindToColor(color, intensity, false); + public void bindToColor(final ObjectProperty<EnabledColor> color) { + bindToColor(color, false); } - public void bindToColor(final ObjectProperty<Color> color, final ObjectProperty<Color.Intensity> intensity, final boolean doColorBackground) { - final BiConsumer<Color, Color.Intensity> recolor = (newColor, newIntensity) -> { + public void bindToColor(final ObjectProperty<EnabledColor> color, final boolean doColorBackground) { + final Consumer<EnabledColor> recolor = (newColor) -> { if (doColorBackground) { final Path shape = (Path) lookup("#shape"); - shape.setFill(newColor.getColor(newIntensity.next(-1))); - shape.setStroke(newColor.getColor(newIntensity.next(-1).next(2))); + shape.setFill(newColor.nextIntensity(-1).getPaintColor()); + shape.setStroke(newColor.getStrokeColor()); } }; - color.addListener(observable -> recolor.accept(color.get(), intensity.get())); - intensity.addListener(observable -> recolor.accept(color.get(), intensity.get())); - recolor.accept(color.get(), intensity.get()); + color.addListener(observable -> recolor.accept(color.get())); + recolor.accept(color.get()); } /** diff --git a/src/main/java/ecdar/presentations/SyncTextFieldPresentation.java b/src/main/java/ecdar/presentations/SyncTextFieldPresentation.java index a4e7b82c..e957cc89 100644 --- a/src/main/java/ecdar/presentations/SyncTextFieldPresentation.java +++ b/src/main/java/ecdar/presentations/SyncTextFieldPresentation.java @@ -1,5 +1,6 @@ package ecdar.presentations; +import ecdar.Ecdar; import ecdar.abstractions.Edge; import ecdar.controllers.EcdarController; import ecdar.controllers.SyncTextFieldController; @@ -22,7 +23,7 @@ public SyncTextFieldPresentation(String placeholder, Edge edge) { controller.textField.setTranslateY(2); } else { controller.textField.setTranslateY(0); - EcdarController.getActiveCanvasPresentation().getController().leaveTextAreas(); + Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().leaveTextAreas(); } }); diff --git a/src/main/java/ecdar/presentations/SystemEdgePresentation.java b/src/main/java/ecdar/presentations/SystemEdgePresentation.java index ffc329a2..1abaf1e0 100644 --- a/src/main/java/ecdar/presentations/SystemEdgePresentation.java +++ b/src/main/java/ecdar/presentations/SystemEdgePresentation.java @@ -1,11 +1,12 @@ package ecdar.presentations; import ecdar.Ecdar; -import ecdar.abstractions.EcdarSystemEdge; +import ecdar.abstractions.SystemEdge; import ecdar.abstractions.EcdarSystem; import ecdar.controllers.EcdarController; import ecdar.controllers.SystemEdgeController; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.ItemDragHelper; import ecdar.utility.helpers.SelectHelper; import javafx.beans.property.DoubleProperty; @@ -26,7 +27,7 @@ public class SystemEdgePresentation extends Group implements SelectHelper.ItemSe * @param edge system edge to present * @param system system of the system edge */ - public SystemEdgePresentation(final EcdarSystemEdge edge, final EcdarSystem system) { + public SystemEdgePresentation(final SystemEdge edge, final EcdarSystem system) { controller = new EcdarFXMLLoader().loadAndGetController("SystemEdgePresentation.fxml", this); controller.setEdge(edge); controller.setSystem(system); @@ -35,7 +36,7 @@ public SystemEdgePresentation(final EcdarSystemEdge edge, final EcdarSystem syst initializeBinding(edge); } - private void initializeBinding(final EcdarSystemEdge edge) { + private void initializeBinding(final SystemEdge edge) { final Link link = new Link(); links.add(link); @@ -50,8 +51,8 @@ private void initializeBinding(final EcdarSystemEdge edge) { link.startYProperty().bind(edge.getTempNode().getEdgeY()); // Bind to mouse position - link.endXProperty().bind(EcdarController.getActiveCanvasPresentation().mouseTracker.xProperty().subtract(Ecdar.CANVAS_PADDING / 2)); - link.endYProperty().bind(EcdarController.getActiveCanvasPresentation().mouseTracker.xProperty().subtract(Ecdar.CANVAS_PADDING / 2)); + link.endXProperty().bind(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().mouseTracker.xProperty().subtract(Ecdar.CANVAS_PADDING / 2)); + link.endYProperty().bind(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().mouseTracker.xProperty().subtract(Ecdar.CANVAS_PADDING / 2)); } // If edge source and target changes, bind @@ -73,7 +74,7 @@ private void initializeBinding(final EcdarSystemEdge edge) { * Binds the end of the last link to the parent of the edge. * @param edge edge to bind with */ - private void bindFinishedEdge(final EcdarSystemEdge edge) { + private void bindFinishedEdge(final SystemEdge edge) { final Link firstLink = links.get(0); final Link lastLink = links.get(links.size() - 1); @@ -87,10 +88,9 @@ private void bindFinishedEdge(final EcdarSystemEdge edge) { /** * Does nothing, as this cannot change color. * @param color not used - * @param intensity not used */ @Override - public void color(final Color color, final Color.Intensity intensity) { + public void color(final EnabledColor color) { } @@ -99,16 +99,7 @@ public void color(final Color color, final Color.Intensity intensity) { * @return null */ @Override - public Color getColor() { - return null; - } - - /** - * Returns null, as this cannot change color. - * @return null - */ - @Override - public Color.Intensity getColorIntensity() { + public EnabledColor getColor() { return null; } diff --git a/src/main/java/ecdar/presentations/SystemPresentation.java b/src/main/java/ecdar/presentations/SystemPresentation.java index 306b8b12..182d0aa8 100644 --- a/src/main/java/ecdar/presentations/SystemPresentation.java +++ b/src/main/java/ecdar/presentations/SystemPresentation.java @@ -1,19 +1,7 @@ package ecdar.presentations; -import ecdar.Ecdar; import ecdar.abstractions.*; -import ecdar.controllers.ModelController; import ecdar.controllers.SystemController; -import ecdar.utility.colors.Color; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.layout.*; -import javafx.scene.shape.Path; -import javafx.scene.shape.Polygon; -import javafx.scene.shape.Rectangle; -import javafx.scene.shape.Shape; - -import java.util.function.BiConsumer; /** * Presentation for a system. @@ -25,166 +13,14 @@ public SystemPresentation(final EcdarSystem system) { controller = new EcdarFXMLLoader().loadAndGetController("SystemPresentation.fxml", this); controller.setSystem(system); - super.initialize(system.getBox()); - - initializeDimensions(system.getBox()); - - // Initialize methods that is sensitive to width and height - final Runnable onUpdateSize = () -> { - initializeToolbar(); - initializeFrame(); - initializeBackground(); - }; - - onUpdateSize.run(); - - // Re-run initialisation on update of width and height property - system.getBox().getWidthProperty().addListener(observable -> onUpdateSize.run()); - system.getBox().getHeightProperty().addListener(observable -> onUpdateSize.run()); - } - - /** - * Initializes the toolbar. - */ - private void initializeToolbar() { - final EcdarSystem system = controller.getSystem(); - - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { - // Set the background of the toolbar - controller.toolbar.setBackground(new Background(new BackgroundFill( - newColor.getColor(newIntensity), - CornerRadii.EMPTY, - Insets.EMPTY - ))); - - controller.toolbar.setPrefHeight(TOOLBAR_HEIGHT); - }; - - system.colorProperty().addListener(observable -> updateColor.accept(system.getColor(), system.getColorIntensity())); - - updateColor.accept(system.getColor(), system.getColorIntensity()); - } - - /** - * Initializes the frame and handling of it. - * The frame is a rectangle minus two cutouts. - */ - private void initializeFrame() { - final EcdarSystem system = controller.getSystem(); - - final Shape[] mask = new Shape[1]; - final Rectangle rectangle = new Rectangle(system.getBox().getWidth(), system.getBox().getHeight()); - - // Generate top right corner (to subtract) - final Polygon topRightCorner = new Polygon( - system.getBox().getWidth(), 0, - system.getBox().getWidth() - (CORNER_SIZE + 2), 0, - system.getBox().getWidth(), CORNER_SIZE + 2 - ); - - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { - // Mask the parent of the frame (will also mask the background) - mask[0] = Path.subtract(rectangle, TOP_LEFT_CORNER); - mask[0] = Path.subtract(mask[0], topRightCorner); - controller.frame.setClip(mask[0]); - controller.background.setClip(Path.union(mask[0], mask[0])); - controller.background.setOpacity(0.5); - - // Bind the missing lines that we cropped away - controller.topLeftLine.setStartX(CORNER_SIZE); - controller.topLeftLine.setStartY(0); - controller.topLeftLine.setEndX(0); - controller.topLeftLine.setEndY(CORNER_SIZE); - controller.topLeftLine.setStroke(newColor.getColor(newIntensity.next(2))); - controller.topLeftLine.setStrokeWidth(1.25); - StackPane.setAlignment(controller.topLeftLine, Pos.TOP_LEFT); - - controller.topRightLine.setStartX(0); - controller.topRightLine.setStartY(0); - controller.topRightLine.setEndX(CORNER_SIZE); - controller.topRightLine.setEndY(CORNER_SIZE); - controller.topRightLine.setStroke(newColor.getColor(newIntensity.next(2))); - controller.topRightLine.setStrokeWidth(1.25); - StackPane.setAlignment(controller.topRightLine, Pos.TOP_RIGHT); - - // Set the stroke color to two shades darker - controller.frame.setBorder(new Border(new BorderStroke( - newColor.getColor(newIntensity.next(2)), - BorderStrokeStyle.SOLID, - CornerRadii.EMPTY, - new BorderWidths(1), - Insets.EMPTY - ))); - }; - - // Update now, and update on color change - updateColor.accept(system.getColor(), system.getColorIntensity()); - system.colorProperty().addListener(observable -> updateColor.accept(system.getColor(), system.getColorIntensity())); - } - - /** - * Initializes the background - */ - private void initializeBackground() { - final EcdarSystem system = controller.getSystem(); - - // Bind the background width and height to the values in the model - controller.background.widthProperty().bindBidirectional(system.getBox().getWidthProperty()); - controller.background.heightProperty().bindBidirectional(system.getBox().getHeightProperty()); - - final BiConsumer<Color, Color.Intensity> updateColor = (newColor, newIntensity) -> { - // Set the background color to the lightest possible version of the color - controller.background.setFill(newColor.getColor(newIntensity.next(-10).next(2))); - }; - - system.colorProperty().addListener(observable -> updateColor.accept(system.getColor(), system.getColorIntensity())); - updateColor.accept(system.getColor(), system.getColorIntensity()); + minHeightProperty().bindBidirectional(system.getBox().getHeightProperty()); + maxHeightProperty().bindBidirectional(system.getBox().getHeightProperty()); + minWidthProperty().bindBidirectional(system.getBox().getWidthProperty()); + maxWidthProperty().bindBidirectional(system.getBox().getWidthProperty()); } @Override - ModelController getModelController() { + public SystemController getController() { return controller; } - - /** - * Gets the minimum allowed width when dragging the anchor. - * It is determined by the position and size of the system nodes. - * @return the minimum allowed width - */ - @Override - double getDragAnchorMinWidth() { - final EcdarSystem system = controller.getSystem(); - double minWidth = system.getSystemRoot().getX() + SystemRoot.WIDTH + Ecdar.CANVAS_PADDING * 2; - - for (final ComponentInstance instance : system.getComponentInstances()) { - minWidth = Math.max(minWidth, instance.getBox().getX() + instance.getBox().getWidth() + Ecdar.CANVAS_PADDING); - } - - for (final ComponentOperator operator : system.getComponentOperators()) { - minWidth = Math.max(minWidth, operator.getBox().getX() + operator.getBox().getWidth() + Ecdar.CANVAS_PADDING); - } - - return minWidth; - } - - /** - * Gets the minimum allowed height when dragging the anchor. - * It is determined by the position and size of the system nodes. - * @return the minimum allowed height - */ - @Override - double getDragAnchorMinHeight() { - final EcdarSystem system = controller.getSystem(); - double minHeight = Ecdar.CANVAS_PADDING * 2; - - for (final ComponentInstance instance : system.getComponentInstances()) { - minHeight = Math.max(minHeight, instance.getBox().getY() + instance.getBox().getHeight() + Ecdar.CANVAS_PADDING); - } - - for (final ComponentOperator operator : system.getComponentOperators()) { - minHeight = Math.max(minHeight, operator.getBox().getY() + operator.getBox().getHeight() + Ecdar.CANVAS_PADDING); - } - - return minHeight; - } } diff --git a/src/main/java/ecdar/presentations/SystemRootPresentation.java b/src/main/java/ecdar/presentations/SystemRootPresentation.java index ac271c7e..b978f410 100644 --- a/src/main/java/ecdar/presentations/SystemRootPresentation.java +++ b/src/main/java/ecdar/presentations/SystemRootPresentation.java @@ -5,6 +5,7 @@ import ecdar.controllers.SystemRootController; import ecdar.utility.Highlightable; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.ItemDragHelper; import ecdar.utility.helpers.SelectHelper; import javafx.beans.property.BooleanProperty; @@ -19,6 +20,7 @@ */ public class SystemRootPresentation extends StackPane implements Highlightable { private final SystemRootController controller; + private final EnabledColor highlightColor = new EnabledColor(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL); public SystemRootPresentation(final EcdarSystem system) { controller = new EcdarFXMLLoader().loadAndGetController("SystemRootPresentation.fxml", this); @@ -104,16 +106,15 @@ private void initializeColor() { * The color will be a bit darker than the color of the system. */ private void dyeFromSystemColor() { - dye(controller.getSystem().getColor(), controller.getSystem().getColorIntensity().next(2)); + dye(controller.getSystem().getColor()); } /** * Dyes the polygon. * @param color the color to dye with - * @param intensity the intensity of the color to use */ - private void dye(final Color color, final Color.Intensity intensity) { - controller.shape.setFill(color.getColor(intensity)); + private void dye(final EnabledColor color) { + controller.shape.setFill(color.getPaintColor()); } /** @@ -133,7 +134,7 @@ private ItemDragHelper.DragBounds getDragBounds() { @Override public void highlight() { - dye(SelectHelper.SELECT_COLOR, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL); + dye(highlightColor); } @Override @@ -143,6 +144,6 @@ public void unhighlight() { @Override public void highlightPurple() { - dye(Color.DEEP_PURPLE, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL); + dye(new EnabledColor(Color.DEEP_PURPLE, SelectHelper.SELECT_COLOR_INTENSITY_NORMAL)); } } diff --git a/src/main/java/ecdar/presentations/TagPresentation.java b/src/main/java/ecdar/presentations/TagPresentation.java index 30786de9..a41d9a5e 100644 --- a/src/main/java/ecdar/presentations/TagPresentation.java +++ b/src/main/java/ecdar/presentations/TagPresentation.java @@ -1,9 +1,11 @@ package ecdar.presentations; +import ecdar.Ecdar; import ecdar.abstractions.Component; import ecdar.controllers.EcdarController; import ecdar.utility.UndoRedoStack; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import ecdar.utility.helpers.ItemDragHelper; import ecdar.utility.helpers.LocationAware; import com.jfoenix.controls.JFXTextField; @@ -24,6 +26,7 @@ import javafx.scene.shape.Path; import java.util.function.BiConsumer; +import java.util.function.Consumer; import static javafx.scene.paint.Color.TRANSPARENT; @@ -69,7 +72,7 @@ void initializeTextFocusHandler() { textFieldFocusProperty().addListener((observable, oldValue, newValue) -> { if (!hadInitialFocus && newValue) { hadInitialFocus = true; - EcdarController.getActiveCanvasPresentation().getController().leaveTextAreas(); + Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().leaveTextAreas(); } }); }); @@ -119,7 +122,9 @@ private void initializeShape() { }); // When enter or escape is pressed release focus - textField.setOnKeyPressed(EcdarController.getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler()); + Platform.runLater(() -> { + textField.setOnKeyPressed(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().getLeaveTextAreaKeyHandler()); + }); } private void initializeDragging(JFXTextField textField, Path shape) { @@ -145,8 +150,8 @@ private void initializeDragging(JFXTextField textField, Path shape) { shape.addEventHandler(MouseEvent.MOUSE_DRAGGED, event -> { event.consume(); - final double dragDistanceX = (event.getSceneX() - dragOffsetX.get()) / EcdarController.getActiveCanvasZoomFactor().get(); - final double dragDistanceY = (event.getSceneY() - dragOffsetY.get()) / EcdarController.getActiveCanvasZoomFactor().get(); + final double dragDistanceX = (event.getSceneX() - dragOffsetX.get()) / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get(); + final double dragDistanceY = (event.getSceneY() - dragOffsetY.get()) / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get(); double draggableNewX = getDragBounds().trimX(draggablePreviousX.get() + dragDistanceX); double draggableNewY = getDragBounds().trimY(draggablePreviousY.get() + dragDistanceY); @@ -237,32 +242,30 @@ void initializeLabel() { l3.xProperty().bind(textField.widthProperty()); } - public void bindToColor(final ObjectProperty<Color> color, final ObjectProperty<Color.Intensity> intensity) { - bindToColor(color, intensity, false); + public void bindToColor(final ObjectProperty<EnabledColor> color) { + bindToColor(color, false); } - public void bindToColor(final ObjectProperty<Color> color, final ObjectProperty<Color.Intensity> intensity, final boolean doColorBackground) { - final BiConsumer<Color, Color.Intensity> recolor = (newColor, newIntensity) -> { + public void bindToColor(final ObjectProperty<EnabledColor> color, final boolean doColorBackground) { + final Consumer<EnabledColor> recolor = (newColor) -> { final JFXTextField textField = (JFXTextField) lookup("#textField"); textField.setUnFocusColor(TRANSPARENT); - textField.setFocusColor(newColor.getColor(newIntensity)); + textField.setFocusColor(newColor.getPaintColor()); if (doColorBackground) { final Path shape = (Path) lookup("#shape"); - shape.setFill(newColor.getColor(newIntensity.next(-1))); - shape.setStroke(newColor.getColor(newIntensity.next(-1).next(2))); + shape.setFill(newColor.nextIntensity(-1).getPaintColor()); + shape.setStroke(newColor.nextIntensity(1).getPaintColor()); - textField.setStyle("-fx-prompt-text-fill: rgba(255, 255, 255, 0.6); -fx-text-fill: " + newColor.getTextColorRgbaString(newIntensity) + ";"); - textField.setFocusColor(newColor.getTextColor(newIntensity)); + textField.setStyle("-fx-prompt-text-fill: rgba(255, 255, 255, 0.6); -fx-text-fill: " + newColor.getTextColorRgbaString() + ";"); + textField.setFocusColor(newColor.getTextColor()); } else { textField.setStyle("-fx-prompt-text-fill: rgba(0, 0, 0, 0.6);"); } }; - color.addListener(observable -> recolor.accept(color.get(), intensity.get())); - intensity.addListener(observable -> recolor.accept(color.get(), intensity.get())); - - recolor.accept(color.get(), intensity.get()); + color.addListener(observable -> recolor.accept(color.get())); + recolor.accept(color.get()); } public void setAndBindString(final StringProperty stringProperty) { diff --git a/src/main/java/ecdar/simulation/SimulationState.java b/src/main/java/ecdar/simulation/SimulationState.java index 538a0441..e1c20729 100644 --- a/src/main/java/ecdar/simulation/SimulationState.java +++ b/src/main/java/ecdar/simulation/SimulationState.java @@ -4,6 +4,7 @@ import EcdarProtoBuf.ObjectProtos.State; import ecdar.Ecdar; import ecdar.backend.SimulationHandler; +import ecdar.controllers.SimulatorController; import javafx.collections.ObservableMap; import javafx.util.Pair; @@ -71,6 +72,6 @@ private String getComponentName(String id) { */ public ObservableMap<String, BigDecimal> getSimulationClocks() { // TODO move clocks from SimulationHandler to SimulationState - return Ecdar.getSimulationHandler().getSimulationClocks(); + return SimulatorController.getSimulationHandler().getSimulationClocks(); } } diff --git a/src/main/java/ecdar/utility/colors/Color.java b/src/main/java/ecdar/utility/colors/Color.java index 04643251..e932507a 100644 --- a/src/main/java/ecdar/utility/colors/Color.java +++ b/src/main/java/ecdar/utility/colors/Color.java @@ -368,6 +368,8 @@ public enum Intensity { A400, A700; + public Intensity lowest() { return next(-this.ordinal()); } + public Intensity next() { return next(1); } @@ -376,9 +378,8 @@ public Intensity next(final int levels) { final Intensity[] values = values(); // One of the first 10 elements + final int index = this.ordinal() + levels; if (this.ordinal() <= 9) { - final int index = this.ordinal() + levels; - if (index < 0) { return values[0]; } else if (index > 9) { @@ -389,8 +390,6 @@ public Intensity next(final int levels) { } // One of the last 4 elements else { - final int index = this.ordinal() + levels; - if (index < 10) { return values[10]; } else if (index > 13) { diff --git a/src/main/java/ecdar/utility/colors/EnabledColor.java b/src/main/java/ecdar/utility/colors/EnabledColor.java index 8ef4e647..fc75b523 100644 --- a/src/main/java/ecdar/utility/colors/EnabledColor.java +++ b/src/main/java/ecdar/utility/colors/EnabledColor.java @@ -5,8 +5,7 @@ import java.util.ArrayList; public class EnabledColor { - - public static final ArrayList<EnabledColor> enabledColors = new ArrayList<EnabledColor>() {{ + public static final ArrayList<EnabledColor> enabledColors = new ArrayList<>() {{ add(new EnabledColor(Color.GREY_BLUE, Color.Intensity.I700, KeyCode.DIGIT0)); add(new EnabledColor(Color.DEEP_ORANGE, Color.Intensity.I700, KeyCode.DIGIT1)); add(new EnabledColor(Color.PINK, Color.Intensity.I500, KeyCode.DIGIT2)); @@ -53,6 +52,42 @@ public static EnabledColor fromIdentifier(final String identifier) { return null; } + public static EnabledColor getDefault() { + return enabledColors.get(0); + } + + public javafx.scene.paint.Color getTextColor() { + return color.getTextColor(intensity); + } + + public javafx.scene.paint.Color getPaintColor() { + return color.getColor(intensity); + } + + public javafx.scene.paint.Color getStrokeColor() { + return nextIntensity(2).getPaintColor(); + } + + public String getTextColorRgbaString() { + return color.getTextColorRgbaString(intensity); + } + + public EnabledColor getLowestIntensity() { + return new EnabledColor(color, intensity.lowest()); + } + + public EnabledColor nextIntensity() { + return nextIntensity(1); + } + + public EnabledColor nextIntensity(final int levelIncrement) { + return new EnabledColor(this.color, this.intensity.next(levelIncrement)); + } + + public EnabledColor setIntensity(int i) { + return getLowestIntensity().nextIntensity(i); + } + @Override public boolean equals(final Object obj) { return obj instanceof EnabledColor && ((EnabledColor) obj).color.equals(this.color); diff --git a/src/main/java/ecdar/utility/helpers/BindingHelper.java b/src/main/java/ecdar/utility/helpers/BindingHelper.java index ddfc0462..5c5b747e 100644 --- a/src/main/java/ecdar/utility/helpers/BindingHelper.java +++ b/src/main/java/ecdar/utility/helpers/BindingHelper.java @@ -1,5 +1,6 @@ package ecdar.utility.helpers; +import ecdar.Ecdar; import ecdar.controllers.EcdarController; import ecdar.model_canvas.arrow_heads.ArrowHead; import ecdar.model_canvas.arrow_heads.ChannelReceiverArrowHead; @@ -35,8 +36,8 @@ public static void bind(final Line subject, final SimTagPresentation target) { } public static void bind(final Circular subject, final ObservableDoubleValue x, final ObservableDoubleValue y) { - subject.xProperty().bind(EcdarController.getActiveCanvasPresentation().mouseTracker.xProperty().subtract(x)); - subject.yProperty().bind(EcdarController.getActiveCanvasPresentation().mouseTracker.yProperty().subtract(y)); + subject.xProperty().bind(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().mouseTracker.xProperty().subtract(x)); + subject.yProperty().bind(Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().mouseTracker.yProperty().subtract(y)); } public static void bind(final Link lineSubject, final ArrowHead arrowHeadSubject, final Circular source, final Circular target) { @@ -75,7 +76,7 @@ public static void bind(final Link subject, final Circle source, final MouseTrac public static void bind(final Link subject, final Circular source, final ObservableDoubleValue x, final ObservableDoubleValue y) { // Calculate the bindings (so that the line will be based on the circle circumference instead of in its center) - final LineBinding lineBinding = LineBinding.getCircularBindings(source, EcdarController.getActiveCanvasPresentation().mouseTracker, x, y); + final LineBinding lineBinding = LineBinding.getCircularBindings(source, Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().mouseTracker, x, y); // Bind the subjects properties accordingly to our calculations subject.startXProperty().bind(lineBinding.startX); @@ -141,7 +142,7 @@ protected double computeValue() { public static void bind(final ArrowHead subject, final Circular source, final ObservableDoubleValue x, final ObservableDoubleValue y) { // Calculate the bindings (so that the line will be based on the circle circumference instead of in its center) - final LineBinding lineBinding = LineBinding.getCircularBindings(source, EcdarController.getActiveCanvasPresentation().mouseTracker, x, y); + final LineBinding lineBinding = LineBinding.getCircularBindings(source, Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().mouseTracker, x, y); ObservableDoubleValue startX = lineBinding.startX; ObservableDoubleValue startY = lineBinding.startY; diff --git a/src/main/java/ecdar/utility/helpers/ImageScaler.java b/src/main/java/ecdar/utility/helpers/ImageScaler.java new file mode 100644 index 00000000..428ba723 --- /dev/null +++ b/src/main/java/ecdar/utility/helpers/ImageScaler.java @@ -0,0 +1,16 @@ +package ecdar.utility.helpers; + +import javafx.scene.image.ImageView; +import javafx.scene.layout.StackPane; + +public class ImageScaler { + public static void fitImageToPane(final ImageView imageView, final StackPane pane) { + pane.widthProperty().addListener((observable, oldValue, newValue) -> + imageView.setFitWidth(pane.getWidth())); + pane.heightProperty().addListener((observable, oldValue, newValue) -> + imageView.setFitHeight(pane.getHeight())); + + imageView.setFitWidth(pane.getWidth()); + imageView.setFitHeight(pane.getHeight()); + } +} diff --git a/src/main/java/ecdar/utility/helpers/ItemDragHelper.java b/src/main/java/ecdar/utility/helpers/ItemDragHelper.java index b082ac9b..a103c8b9 100644 --- a/src/main/java/ecdar/utility/helpers/ItemDragHelper.java +++ b/src/main/java/ecdar/utility/helpers/ItemDragHelper.java @@ -1,5 +1,6 @@ package ecdar.utility.helpers; +import ecdar.Ecdar; import ecdar.controllers.EcdarController; import ecdar.controllers.EdgeController; import ecdar.presentations.ComponentOperatorPresentation; @@ -123,8 +124,8 @@ public static void makeDraggable(final Node mouseSubject, final Node draggable, final DragBounds dragBounds = getDragBounds.get(); - final double dragDistanceX = (event.getSceneX() - dragOffsetX.get()) / EcdarController.getActiveCanvasZoomFactor().get(); - final double dragDistanceY = (event.getSceneY() - dragOffsetY.get()) / EcdarController.getActiveCanvasZoomFactor().get(); + final double dragDistanceX = (event.getSceneX() - dragOffsetX.get()) / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get(); + final double dragDistanceY = (event.getSceneY() - dragOffsetY.get()) / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get(); double draggableNewX = dragBounds.trimX(draggablePreviousX.get() + dragDistanceX); double draggableNewY = dragBounds.trimY(draggablePreviousY.get() + dragDistanceY); diff --git a/src/main/java/ecdar/utility/helpers/MouseCircular.java b/src/main/java/ecdar/utility/helpers/MouseCircular.java index d1779988..c36f470f 100644 --- a/src/main/java/ecdar/utility/helpers/MouseCircular.java +++ b/src/main/java/ecdar/utility/helpers/MouseCircular.java @@ -1,5 +1,6 @@ package ecdar.utility.helpers; +import ecdar.Ecdar; import ecdar.controllers.EcdarController; import ecdar.utility.mouse.MouseTracker; import javafx.beans.property.DoubleProperty; @@ -14,7 +15,7 @@ public class MouseCircular implements Circular { private final DoubleProperty originalMouseY = new SimpleDoubleProperty(0d); private final DoubleProperty radius = new SimpleDoubleProperty(10); private final SimpleDoubleProperty scale = new SimpleDoubleProperty(1d); - private final MouseTracker mouseTracker = EcdarController.getActiveCanvasPresentation().mouseTracker; + private final MouseTracker mouseTracker = Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().mouseTracker; public MouseCircular(Circular initLocation) { //Set the initial x and y coordinates of the circular @@ -29,10 +30,10 @@ public MouseCircular(Circular initLocation) { mouseTracker.registerOnMouseDraggedEventHandler(event -> updatePosition()); // If the component is dragged while we are drawing an edge, update the coordinates accordingly - EcdarController.getActiveCanvasPresentation().getController().modelPane.translateXProperty().addListener((observable, oldValue, newValue) -> originalX.set( - originalX.get() - (newValue.doubleValue() - oldValue.doubleValue()) / EcdarController.getActiveCanvasZoomFactor().get())); - EcdarController.getActiveCanvasPresentation().getController().modelPane.translateYProperty().addListener((observable, oldValue, newValue) -> originalY.set( - originalY.get() - (newValue.doubleValue() - oldValue.doubleValue()) / EcdarController.getActiveCanvasZoomFactor().get())); + Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().modelPane.translateXProperty().addListener((observable, oldValue, newValue) -> originalX.set( + originalX.get() - (newValue.doubleValue() - oldValue.doubleValue()) / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get())); + Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().modelPane.translateYProperty().addListener((observable, oldValue, newValue) -> originalY.set( + originalY.get() - (newValue.doubleValue() - oldValue.doubleValue()) / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get())); // ToDo NIELS: When the width or height of the scene is changed, the coordinates should be updated } @@ -41,8 +42,8 @@ private void updatePosition() { final double dragDistanceX = mouseTracker.xProperty().get() - originalMouseX.get(); final double dragDistanceY = mouseTracker.yProperty().get() - originalMouseY.get(); - x.set(originalX.get() + dragDistanceX / EcdarController.getActiveCanvasZoomFactor().get()); - y.set(originalY.get() + dragDistanceY / EcdarController.getActiveCanvasZoomFactor().get()); + x.set(originalX.get() + dragDistanceX / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get()); + y.set(originalY.get() + dragDistanceY / Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasZoomFactor().get()); } @Override diff --git a/src/main/java/ecdar/utility/helpers/MouseTrackable.java b/src/main/java/ecdar/utility/helpers/MouseTrackable.java deleted file mode 100644 index f0ce91cf..00000000 --- a/src/main/java/ecdar/utility/helpers/MouseTrackable.java +++ /dev/null @@ -1,7 +0,0 @@ -package ecdar.utility.helpers; - -import ecdar.utility.mouse.MouseTracker; - -public interface MouseTrackable extends LocationAware { - MouseTracker getMouseTracker(); -} diff --git a/src/main/java/ecdar/utility/helpers/SelectHelper.java b/src/main/java/ecdar/utility/helpers/SelectHelper.java index d4f94ba9..47e23f00 100644 --- a/src/main/java/ecdar/utility/helpers/SelectHelper.java +++ b/src/main/java/ecdar/utility/helpers/SelectHelper.java @@ -1,8 +1,10 @@ package ecdar.utility.helpers; +import ecdar.Ecdar; import ecdar.code_analysis.Nearable; import ecdar.controllers.EcdarController; import ecdar.utility.colors.Color; +import ecdar.utility.colors.EnabledColor; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -18,7 +20,7 @@ public class SelectHelper { public static void select(final ItemSelectable selectable) { - EcdarController.getActiveCanvasPresentation().getController().leaveTextAreas(); + Ecdar.getPresentation().getController().getEditorPresentation().getController().getActiveCanvasPresentation().getController().leaveTextAreas(); // Check if the element is already selected if (selectedElements.contains(selectable)) return; @@ -79,11 +81,9 @@ public interface Selectable { } public interface ItemSelectable extends Selectable, LocationAware { - void color(Color color, Color.Intensity intensity); + void color(EnabledColor color); - Color getColor(); - - Color.Intensity getColorIntensity(); + EnabledColor getColor(); ItemDragHelper.DragBounds getDragBounds(); diff --git a/src/main/java/ecdar/utility/helpers/UPPAALSyntaxHighlighter.java b/src/main/java/ecdar/utility/helpers/UPPAALSyntaxHighlighter.java new file mode 100644 index 00000000..4cdbabf8 --- /dev/null +++ b/src/main/java/ecdar/utility/helpers/UPPAALSyntaxHighlighter.java @@ -0,0 +1,40 @@ +package ecdar.utility.helpers; + +import org.fxmisc.richtext.model.StyleSpans; +import org.fxmisc.richtext.model.StyleSpansBuilder; + +import java.util.Collection; +import java.util.Collections; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class UPPAALSyntaxHighlighter { + private static final String uppaalKeywords = "clock|chan|urgent|broadcast"; + private static final String cKeywords = "auto|bool|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while"; + private static final Pattern UPPAAL = Pattern.compile("" + + "(" + uppaalKeywords + ")" + + "|(" + cKeywords + ")" + + "|(//.*|(\"(?:\\\\[^\"]|\\\\\"|.)*?\")|(?s)/\\*.*?\\*/)"); + + public static StyleSpans<Collection<String>> computeHighlighting(final String text) { + final Matcher matcher = UPPAAL.matcher(text); + int lastKwEnd = 0; + final StyleSpansBuilder<Collection<String>> spansBuilder = new StyleSpansBuilder<>(); + while (matcher.find()) { + spansBuilder.add(Collections.emptyList(), matcher.start() - lastKwEnd); + + if (matcher.group(1) != null) { + spansBuilder.add(Collections.singleton("uppaal-keyword"), matcher.end(1) - matcher.start(1)); + } else if (matcher.group(2) != null) { + spansBuilder.add(Collections.singleton("c-keyword"), matcher.end(2) - matcher.start(2)); + } else if (matcher.group(3) != null) { + spansBuilder.add(Collections.singleton("comment"), matcher.end(3) - matcher.start(3)); + } + + lastKwEnd = matcher.end(); + } + + spansBuilder.add(Collections.emptyList(), text.length() - lastKwEnd); + return spansBuilder.create(); + } +} diff --git a/src/main/java/ecdar/utility/helpers/UnoccupiedSpaceFinder.java b/src/main/java/ecdar/utility/helpers/UnoccupiedSpaceFinder.java new file mode 100644 index 00000000..33bf9c72 --- /dev/null +++ b/src/main/java/ecdar/utility/helpers/UnoccupiedSpaceFinder.java @@ -0,0 +1,115 @@ +package ecdar.utility.helpers; + +import ecdar.abstractions.Box; +import javafx.geometry.Point2D; + +import java.util.Collection; + +import static ecdar.presentations.ModelPresentation.TOOLBAR_HEIGHT; + +public class UnoccupiedSpaceFinder { + /** + * Finds an unoccupied space within the provided box, starting from the preferred placement. + * Returns null if no such space could be found given the existing locations + * @param bounds bounds within which to find the unoccupied space + * @param occupiedSpaces array of coordinates that are occupied + * @param preferredPlacement the preferred coordinates for the free space + * @param spacing the spacing between any two points and any point and the edges of the box + * @return a free space or null if unable to find one + */ + public static Point2D getUnoccupiedSpace(final Box bounds, final Collection<Point2D> occupiedSpaces, final Point2D preferredPlacement, final double spacing) { + boolean hit = false; + + double latestHitRight = 0, + latestHitDown = 0, + latestHitLeft = 0, + latestHitUp = 0; + + //Check to see if the location is placed on top of another location + for (Point2D entry : occupiedSpaces) { + if (Math.abs(entry.getX() - (preferredPlacement.getX())) < spacing && Math.abs(entry.getY() - (preferredPlacement.getY())) < spacing) { + hit = true; + latestHitRight = entry.getX(); + latestHitDown = entry.getY(); + latestHitLeft = entry.getX(); + latestHitUp = entry.getY(); + break; + } + } + + //If the location is not placed on top of any other locations, do not do anything + if (!hit) { + return preferredPlacement; + } + hit = false; + + //Find an unoccupied space for the location + for (int i = 1; i < bounds.getWidth() / spacing; i++) { + //Check to see, if the location can be placed to the right of an existing location + if (latestHitRight > spacing && bounds.getWidth() > latestHitRight + spacing) { + for (Point2D entry : occupiedSpaces) { + if (Math.abs(entry.getX() - (latestHitRight + spacing)) < spacing && Math.abs(entry.getY() - (preferredPlacement.getY())) < spacing) { + hit = true; + latestHitRight = entry.getX(); + break; + } + } + + if (!hit) { + return new Point2D(latestHitRight + spacing, preferredPlacement.getY()); + } + } + hit = false; + + //Check to see, if the location can be placed below an existing location + if (latestHitDown > spacing && bounds.getHeight() > latestHitDown + spacing) { + for (Point2D entry : occupiedSpaces) { + if (Math.abs(entry.getX() - (preferredPlacement.getX())) < spacing && Math.abs(entry.getY() - (latestHitDown + spacing)) < spacing) { + hit = true; + latestHitDown = entry.getY(); + break; + } + } + + if (!hit) { + return new Point2D(preferredPlacement.getX(), latestHitDown + spacing); + } + } + hit = false; + + //Check to see, if the location can be placed to the left of the existing location + if (latestHitLeft > spacing && bounds.getWidth() > latestHitLeft - spacing) { + for (Point2D entry : occupiedSpaces) { + if (Math.abs(entry.getX() - (latestHitLeft - spacing)) < spacing && Math.abs(entry.getY() - (preferredPlacement.getY())) < spacing) { + hit = true; + latestHitLeft = entry.getX(); + break; + } + } + + if (!hit) { + return new Point2D(latestHitLeft - spacing, preferredPlacement.getY()); + } + } + hit = false; + + //Check to see, if the location can be placed above an existing location + if (latestHitUp > spacing + TOOLBAR_HEIGHT && bounds.getHeight() > latestHitUp - spacing) { + for (Point2D entry : occupiedSpaces) { + if (Math.abs(entry.getX() - (preferredPlacement.getX())) < spacing && Math.abs(entry.getY() - (latestHitUp - spacing)) < spacing) { + hit = true; + latestHitUp = entry.getY(); + break; + } + } + + if (!hit) { + return new Point2D(preferredPlacement.getX(), latestHitUp - spacing); + } + } + hit = false; + } + + return null; + } +} diff --git a/src/main/java/ecdar/utility/helpers/ZoomHelper.java b/src/main/java/ecdar/utility/helpers/ZoomHelper.java index be0c1f28..b61e12b1 100644 --- a/src/main/java/ecdar/utility/helpers/ZoomHelper.java +++ b/src/main/java/ecdar/utility/helpers/ZoomHelper.java @@ -1,9 +1,8 @@ package ecdar.utility.helpers; import ecdar.Ecdar; -import ecdar.controllers.EcdarController; -import ecdar.presentations.CanvasPresentation; -import ecdar.presentations.ModelPresentation; +import ecdar.controllers.ComponentController; +import ecdar.presentations.*; import javafx.application.Platform; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; @@ -14,7 +13,7 @@ public class ZoomHelper { public double maxZoomFactor = 4; private CanvasPresentation canvasPresentation; - private ModelPresentation model; + private HighLevelModelPresentation model; private boolean active = true; /** @@ -24,13 +23,13 @@ public class ZoomHelper { */ public void setCanvas(CanvasPresentation newCanvasPresentation) { canvasPresentation = newCanvasPresentation; - model = canvasPresentation.getController().getActiveComponentPresentation(); + model = canvasPresentation.getController().getActiveModelPresentation(); // Update the model whenever the component is updated - canvasPresentation.getController().activeComponentProperty().addListener((observable) -> { + canvasPresentation.getController().activeModelProperty().addListener((observable) -> { // Run later to ensure that the active component presentation is up-to-date Platform.runLater(() -> { - model = canvasPresentation.getController().getActiveComponentPresentation(); + model = canvasPresentation.getController().getActiveModelPresentation(); }); }); @@ -42,43 +41,43 @@ public Double getZoomLevel() { } public void setZoomLevel(Double zoomLevel) { - if (active && model != null) { - currentZoomFactor.set(zoomLevel); - } + if (!active || model == null) return; + + currentZoomFactor.set(zoomLevel); } /** * Zoom in with a delta of 1.2 */ public void zoomIn() { - if (active) { - double delta = 1.2; - double newScale = currentZoomFactor.get() * delta; + if (!active) return; - //Limit for zooming in - if (newScale > maxZoomFactor) { - return; - } + double delta = 1.2; + double newScale = currentZoomFactor.get() * delta; - currentZoomFactor.set(newScale); + //Limit for zooming in + if (newScale > maxZoomFactor) { + return; } + + currentZoomFactor.set(newScale); } /** * Zoom out with a delta of 1.2 */ public void zoomOut() { - if (active) { - double delta = 1.2; - double newScale = currentZoomFactor.get() / delta; + if (!active) return; - //Limit for zooming out - if (newScale < minZoomFactor) { - return; - } + double delta = 1.2; + double newScale = currentZoomFactor.get() / delta; - currentZoomFactor.set(newScale); + //Limit for zooming out + if (newScale < minZoomFactor) { + return; } + + currentZoomFactor.set(newScale); } /** @@ -86,26 +85,33 @@ public void zoomOut() { */ public void resetZoom() { currentZoomFactor.set(1); + if (canvasPresentation + .getController() + .getActiveModelPresentation() instanceof DeclarationsPresentation) alignDeclaration(); } /** * Zoom in to fit the component on screen */ public void zoomToFit() { - if (active) { - if (EcdarController.getActiveCanvasPresentation().getController().getActiveModel() == null) { - resetZoom(); - return; - } - - double neededWidth = canvasPresentation.getWidth() / (model.getWidth() - + canvasPresentation.getController().getActiveComponentPresentation().getController().inputSignatureContainer.getWidth() - + canvasPresentation.getController().getActiveComponentPresentation().getController().outputSignatureContainer.getWidth()); - double newScale = Math.min(neededWidth, canvasPresentation.getHeight() / model.getHeight() - 0.2); //0.1 for width and 0.2 for height subtracted for margin - - currentZoomFactor.set(newScale); - centerComponent(); + if (!active || model == null) return; + + double neededWidth = getWidthNeededForModel(); + double newScale = Math.min(canvasPresentation.getWidth() / neededWidth, canvasPresentation.getHeight() / model.getMinHeight() - 0.2); // 0.2 subtracted for margin + + currentZoomFactor.set(newScale); + centerComponentOrSystem(); + } + + private double getWidthNeededForModel() { + if (model instanceof ComponentPresentation) { + ComponentController componentController = (ComponentController) model.getController(); + return model.getMinWidth() + + componentController.inputSignatureContainer.getWidth() + + componentController.outputSignatureContainer.getWidth(); } + + return model.getMinWidth(); } /** @@ -115,16 +121,22 @@ public void setActive(boolean activeState) { this.active = activeState; if (!activeState) { // If zoom has been disabled, reset the zoom level - resetZoom(); + Platform.runLater(this::resetZoom); } } - private void centerComponent() { - EcdarController.getActiveCanvasPresentation().getController().modelPane.setTranslateX(0); - EcdarController.getActiveCanvasPresentation().getController().modelPane.setTranslateY(-Ecdar.CANVAS_PADDING * 2); // 0 is slightly below center, this looks better + private void centerComponentOrSystem() { + // 0 is slightly below center, this looks better + canvasPresentation.getController().modelPane.setTranslateY(-Ecdar.CANVAS_PADDING * 2); + canvasPresentation.getController().modelPane.setTranslateX(0); // Center the model within the modelPane to account for resized model model.setTranslateX(0); model.setTranslateY(0); } + + private void alignDeclaration() { + canvasPresentation.getController().modelPane.setTranslateX(0); + canvasPresentation.getController().modelPane.setTranslateY(0); + } } diff --git a/src/main/proto b/src/main/proto index 1e0172b6..e42e35db 160000 --- a/src/main/proto +++ b/src/main/proto @@ -1 +1 @@ -Subproject commit 1e0172b68466459d18efc0dd41eb494f542a488c +Subproject commit e42e35db63efacd7ab42aecd17c9a52b291c7be9 diff --git a/src/main/resources/ecdar/main.css b/src/main/resources/ecdar/main.css index 0d99400f..16c63c23 100644 --- a/src/main/resources/ecdar/main.css +++ b/src/main/resources/ecdar/main.css @@ -259,14 +259,14 @@ -fx-background-insets: 0em 0em 0em 0em; } -.backend-instances-list { +.engine-instances-list { -fx-padding: 5; -fx-border-style: SOLID HIDDEN SOLID HIDDEN; -fx-border-color: -divider-color; -fx-border-width: 2px; } -.backend-instance { +.engine-instance { -fx-border-style: SOLID SOLID SOLID SOLID; -fx-border-color: -divider-color; -fx-border-width: 1px; diff --git a/src/main/resources/ecdar/presentations/DeclarationPresentation.fxml b/src/main/resources/ecdar/presentations/DeclarationsPresentation.fxml similarity index 100% rename from src/main/resources/ecdar/presentations/DeclarationPresentation.fxml rename to src/main/resources/ecdar/presentations/DeclarationsPresentation.fxml diff --git a/src/main/resources/ecdar/presentations/EcdarPresentation.fxml b/src/main/resources/ecdar/presentations/EcdarPresentation.fxml index 4421fd18..cb72110a 100644 --- a/src/main/resources/ecdar/presentations/EcdarPresentation.fxml +++ b/src/main/resources/ecdar/presentations/EcdarPresentation.fxml @@ -202,7 +202,7 @@ <SeparatorMenuItem/> - <MenuItem fx:id="menuBarOptionsBackendOptions" text="Backend options"> + <MenuItem fx:id="menuBarOptionsEngineOptions" text="Engine Options"> <graphic> <FontIcon iconLiteral="gmi-settings-input-component" styleClass="icon-size-medium" fill="black"/> @@ -484,9 +484,9 @@ </JFXDialog> </StackPane> - <!-- Backends Dialog --> - <StackPane fx:id="backendOptionsDialogContainer" style="-fx-background-color: #0000007F;" mouseTransparent="true"> - <BackendOptionsDialogPresentation fx:id="backendOptionsDialog"/> + <!-- Engines Dialog --> + <StackPane fx:id="engineOptionsDialogContainer" style="-fx-background-color: #0000007F;" mouseTransparent="true"> + <EngineOptionsDialogPresentation fx:id="engineOptionsDialog"/> </StackPane> <!-- Simulation initialization Dialog --> diff --git a/src/main/resources/ecdar/presentations/BackendOptionsDialogPresentation.fxml b/src/main/resources/ecdar/presentations/EngineOptionsDialogPresentation.fxml similarity index 80% rename from src/main/resources/ecdar/presentations/BackendOptionsDialogPresentation.fxml rename to src/main/resources/ecdar/presentations/EngineOptionsDialogPresentation.fxml index 3cdcdc00..702d6fc2 100644 --- a/src/main/resources/ecdar/presentations/BackendOptionsDialogPresentation.fxml +++ b/src/main/resources/ecdar/presentations/EngineOptionsDialogPresentation.fxml @@ -10,7 +10,7 @@ <?import javafx.geometry.Insets?> <fx:root xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" - type="JFXDialog" fx:controller="ecdar.controllers.BackendOptionsDialogController" + type="JFXDialog" fx:controller="ecdar.controllers.EngineOptionsDialogController" prefHeight="400.0" prefWidth="600.0" style="-fx-background-color: #0000007F;"> <VBox> @@ -22,17 +22,17 @@ <Insets topRightBottomLeft="10"/> </padding> <HBox> - <Text styleClass="headline" HBox.hgrow="ALWAYS">Backends</Text> + <Text styleClass="headline" HBox.hgrow="ALWAYS">Engines</Text> <Region HBox.hgrow="ALWAYS"/> - <JFXButton fx:id="resetBackendsButton" text="Reset backends" styleClass="button-danger"/> + <JFXButton fx:id="resetEnginesButton" text="Reset Engines" styleClass="button-danger"/> </HBox> <Region prefHeight="10"/> - <ScrollPane styleClass="backend-instances-list" + <ScrollPane styleClass="engine-instances-list" style="-fx-background-color:transparent;" hbarPolicy="NEVER" fitToWidth="true" fitToHeight="true" maxHeight="600"> - <VBox fx:id="backendInstanceList" prefWidth="600" spacing="5"/> + <VBox fx:id="engineInstanceList" prefWidth="600" spacing="5"/> </ScrollPane> - <JFXRippler fx:id="addBackendButton" alignment="CENTER_RIGHT"> + <JFXRippler fx:id="addEngineButton" alignment="CENTER_RIGHT"> <StackPane minWidth="300" minHeight="40" StackPane.alignment="CENTER"> <FontIcon iconLiteral="gmi-add" styleClass="icon-size-small" fill="black"/> diff --git a/src/main/resources/ecdar/presentations/BackendInstancePresentation.fxml b/src/main/resources/ecdar/presentations/EnginePresentation.fxml similarity index 77% rename from src/main/resources/ecdar/presentations/BackendInstancePresentation.fxml rename to src/main/resources/ecdar/presentations/EnginePresentation.fxml index 61d57bbb..af686165 100644 --- a/src/main/resources/ecdar/presentations/BackendInstancePresentation.fxml +++ b/src/main/resources/ecdar/presentations/EnginePresentation.fxml @@ -10,17 +10,17 @@ <?import javafx.scene.control.Label?> <fx:root xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.76-ea" - type="StackPane" fx:controller="ecdar.controllers.BackendInstanceController" - styleClass="backend-instance"> + type="StackPane" fx:controller="ecdar.controllers.EngineInstanceController" + styleClass="engine-instance"> <HBox spacing="10"> <VBox alignment="CENTER" minWidth="50" maxWidth="50" style="-fx-background-color: -primary-color-darker"> - <JFXRippler fx:id="moveBackendInstanceUpRippler" alignment="CENTER_RIGHT"> + <JFXRippler fx:id="moveEngineInstanceUpRippler" alignment="CENTER_RIGHT"> <StackPane minWidth="40" minHeight="40" StackPane.alignment="CENTER_LEFT"> <FontIcon iconLiteral="gmi-expand-less" styleClass="icon-size-small" fill="white"/> </StackPane> </JFXRippler> - <JFXRippler fx:id="moveBackendInstanceDownRippler" alignment="CENTER_RIGHT"> + <JFXRippler fx:id="moveEngineInstanceDownRippler" alignment="CENTER_RIGHT"> <StackPane minWidth="40" minHeight="40" StackPane.alignment="CENTER_LEFT"> <FontIcon iconLiteral="gmi-expand-more" styleClass="icon-size-small" fill="white"/> @@ -33,8 +33,8 @@ </padding> <HBox alignment="CENTER_LEFT"> <VBox> - <JFXTextField fx:id="backendName" styleClass="subhead" promptText="Backend name"/> - <Label fx:id="backendNameIssue" styleClass="input-violation, sub-caption" visible="false"/> + <JFXTextField fx:id="engineName" styleClass="subhead" promptText="Engine name"/> + <Label fx:id="engineNameIssue" styleClass="input-violation, sub-caption" visible="false"/> </VBox> <JFXRippler alignment="CENTER_RIGHT"> <StackPane minWidth="40" minHeight="40" @@ -43,10 +43,10 @@ <FontIcon fx:id="expansionIcon" iconLiteral="gmi-expand-less" styleClass="icon-size-small" fill="black"/> </StackPane> </JFXRippler> - <JFXRippler fx:id="removeBackendRippler" alignment="CENTER_RIGHT"> + <JFXRippler fx:id="removeEngineRippler" alignment="CENTER_RIGHT"> <StackPane minWidth="40" minHeight="40" StackPane.alignment="TOP_RIGHT"> - <FontIcon fx:id="removeBackendIcon" iconLiteral="gmi-delete" styleClass="icon-size-small" fill="black"/> + <FontIcon fx:id="removeEngineIcon" iconLiteral="gmi-delete" styleClass="icon-size-small" fill="black"/> </StackPane> </JFXRippler> </HBox> @@ -63,14 +63,14 @@ <Text styleClass="subhead">Address: </Text> <JFXTextField fx:id="address" promptText="xxx.xxx.xxx.xxx"/> </HBox> - <HBox fx:id="pathToBackendSection" spacing="10" alignment="CENTER_LEFT"> + <HBox fx:id="pathToEngineSection" spacing="10" alignment="CENTER_LEFT"> <Text styleClass="subhead">Path: </Text> - <JFXTextField fx:id="pathToBackend" promptText="Absolute path"/> - <JFXRippler fx:id="pickPathToBackend"> + <JFXTextField fx:id="pathToEngine" promptText="Absolute path"/> + <JFXRippler fx:id="pickPathToEngine"> <StackPane minWidth="20" minHeight="20" - onMouseClicked="#openPathToBackendDialog" + onMouseClicked="#openPathToEngineDialog" StackPane.alignment="TOP_RIGHT"> - <FontIcon fx:id="pickPathToBackendIcon" iconLiteral="gmi-open-in-new" iconSize="20" fill="black"/> + <FontIcon fx:id="pickPathToEngineIcon" iconLiteral="gmi-open-in-new" iconSize="20" fill="black"/> </StackPane> </JFXRippler> </HBox> @@ -95,8 +95,8 @@ </VBox> </StackPane> <HBox spacing="20"> - <JFXRadioButton fx:id="defaultBackendRadioButton" styleClass="subhead">Default</JFXRadioButton> - <JFXCheckBox fx:id="threadSafeBackendCheckBox" styleClass="subhead">Thread Safe</JFXCheckBox> + <JFXRadioButton fx:id="defaultEngineRadioButton" styleClass="subhead">Default</JFXRadioButton> + <JFXCheckBox fx:id="threadSafeEngineCheckBox" styleClass="subhead">Thread Safe</JFXCheckBox> </HBox> </VBox> </HBox> diff --git a/src/main/resources/ecdar/presentations/FilePresentation.fxml b/src/main/resources/ecdar/presentations/FilePresentation.fxml index 6cc2efa7..31996483 100644 --- a/src/main/resources/ecdar/presentations/FilePresentation.fxml +++ b/src/main/resources/ecdar/presentations/FilePresentation.fxml @@ -8,23 +8,23 @@ <?import javafx.geometry.Insets?> <fx:root xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.76-ea" - type="AnchorPane" + type="AnchorPane" fx:id="root" fx:controller="ecdar.controllers.FileController"> - <JFXRippler id="rippler" AnchorPane.topAnchor="0.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0"> + <JFXRippler fx:id="rippler" AnchorPane.topAnchor="0.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0"> <HBox style="-fx-min-height: 3.7em; -fx-max-height: 3.7em" alignment="CENTER"> <padding> <Insets top="10" bottom="10"/> </padding> <HBox alignment="CENTER_LEFT" HBox.hgrow="ALWAYS"> <StackPane style="-fx-padding: 0em 1em 0em 1em;"> - <Circle id="iconBackground" radius="1.25" styleClass="responsive-circle-radius"/> + <Circle fx:id="iconBackground" radius="1.25" styleClass="responsive-circle-radius"/> <StackPane fx:id="fileImageStackPane" styleClass="responsive-small-image-sizing"> <ImageView fx:id="fileImage"/> </StackPane> </StackPane> <StackPane> - <Label id="fileName" styleClass="body1"/> + <Label fx:id="fileName" styleClass="body1"/> </StackPane> </HBox> @@ -32,7 +32,7 @@ <!-- MORE INFORMATION --> <JFXRippler fx:id="moreInformation" visible="false"> <StackPane styleClass="responsive-icon-stack-pane-sizing"> - <FontIcon id="moreInformationIcon" iconLiteral="gmi-more-vert" fill="white" + <FontIcon fx:id="moreInformationIcon" iconLiteral="gmi-more-vert" fill="white" styleClass="icon-size-medium"/> </StackPane> </JFXRippler> diff --git a/src/main/resources/ecdar/presentations/MessageCollectionPresentation.fxml b/src/main/resources/ecdar/presentations/MessageCollectionPresentation.fxml index 7e0364c0..54d9804e 100644 --- a/src/main/resources/ecdar/presentations/MessageCollectionPresentation.fxml +++ b/src/main/resources/ecdar/presentations/MessageCollectionPresentation.fxml @@ -6,11 +6,12 @@ <?import org.kordamp.ikonli.javafx.FontIcon?> <fx:root xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.76-ea" - type="VBox"> + type="VBox" fx:controller="ecdar.controllers.MessageCollectionController" + fx:id="root"> <HBox> <StackPane> - <Circle id="indicator" radius="16"/> + <Circle fx:id="indicator" radius="16"/> <FontIcon iconLiteral="gmi-description" iconSize="22" fill="white" mouseTransparent="true"/> </StackPane> @@ -18,9 +19,9 @@ </HBox> <HBox translateX="15"> - <Line endY="16" strokeWidth="2" id="line"/> + <Line endY="16" strokeWidth="2" fx:id="line"/> <Region minWidth="24"/> - <VBox id="children"/> + <VBox fx:id="messageBox"/> </HBox> </fx:root> \ No newline at end of file diff --git a/src/main/resources/ecdar/presentations/QueryPresentation.fxml b/src/main/resources/ecdar/presentations/QueryPresentation.fxml index 6b89a210..9bbbef91 100644 --- a/src/main/resources/ecdar/presentations/QueryPresentation.fxml +++ b/src/main/resources/ecdar/presentations/QueryPresentation.fxml @@ -65,7 +65,7 @@ </JFXRippler> </HBox> <StackPane style="-fx-min-width: 6.5em; -fx-max-width: 6.5em;"> - <JFXComboBox fx:id="backendsDropdown" styleClass="caption, responsive-text-area-sizing"/> + <JFXComboBox fx:id="enginesDropdown" styleClass="caption, responsive-text-area-sizing"/> </StackPane> </VBox> <StackPane prefHeight="1" style="-fx-background-color:-grey-300;" AnchorPane.rightAnchor="0" diff --git a/src/test/java/ecdar/abstractions/ComponentTest.java b/src/test/java/ecdar/abstractions/ComponentTest.java index 0946a548..b6f822ad 100644 --- a/src/test/java/ecdar/abstractions/ComponentTest.java +++ b/src/test/java/ecdar/abstractions/ComponentTest.java @@ -1,14 +1,20 @@ package ecdar.abstractions; import ecdar.Ecdar; +import ecdar.mutation.ComponentVerificationTransformer; +import ecdar.utility.colors.EnabledColor; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Assertions; import java.util.List; +import static ecdar.abstractions.Project.LOCATION; + public class ComponentTest { + private int counter = 0; + @BeforeAll static void setup() { Ecdar.setUpForTest(); @@ -16,13 +22,13 @@ static void setup() { @Test public void testCloneSameId() { - final Component original = new Component(false); + final Component original = new Component(EnabledColor.getDefault(), "test_comp"); final Location loc1 = new Location(); original.addLocation(loc1); final String id1 = loc1.getId(); - final Component clone = original.cloneForVerification(); + final Component clone = ComponentVerificationTransformer.cloneForVerification(original); // Clone has a location with the same id Assertions.assertNotNull(clone.findLocation(id1)); @@ -30,16 +36,16 @@ public void testCloneSameId() { @Test public void testCloneChangeTargetOfOriginal() { - final Component original = new Component(false); + final Component original = new Component(EnabledColor.getDefault(), "test_comp"); Ecdar.getProject().getComponents().add(original); final Location loc1 = new Location(); - loc1.initialize(); + loc1.initialize(getUniqueLocationId()); original.addLocation(loc1); final String id1 = loc1.getId(); final Location loc2 = new Location(); - loc2.initialize(); + loc2.initialize(getUniqueLocationId()); original.addLocation(loc2); final String id2 = loc2.getId(); @@ -47,7 +53,7 @@ public void testCloneChangeTargetOfOriginal() { edge1.setTargetLocation(loc1); original.addEdge(edge1); - final Component clone = original.cloneForVerification(); + final Component clone = ComponentVerificationTransformer.cloneForVerification(original); // The two ids should be different Assertions.assertNotEquals(id1, id2); @@ -65,16 +71,16 @@ public void testCloneChangeTargetOfOriginal() { @Test public void testCloneChangeTargetOfClone() { - final Component original = new Component(false); + final Component original = new Component(EnabledColor.getDefault(), "test_comp"); Ecdar.getProject().getComponents().add(original); final Location loc1 = new Location(); - loc1.initialize(); + loc1.initialize(getUniqueLocationId()); original.addLocation(loc1); final String id1 = loc1.getId(); final Location loc2 = new Location(); - loc2.initialize(); + loc2.initialize(getUniqueLocationId()); original.addLocation(loc2); final String id2 = loc2.getId(); @@ -82,7 +88,7 @@ public void testCloneChangeTargetOfClone() { edge1.setTargetLocation(loc1); original.addEdge(edge1); - final Component clone = original.cloneForVerification(); + final Component clone = ComponentVerificationTransformer.cloneForVerification(original); // The two ids should be different Assertions.assertNotEquals(id1, id2); @@ -105,17 +111,17 @@ public void testAngelicCompletion() { // Has no outgoing edges final Location l1 = new Location(); - l1.initialize(); + l1.initialize(getUniqueLocationId()); c.addLocation(l1); // Has outgoing a input edge without guard final Location l2 = new Location(); - l2.initialize(); + l2.initialize(getUniqueLocationId()); c.addLocation(l2); // Has outgoing b input edge with guard x <= 3 final Location l3 = new Location(); - l3.initialize(); + l3.initialize(getUniqueLocationId()); c.addLocation(l3); final Edge e1 = new Edge(l2, EdgeStatus.INPUT); @@ -141,7 +147,7 @@ public void testAngelicCompletion() { Assertions.assertEquals(3, c.getLocations().size()); Assertions.assertEquals(3, c.getEdges().size()); - c.applyAngelicCompletion(); + ComponentVerificationTransformer.applyAngelicCompletionForComponent(c); Assertions.assertEquals(3, c.getLocations().size()); @@ -193,7 +199,7 @@ public void testAngelicCompletionConjunction() { final Component c = new Component(); final Location l1 = new Location(); - l1.initialize(); + l1.initialize(getUniqueLocationId()); c.addLocation(l1); final Edge e1 = new Edge(l1, EdgeStatus.INPUT); @@ -207,7 +213,7 @@ public void testAngelicCompletionConjunction() { Assertions.assertEquals(1, c.getLocations().size()); Assertions.assertEquals(1, c.getEdges().size()); - c.applyAngelicCompletion(); + ComponentVerificationTransformer.applyAngelicCompletionForComponent(c); Assertions.assertEquals(1, c.getLocations().size()); Assertions.assertEquals(3, c.getEdges().size()); @@ -232,7 +238,7 @@ public void testAngelicCompletionDisjunction() { final Component c = new Component(); final Location l1 = new Location(); - l1.initialize(); + l1.initialize(getUniqueLocationId()); c.addLocation(l1); final Edge e1 = new Edge(l1, EdgeStatus.INPUT); @@ -252,7 +258,7 @@ public void testAngelicCompletionDisjunction() { Assertions.assertEquals(1, c.getLocations().size()); Assertions.assertEquals(2, c.getEdges().size()); - c.applyAngelicCompletion(); + ComponentVerificationTransformer.applyAngelicCompletionForComponent(c); Assertions.assertEquals(1, c.getLocations().size()); Assertions.assertEquals(3, c.getEdges().size()); @@ -270,7 +276,7 @@ public void testAngelicCompletionMathInGuard() { final Component c = new Component(); final Location l1 = new Location(); - l1.initialize(); + l1.initialize(getUniqueLocationId()); c.addLocation(l1); final Edge e1 = new Edge(l1, EdgeStatus.INPUT); @@ -284,7 +290,7 @@ public void testAngelicCompletionMathInGuard() { Assertions.assertEquals(1, c.getLocations().size()); Assertions.assertEquals(1, c.getEdges().size()); - c.applyAngelicCompletion(); + ComponentVerificationTransformer.applyAngelicCompletionForComponent(c); Assertions.assertEquals(1, c.getLocations().size()); Assertions.assertEquals(2, c.getEdges().size()); @@ -387,4 +393,9 @@ public void getLocalVariablesBoolAssignment() { Assertions.assertEquals(1, vars.size()); Assertions.assertEquals("sound", vars.get(0)); } + + private String getUniqueLocationId() { + counter++; + return LOCATION + counter; + } } \ No newline at end of file diff --git a/src/test/java/ecdar/backend/QueryHandlerTest.java b/src/test/java/ecdar/backend/QueryHandlerTest.java deleted file mode 100644 index be0fe103..00000000 --- a/src/test/java/ecdar/backend/QueryHandlerTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package ecdar.backend; - -import ecdar.abstractions.Query; -import ecdar.abstractions.QueryState; -import org.junit.jupiter.api.Test; - -import static org.mockito.Mockito.*; - -public class QueryHandlerTest { - @Test - public void testExecuteQuery() { - BackendDriver bd = mock(BackendDriver.class); - QueryHandler qh = new QueryHandler(bd); - - Query q = new Query("refinement: (Administration || Machine || Researcher) \u003c\u003d Spec", "", QueryState.UNKNOWN); - qh.executeQuery(q); - - verify(bd, times(1)).addRequestToExecutionQueue(any()); - } -} diff --git a/src/test/java/ecdar/backend/QueryTest.java b/src/test/java/ecdar/backend/QueryTest.java new file mode 100644 index 00000000..3b9a07ad --- /dev/null +++ b/src/test/java/ecdar/backend/QueryTest.java @@ -0,0 +1,18 @@ +package ecdar.backend; + +import ecdar.abstractions.Query; +import ecdar.abstractions.QueryState; +import org.junit.jupiter.api.Test; + +import static org.mockito.Mockito.*; + +public class QueryTest { + @Test + public void testExecuteQuery() { + Engine e = mock(Engine.class); + Query q = new Query("refinement: (Administration || Machine || Researcher) <= Spec", "", QueryState.UNKNOWN, e); + q.execute(); + + verify(e, times(1)).enqueueQuery(eq(q), any(), any()); + } +} diff --git a/src/test/java/ecdar/mutation/ComponentVerificationTransformerTest.java b/src/test/java/ecdar/mutation/ComponentVerificationTransformerTest.java new file mode 100644 index 00000000..1f0bbf0c --- /dev/null +++ b/src/test/java/ecdar/mutation/ComponentVerificationTransformerTest.java @@ -0,0 +1,26 @@ +package ecdar.mutation; + +import ecdar.abstractions.Component; +import ecdar.abstractions.Location; +import ecdar.abstractions.Project; +import ecdar.utility.colors.EnabledColor; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static ecdar.mutation.ComponentVerificationTransformer.applyAngelicCompletionForComponent; +import static ecdar.mutation.ComponentVerificationTransformer.applyDemonicCompletionToComponent; + +public class ComponentVerificationTransformerTest { + @Test + public void demonicCompletionAddsUniversalLocationAndMatchAllInputAndOutputEdges() { + final Project project = new Project(); + final Component comp = new Component(EnabledColor.getDefault(), "Comp"); + project.addComponent(comp); + + applyDemonicCompletionToComponent(comp); + + Assertions.assertTrue(comp.getLocations().stream().anyMatch(e -> e.getType().equals(Location.Type.UNIVERSAL))); + Assertions.assertTrue(comp.getListOfEdgesFromDisplayableEdges(comp.getInputEdges()).stream().anyMatch(e -> e.getSync().equals("*"))); + Assertions.assertTrue(comp.getListOfEdgesFromDisplayableEdges(comp.getOutputEdges()).stream().anyMatch(e -> e.getSync().equals("*"))); + } +} diff --git a/src/test/java/ecdar/mutation/operators/ChangeTargetOperatorTest.java b/src/test/java/ecdar/mutation/operators/ChangeTargetOperatorTest.java index fe3a869e..bc543b3b 100644 --- a/src/test/java/ecdar/mutation/operators/ChangeTargetOperatorTest.java +++ b/src/test/java/ecdar/mutation/operators/ChangeTargetOperatorTest.java @@ -5,6 +5,7 @@ import ecdar.abstractions.Edge; import ecdar.abstractions.EdgeStatus; import ecdar.abstractions.Location; +import ecdar.utility.colors.EnabledColor; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Assertions; @@ -18,7 +19,7 @@ static void setup() { @Test public void testNumberOfMutants() { - final Component component = new Component(false); + final Component component = new Component(EnabledColor.getDefault(), "test_comp"); Ecdar.getProject().getComponents().add(component); // 3 locations in addition to the already created initial location diff --git a/src/test/java/ecdar/simulation/ReachabilityTest.java b/src/test/java/ecdar/simulation/ReachabilityTest.java index 06020049..ab1aac1a 100644 --- a/src/test/java/ecdar/simulation/ReachabilityTest.java +++ b/src/test/java/ecdar/simulation/ReachabilityTest.java @@ -3,9 +3,9 @@ import ecdar.Ecdar; import ecdar.abstractions.Component; import ecdar.abstractions.Location; -import ecdar.backend.BackendDriver; -import ecdar.backend.BackendHelper; import ecdar.backend.SimulationHandler; +import ecdar.controllers.SimLocationController; +import ecdar.controllers.SimulatorController; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -31,7 +31,7 @@ void reachabilityQuerySyntaxTestSuccess() { var component = new Component(); component.setName("C1"); - SimulationHandler simulationHandler = new SimulationHandler(new BackendDriver()); + SimulationHandler simulationHandler = new SimulationHandler(); List<String> components = new ArrayList<>(); components.add("C1"); @@ -40,9 +40,9 @@ void reachabilityQuerySyntaxTestSuccess() { simulationHandler.setComponentsInSimulation(components); - Ecdar.setSimulationHandler(simulationHandler); + SimulatorController.setSimulationHandler(simulationHandler); - var result = BackendHelper.getLocationReachableQuery(location, component, "query"); + var result = SimLocationController.getSimLocationReachableQuery(location, component, "query"); assertTrue(result.matches(regex)); } @@ -54,7 +54,7 @@ void reachabilityQueryLocationPosition1TestSuccess() { var component = new Component(); component.setName("C1"); - SimulationHandler simulationHandler = new SimulationHandler(new BackendDriver()); + SimulationHandler simulationHandler = new SimulationHandler(); List<String> components = new ArrayList<>(); components.add("C1"); @@ -65,7 +65,7 @@ void reachabilityQueryLocationPosition1TestSuccess() { Ecdar.setSimulationHandler(simulationHandler); - var result = BackendHelper.getLocationReachableQuery(location, component, "query"); + var result = SimLocationController.getSimLocationReachableQuery(location, component, "query"); int indexOfOpeningBracket = result.indexOf('['); int indexOfComma = 0; @@ -88,7 +88,7 @@ void reachabilityQueryLocationPosition2TestSuccess() { var component = new Component(); component.setName("C2"); - SimulationHandler simulationHandler = new SimulationHandler(new BackendDriver()); + SimulationHandler simulationHandler = new SimulationHandler(); List<String> components = new ArrayList<>(); components.add("C1"); @@ -99,7 +99,7 @@ void reachabilityQueryLocationPosition2TestSuccess() { Ecdar.setSimulationHandler(simulationHandler); - var result = BackendHelper.getLocationReachableQuery(location, component, "query"); + var result = SimLocationController.getSimLocationReachableQuery(location, component, "query"); int indexOfFirstComma = 0; int indexofSecondComma = 0; @@ -128,7 +128,7 @@ void reachabilityQueryLocationPosition3TestSuccess() { var component = new Component(); component.setName("C3"); - SimulationHandler simulationHandler = new SimulationHandler(new BackendDriver()); + SimulationHandler simulationHandler = new SimulationHandler(); List<String> components = new ArrayList<>(); components.add("C1"); @@ -139,7 +139,7 @@ void reachabilityQueryLocationPosition3TestSuccess() { Ecdar.setSimulationHandler(simulationHandler); - var query = BackendHelper.getLocationReachableQuery(location, component, "query"); + var query = SimLocationController.getSimLocationReachableQuery(location, component, "query"); int indexOfLastComma = 0; @@ -164,7 +164,7 @@ void reachabilityQueryNumberOfLocationsTestSuccess() { var component = new Component(); component.setName("C1"); - SimulationHandler simulationHandler = new SimulationHandler(new BackendDriver()); + SimulationHandler simulationHandler = new SimulationHandler(); List<String> components = new ArrayList<>(); components.add("C1"); @@ -176,7 +176,7 @@ void reachabilityQueryNumberOfLocationsTestSuccess() { Ecdar.setSimulationHandler(simulationHandler); - var query = BackendHelper.getLocationReachableQuery(location, component, "query"); + var query = SimLocationController.getSimLocationReachableQuery(location, component, "query"); int commaCount = 0; for (int i = 0; i < query.length(); i++) { if (query.charAt(i) == ',') { @@ -186,6 +186,6 @@ void reachabilityQueryNumberOfLocationsTestSuccess() { int expected = commaCount + 1; - assertEquals(expected, Ecdar.getSimulationHandler().getComponentsInSimulation().size()); + assertEquals(expected, SimulatorController.getSimulationHandler().getComponentsInSimulation().size()); } } diff --git a/src/test/java/ecdar/ui/SidePaneTest.java b/src/test/java/ecdar/ui/SidePaneTest.java index b38a65de..fd05dc52 100644 --- a/src/test/java/ecdar/ui/SidePaneTest.java +++ b/src/test/java/ecdar/ui/SidePaneTest.java @@ -28,7 +28,7 @@ public void activeFilePresentationHasDifferentColorInProjectPane() { ); FilePresentation comp1 = from(lookup("#leftPane").queryAll()).lookup((Predicate<Node>) child -> child instanceof FilePresentation && - ((FilePresentation) child).getModel().getName().equals("Component1")).query(); + ((FilePresentation) child).getController().getModel().getName().equals("Component1")).query(); Assertions.assertEquals(comp1.getBackground().getFills().get(0), baseBackgroundFill); var activeColorIntensity = Color.Intensity.I50.next(1); @@ -39,12 +39,13 @@ public void activeFilePresentationHasDifferentColorInProjectPane() { ); FilePresentation comp2 = from(lookup("#leftPane").queryAll()).lookup((Predicate<Node>) child -> child instanceof FilePresentation && - ((FilePresentation) child).getModel().getName().equals("Component2")).query(); + ((FilePresentation) child).getController().getModel().getName().equals("Component2")).query(); Assertions.assertEquals(comp2.getBackground().getFills().get(0), activeBackgroundFill); } @Test - public void whenDeclarationIsPressedFilePresentationsAreNotActive() throws TimeoutException { + public void whenDeclarationIsPressedFilePresentationsAreNotActive() { + clickOn("#createComponent"); WaitForAsyncUtils.waitForFxEvents(); clickOn("Global Declarations"); @@ -58,7 +59,7 @@ public void whenDeclarationIsPressedFilePresentationsAreNotActive() throws Timeo ); Set<FilePresentation> filePresentations = from(lookup("#leftPane").queryAll()).lookup((Predicate<Node>) child -> child instanceof FilePresentation && - !((FilePresentation) child).getModel().getName().equals("Global Declarations")).queryAll(); + !((FilePresentation) child).getController().getModel().getName().equals("Global Declarations")).queryAll(); for (FilePresentation fp : filePresentations) { Assertions.assertEquals(fp.getBackground().getFills().get(0), baseBackgroundFill); } diff --git a/src/test/java/ecdar/utility/UnoccupiedSpaceFinderTest.java b/src/test/java/ecdar/utility/UnoccupiedSpaceFinderTest.java new file mode 100644 index 00000000..209c2cc1 --- /dev/null +++ b/src/test/java/ecdar/utility/UnoccupiedSpaceFinderTest.java @@ -0,0 +1,64 @@ +package ecdar.utility; + +import ecdar.Ecdar; +import ecdar.abstractions.Box; +import ecdar.presentations.LocationPresentation; +import ecdar.utility.helpers.UnoccupiedSpaceFinder; +import javafx.geometry.Point2D; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +public class UnoccupiedSpaceFinderTest { + @Test + public void testUnoccupiedPointIsGivenAndReturned() { + Box box = new Box(); + box.setWidth(300); + box.setHeight(300); + List<Point2D> locations = new ArrayList<>(); + locations.add(new Point2D(100, 40)); + locations.add(new Point2D(40, 100)); + locations.add(new Point2D(100, 100)); + locations.add(new Point2D(150, 150)); + Point2D preferredPlacement = new Point2D(200, 200); + + Point2D freePoint = UnoccupiedSpaceFinder.getUnoccupiedSpace(box, locations, preferredPlacement, LocationPresentation.RADIUS * 2 + Ecdar.CANVAS_PADDING); + + Assertions.assertEquals(preferredPlacement, freePoint); + } + + @Test + public void testPlacementOnOccupiedPointReturnsFreePoint() { + Box box = new Box(); + box.setWidth(300); + box.setHeight(300); + Point2D preferredPlacement = new Point2D(100, 100); + + Point2D freePoint = UnoccupiedSpaceFinder.getUnoccupiedSpace(box, new ArrayList<>(List.of(preferredPlacement)), preferredPlacement, LocationPresentation.RADIUS * 2 + Ecdar.CANVAS_PADDING); + + Assertions.assertNotNull(freePoint); + Assertions.assertNotEquals(preferredPlacement, freePoint); + } + + @Test + public void testPlacementInFullBoxReturnsNull() { + Box box = new Box(); + box.setWidth(100); + box.setHeight(100); + List<Point2D> locations = new ArrayList<>(); + + // Fill box + for (int i = 0; i < box.getWidth(); i += LocationPresentation.RADIUS * 2) { + for (int j = 0; j < box.getHeight(); j += LocationPresentation.RADIUS * 2) { + locations.add(new Point2D(i, j)); + } + } + Point2D preferredPlacement = new Point2D(50, 50); + + Point2D freePoint = UnoccupiedSpaceFinder.getUnoccupiedSpace(box, locations, preferredPlacement, LocationPresentation.RADIUS * 2 + Ecdar.CANVAS_PADDING); + + Assertions.assertNull(freePoint); + } +}