Establish buildable modules and shared host-service boundaries
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" ?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>de.neitzel.fxdesigner</groupId>
|
||||
<artifactId>neitzel-fx-designer</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>designer-contracts</artifactId>
|
||||
<properties>
|
||||
<module.java.release>21</module.java.release>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-enforcer-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>module-dependency-boundary</id>
|
||||
<goals>
|
||||
<goal>enforce</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<rules>
|
||||
<bannedDependencies>
|
||||
<searchTransitive>true</searchTransitive>
|
||||
<excludes>
|
||||
<exclude>com.intellij:*</exclude>
|
||||
<exclude>com.jetbrains:*</exclude>
|
||||
<exclude>com.jetbrains.intellij.platform:*</exclude>
|
||||
<exclude>com.gluonhq.scenebuilder:*</exclude>
|
||||
<exclude>org.openjfx:*</exclude>
|
||||
<exclude>de.neitzel.lib:*</exclude>
|
||||
</excludes>
|
||||
</bannedDependencies>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package de.neitzel.fxdesigner.contracts;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Objects;
|
||||
|
||||
/** Immutable host-owned draft; revision ordering is independent of native undo stamps. */
|
||||
public record DocumentSnapshot(
|
||||
URI document, long revision, String text, boolean writable, boolean dirty) {
|
||||
public DocumentSnapshot {
|
||||
Objects.requireNonNull(document);
|
||||
Objects.requireNonNull(text);
|
||||
if (revision < 0) throw new IllegalArgumentException("Negative revision");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package de.neitzel.fxdesigner.contracts;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/** One named undo transaction. All UTF-16 ranges address the same expected revision. */
|
||||
public record EditBatch(long expectedRevision, String name, List<Replacement> replacements) {
|
||||
public record Replacement(int start, int end, String text) {
|
||||
public Replacement {
|
||||
if (start < 0 || end < start) throw new IllegalArgumentException("Invalid range");
|
||||
Objects.requireNonNull(text);
|
||||
}
|
||||
}
|
||||
|
||||
public EditBatch {
|
||||
if (expectedRevision < 0) throw new IllegalArgumentException("Negative revision");
|
||||
if (name == null || name.isBlank()) throw new IllegalArgumentException("Missing command name");
|
||||
replacements = List.copyOf(replacements);
|
||||
}
|
||||
|
||||
/** Validates every range before computing a result; never partially edits a host draft. */
|
||||
public String applyTo(DocumentSnapshot draft) {
|
||||
if (!draft.writable()) throw new IllegalStateException("Document is read-only");
|
||||
if (draft.revision() != expectedRevision)
|
||||
throw new ConcurrentModificationException("Stale document revision");
|
||||
var ordered = new ArrayList<>(replacements);
|
||||
ordered.sort(Comparator.comparingInt(Replacement::start));
|
||||
int previousEnd = -1;
|
||||
for (var r : ordered) {
|
||||
if (r.end() > draft.text().length() || r.start() < previousEnd)
|
||||
throw new IllegalArgumentException("Overlapping/out-of-bounds edits");
|
||||
previousEnd = r.end();
|
||||
}
|
||||
var result = new StringBuilder(draft.text());
|
||||
for (int i = ordered.size() - 1; i >= 0; i--) {
|
||||
var r = ordered.get(i);
|
||||
result.replace(r.start(), r.end(), r.text());
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package de.neitzel.fxdesigner.contracts;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class EditBatchTest {
|
||||
private DocumentSnapshot draft(long r, boolean writable) {
|
||||
return new DocumentSnapshot(URI.create("file:/sample.fxml"), r, "abcdef", writable, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleRangesUseOriginalOffsets() {
|
||||
var b =
|
||||
new EditBatch(
|
||||
4,
|
||||
"Rename",
|
||||
List.of(new EditBatch.Replacement(1, 2, "LONG"), new EditBatch.Replacement(4, 6, "!")));
|
||||
assertEquals("aLONGcd!", b.applyTo(draft(4, true)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsStaleReadOnlyAndOverlappingTransactions() {
|
||||
var b = new EditBatch(3, "Change", List.of(new EditBatch.Replacement(0, 2, "x")));
|
||||
assertThrows(ConcurrentModificationException.class, () -> b.applyTo(draft(4, true)));
|
||||
assertThrows(IllegalStateException.class, () -> b.applyTo(draft(3, false)));
|
||||
var overlap =
|
||||
new EditBatch(
|
||||
3,
|
||||
"Bad",
|
||||
List.of(new EditBatch.Replacement(1, 4, ""), new EditBatch.Replacement(3, 5, "")));
|
||||
assertThrows(IllegalArgumentException.class, () -> overlap.applyTo(draft(3, true)));
|
||||
assertEquals("abcdef", draft(3, true).text());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user