From 7b84eb10c7c70dc83acd21afcc1ae631369428eb Mon Sep 17 00:00:00 2001 From: "@syxhe" Date: Wed, 1 Oct 2025 21:26:08 -0500 Subject: Implement ChannelNode --- src/main/java/ChannelNode.java | 153 +++++++++++++++++++++++++++++++++++++ src/main/java/IChannelNode.java | 14 +++- src/test/java/ChannelNodeTest.java | 58 ++++++++++++++ 3 files changed, 221 insertions(+), 4 deletions(-) create mode 100644 src/main/java/ChannelNode.java diff --git a/src/main/java/ChannelNode.java b/src/main/java/ChannelNode.java new file mode 100644 index 0000000..e984cf0 --- /dev/null +++ b/src/main/java/ChannelNode.java @@ -0,0 +1,153 @@ +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +public class ChannelNode implements IChannelNode { + private Map incoming; + private Map outgoing; + + public ChannelNode() { + incoming = new TreeMap<>(); + outgoing = new TreeMap<>(); + } + + @Override + public void setConnections(Map conmap, Direction dir) throws IllegalArgumentException { + if(conmap == null) throw new IllegalArgumentException("connection map is null"); + if(dir == null) throw new IllegalArgumentException("direction is null"); + if(conmap.containsKey(this)) throw new IllegalArgumentException("connection map contains this node"); + + int dv = dir.getVal(); + if(dv >= Direction.INCOMING.getVal()) incoming = conmap; + if(dv >= Direction.OUTGOING.getVal()) outgoing = conmap; + } + + @Override + public void setNumConnections(IChannelNode node, Direction dir, int num) throws IllegalArgumentException { + if(node == null) throw new IllegalArgumentException("node is null"); + if(dir == null) throw new IllegalArgumentException("dir is null"); + if(num < 0) throw new IllegalArgumentException("num < 0"); + + int dv = dir.getVal(); + if(dv >= Direction.INCOMING.getVal()) incoming.put(node, num); + if(dv >= Direction.OUTGOING.getVal()) outgoing.put(node, num); + } + + @Override + public void addConnection(IChannelNode node, Direction dir) { + if(node == null) throw new IllegalArgumentException("node is null"); + if(node == this) throw new IllegalArgumentException("node is self"); + if(dir == null) throw new IllegalStateException("dir is null"); + + int dv = dir.getVal(); + if(dv >= Direction.INCOMING.getVal()) incoming.put(node, incoming.get(node) + 1); + if(dv >= Direction.OUTGOING.getVal()) outgoing.put(node, outgoing.get(node) + 1); + } + + @Override + public void addConnections(Iterable nodes, Direction dir) { + if(nodes == null) throw new IllegalArgumentException("node is null"); + if(dir == null) throw new IllegalArgumentException("dir is null"); + nodes.forEach((node) -> {if(node == this) throw new IllegalArgumentException("one of the included nodes is this node");}); + + int dv = dir.getVal(), + incomingV = Direction.INCOMING.getVal(), + outgoingV = Direction.OUTGOING.getVal(); + + nodes.forEach((node) -> { + if(dv >= incomingV) incoming.put(node, incoming.get(node) + 1); + if(dv >= outgoingV) outgoing.put(node, outgoing.get(node) + 1); + }); + } + + @Override + public void removeConnection(IChannelNode node, Direction dir) { + if(node == null) throw new IllegalArgumentException("node is null"); + if(node == this) throw new IllegalArgumentException("node is self"); + if(dir == null) throw new IllegalStateException("dir is null"); + + int dv = dir.getVal(); + if(dv >= Direction.INCOMING.getVal()) incoming.put(node, Math.max(incoming.get(node) - 1, 0)); + if(dv >= Direction.OUTGOING.getVal()) outgoing.put(node, Math.max(outgoing.get(node) - 1, 0)); + } + + @Override + public void removeConnections(Iterable nodes, Direction dir) { + if(nodes == null) throw new IllegalArgumentException("node is null"); + if(dir == null) throw new IllegalArgumentException("dir is null"); + nodes.forEach((node) -> {if(node == this) throw new IllegalArgumentException("one of the included nodes is this node");}); + + int dv = dir.getVal(), + incomingV = Direction.INCOMING.getVal(), + outgoingV = Direction.OUTGOING.getVal(); + + nodes.forEach((node) -> { + if(dv >= incomingV) incoming.put(node, Math.max(incoming.get(node) - 1, 0)); + if(dv >= outgoingV) outgoing.put(node, Math.max(outgoing.get(node) - 1, 0)); + }); + } + + @Override + public void clearConnections(Direction dir) { + if(dir == null) throw new IllegalArgumentException("dir is null"); + + int dv = dir.getVal(); + if(dv >= Direction.INCOMING.getVal()) incoming.clear(); + if(dv >= Direction.OUTGOING.getVal()) outgoing.clear(); + } + + @Override + public boolean connectionExists(IChannelNode node, Direction dir) { + if(node == null) throw new IllegalArgumentException("node is null"); + if(dir == null) throw new IllegalArgumentException("dir is null"); + + switch (dir) { + case INCOMING -> {return incoming.containsKey(node);} + case OUTGOING -> {return outgoing.containsKey(node);} + case BOTH -> {return (incoming.containsKey(node) && outgoing.containsKey(node));} + default -> throw new IllegalStateException("got unknown direction"); + } + } + + @Override + public int getNumConnections(Direction dir) { + int total = 0; + int dv = dir.getVal(); + + if(dv >= Direction.INCOMING.getVal()) for(int i: incoming.values()) total += i; + if(dv >= Direction.OUTGOING.getVal()) for(int i: outgoing.values()) total += i; + return total; + } + + @Override + public Map getIncomingConnections() { + return incoming; + } + + @Override + public Map getOutgoingConnections() { + return outgoing; + } + + @Override + public List> getConnections(Direction dir) { + if(dir == null) throw new IllegalArgumentException("dir is null"); + + int dv = dir.getVal(); + ArrayList> res = new ArrayList<>(); + if(dv >= Direction.INCOMING.getVal()) res.add(incoming); + if(dv >= Direction.OUTGOING.getVal()) res.add(outgoing); + + return res; + } + + @Override + public int compareTo(IChannelNode iChannelNode) { + if(iChannelNode == null) throw new IllegalArgumentException("comparison node is null"); + int selfTotal = getNumConnections(Direction.BOTH), + theirTotal = iChannelNode.getNumConnections(Direction.BOTH); + + return selfTotal - theirTotal; + } +} diff --git a/src/main/java/IChannelNode.java b/src/main/java/IChannelNode.java index 463f3ac..baf7572 100644 --- a/src/main/java/IChannelNode.java +++ b/src/main/java/IChannelNode.java @@ -1,14 +1,19 @@ import java.util.List; import java.util.Map; -public interface IChannelNode { +public interface IChannelNode extends Comparable { enum Direction { - INCOMING, - OUTGOING, - BOTH + INCOMING(0), // Users incoming from outside channel (aka: other channel forwarded this channel's post) + OUTGOING(1), // Users outgoing from this channel (aka: this channel forwarded someone else's post) + BOTH(2); // Modify both incoming and outgoing counts at once + + private final int val; + Direction(int val) {this.val = val;} + public int getVal() {return this.val;} } void setConnections(Map conmap, Direction dir); + void setNumConnections(IChannelNode node, Direction dir, int num); void addConnection(IChannelNode node, Direction dir); void addConnections(Iterable nodes, Direction dir); void removeConnection(IChannelNode node, Direction dir); @@ -16,6 +21,7 @@ public interface IChannelNode { void clearConnections(Direction dir); boolean connectionExists(IChannelNode node, Direction dir); + int getNumConnections(Direction dir); Map getIncomingConnections(); Map getOutgoingConnections(); List> getConnections(Direction dir); diff --git a/src/test/java/ChannelNodeTest.java b/src/test/java/ChannelNodeTest.java index 5623d72..6268a97 100644 --- a/src/test/java/ChannelNodeTest.java +++ b/src/test/java/ChannelNodeTest.java @@ -23,6 +23,14 @@ class ChannelNodeTest { // Null direction should always throw assertThrows(IllegalArgumentException.class, () -> testNodeA.setConnections(new HashMap<>(), null)); + // Self endpoint should throw + Map temp = new HashMap<>(); + temp.put(testNodeA, 1); + + assertThrows(IllegalArgumentException.class, () -> testNodeA.setConnections(temp, IChannelNode.Direction.INCOMING)); + assertThrows(IllegalArgumentException.class, () -> testNodeA.setConnections(temp, IChannelNode.Direction.OUTGOING)); + assertThrows(IllegalArgumentException.class, () -> testNodeA.setConnections(temp, IChannelNode.Direction.BOTH)); + // Should not throw assertDoesNotThrow(() -> testNodeA.setConnections(new HashMap<>(), IChannelNode.Direction.INCOMING)); assertDoesNotThrow(() -> testNodeA.setConnections(new HashMap<>(), IChannelNode.Direction.OUTGOING)); @@ -30,6 +38,36 @@ class ChannelNodeTest { + // Null endpoint should throw + assertThrows(IllegalArgumentException.class, () -> testNodeA.setNumConnections(null, null, -1)); + assertThrows(IllegalArgumentException.class, () -> testNodeA.setNumConnections(null, IChannelNode.Direction.INCOMING, 1)); + assertThrows(IllegalArgumentException.class, () -> testNodeA.setNumConnections(null, IChannelNode.Direction.OUTGOING, 1)); + assertThrows(IllegalArgumentException.class, () -> testNodeA.setNumConnections(null, IChannelNode.Direction.BOTH, 1)); + + // Null direction should throw + assertThrows(IllegalArgumentException.class, () -> testNodeA.setNumConnections(testNodeB, null, 1)); + + // Self endpoint should throw + assertThrows(IllegalArgumentException.class, () -> testNodeA.setNumConnections(testNodeA, IChannelNode.Direction.INCOMING, 1)); + assertThrows(IllegalArgumentException.class, () -> testNodeA.setNumConnections(testNodeA, IChannelNode.Direction.OUTGOING, 1)); + assertThrows(IllegalArgumentException.class, () -> testNodeA.setNumConnections(testNodeA, IChannelNode.Direction.BOTH, 1)); + + // Negative connections should throw + assertThrows(IllegalArgumentException.class, () -> testNodeA.setNumConnections(testNodeB, IChannelNode.Direction.INCOMING, -1)); + assertThrows(IllegalArgumentException.class, () -> testNodeA.setNumConnections(testNodeB, IChannelNode.Direction.OUTGOING, -1)); + assertThrows(IllegalArgumentException.class, () -> testNodeA.setNumConnections(testNodeB, IChannelNode.Direction.BOTH, -1)); + + // Should not throw + assertDoesNotThrow(() -> testNodeA.setNumConnections(testNodeB, IChannelNode.Direction.INCOMING, 10)); + assertDoesNotThrow(() -> testNodeA.setNumConnections(testNodeB, IChannelNode.Direction.OUTGOING, 10)); + assertDoesNotThrow(() -> testNodeA.setNumConnections(testNodeB, IChannelNode.Direction.BOTH, 3)); + + assertDoesNotThrow(() -> testNodeB.setNumConnections(testNodeA, IChannelNode.Direction.INCOMING, 3)); + assertDoesNotThrow(() -> testNodeB.setNumConnections(testNodeA, IChannelNode.Direction.OUTGOING, 5)); + assertDoesNotThrow(() -> testNodeB.setNumConnections(testNodeA, IChannelNode.Direction.BOTH, 90)); + + + // Null endpoint should always throw assertThrows(IllegalArgumentException.class, () -> testNodeA.addConnection(null, null)); assertThrows(IllegalArgumentException.class, () -> testNodeA.addConnection(null, IChannelNode.Direction.INCOMING)); @@ -45,6 +83,11 @@ class ChannelNodeTest { assertThrows(IllegalArgumentException.class, () -> testNodeA.addConnection(testNodeB, null)); assertThrows(IllegalArgumentException.class, () -> testNodeB.addConnection(testNodeA, null)); + // Should not throw + assertDoesNotThrow(() -> testNodeA.addConnection(testNodeB, IChannelNode.Direction.INCOMING)); + assertDoesNotThrow(() -> testNodeA.addConnection(testNodeB, IChannelNode.Direction.OUTGOING)); + assertDoesNotThrow(() -> testNodeA.addConnection(testNodeB, IChannelNode.Direction.BOTH)); + // Null iterable should always throw @@ -62,6 +105,10 @@ class ChannelNodeTest { assertThrows(IllegalArgumentException.class, () -> testNodeA.addConnections(List.of(new IChannelNode[]{testNodeA}), IChannelNode.Direction.OUTGOING)); assertThrows(IllegalArgumentException.class, () -> testNodeA.addConnections(List.of(new IChannelNode[]{testNodeA}), IChannelNode.Direction.BOTH)); + // Should not throw + assertDoesNotThrow(() -> testNodeA.addConnections(List.of(new IChannelNode[]{testNodeB}), IChannelNode.Direction.INCOMING)); + assertDoesNotThrow(() -> testNodeA.addConnections(List.of(new IChannelNode[]{testNodeB}), IChannelNode.Direction.OUTGOING)); + assertDoesNotThrow(() -> testNodeA.addConnections(List.of(new IChannelNode[]{testNodeB}), IChannelNode.Direction.BOTH)); // Null node should always throw @@ -113,8 +160,19 @@ class ChannelNodeTest { + // Null direction should always throw + assertThrows(IllegalArgumentException.class, () -> testNodeA.getNumConnections(null)); + assertDoesNotThrow(() -> testNodeA.getNumConnections(IChannelNode.Direction.INCOMING)); + assertDoesNotThrow(() -> testNodeA.getNumConnections(IChannelNode.Direction.OUTGOING)); + assertDoesNotThrow(() -> testNodeA.getNumConnections(IChannelNode.Direction.BOTH)); + + + // Null direction should always throw assertThrows(IllegalArgumentException.class, () -> testNodeA.getConnections(null)); + assertDoesNotThrow(() -> testNodeA.getConnections(IChannelNode.Direction.INCOMING)); + assertDoesNotThrow(() -> testNodeA.getConnections(IChannelNode.Direction.OUTGOING)); + assertDoesNotThrow(() -> testNodeA.getConnections(IChannelNode.Direction.BOTH)); } } // End of ChannelNodeTest \ No newline at end of file -- cgit v1.2.3