diff --git a/traqtor-aio/src/main/java/link/pagan/traqtor/Traqtor.java b/traqtor-aio/src/main/java/link/pagan/traqtor/Traqtor.java index 52678c4..2cb53dc 100644 --- a/traqtor-aio/src/main/java/link/pagan/traqtor/Traqtor.java +++ b/traqtor-aio/src/main/java/link/pagan/traqtor/Traqtor.java @@ -1,54 +1,17 @@ package link.pagan.traqtor; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.module.SimpleModule; -import link.pagan.traqtor.util.FileHandle; -import link.pagan.traqtor.project.Project; -import link.pagan.traqtor.project.universe.UniverseProject; -import link.pagan.traqtor.project.universe.UniverseProjectSerializer; -import link.pagan.traqtor.project.universe.element.Element; -import link.pagan.traqtor.project.universe.element.ElementSerializer; -import link.pagan.traqtor.project.universe.element.Particle; -import link.pagan.traqtor.project.universe.element.PaticleInfoSerializer; -import link.pagan.traqtor.project.universe.link.Link; -import link.pagan.traqtor.project.universe.link.LinkSerializer; -import link.pagan.traqtor.project.universe.schema.Constraint; -import link.pagan.traqtor.project.universe.schema.ConstraintInfoSerializer; -import link.pagan.traqtor.util.Name; -import link.pagan.traqtor.util.NameSerializer; +import link.pagan.traqtor.deck.IoMaster; +import link.pagan.traqtor.deck.JsonMaster; /** @author Edward M. Kagan {@literal <}kaganem{@literal @}2pm.tech{@literal >} */ -public class Traqtor { +public abstract class Traqtor { - private static final Traqtor PROJECT_IO = new Traqtor(); - - private final ObjectMapper mapper; - - private Traqtor () { - mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); - SimpleModule module = new SimpleModule(); - module.addSerializer(Name.class, new NameSerializer()); - module.addSerializer(UniverseProject.class, new UniverseProjectSerializer()); - module.addSerializer(Element.class, new ElementSerializer()); - module.addSerializer(Particle.class, new PaticleInfoSerializer()); - module.addSerializer(Constraint.class, new ConstraintInfoSerializer()); - module.addSerializer(Link.class, new LinkSerializer()); - mapper.registerModule(module); + public static JsonMaster json () { + return JsonMaster.instance(); } - public static Traqtor io () { - return PROJECT_IO; - } - - public FileHandle toFileHandle (Project project) throws JsonProcessingException { - - if (project.getClass().equals(UniverseProject.class)) { - String json = mapper.writeValueAsString((UniverseProject) project); - return new FileHandle(project.name().asDotted(), "uni", json); - } - return null; + public static IoMaster io () { + return IoMaster.instance(); } } diff --git a/traqtor-aio/src/main/java/link/pagan/traqtor/Workspace.java b/traqtor-aio/src/main/java/link/pagan/traqtor/Workspace.java index c09c827..66eeb2c 100644 --- a/traqtor-aio/src/main/java/link/pagan/traqtor/Workspace.java +++ b/traqtor-aio/src/main/java/link/pagan/traqtor/Workspace.java @@ -1,16 +1,33 @@ package link.pagan.traqtor; +import java.io.File; import link.pagan.traqtor.project.Project; import link.pagan.traqtor.util.Name; /** @author Edward M. Kagan {@literal <}kaganem{@literal @}2pm.tech{@literal >} */ -class Workspace { +public class Workspace { + + private File root; + + private Name name; + + private boolean dirty; + + public Workspace () { + this.root = null; + this.dirty = true; + } + + public Name name () { + return this.name; + } public Workspace name (String... parts) { return name(Name.of(parts)); } public Workspace name (Name name) { + this.name = name; return this; } @@ -18,4 +35,21 @@ class Workspace { return this; } + public File root () { + return root; + } + + public Workspace root (File root) { + this.root = root; + return this; + } + + public void dirty (boolean dirty) { + this.dirty = dirty; + } + + public boolean dirty () { + return dirty; + } + } diff --git a/traqtor-aio/src/main/java/link/pagan/traqtor/WorkspaceSerializer.java b/traqtor-aio/src/main/java/link/pagan/traqtor/WorkspaceSerializer.java new file mode 100644 index 0000000..d5f861e --- /dev/null +++ b/traqtor-aio/src/main/java/link/pagan/traqtor/WorkspaceSerializer.java @@ -0,0 +1,28 @@ +package link.pagan.traqtor; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import java.io.IOException; + +/** @author Edward M. Kagan {@literal <}kaganem{@literal @}2pm.tech{@literal >} */ +public class WorkspaceSerializer extends StdSerializer { + + private static final long serialVersionUID = 1L; + + public WorkspaceSerializer () { + this(null); + } + + public WorkspaceSerializer (Class t) { + super(t); + } + + @Override + public void serialize (Workspace value, JsonGenerator gen, SerializerProvider provider) throws IOException { + gen.writeStartObject(); + gen.writeObjectField("name", value.name()); + gen.writeEndObject(); + } + +} diff --git a/traqtor-aio/src/main/java/link/pagan/traqtor/deck/IoMaster.java b/traqtor-aio/src/main/java/link/pagan/traqtor/deck/IoMaster.java new file mode 100644 index 0000000..50dc6eb --- /dev/null +++ b/traqtor-aio/src/main/java/link/pagan/traqtor/deck/IoMaster.java @@ -0,0 +1,112 @@ +package link.pagan.traqtor.deck; + +import java.io.File; +import link.pagan.traqtor.Traqtor; +import link.pagan.traqtor.deck.op.OperationResult; +import link.pagan.traqtor.Workspace; + +/** @author Edward M. Kagan {@literal <}kaganem{@literal @}2pm.tech{@literal >} */ +public class IoMaster { + + private static final IoMaster INSTANCE = new IoMaster(); + + private IoMaster () {} + + public static IoMaster instance () { + return INSTANCE; + } + + public OperationResult saveAs (Workspace workspace, File parent) { + return saveAs(workspace, parent, OperationResult.start()); + } + + public OperationResult saveAs (Workspace workspace, File parent, OperationResult result) { + + if (parent == null) { + return result + .info("Target directory must be set, are you OK?") + .fail("Failed to save workspace, no new root directory was passed"); + } + + if (!parent.exists()) { + return result + .info("Directory " + parent.getAbsolutePath() + " does not exist, deleted/moved/unmounted?") + .fail("Failed to save workspace, due non existent parent direcory for new workspace"); + } + + if (workspace.name() == null) { + return result + .info("Workspace does not contain name - how did you even achieve that?") + .fail("Failed to save workspace, due unknown name of it, huh..."); + } + + File root = new File(parent, workspace.name().asTiled()); + + if (!root.exists()) { + result.info("Creating new root direcory at " + root.getAbsolutePath()); + + if (!root.mkdirs()) { + return result + .info("Unable to create directory " + root.getAbsolutePath() + " - permission problem?") + .fail("Failed to save workspace, can not create new root directory"); + } + } + + if (root.listFiles().length > 0) { + return result + .info("Directory " + root.getAbsolutePath() + " is not empty, not safe to save?") + .fail("Failed to save workspace, due to polluted new root direcory"); + } + File oldRoot = workspace.root(); + workspace.root(root); + result.info("Setting workspace root to " + root.getAbsolutePath()); + save(workspace, result.startSubresult()); + + if (!result.ok()) { + result.info("Reverting workspace root to " + oldRoot.getAbsolutePath()); + workspace.root(oldRoot); + } + return result; + } + + public OperationResult save (Workspace workspace) { + return save(workspace, OperationResult.start()); + } + + public OperationResult save (Workspace workspace, OperationResult result) { + result.info("Saving workspace " + workspace.name().asTiled() + " ..."); + + if (workspace.root() == null) { + return result + .info("Workspace root is not set - is this a new workspace?") + .fail("Failed to save workspace, due to unknown root directory"); + } + + if (!workspace.root().exists()) { + return result + .info("Directory " + workspace.root().getAbsolutePath() + + " does not exist, deleted/moved/unmounted?") + .fail("Failed to save workspace, due non existent root direcory"); + } + + if (!workspace.root().canRead()) { + return result + .info("Unable to read " + workspace.root().getAbsolutePath() + " - permission problem?") + .fail("Failed to save workspace, due unreadable root direcory"); + } + + if (!workspace.root().canWrite()) { + return result + .info("Unable to write " + workspace.root().getAbsolutePath() + " - permission problem?") + .fail("Failed to save workspace, due write restriction on root direcory"); + + } + + result.addSubresult(Traqtor.json().save(workspace)); + + if (result.ok()) { workspace.dirty(false); } + + return result; + } + +} diff --git a/traqtor-aio/src/main/java/link/pagan/traqtor/deck/JsonMaster.java b/traqtor-aio/src/main/java/link/pagan/traqtor/deck/JsonMaster.java new file mode 100644 index 0000000..897ef43 --- /dev/null +++ b/traqtor-aio/src/main/java/link/pagan/traqtor/deck/JsonMaster.java @@ -0,0 +1,66 @@ +package link.pagan.traqtor.deck; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.module.SimpleModule; +import java.io.File; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; +import link.pagan.traqtor.Workspace; +import link.pagan.traqtor.WorkspaceSerializer; +import link.pagan.traqtor.deck.op.OperationResult; +import link.pagan.traqtor.project.universe.UniverseProject; +import link.pagan.traqtor.project.universe.UniverseProjectSerializer; +import link.pagan.traqtor.project.universe.element.Element; +import link.pagan.traqtor.project.universe.element.ElementSerializer; +import link.pagan.traqtor.project.universe.element.Particle; +import link.pagan.traqtor.project.universe.element.PaticleInfoSerializer; +import link.pagan.traqtor.project.universe.link.Link; +import link.pagan.traqtor.project.universe.link.LinkSerializer; +import link.pagan.traqtor.project.universe.schema.Constraint; +import link.pagan.traqtor.project.universe.schema.ConstraintInfoSerializer; +import link.pagan.traqtor.util.Name; +import link.pagan.traqtor.util.NameSerializer; + +/** @author Edward M. Kagan {@literal <}kaganem{@literal @}2pm.tech{@literal >} */ +public class JsonMaster { + + private static final JsonMaster INSTANCE = new JsonMaster(); + + private final ObjectMapper mapper; + + public JsonMaster () { + this.mapper = new ObjectMapper() + .enable(SerializationFeature.INDENT_OUTPUT) + .registerModule(new SimpleModule() + .addSerializer(Name.class, new NameSerializer()) + .addSerializer(UniverseProject.class, new UniverseProjectSerializer()) + .addSerializer(Element.class, new ElementSerializer()) + .addSerializer(Particle.class, new PaticleInfoSerializer()) + .addSerializer(Constraint.class, new ConstraintInfoSerializer()) + .addSerializer(Link.class, new LinkSerializer()) + .addSerializer(Workspace.class, new WorkspaceSerializer())); + + } + + public static JsonMaster instance () { + return INSTANCE; + } + + public OperationResult save (Workspace workspace) { + OperationResult result = OperationResult.start(); + File workspaceDesciptorFile = new File(workspace.root(), "workspace.json"); + result.info("Writing workspace descriptor file to " + workspaceDesciptorFile.getAbsolutePath() + " ..."); + + try { + mapper.writeValue(workspaceDesciptorFile, workspace); + } catch (IOException ex) { + Logger.getLogger(IoMaster.class.getName()).log(Level.SEVERE, null, ex); + result.fail("Failed to write file " + workspaceDesciptorFile.getAbsolutePath() + " - IO exception"); + result.fail(ex); + } + return result; + } + +} diff --git a/traqtor-aio/src/main/java/link/pagan/traqtor/deck/op/MessageType.java b/traqtor-aio/src/main/java/link/pagan/traqtor/deck/op/MessageType.java new file mode 100644 index 0000000..310d061 --- /dev/null +++ b/traqtor-aio/src/main/java/link/pagan/traqtor/deck/op/MessageType.java @@ -0,0 +1,8 @@ +package link.pagan.traqtor.deck.op; + +/** @author Edward M. Kagan {@literal <}kaganem{@literal @}2pm.tech{@literal >} */ +public enum MessageType { + INFO, + WARN, + FAIL +} diff --git a/traqtor-aio/src/main/java/link/pagan/traqtor/deck/op/OperationMessage.java b/traqtor-aio/src/main/java/link/pagan/traqtor/deck/op/OperationMessage.java new file mode 100644 index 0000000..0126210 --- /dev/null +++ b/traqtor-aio/src/main/java/link/pagan/traqtor/deck/op/OperationMessage.java @@ -0,0 +1,56 @@ +package link.pagan.traqtor.deck.op; + +import java.text.Format; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** @author Edward M. Kagan {@literal <}kaganem{@literal @}2pm.tech{@literal >} */ +public class OperationMessage { + + private static final Format TIMESTAMP_PRINTABLE_FORMAT = new SimpleDateFormat("HH:mm:ss.SSS"); + + private final String message; + + private final MessageType type; + + private final long timestamp; + + private OperationMessage (String message, MessageType type) { + this.message = message; + this.type = type; + this.timestamp = System.currentTimeMillis(); + } + + static OperationMessage warn (String message) { + return new OperationMessage(message, MessageType.WARN); + } + + static OperationMessage info (String message) { + return new OperationMessage(message, MessageType.INFO); + } + + static OperationMessage fail (String message) { + return new OperationMessage(message, MessageType.FAIL); + } + + public String message () { + return message; + } + + public MessageType type () { + return type; + } + + public long timestamp () { + return timestamp; + } + + @Override + public String toString () { + return TIMESTAMP_PRINTABLE_FORMAT.format(new Date(this.timestamp)) + " [" + + this.type.toString() + + "] " + + this.message; + } + +} diff --git a/traqtor-aio/src/main/java/link/pagan/traqtor/deck/op/OperationResult.java b/traqtor-aio/src/main/java/link/pagan/traqtor/deck/op/OperationResult.java new file mode 100644 index 0000000..3c5b4e6 --- /dev/null +++ b/traqtor-aio/src/main/java/link/pagan/traqtor/deck/op/OperationResult.java @@ -0,0 +1,82 @@ +package link.pagan.traqtor.deck.op; + +import java.util.LinkedList; + +/** @author Edward M. Kagan {@literal <}kaganem{@literal @}2pm.tech{@literal >} */ +public class OperationResult { + + private final long timestamp; + + private final LinkedList messages; + + private final LinkedList subresults; + + private OperationResult () { + this.timestamp = System.currentTimeMillis(); + this.messages = new LinkedList(); + this.subresults = new LinkedList(); + } + + public OperationResult info (String message) { + this.messages.add(OperationMessage.info(message)); + return this; + } + + public OperationResult warn (String message) { + this.messages.add(OperationMessage.warn(message)); + return this; + } + + public OperationResult fail (Exception ex) { + this.messages.add(OperationMessage.fail(ex.getMessage())); + return this; + } + + public OperationResult fail (String message) { + this.messages.add(OperationMessage.fail(message)); + return this; + } + + public boolean ok () { + boolean proxiedOK = proxiedOK(); + + if (!proxiedOK) { for (OperationMessage message : this.messages) { System.out.println(message.toString()); } } + return proxiedOK; + } + + private boolean proxiedOK () { + + if (this.messages.stream().anyMatch(message -> (message.type().equals(MessageType.FAIL)))) { return false; } + + if (!this.subresults.stream().noneMatch(subresult -> (!subresult.ok()))) { return false; } + return true; + } + + public long timestamp () { + return timestamp; + } + + public OperationMessage[] messages () { + return this.messages.toArray(new OperationMessage[this.messages.size()]); + } + + public OperationResult[] subresults () { + return this.subresults.toArray(new OperationResult[this.subresults.size()]); + } + + public static OperationResult start () { + return new OperationResult(); + } + + public OperationResult startSubresult () { + OperationResult subresult = new OperationResult(); + this.subresults.add(subresult); + return subresult; + } + + public OperationResult addSubresult (OperationResult subresult) { + this.subresults.add(subresult); + return this; + } + +} diff --git a/traqtor-aio/src/main/java/link/pagan/traqtor/util/Name.java b/traqtor-aio/src/main/java/link/pagan/traqtor/util/Name.java index 41ff770..6805646 100644 --- a/traqtor-aio/src/main/java/link/pagan/traqtor/util/Name.java +++ b/traqtor-aio/src/main/java/link/pagan/traqtor/util/Name.java @@ -14,6 +14,17 @@ public class Name implements Comparable { return new Name(parts); } + public String asTiled () { + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < this.parts.length; i++) { + sb.append(parts[i]); + + if (i != this.parts.length - 1) { sb.append('-'); } + } + return sb.toString(); + } + public String asDotted () { StringBuilder sb = new StringBuilder(); diff --git a/traqtor-aio/src/test/java/link/pagan/traqtor/EndToEndTest.java b/traqtor-aio/src/test/java/link/pagan/traqtor/EndToEndTest.java index d777798..bcef5e2 100644 --- a/traqtor-aio/src/test/java/link/pagan/traqtor/EndToEndTest.java +++ b/traqtor-aio/src/test/java/link/pagan/traqtor/EndToEndTest.java @@ -155,8 +155,6 @@ public class EndToEndTest { .schemas(LITERAL) .elements(organization); - System.out.println(Traqtor.io().toFileHandle(baseUniverse).content()); - System.out.println(Traqtor.io().toFileHandle(dataUniverse).content()); // try { // // String writeValueAsString = mapper.writeValueAsString(dataUniverse); @@ -167,12 +165,16 @@ public class EndToEndTest { // // .name("public") // // .tables(Database.table() // // .columns())); - // // new Workspace() - // // .name("link", "pagan") - // // .projects(baseUniverse, dataUniverse, mainDatabase); + Workspace workspace = new Workspace() + .name("link", "pagan") + .projects(baseUniverse, dataUniverse); // } catch (JsonProcessingException ex) { // Logger.getLogger(EndToEndTest.class.getName()).log(Level.SEVERE, null, ex); // } + + Traqtor.io().save(workspace); + // System.out.println(Traqtor.io().toFileHandle(baseUniverse).content()); + // System.out.println(Traqtor.io().toFileHandle(dataUniverse).content()); } } diff --git a/traqtor-aio/src/test/java/link/pagan/traqtor/IoTest.java b/traqtor-aio/src/test/java/link/pagan/traqtor/IoTest.java new file mode 100644 index 0000000..bbab233 --- /dev/null +++ b/traqtor-aio/src/test/java/link/pagan/traqtor/IoTest.java @@ -0,0 +1,35 @@ +package link.pagan.traqtor; + +import java.io.IOException; +import link.pagan.traqtor.test.TestUtils; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** @author Edward M. Kagan {@literal <}kaganem{@literal @}2pm.tech{@literal >} */ +public class IoTest { + + @BeforeEach + public void cleanTestDir () throws IOException { + TestUtils.rebuildTestRoot(); + } + + @AfterEach + public void killCore () throws IOException { + TestUtils.deleteTestRoot(); + } + + @Test + @DisplayName("Workspace save") + void workspaceSave () throws IOException { + Workspace workspace = new Workspace().name("traqtor", "demo", "workspace"); + Assertions.assertTrue(!Traqtor.io().save(workspace).ok()); + TestUtils.deleteTestRoot(); + Assertions.assertTrue(!Traqtor.io().save(workspace.root(TestUtils.testRoot())).ok()); + TestUtils.rebuildTestRoot(); + Assertions.assertTrue(Traqtor.io().saveAs(workspace, TestUtils.testRoot()).ok()); + } + +} diff --git a/traqtor-aio/src/test/java/link/pagan/traqtor/test/TestUtils.java b/traqtor-aio/src/test/java/link/pagan/traqtor/test/TestUtils.java new file mode 100644 index 0000000..badcb03 --- /dev/null +++ b/traqtor-aio/src/test/java/link/pagan/traqtor/test/TestUtils.java @@ -0,0 +1,33 @@ +package link.pagan.traqtor.test; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; + +/** @author Edward M. Kagan {@literal <}kaganem{@literal @}2pm.tech{@literal >} */ +public class TestUtils { + + private static final File TEST_DIR = new File(new File("").getAbsoluteFile().getParentFile(), "traqtor-test-root"); + + public static final void rebuildTestRoot () throws IOException { + deleteTestRoot(); + TEST_DIR.mkdirs(); + } + + public static final void deleteTestRoot () throws IOException { + + if (TEST_DIR.exists()) { + Files.walk(TEST_DIR.toPath()) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } + } + + public static File testRoot () { + return TEST_DIR; + } + +}