diff --git a/.gitignore b/.gitignore index 731eb43..6cfda9f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ -/target/ -/.settings/ +# Maven build directories +target/ + +# Eclipse settings directories +.settings/ diff --git a/.project b/.project index 9c30c27..1f09710 100644 --- a/.project +++ b/.project @@ -5,11 +5,6 @@ - - org.eclipse.jdt.core.javabuilder - - - org.eclipse.m2e.core.maven2Builder @@ -17,7 +12,6 @@ - org.eclipse.jdt.core.javanature org.eclipse.m2e.core.maven2Nature diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..ae2c663 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,40 @@ +pipeline { + agent any + + options { + ansiColor('xterm') + } + + stages { + stage('Build') { + steps { + sh 'mvn -DskipTests clean package' + } + } + stage('Test') { + steps { + sh 'mvn test' + } + post { + always { + junit '*/target/surefire-reports/*.xml' + } + } + } + stage('SonarQube Analysis') { + when { + branch 'develop' + } + steps { + withSonarQubeEnv('KSKE SonarQube') { + sh 'mvn org.sonarsource.scanner.maven:sonar-maven-plugin:3.9.1.2184:sonar' + } + } + } + } + post { + success { + archiveArtifacts artifacts: '*/target/undo-redo-*.jar' + } + } +} diff --git a/.classpath b/core/.classpath similarity index 100% rename from .classpath rename to core/.classpath diff --git a/core/.project b/core/.project new file mode 100644 index 0000000..49781d8 --- /dev/null +++ b/core/.project @@ -0,0 +1,23 @@ + + + undo-redo-core + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/core/pom.xml b/core/pom.xml new file mode 100644 index 0000000..4cfd653 --- /dev/null +++ b/core/pom.xml @@ -0,0 +1,15 @@ + + 4.0.0 + + undo-redo-core + Undo-Redo Core + + + dev.kske + undo-redo + 0.0.1-SNAPSHOT + + + diff --git a/core/src/main/java/dev/kske/undoredo/core/Change.java b/core/src/main/java/dev/kske/undoredo/core/Change.java new file mode 100644 index 0000000..4252e03 --- /dev/null +++ b/core/src/main/java/dev/kske/undoredo/core/Change.java @@ -0,0 +1,33 @@ +package dev.kske.undoredo.core; + +/** + * Base interface for changes to be registered in an undo manager. + * + * @author Maximilian Käfer + * @since 0.0.1 + */ +public interface Change { + + /** + * Performs the action implemented by this change. + * + * @since 0.0.1 + */ + void apply(); + + /** + * Inverts this change. + * + * @implSpec This method is not supposed to alter the state of this change, but rather to create + * a new complementary change. + * @return the inverted change + * @since 0.0.1 + */ + Change invert(); + + /** + * @return whether the application of this change would result in an identical state + * @since 0.0.1 + */ + boolean isIdentity(); +} diff --git a/core/src/main/java/dev/kske/undoredo/core/ChangeManager.java b/core/src/main/java/dev/kske/undoredo/core/ChangeManager.java new file mode 100644 index 0000000..7b887e7 --- /dev/null +++ b/core/src/main/java/dev/kske/undoredo/core/ChangeManager.java @@ -0,0 +1,73 @@ +package dev.kske.undoredo.core; + +import java.util.List; + +/** + * A change manager keeps track of subsequent changes and allows un- and redoing them. A specific + * change can be marked using {@link #mark()} to keep track of a saved state in the application that + * uses the manager. + * + * @param the change type to store in this change manager + * @author Maximilian Käfer + * @author Kai S. K. Engelbart + * @since 0.0.1 + */ +public interface ChangeManager { + + /** + * Applies the given change and appends it to the change list. + * + * @param change the change to add + * @since 0.0.1 + */ + void addChange(C change); + + /** + * Undoes the current change. + * + * @return whether an action was performed + * @since 0.1.0 + */ + boolean undo(); + + /** + * Applies the change that was undone before. + * + * @return whether an action was performed + * @since 0.0.1 + */ + boolean redo(); + + /** + * Marks the current change. + * + * @since 0.0.1 + */ + void mark(); + + /** + * @return whether the current change is marked + * @since 0.0.1 + */ + boolean isAtMarkedIndex(); + + /** + * @return whether a change is present that can be undone + * @since 0.0.1 + */ + boolean isUndoAvailable(); + + /** + * @return whether a change is present that can be redone + * @since 0.0.1 + */ + boolean isRedoAvailable(); + + /** + * Provides an unmodifiable view of the changes stored in this change manager. + * + * @return all stored changes + * @since 0.0.1 + */ + List getChanges(); +} diff --git a/core/src/main/java/dev/kske/undoredo/core/UnlimitedChangeManager.java b/core/src/main/java/dev/kske/undoredo/core/UnlimitedChangeManager.java new file mode 100644 index 0000000..8a0ff35 --- /dev/null +++ b/core/src/main/java/dev/kske/undoredo/core/UnlimitedChangeManager.java @@ -0,0 +1,69 @@ +package dev.kske.undoredo.core; + +import java.util.*; + +/** + * @param the change type to store in this change manager + * @author Maximilian Käfer + * @author Kai S. K. Engelbart + * @since 0.0.1 + */ +public final class UnlimitedChangeManager implements ChangeManager { + + private final List changes = new ArrayList<>(); + + private int index = -1; + private int markedIndex = -1; + + @Override + public void addChange(C change) { + change.apply(); + changes.add(change); + ++index; + } + + @Override + public boolean undo() { + if (isUndoAvailable()) { + changes.get(index).invert().apply(); + --index; + return true; + } + return false; + } + + @Override + public boolean redo() { + if (isRedoAvailable()) { + changes.get(index + 1).apply(); + ++index; + return true; + } + return false; + } + + @Override + public void mark() { + markedIndex = index; + } + + @Override + public boolean isAtMarkedIndex() { + return markedIndex == index; + } + + @Override + public boolean isUndoAvailable() { + return index > -1; + } + + @Override + public boolean isRedoAvailable() { + return index < changes.size() - 1; + } + + @Override + public List getChanges() { + return Collections.unmodifiableList(changes); + } +} diff --git a/src/main/java/dev/kske/undoredo/package-info.java b/core/src/main/java/dev/kske/undoredo/core/package-info.java similarity index 83% rename from src/main/java/dev/kske/undoredo/package-info.java rename to core/src/main/java/dev/kske/undoredo/core/package-info.java index 1cda98b..b4cc131 100644 --- a/src/main/java/dev/kske/undoredo/package-info.java +++ b/core/src/main/java/dev/kske/undoredo/core/package-info.java @@ -5,4 +5,4 @@ * @author Kai S. K. Engelbart * @since 0.0.1 */ -package dev.kske.undoredo; +package dev.kske.undoredo.core; diff --git a/src/main/java/module-info.java b/core/src/main/java/module-info.java similarity index 71% rename from src/main/java/module-info.java rename to core/src/main/java/module-info.java index d8d1033..ccaee99 100644 --- a/src/main/java/module-info.java +++ b/core/src/main/java/module-info.java @@ -5,7 +5,7 @@ * @author Kai S. K. Engelbart * @since 0.0.1 */ -module dev.kske.undoredo { +module dev.kske.undoredo.core { - exports dev.kske.undoredo; + exports dev.kske.undoredo.core; } diff --git a/core/src/test/java/dev/kske/undoredo/core/ChangeManagerTest.java b/core/src/test/java/dev/kske/undoredo/core/ChangeManagerTest.java new file mode 100644 index 0000000..4643970 --- /dev/null +++ b/core/src/test/java/dev/kske/undoredo/core/ChangeManagerTest.java @@ -0,0 +1,104 @@ +package dev.kske.undoredo.core; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.*; + +/** + * @author Kai S. K. Engelbart + * @since 0.0.1 + */ +class ChangeManagerTest { + + ChangeManager manager; + IntWrapper wrapper; + IntChange change; + + @BeforeEach + void prepareChangeManager() { + manager = new UnlimitedChangeManager<>(); + wrapper = new IntWrapper(); + change = new IntChange(wrapper, 1); + } + + /** + * Tests adding a change. + * + * @since 0.0.1 + */ + @Test + @Order(1) + void testAddChange() { + assertSame(0, wrapper.value); + manager.addChange(change); + assertSame(1, wrapper.value); + } + + /** + * Tests the consistency of the change list. + * + * @since 0.0.1 + */ + @Test + @Order(2) + void testGetChanges() { + assertTrue(manager.getChanges().isEmpty()); + manager.addChange(change); + assertSame(1, manager.getChanges().size()); + assertEquals(change, manager.getChanges().get(0)); + } + + /** + * Test undoing a change. + * + * @since 0.0.1 + */ + @Test + @Order(2) + void testUndo() { + assertFalse(manager.isUndoAvailable()); + assertFalse(manager.undo()); + manager.addChange(change); + assertTrue(manager.isUndoAvailable()); + assertTrue(manager.undo()); + assertFalse(manager.isUndoAvailable()); + assertFalse(manager.undo()); + } + + /** + * Tests redoing a change. + * + * @since 0.0.1 + */ + @Test + @Order(4) + void testRedo() { + assertFalse(manager.isRedoAvailable()); + assertFalse(manager.redo()); + manager.addChange(change); + assertFalse(manager.isRedoAvailable()); + assertFalse(manager.redo()); + manager.undo(); + assertTrue(manager.isRedoAvailable()); + assertTrue(manager.redo()); + assertFalse(manager.isRedoAvailable()); + assertFalse(manager.redo()); + } + + /** + * Tests marking a change. + * + * @since 0.0.1 + */ + @Test + @Order(5) + void testMark() { + assertTrue(manager.isAtMarkedIndex()); + manager.addChange(change); + assertFalse(manager.isAtMarkedIndex()); + manager.mark(); + assertTrue(manager.isAtMarkedIndex()); + manager.undo(); + assertFalse(manager.isAtMarkedIndex()); + } +} diff --git a/core/src/test/java/dev/kske/undoredo/core/IntChange.java b/core/src/test/java/dev/kske/undoredo/core/IntChange.java new file mode 100644 index 0000000..f6e8a32 --- /dev/null +++ b/core/src/test/java/dev/kske/undoredo/core/IntChange.java @@ -0,0 +1,31 @@ +package dev.kske.undoredo.core; + +/** + * @author Kai S. K. Engelbart + * @since 0.0.1 + */ +class IntChange implements Change { + + private final IntWrapper wrapper; + private final int value; + + IntChange(IntWrapper wrapper, int value) { + this.wrapper = wrapper; + this.value = value; + } + + @Override + public void apply() { + wrapper.value += value; + } + + @Override + public Change invert() { + return new IntChange(wrapper, -value); + } + + @Override + public boolean isIdentity() { + return value == 0; + } +} diff --git a/core/src/test/java/dev/kske/undoredo/core/IntWrapper.java b/core/src/test/java/dev/kske/undoredo/core/IntWrapper.java new file mode 100644 index 0000000..2b6907d --- /dev/null +++ b/core/src/test/java/dev/kske/undoredo/core/IntWrapper.java @@ -0,0 +1,10 @@ +package dev.kske.undoredo.core; + +/** + * @author Kai S. K. Engelbart + * @since 0.0.1 + */ +class IntWrapper { + + int value; +} diff --git a/pom.xml b/pom.xml index f32663d..70f0e57 100644 --- a/pom.xml +++ b/pom.xml @@ -6,10 +6,15 @@ dev.kske undo-redo 0.0.1-SNAPSHOT + pom Undo-Redo A Java library for managing changes in an editor history. https://git.kske.dev/kske/event-bus + + + core + @@ -165,8 +170,12 @@ org.apache.maven.plugins maven-surefire-plugin 3.0.0-M5 + + --add-opens dev.kske.undoredo.core/dev.kske.undoredo.core=ALL-UNNAMED + - + +