diff --git a/liquidjava-api/src/main/java/liquidjava/specification/StateSet.java b/liquidjava-api/src/main/java/liquidjava/specification/StateSet.java index ad074c48..35f668f5 100644 --- a/liquidjava-api/src/main/java/liquidjava/specification/StateSet.java +++ b/liquidjava-api/src/main/java/liquidjava/specification/StateSet.java @@ -45,6 +45,20 @@ */ String[] value(); + /** + * Optional name of the state set, used in messages and usable in predicates as a function that gives the current + * state of the set (e.g. {@code redo(this) == old(redo(this))}). It must be a lowercase identifier that is not + * already the name of a state, ghost or other state set of the class. Unnamed sets get a generated name. + *
+ * Example: + *
+ * {@code
+ * @StateSet(name = "redo", value = {"redoable", "notRedoable"})
+ * }
+ *
+ */
+ String name() default "";
+
/**
* Container annotation used by {@link Repeatable} to support multiple state sets.
*/
diff --git a/liquidjava-example/src/main/java/testSuite/ErrorStateHintMultipleSets.java b/liquidjava-example/src/main/java/testSuite/ErrorStateHintMultipleSets.java
index f3bc2ce1..7c1f696e 100644
--- a/liquidjava-example/src/main/java/testSuite/ErrorStateHintMultipleSets.java
+++ b/liquidjava-example/src/main/java/testSuite/ErrorStateHintMultipleSets.java
@@ -3,8 +3,8 @@
import liquidjava.specification.StateRefinement;
import liquidjava.specification.StateSet;
-@StateSet({"closed", "opened"})
-@StateSet({"unlocked", "locked"})
+@StateSet(name = "doorState", value = {"closed", "opened"})
+@StateSet(name = "lockState", value = {"unlocked", "locked"})
public class ErrorStateHintMultipleSets {
@StateRefinement(to = "closed(this) && unlocked(this)")
diff --git a/liquidjava-example/src/main/java/testSuite/ErrorStateSetNames.java b/liquidjava-example/src/main/java/testSuite/ErrorStateSetNames.java
new file mode 100644
index 00000000..c38a69b1
--- /dev/null
+++ b/liquidjava-example/src/main/java/testSuite/ErrorStateSetNames.java
@@ -0,0 +1,20 @@
+package testSuite;
+
+import liquidjava.specification.Ghost;
+import liquidjava.specification.StateSet;
+
+// A state set name must be a lowercase identifier that no state, ghost or other state set of the class uses
+@StateSet(name = "Door", value = {"closed", "opened"}) // Expect: Error
+public class ErrorStateSetNames {}
+
+@StateSet(name = "locked", value = {"closed", "opened"}) // Expect: Error
+@StateSet({"unlocked", "locked"})
+class ErrorStateSetNameOfAState {}
+
+@StateSet(name = "lock", value = {"closed", "opened"}) // Expect: Error
+@Ghost("int lock")
+class ErrorStateSetNameOfAGhost {}
+
+@StateSet(name = "door", value = {"closed", "opened"}) // Expect: Error
+@StateSet(name = "door", value = {"unlocked", "locked"})
+class ErrorStateSetNameOfAnotherSet {}
diff --git a/liquidjava-example/src/main/java/testSuite/classes/state_hint_study_undo_manager_correct/EditCommand.java b/liquidjava-example/src/main/java/testSuite/classes/state_hint_study_undo_manager_correct/EditCommand.java
index cadcced5..9db91bee 100644
--- a/liquidjava-example/src/main/java/testSuite/classes/state_hint_study_undo_manager_correct/EditCommand.java
+++ b/liquidjava-example/src/main/java/testSuite/classes/state_hint_study_undo_manager_correct/EditCommand.java
@@ -26,6 +26,14 @@ public void execute() {
default -> { }
}
}
+
+ // When canUndo() returns false its spec keeps the undo set by name, so the manager is still notUndoable
+ static void undoAfterAsking(javax.swing.undo.UndoableEdit edit) {
+ UndoManager undoManager = new UndoManager();
+ if (!undoManager.canUndo())
+ undoManager.addEdit(edit);
+ undoManager.undo();
+ }
}
enum StandardActions {
diff --git a/liquidjava-example/src/main/java/testSuite/classes/state_hint_study_undo_manager_correct/UndoManagerRefinementsExpert.java b/liquidjava-example/src/main/java/testSuite/classes/state_hint_study_undo_manager_correct/UndoManagerRefinementsExpert.java
index 7015d9d7..24b334cc 100644
--- a/liquidjava-example/src/main/java/testSuite/classes/state_hint_study_undo_manager_correct/UndoManagerRefinementsExpert.java
+++ b/liquidjava-example/src/main/java/testSuite/classes/state_hint_study_undo_manager_correct/UndoManagerRefinementsExpert.java
@@ -16,9 +16,9 @@
// JDK 21: after end() the manager is one CompoundEdit, and undo/redo alternate over all edits at
// once (AbstractUndoableEdit.hasBeenDone), so end() makes it undoable and not redoable.
@ExternalRefinementsFor("javax.swing.undo.UndoManager")
-@StateSet({"start", "canEdit", "end"})
-@StateSet({"undoable", "notUndoable"})
-@StateSet({"redoable", "notRedoable"})
+@StateSet(name="phase", value={"start", "canEdit", "end"})
+@StateSet(name="undo", value={"undoable", "notUndoable"})
+@StateSet(name="redo", value={"redoable", "notRedoable"})
public interface UndoManagerRefinementsExpert {
@StateRefinement(to="start(this) && notUndoable(this) && notRedoable(this)")
@@ -81,8 +81,9 @@ public interface UndoManagerRefinementsExpert {
// Queries: they never change the manager, they tell the verifier which state it is in. When it is
// already known to be undoable / redoable the answer is true; otherwise (not known, or a field whose
// state is unknown) the answer decides.
+ // When canUndo() returns false it keeps the undo set by name.
@StateRefinement(from="undoable(this)", to="return && undoable(this)")
- @StateRefinement(to="return ? undoable(this) : notUndoable(this)")
+ @StateRefinement(to="return ? undoable(this) : undo(this) == undo(old(this))")
boolean canUndo();
@StateRefinement(from="redoable(this)", to="return && redoable(this)")
diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java
index 077c1a74..d152d9c4 100644
--- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java
+++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java
@@ -119,12 +119,11 @@ protected void getGhostFunction(String value, CtElement element, SourcePosition
}
@Override
- protected Optional