package unit; import java.util.*; /** * artemis-0001: BindingsImpl routeFromCluster O(R×A) → O(R+A) * * Simulates the routing logic in: * artemis-server/.../core/postoffice/impl/BindingsImpl.java * Method: routeFromCluster (lines 630–664) * * Standalone — no JUnit, no Artemis deps. */ public class BindingsRouteFromClusterAlgorithm { // ----------------------------------------------------------------------- // Result types // ----------------------------------------------------------------------- static class RouteResult { final long ops; final List routedWithAck; final List routedWithoutAck; RouteResult(long ops, List routedWithAck, List routedWithoutAck) { this.ops = ops; this.routedWithAck = routedWithAck; this.routedWithoutAck = routedWithoutAck; } } // ----------------------------------------------------------------------- // Slow: ArrayList.contains inside loop (production code) // ----------------------------------------------------------------------- static RouteResult routeFromClusterSlow(long[] routeIds, long[] ackIds) { long ops = 0; // Build idsToAckList as ArrayList (defect) List idsToAckList = new ArrayList<>(); for (long id : ackIds) { idsToAckList.add(id); } List routedWithAck = new ArrayList<>(); List routedWithoutAck = new ArrayList<>(); for (long bindingID : routeIds) { // O(R) loop // List.contains: O(A) scan boolean needsAck = false; for (Long ackId : idsToAckList) { ops++; if (ackId.equals(bindingID)) { needsAck = true; break; } } if (needsAck) { routedWithAck.add(bindingID); } else { routedWithoutAck.add(bindingID); } } return new RouteResult(ops, routedWithAck, routedWithoutAck); } // ----------------------------------------------------------------------- // Fast: HashSet for O(1) lookup // ----------------------------------------------------------------------- static RouteResult routeFromClusterFast(long[] routeIds, long[] ackIds) { long ops = 0; // Build idsToAckSet as HashSet (fix) Set idsToAckSet = new HashSet<>(); for (long id : ackIds) { ops++; idsToAckSet.add(id); } List routedWithAck = new ArrayList<>(); List routedWithoutAck = new ArrayList<>(); for (long bindingID : routeIds) { // O(R) loop ops++; if (idsToAckSet.contains(bindingID)) { // O(1) routedWithAck.add(bindingID); } else { routedWithoutAck.add(bindingID); } } return new RouteResult(ops, routedWithAck, routedWithoutAck); } // ----------------------------------------------------------------------- // Test harness // ----------------------------------------------------------------------- public static void main(String[] args) { int passed = 0; int total = 0; // ---- Test 1: basic routing correctness ---- total++; long[] routes = {1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L}; long[] acks = {2L, 4L, 6L, 8L, 10L}; RouteResult slow1 = routeFromClusterSlow(routes, acks); RouteResult fast1 = routeFromClusterFast(routes, acks); boolean match1 = new HashSet<>(slow1.routedWithAck).equals(new HashSet<>(fast1.routedWithAck)) && new HashSet<>(slow1.routedWithoutAck).equals(new HashSet<>(fast1.routedWithoutAck)); if (match1) { System.out.println("PASS test1: basic routing — ack=" + slow1.routedWithAck.size() + " noack=" + slow1.routedWithoutAck.size()); passed++; } else { System.out.println("FAIL test1: routing mismatch"); } // ---- Test 2: no ACKs required ---- total++; long[] noAcks = {}; RouteResult slow2 = routeFromClusterSlow(routes, noAcks); RouteResult fast2 = routeFromClusterFast(routes, noAcks); boolean match2 = slow2.routedWithAck.isEmpty() && fast2.routedWithAck.isEmpty() && slow2.routedWithoutAck.size() == routes.length && fast2.routedWithoutAck.size() == routes.length; if (match2) { System.out.println("PASS test2: no-ACK case — all routes without ack"); passed++; } else { System.out.println("FAIL test2: no-ACK case mismatch"); } // ---- Test 3: all ACKs required ---- total++; RouteResult slow3 = routeFromClusterSlow(routes, routes); RouteResult fast3 = routeFromClusterFast(routes, routes); boolean match3 = slow3.routedWithAck.size() == routes.length && fast3.routedWithAck.size() == routes.length && slow3.routedWithoutAck.isEmpty() && fast3.routedWithoutAck.isEmpty(); if (match3) { System.out.println("PASS test3: all-ACK case — all routes with ack"); passed++; } else { System.out.println("FAIL test3: all-ACK case mismatch"); } // ---- Test 4: large scale ops ratio ---- total++; int R = 100; // route targets int A = 50; // ACK targets long[] bigRoutes = new long[R]; for (int i = 0; i < R; i++) bigRoutes[i] = i; long[] bigAcks = new long[A]; for (int i = 0; i < A; i++) bigAcks[i] = i * 2; // every other route needs ACK RouteResult slowBig = routeFromClusterSlow(bigRoutes, bigAcks); RouteResult fastBig = routeFromClusterFast(bigRoutes, bigAcks); boolean matchBig = new HashSet<>(slowBig.routedWithAck).equals(new HashSet<>(fastBig.routedWithAck)); if (!matchBig) { System.out.println("FAIL test4: large scale result mismatch"); } else { double ratio = (double) slowBig.ops / fastBig.ops; if (ratio >= 10.0) { System.out.printf("PASS test4: large scale slow=%d ops, fast=%d ops, ratio=%.1fx%n", slowBig.ops, fastBig.ops, ratio); passed++; } else { System.out.printf("FAIL test4: ratio=%.1fx (need >=10x) slow=%d fast=%d%n", ratio, slowBig.ops, fastBig.ops); } } // ---- Test 5: route IDs not in ACK list ---- total++; long[] disjointAcks = {200L, 201L, 202L}; // none match routes 0..9 RouteResult slow5 = routeFromClusterSlow(routes, disjointAcks); RouteResult fast5 = routeFromClusterFast(routes, disjointAcks); boolean match5 = slow5.routedWithAck.isEmpty() && fast5.routedWithAck.isEmpty(); if (match5) { System.out.println("PASS test5: disjoint ACK IDs — no routes with ack"); passed++; } else { System.out.println("FAIL test5: disjoint ACK IDs mismatch"); } System.out.println("\n" + passed + "/" + total + " PASS"); if (passed != total) { System.exit(1); } } }