diff --git a/build.gradle b/build.gradle index a53f89f..a730ba4 100644 --- a/build.gradle +++ b/build.gradle @@ -20,6 +20,11 @@ dependencies { implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.3' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.15.3' implementation 'com.github.ben-manes.caffeine:jcache:3.1.5' + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.1' compileOnly 'org.projectlombok:lombok:1.18.30' annotationProcessor 'org.projectlombok:lombok:1.18.30' +} + +test { + useJUnitPlatform() } \ No newline at end of file diff --git a/src/main/java/io/github/chronosx88/JGUN/GraphNodeBuilder.java b/src/main/java/io/github/chronosx88/JGUN/GraphNodeBuilder.java new file mode 100644 index 0000000..2234646 --- /dev/null +++ b/src/main/java/io/github/chronosx88/JGUN/GraphNodeBuilder.java @@ -0,0 +1,99 @@ +package io.github.chronosx88.JGUN; + +import io.github.chronosx88.JGUN.models.MemoryGraph; +import io.github.chronosx88.JGUN.models.Node; +import io.github.chronosx88.JGUN.models.NodeLink; +import io.github.chronosx88.JGUN.models.NodeMetadata; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; +import java.util.UUID; + +public class GraphNodeBuilder { + private final MemoryGraph graph; + private final Node rootNode; + protected static final String ROOT_NODE = "__ROOT__"; + + public GraphNodeBuilder() { + this.graph = MemoryGraph.builder().build(); + this.rootNode = Node.builder() + .metadata(NodeMetadata.builder() + .nodeID(null) + .build()) + .build(); + graph.nodes.put(ROOT_NODE, this.rootNode); + } + + private GraphNodeBuilder addScalar(String name, Object value) { + rootNode.values.put(name, value); + rootNode.getMetadata().getStates().put(name, System.currentTimeMillis()); + return this; + } + + public GraphNodeBuilder add(String name, String value) { + return addScalar(name, value); + } + + public GraphNodeBuilder add(String name, BigInteger value) { + return addScalar(name, value); + } + + public GraphNodeBuilder add(String name, BigDecimal value) { + return addScalar(name, value); + } + + public GraphNodeBuilder add(String name, int value) { + return addScalar(name, value); + } + + public GraphNodeBuilder add(String name, long value) { + return addScalar(name, value); + } + + public GraphNodeBuilder add(String name, double value) { + return addScalar(name, value); + } + + public GraphNodeBuilder add(String name, boolean value) { + return addScalar(name, value); + + } + + public GraphNodeBuilder addNull(String name) { + return addScalar(name, null); + } + + public GraphNodeBuilder add(String name, GraphNodeBuilder builder) { + String newNodeID = UUID.randomUUID().toString(); + rootNode.values.put(name, NodeLink.builder() + .link(newNodeID) + .build()); + MemoryGraph innerGraph = builder.build(); + innerGraph.nodes.get(ROOT_NODE).getMetadata().setNodeID(newNodeID); + innerGraph.nodes.put(newNodeID, innerGraph.nodes.get(ROOT_NODE)); + innerGraph.nodes.remove(ROOT_NODE); + graph.nodes.putAll(innerGraph.nodes); + return this; + } + + public GraphNodeBuilder add(String name, NodeArrayBuilder builder) { + MemoryGraph innerGraph = builder.build(); + //noinspection unchecked + var innerArray = (List) innerGraph.nodes.get(ROOT_NODE).values.get(NodeArrayBuilder.ARRAY_FIELD); + rootNode.values.put(name, innerArray); + rootNode.getMetadata().getStates().put(name, innerGraph + .nodes + .get(ROOT_NODE) + .getMetadata() + .getStates() + .get(NodeArrayBuilder.ARRAY_FIELD)); + innerGraph.nodes.remove(ROOT_NODE); + graph.nodes.putAll(innerGraph.nodes); + return this; + } + + public MemoryGraph build() { + return this.graph; + } +} \ No newline at end of file diff --git a/src/main/java/io/github/chronosx88/JGUN/Gun.java b/src/main/java/io/github/chronosx88/JGUN/Gun.java index 74882d8..141ef98 100644 --- a/src/main/java/io/github/chronosx88/JGUN/Gun.java +++ b/src/main/java/io/github/chronosx88/JGUN/Gun.java @@ -1,25 +1,34 @@ package io.github.chronosx88.JGUN; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import io.github.chronosx88.JGUN.futures.FuturePut; import io.github.chronosx88.JGUN.models.MemoryGraph; +import io.github.chronosx88.JGUN.models.requests.PutRequest; import io.github.chronosx88.JGUN.nodes.GunClient; import io.github.chronosx88.JGUN.storage.Storage; import java.net.InetAddress; import java.net.URISyntaxException; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; public class Gun { - private GunClient gunClient; + private GunClient peer; private final Storage storage; + private final ObjectMapper objectMapper; + private final Executor executorService = Executors.newCachedThreadPool(); public Gun(InetAddress address, int port, Storage storage) { + this.objectMapper = new ObjectMapper(); + objectMapper.registerModule(new Jdk8Module()); this.storage = storage; try { - this.gunClient = new GunClient(address, port, storage); - this.gunClient.connectBlocking(); + this.peer = new GunClient(address, port, storage); + this.peer.connectBlocking(); } catch (URISyntaxException | InterruptedException e) { - e.printStackTrace(); + throw new RuntimeException(e); } } @@ -37,11 +46,27 @@ public class Gun { storage.addMapChangeListener(nodeID, listener); } - protected void sendPutRequest(MemoryGraph data) { - // TODO + protected FuturePut sendPutRequest(MemoryGraph data) { + String reqID = Dup.random(); + executorService.execute(() -> { + storage.mergeUpdate(data); + var request = PutRequest.builder() + .id(reqID) + .graph(data) + .build(); + String encodedRequest; + try { + encodedRequest = this.objectMapper.writeValueAsString(request); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + peer.emit(encodedRequest); + }); + return new FuturePut(reqID); } protected void sendGetRequest(String key, String field) { // TODO + throw new UnsupportedOperationException("TODO"); } } diff --git a/src/main/java/io/github/chronosx88/JGUN/NetworkHandler.java b/src/main/java/io/github/chronosx88/JGUN/NetworkHandler.java index f55ee0d..b97c6c8 100644 --- a/src/main/java/io/github/chronosx88/JGUN/NetworkHandler.java +++ b/src/main/java/io/github/chronosx88/JGUN/NetworkHandler.java @@ -2,6 +2,7 @@ package io.github.chronosx88.JGUN; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import io.github.chronosx88.JGUN.futures.FutureGet; import io.github.chronosx88.JGUN.futures.FuturePut; import io.github.chronosx88.JGUN.futures.GetResult; @@ -31,12 +32,15 @@ public class NetworkHandler { private final Storage storage; private final Dup dup; private final Executor executorService = Executors.newCachedThreadPool(); - private final ObjectMapper objectMapper = new ObjectMapper(); + private final ObjectMapper objectMapper; public NetworkHandler(Storage storage, Peer peer, Dup dup) { this.storage = storage; this.peer = peer; this.dup = dup; + + this.objectMapper = new ObjectMapper(); + objectMapper.registerModule(new Jdk8Module()); } public void addPendingGetRequest(FutureGet future) { diff --git a/src/main/java/io/github/chronosx88/JGUN/NodeArrayBuilder.java b/src/main/java/io/github/chronosx88/JGUN/NodeArrayBuilder.java new file mode 100644 index 0000000..6e46ca0 --- /dev/null +++ b/src/main/java/io/github/chronosx88/JGUN/NodeArrayBuilder.java @@ -0,0 +1,88 @@ +package io.github.chronosx88.JGUN; + +import io.github.chronosx88.JGUN.models.MemoryGraph; +import io.github.chronosx88.JGUN.models.Node; +import io.github.chronosx88.JGUN.models.NodeLink; +import io.github.chronosx88.JGUN.models.NodeMetadata; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.*; + +public class NodeArrayBuilder { + private final MemoryGraph graph; + private final Node rootNode; + private final List innerArray; + protected static final String ARRAY_FIELD = "__ARRAY__"; + + public NodeArrayBuilder() { + this.graph = MemoryGraph.builder().build(); + this.innerArray = new ArrayList<>(); + this.rootNode = Node.builder() + .metadata(NodeMetadata.builder() + .nodeID(null) + .states(new HashMap<>(Map.of(ARRAY_FIELD, System.currentTimeMillis()))) + .build()) + .values(Map.of(ARRAY_FIELD, innerArray)) + .build(); + graph.nodes.put(GraphNodeBuilder.ROOT_NODE, this.rootNode); + } + + private NodeArrayBuilder addScalar(Object value) { + this.innerArray.add(value); + this.rootNode.getMetadata().getStates().put(ARRAY_FIELD, System.currentTimeMillis()); + return this; + } + + public NodeArrayBuilder add(String value) { + return addScalar(value); + } + + public NodeArrayBuilder add(BigInteger value) { + return addScalar(value); + } + + public NodeArrayBuilder add(BigDecimal value) { + return addScalar(value); + } + + public NodeArrayBuilder add(int value) { + return addScalar(value); + } + + public NodeArrayBuilder add(long value) { + return addScalar(value); + } + + public NodeArrayBuilder add(double value) { + return addScalar(value); + } + + public NodeArrayBuilder add(boolean value) { + return addScalar(value); + + } + + public NodeArrayBuilder addNull(String name) { + return addScalar(null); + } + + public NodeArrayBuilder add(GraphNodeBuilder builder) { + String newNodeID = UUID.randomUUID().toString(); + //noinspection unchecked + List innerArray = (List) rootNode.values.get(ARRAY_FIELD); + innerArray.add(NodeLink.builder() + .link(newNodeID) + .build()); + MemoryGraph innerGraph = builder.build(); + innerGraph.nodes.get(GraphNodeBuilder.ROOT_NODE).getMetadata().setNodeID(newNodeID); + innerGraph.nodes.put(newNodeID, innerGraph.nodes.get(GraphNodeBuilder.ROOT_NODE)); + innerGraph.nodes.remove(GraphNodeBuilder.ROOT_NODE); + this.graph.nodes.putAll(innerGraph.nodes); + return this; + } + + public MemoryGraph build() { + return this.graph; + } +} diff --git a/src/main/java/io/github/chronosx88/JGUN/PathReference.java b/src/main/java/io/github/chronosx88/JGUN/PathReference.java index 747f80a..f872add 100644 --- a/src/main/java/io/github/chronosx88/JGUN/PathReference.java +++ b/src/main/java/io/github/chronosx88/JGUN/PathReference.java @@ -2,15 +2,15 @@ package io.github.chronosx88.JGUN; import io.github.chronosx88.JGUN.futures.FutureGet; import io.github.chronosx88.JGUN.futures.FuturePut; -import io.github.chronosx88.JGUN.nodes.GunClient; +import io.github.chronosx88.JGUN.models.MemoryGraph; import java.util.ArrayList; -import java.util.HashMap; +import java.util.List; public class PathReference { - private final ArrayList path = new ArrayList<>(); + private final List path = new ArrayList<>(); - private Gun gun; + private final Gun gun; public PathReference(Gun gun) { this.gun = gun; @@ -21,14 +21,13 @@ public class PathReference { return this; } - public FutureGet getData() { + public FutureGet once() { // TODO - return null; + throw new UnsupportedOperationException("TODO"); } - public FuturePut put(HashMap data) { - // TODO - return null; + public FuturePut put(MemoryGraph graph) { + return gun.sendPutRequest(graph); } public void on(NodeChangeListener changeListener) { diff --git a/src/main/java/io/github/chronosx88/JGUN/futures/BaseCompletableFuture.java b/src/main/java/io/github/chronosx88/JGUN/futures/BaseCompletableFuture.java index 813ebe4..26021a2 100644 --- a/src/main/java/io/github/chronosx88/JGUN/futures/BaseCompletableFuture.java +++ b/src/main/java/io/github/chronosx88/JGUN/futures/BaseCompletableFuture.java @@ -1,10 +1,8 @@ package io.github.chronosx88.JGUN.futures; -import java.util.concurrent.ExecutionException; - -import java.util.concurrent.CompletableFuture; import lombok.Getter; +import java.util.concurrent.CompletableFuture; @Getter public class BaseCompletableFuture extends CompletableFuture { @@ -14,24 +12,4 @@ public class BaseCompletableFuture extends CompletableFuture { super(); futureID = id; } - - public void addListener(final BaseFutureListener listener) { - this.whenCompleteAsync((t, throwable) -> { - if(throwable == null) { - listener.onComplete(t); - } else { - listener.onError(t, throwable); - } - }); - } - - public T await() { - T t = null; - try { - t = super.get(); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - } - return t; - } } diff --git a/src/main/java/io/github/chronosx88/JGUN/futures/BaseFutureListener.java b/src/main/java/io/github/chronosx88/JGUN/futures/BaseFutureListener.java deleted file mode 100644 index 0bd6361..0000000 --- a/src/main/java/io/github/chronosx88/JGUN/futures/BaseFutureListener.java +++ /dev/null @@ -1,6 +0,0 @@ -package io.github.chronosx88.JGUN.futures; - -public interface BaseFutureListener { - void onComplete(T result); - void onError(T result, Throwable exception); -} diff --git a/src/main/java/io/github/chronosx88/JGUN/models/NodeLink.java b/src/main/java/io/github/chronosx88/JGUN/models/NodeLink.java new file mode 100644 index 0000000..7772c6a --- /dev/null +++ b/src/main/java/io/github/chronosx88/JGUN/models/NodeLink.java @@ -0,0 +1,14 @@ +package io.github.chronosx88.JGUN.models; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; + +@Data +@Builder +@Jacksonized +public class NodeLink { + @JsonProperty("#") + String link; +} diff --git a/src/main/java/io/github/chronosx88/JGUN/models/NodeMetadata.java b/src/main/java/io/github/chronosx88/JGUN/models/NodeMetadata.java index 8d6ae11..ed79122 100644 --- a/src/main/java/io/github/chronosx88/JGUN/models/NodeMetadata.java +++ b/src/main/java/io/github/chronosx88/JGUN/models/NodeMetadata.java @@ -6,6 +6,7 @@ import lombok.Data; import lombok.extern.jackson.Jacksonized; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; @Data @@ -14,7 +15,7 @@ import java.util.Map; public class NodeMetadata { @JsonProperty(">") @Builder.Default - private Map states = new HashMap<>(); // field -> state + private Map states = new LinkedHashMap<>(); // field -> state @JsonProperty("#") private String nodeID; diff --git a/src/main/java/io/github/chronosx88/JGUN/models/requests/GetRequest.java b/src/main/java/io/github/chronosx88/JGUN/models/requests/GetRequest.java index 758c277..67bdb18 100644 --- a/src/main/java/io/github/chronosx88/JGUN/models/requests/GetRequest.java +++ b/src/main/java/io/github/chronosx88/JGUN/models/requests/GetRequest.java @@ -5,10 +5,11 @@ import io.github.chronosx88.JGUN.models.BaseMessage; import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.experimental.SuperBuilder; import lombok.extern.jackson.Jacksonized; @Data -@Builder +@SuperBuilder @EqualsAndHashCode(callSuper = true) @Jacksonized public class GetRequest extends BaseMessage { diff --git a/src/main/java/io/github/chronosx88/JGUN/models/requests/PutRequest.java b/src/main/java/io/github/chronosx88/JGUN/models/requests/PutRequest.java index fc8db52..d866790 100644 --- a/src/main/java/io/github/chronosx88/JGUN/models/requests/PutRequest.java +++ b/src/main/java/io/github/chronosx88/JGUN/models/requests/PutRequest.java @@ -7,11 +7,12 @@ import io.github.chronosx88.JGUN.models.Node; import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.experimental.SuperBuilder; import lombok.extern.jackson.Jacksonized; @Data @Jacksonized -@Builder +@SuperBuilder @EqualsAndHashCode(callSuper = true) public class PutRequest extends BaseMessage { @JsonProperty("put") diff --git a/src/main/java/io/github/chronosx88/JGUN/nodes/GunClient.java b/src/main/java/io/github/chronosx88/JGUN/nodes/GunClient.java index d4ebb42..bb78945 100644 --- a/src/main/java/io/github/chronosx88/JGUN/nodes/GunClient.java +++ b/src/main/java/io/github/chronosx88/JGUN/nodes/GunClient.java @@ -2,6 +2,8 @@ package io.github.chronosx88.JGUN.nodes; import io.github.chronosx88.JGUN.Dup; import io.github.chronosx88.JGUN.NetworkHandler; +import io.github.chronosx88.JGUN.futures.FutureGet; +import io.github.chronosx88.JGUN.futures.FuturePut; import io.github.chronosx88.JGUN.storage.Storage; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; @@ -44,4 +46,19 @@ public class GunClient extends WebSocketClient implements Peer { public void emit(String data) { this.send(data); } + + @Override + public void addPendingPutRequest(FuturePut futurePut) { + this.handler.addPendingPutRequest(futurePut); + } + + @Override + public void addPendingGetRequest(FutureGet futureGet) { + this.handler.addPendingGetRequest(futureGet); + } + + @Override + public void start() { + this.connect(); + } } diff --git a/src/main/java/io/github/chronosx88/JGUN/nodes/GunNodeBuilder.java b/src/main/java/io/github/chronosx88/JGUN/nodes/GunNodeBuilder.java deleted file mode 100644 index dc8221c..0000000 --- a/src/main/java/io/github/chronosx88/JGUN/nodes/GunNodeBuilder.java +++ /dev/null @@ -1,4 +0,0 @@ -package io.github.chronosx88.JGUN.nodes; - -public class GunNodeBuilder { -} diff --git a/src/main/java/io/github/chronosx88/JGUN/nodes/Peer.java b/src/main/java/io/github/chronosx88/JGUN/nodes/Peer.java index 913c41a..8abfa46 100644 --- a/src/main/java/io/github/chronosx88/JGUN/nodes/Peer.java +++ b/src/main/java/io/github/chronosx88/JGUN/nodes/Peer.java @@ -1,5 +1,12 @@ package io.github.chronosx88.JGUN.nodes; +import io.github.chronosx88.JGUN.futures.BaseCompletableFuture; +import io.github.chronosx88.JGUN.futures.FutureGet; +import io.github.chronosx88.JGUN.futures.FuturePut; + public interface Peer { void emit(String data); + void addPendingPutRequest(FuturePut futurePut); + void addPendingGetRequest(FutureGet futureGet); + void start(); } diff --git a/src/main/java/io/github/chronosx88/JGUN/storage/Storage.java b/src/main/java/io/github/chronosx88/JGUN/storage/Storage.java index ba0fccb..d81a195 100644 --- a/src/main/java/io/github/chronosx88/JGUN/storage/Storage.java +++ b/src/main/java/io/github/chronosx88/JGUN/storage/Storage.java @@ -36,7 +36,7 @@ public abstract class Storage { */ public void mergeUpdate(MemoryGraph update) { long machine = System.currentTimeMillis(); - MemoryGraph diff = new MemoryGraph(); + MemoryGraph diff = MemoryGraph.builder().build(); for (Map.Entry entry : update.getNodes().entrySet()) { Node node = entry.getValue(); Node diffNode = this.mergeNode(node, machine); diff --git a/src/test/java/io/github/chronosx88/JGUN/GraphNodeBuilderTest.java b/src/test/java/io/github/chronosx88/JGUN/GraphNodeBuilderTest.java new file mode 100644 index 0000000..1a76a0e --- /dev/null +++ b/src/test/java/io/github/chronosx88/JGUN/GraphNodeBuilderTest.java @@ -0,0 +1,63 @@ +package io.github.chronosx88.JGUN; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import org.junit.jupiter.api.Test; + +class GraphNodeBuilderTest { + + @Test + void Test_sampleGraph1() { + var objectMapper = new ObjectMapper(); + objectMapper.registerModule(new Jdk8Module()); + + var graph = new GraphNodeBuilder() + .add("firstName", "John") + .add("lastName", "Smith") + .add("age", 25) + .add("address", new GraphNodeBuilder() + .add("streetAddress", "21 2nd Street") + .add("city", "New York") + .add("state", "NY") + .add("postalCode", "10021")) + .add("phoneNumber", new NodeArrayBuilder() + .add(new GraphNodeBuilder() + .add("type", "home") + .add("number", "212 555-1234")) + .add(new GraphNodeBuilder() + .add("type", "fax") + .add("number", "646 555-4567"))) + .build(); + + String graphJSON = null; + try { + graphJSON = objectMapper.writeValueAsString(graph); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + System.out.println(graphJSON); + } + + @Test + void Test_sampleGraph2() { + var objectMapper = new ObjectMapper(); + objectMapper.registerModule(new Jdk8Module()); + + var graph = new GraphNodeBuilder() + .add("a", new NodeArrayBuilder() + .add(new GraphNodeBuilder() + .add("b", new GraphNodeBuilder() + .add("c", true))) + .add(0)) + .build(); + + String graphJSON = null; + try { + graphJSON = objectMapper.writeValueAsString(graph); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + System.out.println(graphJSON); + } +} \ No newline at end of file