diff options
| -rw-r--r-- | src/main/java/ChannelNode.java | 153 | ||||
| -rw-r--r-- | src/main/java/IChannelNode.java | 14 | ||||
| -rw-r--r-- | src/test/java/ChannelNodeTest.java | 58 |
3 files changed, 221 insertions, 4 deletions
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 @@ | |||
| 1 | import java.util.ArrayList; | ||
| 2 | import java.util.List; | ||
| 3 | import java.util.Map; | ||
| 4 | import java.util.TreeMap; | ||
| 5 | |||
| 6 | public class ChannelNode implements IChannelNode { | ||
| 7 | private Map<IChannelNode, Integer> incoming; | ||
| 8 | private Map<IChannelNode, Integer> outgoing; | ||
| 9 | |||
| 10 | public ChannelNode() { | ||
| 11 | incoming = new TreeMap<>(); | ||
| 12 | outgoing = new TreeMap<>(); | ||
| 13 | } | ||
| 14 | |||
| 15 | @Override | ||
| 16 | public void setConnections(Map<IChannelNode, Integer> conmap, Direction dir) throws IllegalArgumentException { | ||
| 17 | if(conmap == null) throw new IllegalArgumentException("connection map is null"); | ||
| 18 | if(dir == null) throw new IllegalArgumentException("direction is null"); | ||
| 19 | if(conmap.containsKey(this)) throw new IllegalArgumentException("connection map contains this node"); | ||
| 20 | |||
| 21 | int dv = dir.getVal(); | ||
| 22 | if(dv >= Direction.INCOMING.getVal()) incoming = conmap; | ||
| 23 | if(dv >= Direction.OUTGOING.getVal()) outgoing = conmap; | ||
| 24 | } | ||
| 25 | |||
| 26 | @Override | ||
| 27 | public void setNumConnections(IChannelNode node, Direction dir, int num) throws IllegalArgumentException { | ||
| 28 | if(node == null) throw new IllegalArgumentException("node is null"); | ||
| 29 | if(dir == null) throw new IllegalArgumentException("dir is null"); | ||
| 30 | if(num < 0) throw new IllegalArgumentException("num < 0"); | ||
| 31 | |||
| 32 | int dv = dir.getVal(); | ||
| 33 | if(dv >= Direction.INCOMING.getVal()) incoming.put(node, num); | ||
| 34 | if(dv >= Direction.OUTGOING.getVal()) outgoing.put(node, num); | ||
| 35 | } | ||
| 36 | |||
| 37 | @Override | ||
| 38 | public void addConnection(IChannelNode node, Direction dir) { | ||
| 39 | if(node == null) throw new IllegalArgumentException("node is null"); | ||
| 40 | if(node == this) throw new IllegalArgumentException("node is self"); | ||
| 41 | if(dir == null) throw new IllegalStateException("dir is null"); | ||
| 42 | |||
| 43 | int dv = dir.getVal(); | ||
| 44 | if(dv >= Direction.INCOMING.getVal()) incoming.put(node, incoming.get(node) + 1); | ||
| 45 | if(dv >= Direction.OUTGOING.getVal()) outgoing.put(node, outgoing.get(node) + 1); | ||
| 46 | } | ||
| 47 | |||
| 48 | @Override | ||
| 49 | public void addConnections(Iterable<IChannelNode> nodes, Direction dir) { | ||
| 50 | if(nodes == null) throw new IllegalArgumentException("node is null"); | ||
| 51 | if(dir == null) throw new IllegalArgumentException("dir is null"); | ||
| 52 | nodes.forEach((node) -> {if(node == this) throw new IllegalArgumentException("one of the included nodes is this node");}); | ||
| 53 | |||
| 54 | int dv = dir.getVal(), | ||
| 55 | incomingV = Direction.INCOMING.getVal(), | ||
| 56 | outgoingV = Direction.OUTGOING.getVal(); | ||
| 57 | |||
| 58 | nodes.forEach((node) -> { | ||
| 59 | if(dv >= incomingV) incoming.put(node, incoming.get(node) + 1); | ||
| 60 | if(dv >= outgoingV) outgoing.put(node, outgoing.get(node) + 1); | ||
| 61 | }); | ||
| 62 | } | ||
| 63 | |||
| 64 | @Override | ||
| 65 | public void removeConnection(IChannelNode node, Direction dir) { | ||
| 66 | if(node == null) throw new IllegalArgumentException("node is null"); | ||
| 67 | if(node == this) throw new IllegalArgumentException("node is self"); | ||
| 68 | if(dir == null) throw new IllegalStateException("dir is null"); | ||
| 69 | |||
| 70 | int dv = dir.getVal(); | ||
| 71 | if(dv >= Direction.INCOMING.getVal()) incoming.put(node, Math.max(incoming.get(node) - 1, 0)); | ||
| 72 | if(dv >= Direction.OUTGOING.getVal()) outgoing.put(node, Math.max(outgoing.get(node) - 1, 0)); | ||
| 73 | } | ||
| 74 | |||
| 75 | @Override | ||
| 76 | public void removeConnections(Iterable<IChannelNode> nodes, Direction dir) { | ||
| 77 | if(nodes == null) throw new IllegalArgumentException("node is null"); | ||
| 78 | if(dir == null) throw new IllegalArgumentException("dir is null"); | ||
| 79 | nodes.forEach((node) -> {if(node == this) throw new IllegalArgumentException("one of the included nodes is this node");}); | ||
| 80 | |||
| 81 | int dv = dir.getVal(), | ||
| 82 | incomingV = Direction.INCOMING.getVal(), | ||
| 83 | outgoingV = Direction.OUTGOING.getVal(); | ||
| 84 | |||
| 85 | nodes.forEach((node) -> { | ||
| 86 | if(dv >= incomingV) incoming.put(node, Math.max(incoming.get(node) - 1, 0)); | ||
| 87 | if(dv >= outgoingV) outgoing.put(node, Math.max(outgoing.get(node) - 1, 0)); | ||
| 88 | }); | ||
| 89 | } | ||
| 90 | |||
| 91 | @Override | ||
| 92 | public void clearConnections(Direction dir) { | ||
| 93 | if(dir == null) throw new IllegalArgumentException("dir is null"); | ||
| 94 | |||
| 95 | int dv = dir.getVal(); | ||
| 96 | if(dv >= Direction.INCOMING.getVal()) incoming.clear(); | ||
| 97 | if(dv >= Direction.OUTGOING.getVal()) outgoing.clear(); | ||
| 98 | } | ||
| 99 | |||
| 100 | @Override | ||
| 101 | public boolean connectionExists(IChannelNode node, Direction dir) { | ||
| 102 | if(node == null) throw new IllegalArgumentException("node is null"); | ||
| 103 | if(dir == null) throw new IllegalArgumentException("dir is null"); | ||
| 104 | |||
| 105 | switch (dir) { | ||
| 106 | case INCOMING -> {return incoming.containsKey(node);} | ||
| 107 | case OUTGOING -> {return outgoing.containsKey(node);} | ||
| 108 | case BOTH -> {return (incoming.containsKey(node) && outgoing.containsKey(node));} | ||
| 109 | default -> throw new IllegalStateException("got unknown direction"); | ||
| 110 | } | ||
| 111 | } | ||
| 112 | |||
| 113 | @Override | ||
| 114 | public int getNumConnections(Direction dir) { | ||
| 115 | int total = 0; | ||
| 116 | int dv = dir.getVal(); | ||
| 117 | |||
| 118 | if(dv >= Direction.INCOMING.getVal()) for(int i: incoming.values()) total += i; | ||
| 119 | if(dv >= Direction.OUTGOING.getVal()) for(int i: outgoing.values()) total += i; | ||
| 120 | return total; | ||
| 121 | } | ||
| 122 | |||
| 123 | @Override | ||
| 124 | public Map<IChannelNode, Integer> getIncomingConnections() { | ||
| 125 | return incoming; | ||
| 126 | } | ||
| 127 | |||
| 128 | @Override | ||
| 129 | public Map<IChannelNode, Integer> getOutgoingConnections() { | ||
| 130 | return outgoing; | ||
| 131 | } | ||
| 132 | |||
| 133 | @Override | ||
| 134 | public List<Map<IChannelNode, Integer>> getConnections(Direction dir) { | ||
| 135 | if(dir == null) throw new IllegalArgumentException("dir is null"); | ||
| 136 | |||
| 137 | int dv = dir.getVal(); | ||
| 138 | ArrayList<Map<IChannelNode, Integer>> res = new ArrayList<>(); | ||
| 139 | if(dv >= Direction.INCOMING.getVal()) res.add(incoming); | ||
| 140 | if(dv >= Direction.OUTGOING.getVal()) res.add(outgoing); | ||
| 141 | |||
| 142 | return res; | ||
| 143 | } | ||
| 144 | |||
| 145 | @Override | ||
| 146 | public int compareTo(IChannelNode iChannelNode) { | ||
| 147 | if(iChannelNode == null) throw new IllegalArgumentException("comparison node is null"); | ||
| 148 | int selfTotal = getNumConnections(Direction.BOTH), | ||
| 149 | theirTotal = iChannelNode.getNumConnections(Direction.BOTH); | ||
| 150 | |||
| 151 | return selfTotal - theirTotal; | ||
| 152 | } | ||
| 153 | } | ||
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 @@ | |||
| 1 | import java.util.List; | 1 | import java.util.List; |
| 2 | import java.util.Map; | 2 | import java.util.Map; |
| 3 | 3 | ||
| 4 | public interface IChannelNode { | 4 | public interface IChannelNode extends Comparable<IChannelNode> { |
| 5 | enum Direction { | 5 | enum Direction { |
| 6 | INCOMING, | 6 | INCOMING(0), // Users incoming from outside channel (aka: other channel forwarded this channel's post) |
| 7 | OUTGOING, | 7 | OUTGOING(1), // Users outgoing from this channel (aka: this channel forwarded someone else's post) |
| 8 | BOTH | 8 | BOTH(2); // Modify both incoming and outgoing counts at once |
| 9 | |||
| 10 | private final int val; | ||
| 11 | Direction(int val) {this.val = val;} | ||
| 12 | public int getVal() {return this.val;} | ||
| 9 | } | 13 | } |
| 10 | 14 | ||
| 11 | void setConnections(Map<IChannelNode, Integer> conmap, Direction dir); | 15 | void setConnections(Map<IChannelNode, Integer> conmap, Direction dir); |
| 16 | void setNumConnections(IChannelNode node, Direction dir, int num); | ||
| 12 | void addConnection(IChannelNode node, Direction dir); | 17 | void addConnection(IChannelNode node, Direction dir); |
| 13 | void addConnections(Iterable<IChannelNode> nodes, Direction dir); | 18 | void addConnections(Iterable<IChannelNode> nodes, Direction dir); |
| 14 | void removeConnection(IChannelNode node, Direction dir); | 19 | void removeConnection(IChannelNode node, Direction dir); |
| @@ -16,6 +21,7 @@ public interface IChannelNode { | |||
| 16 | void clearConnections(Direction dir); | 21 | void clearConnections(Direction dir); |
| 17 | 22 | ||
| 18 | boolean connectionExists(IChannelNode node, Direction dir); | 23 | boolean connectionExists(IChannelNode node, Direction dir); |
| 24 | int getNumConnections(Direction dir); | ||
| 19 | Map<IChannelNode, Integer> getIncomingConnections(); | 25 | Map<IChannelNode, Integer> getIncomingConnections(); |
| 20 | Map<IChannelNode, Integer> getOutgoingConnections(); | 26 | Map<IChannelNode, Integer> getOutgoingConnections(); |
| 21 | List<Map<IChannelNode, Integer>> getConnections(Direction dir); | 27 | List<Map<IChannelNode, Integer>> 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 { | |||
| 23 | // Null direction should always throw | 23 | // Null direction should always throw |
| 24 | assertThrows(IllegalArgumentException.class, () -> testNodeA.setConnections(new HashMap<>(), null)); | 24 | assertThrows(IllegalArgumentException.class, () -> testNodeA.setConnections(new HashMap<>(), null)); |
| 25 | 25 | ||
| 26 | // Self endpoint should throw | ||
| 27 | Map<IChannelNode, Integer> temp = new HashMap<>(); | ||
| 28 | temp.put(testNodeA, 1); | ||
| 29 | |||
| 30 | assertThrows(IllegalArgumentException.class, () -> testNodeA.setConnections(temp, IChannelNode.Direction.INCOMING)); | ||
| 31 | assertThrows(IllegalArgumentException.class, () -> testNodeA.setConnections(temp, IChannelNode.Direction.OUTGOING)); | ||
| 32 | assertThrows(IllegalArgumentException.class, () -> testNodeA.setConnections(temp, IChannelNode.Direction.BOTH)); | ||
| 33 | |||
| 26 | // Should not throw | 34 | // Should not throw |
| 27 | assertDoesNotThrow(() -> testNodeA.setConnections(new HashMap<>(), IChannelNode.Direction.INCOMING)); | 35 | assertDoesNotThrow(() -> testNodeA.setConnections(new HashMap<>(), IChannelNode.Direction.INCOMING)); |
| 28 | assertDoesNotThrow(() -> testNodeA.setConnections(new HashMap<>(), IChannelNode.Direction.OUTGOING)); | 36 | assertDoesNotThrow(() -> testNodeA.setConnections(new HashMap<>(), IChannelNode.Direction.OUTGOING)); |
| @@ -30,6 +38,36 @@ class ChannelNodeTest { | |||
| 30 | 38 | ||
| 31 | 39 | ||
| 32 | 40 | ||
| 41 | // Null endpoint should throw | ||
| 42 | assertThrows(IllegalArgumentException.class, () -> testNodeA.setNumConnections(null, null, -1)); | ||
| 43 | assertThrows(IllegalArgumentException.class, () -> testNodeA.setNumConnections(null, IChannelNode.Direction.INCOMING, 1)); | ||
| 44 | assertThrows(IllegalArgumentException.class, () -> testNodeA.setNumConnections(null, IChannelNode.Direction.OUTGOING, 1)); | ||
| 45 | assertThrows(IllegalArgumentException.class, () -> testNodeA.setNumConnections(null, IChannelNode.Direction.BOTH, 1)); | ||
| 46 | |||
| 47 | // Null direction should throw | ||
| 48 | assertThrows(IllegalArgumentException.class, () -> testNodeA.setNumConnections(testNodeB, null, 1)); | ||
| 49 | |||
| 50 | // Self endpoint should throw | ||
| 51 | assertThrows(IllegalArgumentException.class, () -> testNodeA.setNumConnections(testNodeA, IChannelNode.Direction.INCOMING, 1)); | ||
| 52 | assertThrows(IllegalArgumentException.class, () -> testNodeA.setNumConnections(testNodeA, IChannelNode.Direction.OUTGOING, 1)); | ||
| 53 | assertThrows(IllegalArgumentException.class, () -> testNodeA.setNumConnections(testNodeA, IChannelNode.Direction.BOTH, 1)); | ||
| 54 | |||
| 55 | // Negative connections should throw | ||
| 56 | assertThrows(IllegalArgumentException.class, () -> testNodeA.setNumConnections(testNodeB, IChannelNode.Direction.INCOMING, -1)); | ||
| 57 | assertThrows(IllegalArgumentException.class, () -> testNodeA.setNumConnections(testNodeB, IChannelNode.Direction.OUTGOING, -1)); | ||
| 58 | assertThrows(IllegalArgumentException.class, () -> testNodeA.setNumConnections(testNodeB, IChannelNode.Direction.BOTH, -1)); | ||
| 59 | |||
| 60 | // Should not throw | ||
| 61 | assertDoesNotThrow(() -> testNodeA.setNumConnections(testNodeB, IChannelNode.Direction.INCOMING, 10)); | ||
| 62 | assertDoesNotThrow(() -> testNodeA.setNumConnections(testNodeB, IChannelNode.Direction.OUTGOING, 10)); | ||
| 63 | assertDoesNotThrow(() -> testNodeA.setNumConnections(testNodeB, IChannelNode.Direction.BOTH, 3)); | ||
| 64 | |||
| 65 | assertDoesNotThrow(() -> testNodeB.setNumConnections(testNodeA, IChannelNode.Direction.INCOMING, 3)); | ||
| 66 | assertDoesNotThrow(() -> testNodeB.setNumConnections(testNodeA, IChannelNode.Direction.OUTGOING, 5)); | ||
| 67 | assertDoesNotThrow(() -> testNodeB.setNumConnections(testNodeA, IChannelNode.Direction.BOTH, 90)); | ||
| 68 | |||
| 69 | |||
| 70 | |||
| 33 | // Null endpoint should always throw | 71 | // Null endpoint should always throw |
| 34 | assertThrows(IllegalArgumentException.class, () -> testNodeA.addConnection(null, null)); | 72 | assertThrows(IllegalArgumentException.class, () -> testNodeA.addConnection(null, null)); |
| 35 | assertThrows(IllegalArgumentException.class, () -> testNodeA.addConnection(null, IChannelNode.Direction.INCOMING)); | 73 | assertThrows(IllegalArgumentException.class, () -> testNodeA.addConnection(null, IChannelNode.Direction.INCOMING)); |
| @@ -45,6 +83,11 @@ class ChannelNodeTest { | |||
| 45 | assertThrows(IllegalArgumentException.class, () -> testNodeA.addConnection(testNodeB, null)); | 83 | assertThrows(IllegalArgumentException.class, () -> testNodeA.addConnection(testNodeB, null)); |
| 46 | assertThrows(IllegalArgumentException.class, () -> testNodeB.addConnection(testNodeA, null)); | 84 | assertThrows(IllegalArgumentException.class, () -> testNodeB.addConnection(testNodeA, null)); |
| 47 | 85 | ||
| 86 | // Should not throw | ||
| 87 | assertDoesNotThrow(() -> testNodeA.addConnection(testNodeB, IChannelNode.Direction.INCOMING)); | ||
| 88 | assertDoesNotThrow(() -> testNodeA.addConnection(testNodeB, IChannelNode.Direction.OUTGOING)); | ||
| 89 | assertDoesNotThrow(() -> testNodeA.addConnection(testNodeB, IChannelNode.Direction.BOTH)); | ||
| 90 | |||
| 48 | 91 | ||
| 49 | 92 | ||
| 50 | // Null iterable should always throw | 93 | // Null iterable should always throw |
| @@ -62,6 +105,10 @@ class ChannelNodeTest { | |||
| 62 | assertThrows(IllegalArgumentException.class, () -> testNodeA.addConnections(List.of(new IChannelNode[]{testNodeA}), IChannelNode.Direction.OUTGOING)); | 105 | assertThrows(IllegalArgumentException.class, () -> testNodeA.addConnections(List.of(new IChannelNode[]{testNodeA}), IChannelNode.Direction.OUTGOING)); |
| 63 | assertThrows(IllegalArgumentException.class, () -> testNodeA.addConnections(List.of(new IChannelNode[]{testNodeA}), IChannelNode.Direction.BOTH)); | 106 | assertThrows(IllegalArgumentException.class, () -> testNodeA.addConnections(List.of(new IChannelNode[]{testNodeA}), IChannelNode.Direction.BOTH)); |
| 64 | 107 | ||
| 108 | // Should not throw | ||
| 109 | assertDoesNotThrow(() -> testNodeA.addConnections(List.of(new IChannelNode[]{testNodeB}), IChannelNode.Direction.INCOMING)); | ||
| 110 | assertDoesNotThrow(() -> testNodeA.addConnections(List.of(new IChannelNode[]{testNodeB}), IChannelNode.Direction.OUTGOING)); | ||
| 111 | assertDoesNotThrow(() -> testNodeA.addConnections(List.of(new IChannelNode[]{testNodeB}), IChannelNode.Direction.BOTH)); | ||
| 65 | 112 | ||
| 66 | 113 | ||
| 67 | // Null node should always throw | 114 | // Null node should always throw |
| @@ -114,7 +161,18 @@ class ChannelNodeTest { | |||
| 114 | 161 | ||
| 115 | 162 | ||
| 116 | // Null direction should always throw | 163 | // Null direction should always throw |
| 164 | assertThrows(IllegalArgumentException.class, () -> testNodeA.getNumConnections(null)); | ||
| 165 | assertDoesNotThrow(() -> testNodeA.getNumConnections(IChannelNode.Direction.INCOMING)); | ||
| 166 | assertDoesNotThrow(() -> testNodeA.getNumConnections(IChannelNode.Direction.OUTGOING)); | ||
| 167 | assertDoesNotThrow(() -> testNodeA.getNumConnections(IChannelNode.Direction.BOTH)); | ||
| 168 | |||
| 169 | |||
| 170 | |||
| 171 | // Null direction should always throw | ||
| 117 | assertThrows(IllegalArgumentException.class, () -> testNodeA.getConnections(null)); | 172 | assertThrows(IllegalArgumentException.class, () -> testNodeA.getConnections(null)); |
| 173 | assertDoesNotThrow(() -> testNodeA.getConnections(IChannelNode.Direction.INCOMING)); | ||
| 174 | assertDoesNotThrow(() -> testNodeA.getConnections(IChannelNode.Direction.OUTGOING)); | ||
| 175 | assertDoesNotThrow(() -> testNodeA.getConnections(IChannelNode.Direction.BOTH)); | ||
| 118 | } | 176 | } |
| 119 | 177 | ||
| 120 | } // End of ChannelNodeTest \ No newline at end of file | 178 | } // End of ChannelNodeTest \ No newline at end of file |
