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
+
-
+
+