Browse Source

Split queue comparators into a class and test.

This allows the logic to be expressed more nicely and tested
independently of the handlers. It also paves the way for a
bit of refactoring to reduce the number of random things
that need to be comparable...
pull/122/head
Chris Smith 8 years ago
parent
commit
bf098e5aa8

+ 10
- 2
irc/src/com/dmdirc/parser/irc/outputqueue/PriorityQueueHandler.java View File

@@ -23,6 +23,7 @@
23 23
 package com.dmdirc.parser.irc.outputqueue;
24 24
 
25 25
 import java.io.PrintWriter;
26
+import java.time.Duration;
26 27
 import java.util.concurrent.BlockingQueue;
27 28
 
28 29
 /**
@@ -37,8 +38,14 @@ public class PriorityQueueHandler extends QueueHandler {
37 38
      * @param queue Queue to use
38 39
      * @param out Output Stream to use
39 40
      */
40
-    public PriorityQueueHandler(final OutputQueue outputQueue, final BlockingQueue<QueueItem> queue, final PrintWriter out) {
41
-        super(outputQueue, queue, out);
41
+    public PriorityQueueHandler(
42
+            final OutputQueue outputQueue,
43
+            final BlockingQueue<QueueItem> queue,
44
+            final PrintWriter out) {
45
+        super(outputQueue,
46
+                queue,
47
+                QueueComparators.byPriorityThenNumber(Duration.ofSeconds(10)),
48
+                out);
42 49
     }
43 50
 
44 51
     /**
@@ -62,4 +69,5 @@ public class PriorityQueueHandler extends QueueHandler {
62 69
             // Do nothing
63 70
         }
64 71
     }
72
+
65 73
 }

+ 110
- 0
irc/src/com/dmdirc/parser/irc/outputqueue/QueueComparators.java View File

@@ -0,0 +1,110 @@
1
+/*
2
+ * Copyright (c) 2006-2016 DMDirc Developers
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.parser.irc.outputqueue;
24
+
25
+import com.dmdirc.parser.common.QueuePriority;
26
+
27
+import java.time.Clock;
28
+import java.time.LocalDateTime;
29
+import java.time.temporal.TemporalAmount;
30
+import java.util.Comparator;
31
+
32
+/**
33
+ * Provides comparators for comparing {@link QueueItem}s.
34
+ */
35
+public final class QueueComparators {
36
+
37
+    private QueueComparators() {
38
+        // Shouldn't be used
39
+    }
40
+
41
+    /**
42
+     * Provides a comparator that checks priority and item number.
43
+     *
44
+     * @return a comparator that compares items by their priority, and then their item number.
45
+     */
46
+    public static Comparator<QueueItem> byPriorityThenNumber() {
47
+        return Comparator.comparing(QueueItem::getPriority)
48
+                .thenComparing(QueueItem::getItemNumber);
49
+    }
50
+
51
+    /**
52
+     * Provides a comparator that checks priority and item number, with special handling for
53
+     * starved items.
54
+     *
55
+     * <p>If an item has been queued for longer than {@code starvationThreshold}, its priority
56
+     * will be effectively ignored (i.e., it will just be compared on item number). This ensures
57
+     * that lower priority items are not kept in the queue indefinitely if there is a constant
58
+     * stream of higher priority items.
59
+     *
60
+     * @param starvationThreshold The time an item must be queued for to be considered starved.
61
+     * @return a comparator that compares items by their priority unless they are starved, and then
62
+     * their item number.
63
+     */
64
+    public static Comparator<QueueItem> byPriorityThenNumber(
65
+            final TemporalAmount starvationThreshold) {
66
+        return byPriorityThenNumber(Clock.systemDefaultZone(), starvationThreshold);
67
+    }
68
+
69
+    /**
70
+     * Provides a comparator that checks priority and item number, with special handling for
71
+     * starved items.
72
+     *
73
+     * <p>If an item has been queued for longer than {@code starvationThreshold}, its priority
74
+     * will be effectively ignored (i.e., it will just be compared on item number). This ensures
75
+     * that lower priority items are not kept in the queue indefinitely if there is a constant
76
+     * stream of higher priority items.
77
+     *
78
+     * @param clock The clock to use to get the current time.
79
+     * @param starvationThreshold The time an item must be queued for to be considered starved.
80
+     * @return a comparator that compares items by their priority unless they are starved, and then
81
+     * their item number.
82
+     */
83
+    public static Comparator<QueueItem> byPriorityThenNumber(
84
+            final Clock clock,
85
+            final TemporalAmount starvationThreshold) {
86
+        return Comparator.<QueueItem, QueuePriority>comparing(
87
+                item -> getPriorityIfNotStarved(item, clock, starvationThreshold))
88
+                .thenComparing(QueueItem::getItemNumber);
89
+    }
90
+
91
+    /**
92
+     * Gets the priority of the item if it was created within the given timespan, or
93
+     * {@link QueuePriority#IMMEDIATE} if the item is older. This allows items that are otherwise
94
+     * being starved to be sorted to the front of a queue.
95
+     *
96
+     * @param item The item to get the priority of.
97
+     * @param clock The clock to use to get the current time.
98
+     * @param timespan The timespan after which an item will be considered starved.
99
+     * @return The priority of the item, or {@link QueuePriority#IMMEDIATE}.
100
+     */
101
+    private static QueuePriority getPriorityIfNotStarved(
102
+            final QueueItem item, final Clock clock, final TemporalAmount timespan) {
103
+        if (item.getTime().isAfter(LocalDateTime.now(clock).minus(timespan))) {
104
+            return item.getPriority();
105
+        } else {
106
+            return QueuePriority.IMMEDIATE;
107
+        }
108
+    }
109
+
110
+}

+ 11
- 33
irc/src/com/dmdirc/parser/irc/outputqueue/QueueHandler.java View File

@@ -39,6 +39,8 @@ public abstract class QueueHandler extends Thread implements Comparator<QueueIte
39 39
     protected final BlockingQueue<QueueItem> queue;
40 40
     /** The output queue that owns us. */
41 41
     protected OutputQueue outputQueue;
42
+    /** Comparator to use to sort queue items. */
43
+    private final Comparator<QueueItem> comparator;
42 44
     /** Where to send the output. */
43 45
     private final PrintWriter out;
44 46
 
@@ -46,13 +48,19 @@ public abstract class QueueHandler extends Thread implements Comparator<QueueIte
46 48
      * Create a new Queue Thread.
47 49
      *
48 50
      * @param outputQueue the OutputQueue that owns us.
49
-     * @param queue Queue to handle
51
+     * @param queue Queue to handle.
52
+     * @param comparator Comparator to use to sort items in the queue.
50 53
      * @param out Writer to send to.
51 54
      */
52
-    public QueueHandler(final OutputQueue outputQueue, final BlockingQueue<QueueItem> queue, final PrintWriter out) {
55
+    public QueueHandler(
56
+            final OutputQueue outputQueue,
57
+            final BlockingQueue<QueueItem> queue,
58
+            final Comparator<QueueItem> comparator,
59
+            final PrintWriter out) {
53 60
         super("IRC Parser queue handler");
54 61
 
55 62
         this.queue = queue;
63
+        this.comparator = comparator;
56 64
         this.out = out;
57 65
         this.outputQueue = outputQueue;
58 66
     }
@@ -81,39 +89,9 @@ public abstract class QueueHandler extends Thread implements Comparator<QueueIte
81 89
         return new QueueItem(this, line, priority);
82 90
     }
83 91
 
84
-    /**
85
-     * Compare two QueueItems for sorting purposes.
86
-     * This is called by the default QueueItem in its compareTo method. The
87
-     * calling object will be the first parameter, the object to compare it to
88
-     * will be second.
89
-     * This allows QueueHandlers to sort items differently if needed.
90
-     *
91
-     * The default implementation works as follows:
92
-     * Compare based on priorty firstly, if the priorities are the same,
93
-     * compare based on the order the items were added to the queue.
94
-     *
95
-     * If an item has been in the queue longer than 10 seconds, it will not
96
-     * check its priority and soley position itself based on adding order.
97
-     *
98
-     * @param mainObject Main object we are comparing against.
99
-     * @param otherObject Object we are comparing to.
100
-     * @return A QueueItem for teh given parameters
101
-     */
102 92
     @Override
103 93
     public int compare(final QueueItem mainObject, final QueueItem otherObject) {
104
-        if (!isStarved(mainObject)
105
-                && mainObject.getPriority().compareTo(otherObject.getPriority()) != 0) {
106
-            return mainObject.getPriority().compareTo(otherObject.getPriority());
107
-        }
108
-
109
-        if (mainObject.getItemNumber() > otherObject.getItemNumber()) {
110
-            return 1;
111
-        } else if (mainObject.getItemNumber() < otherObject.getItemNumber()) {
112
-            return -1;
113
-        } else {
114
-            // This can't happen.
115
-            return 0;
116
-        }
94
+        return comparator.compare(mainObject, otherObject);
117 95
     }
118 96
 
119 97
     /**

+ 14
- 1
irc/src/com/dmdirc/parser/irc/outputqueue/QueueItem.java View File

@@ -24,6 +24,7 @@ package com.dmdirc.parser.irc.outputqueue;
24 24
 
25 25
 import com.dmdirc.parser.common.QueuePriority;
26 26
 
27
+import java.time.Clock;
27 28
 import java.time.LocalDateTime;
28 29
 
29 30
 /**
@@ -52,11 +53,23 @@ public class QueueItem implements Comparable<QueueItem> {
52 53
      * @param priority Priority for the queue item
53 54
      */
54 55
     public QueueItem(final QueueHandler handler, final String line, final QueuePriority priority) {
56
+        this(handler, Clock.systemDefaultZone(), line, priority);
57
+    }
58
+
59
+    /**
60
+     * Create a new QueueItem.
61
+     *
62
+     * @param handler Handler for this QueueItem
63
+     * @param line Line to send
64
+     * @param priority Priority for the queue item
65
+     */
66
+    public QueueItem(final QueueHandler handler, final Clock clock, final String line,
67
+            final QueuePriority priority) {
55 68
         this.handler = handler;
56 69
         this.line = line;
57 70
         this.priority = priority;
58 71
 
59
-        this.time = LocalDateTime.now();
72
+        this.time = LocalDateTime.now(clock);
60 73
         this.itemNumber = number++;
61 74
     }
62 75
 

+ 6
- 18
irc/src/com/dmdirc/parser/irc/outputqueue/SimpleRateLimitedQueueHandler.java View File

@@ -57,8 +57,11 @@ public class SimpleRateLimitedQueueHandler extends QueueHandler {
57 57
      * @param queue Queue to use
58 58
      * @param out Output Stream to use
59 59
      */
60
-    public SimpleRateLimitedQueueHandler(final OutputQueue outputQueue, final BlockingQueue<QueueItem> queue, final PrintWriter out) {
61
-        super(outputQueue, queue, out);
60
+    public SimpleRateLimitedQueueHandler(
61
+            final OutputQueue outputQueue,
62
+            final BlockingQueue<QueueItem> queue,
63
+            final PrintWriter out) {
64
+        super(outputQueue, queue, QueueComparators.byPriorityThenNumber(), out);
62 65
     }
63 66
 
64 67
     /**
@@ -163,21 +166,6 @@ public class SimpleRateLimitedQueueHandler extends QueueHandler {
163 166
         return isLimiting;
164 167
     }
165 168
 
166
-    /**
167
-     * Compare queue items, if priorities differ, then  higher priority items
168
-     * will always be put further ahead in the queue (This queue ignores the
169
-     * 10-second rule of the normal queue) otherwise the normal comparison is
170
-     * used.
171
-     */
172
-    @Override
173
-    public int compare(final QueueItem mainObject, final QueueItem otherObject) {
174
-        if (mainObject.getPriority().compareTo(otherObject.getPriority()) == 0) {
175
-            return super.compare(mainObject, otherObject);
176
-        } else {
177
-            return mainObject.getPriority().compareTo(otherObject.getPriority());
178
-        }
179
-    }
180
-
181 169
     @Override
182 170
     public QueueItem getQueueItem(final String line, final QueuePriority priority) {
183 171
         // Was the last line added less than limitTime ago?
@@ -186,7 +174,7 @@ public class SimpleRateLimitedQueueHandler extends QueueHandler {
186 174
             if (overTime) {
187 175
                 // If we are not currently limiting, and this is the items-th item
188 176
                 // added in the last limitTime, start limiting.
189
-                if (!isLimiting && ++count > (items - 1)) {
177
+                if (!isLimiting && ++count >= items) {
190 178
                     isLimiting = true;
191 179
                     count = 0;
192 180
                 }

+ 101
- 0
irc/test/com/dmdirc/parser/irc/outputqueue/QueueComparatorsTest.java View File

@@ -0,0 +1,101 @@
1
+/*
2
+ * Copyright (c) 2006-2016 DMDirc Developers
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.parser.irc.outputqueue;
24
+
25
+import com.dmdirc.parser.common.QueuePriority;
26
+
27
+import java.time.Clock;
28
+import java.time.Duration;
29
+import java.time.Instant;
30
+import java.time.ZoneId;
31
+
32
+import org.junit.Test;
33
+
34
+import static org.junit.Assert.assertEquals;
35
+import static org.junit.Assert.assertTrue;
36
+
37
+public class QueueComparatorsTest {
38
+
39
+    private static final Clock NOW = Clock.fixed(Instant.ofEpochMilli(100 * 1000),
40
+            ZoneId.systemDefault());
41
+
42
+    private static final Clock NOW_MINUS_FIVE = Clock.fixed(Instant.ofEpochMilli(95 * 1000),
43
+            ZoneId.systemDefault());
44
+
45
+    private static final Clock NOW_MINUS_TEN = Clock.fixed(Instant.ofEpochMilli(90 * 1000),
46
+            ZoneId.systemDefault());
47
+
48
+    private final QueueItem currentLowPriority1 = new QueueItem(null, NOW_MINUS_FIVE,
49
+            "", QueuePriority.LOW);
50
+
51
+    private final QueueItem currentLowPriority2 = new QueueItem(null, NOW_MINUS_FIVE,
52
+            "", QueuePriority.LOW);
53
+
54
+    private final QueueItem currentNormalPriority = new QueueItem(null, NOW_MINUS_FIVE,
55
+            "", QueuePriority.NORMAL);
56
+
57
+    private final QueueItem currentHighPriority = new QueueItem(null, NOW_MINUS_FIVE,
58
+            "", QueuePriority.HIGH);
59
+
60
+    private final QueueItem oldLowPriority = new QueueItem(null, NOW_MINUS_TEN,
61
+            "", QueuePriority.LOW);
62
+
63
+    @Test
64
+    public void testComparesByPriority() {
65
+        assertEquals(0, QueueComparators.byPriorityThenNumber().compare(
66
+                currentLowPriority1,
67
+                currentLowPriority1));
68
+        assertTrue(QueueComparators.byPriorityThenNumber().compare(
69
+                currentNormalPriority,
70
+                currentLowPriority2) < 0);
71
+        assertTrue(QueueComparators.byPriorityThenNumber().compare(
72
+                currentHighPriority,
73
+                oldLowPriority) < 0);
74
+        assertTrue(QueueComparators.byPriorityThenNumber().compare(
75
+                currentNormalPriority,
76
+                currentHighPriority) > 0);
77
+    }
78
+
79
+    @Test
80
+    public void testComparesByNumber() {
81
+        // If the priority is the same, then the lowest number comes first.
82
+        assertTrue(QueueComparators.byPriorityThenNumber().compare(
83
+                currentLowPriority1,
84
+                currentLowPriority2) < 0);
85
+        assertTrue(QueueComparators.byPriorityThenNumber().compare(
86
+                currentLowPriority2,
87
+                currentLowPriority1) > 0);
88
+    }
89
+
90
+    @Test
91
+    public void testStarvedItemsFirst() {
92
+        // If an item is starved then it gets queued first
93
+        assertTrue(QueueComparators.byPriorityThenNumber(NOW, Duration.ofSeconds(7)).compare(
94
+                oldLowPriority,
95
+                currentHighPriority) < 0);
96
+        assertTrue(QueueComparators.byPriorityThenNumber(NOW, Duration.ofSeconds(7)).compare(
97
+                currentLowPriority1,
98
+                oldLowPriority) > 0);
99
+    }
100
+
101
+}

Loading…
Cancel
Save