From 4045e8eb8ca8c8dc534b7f0fd69953a839ca801a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 6 May 2014 20:29:05 -0400 Subject: core: Add a Priority class for loosely coupled, infinitely divisible weights. --- .../java/com/tavianator/sangria/core/Priority.java | 149 +++++++++++++++++++++ .../com/tavianator/sangria/core/PriorityTest.java | 100 ++++++++++++++ 2 files changed, 249 insertions(+) create mode 100644 sangria-core/src/main/java/com/tavianator/sangria/core/Priority.java create mode 100644 sangria-core/src/test/java/com/tavianator/sangria/core/PriorityTest.java (limited to 'sangria-core') diff --git a/sangria-core/src/main/java/com/tavianator/sangria/core/Priority.java b/sangria-core/src/main/java/com/tavianator/sangria/core/Priority.java new file mode 100644 index 0000000..6e6a2ea --- /dev/null +++ b/sangria-core/src/main/java/com/tavianator/sangria/core/Priority.java @@ -0,0 +1,149 @@ +/**************************************************************************** + * Sangria * + * Copyright (C) 2014 Tavian Barnes * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ****************************************************************************/ + +package com.tavianator.sangria.core; + +import java.util.*; + +import com.google.common.collect.ComparisonChain; +import com.google.common.primitives.Ints; + +/** + * A loosely-coupled, infinitely divisible priority/weight system. + * + *

+ * This class implements an extensible priority system based on lexicographical ordering. In its simplest use, {@code + * Priority.create(0)} is ordered before {@code Priority.create(1)}, then {@code Priority.create(2)}, etc. + *

+ * + *

+ * To create a priority that is ordered between two existing ones, simply add another parameter: {@code + * Priority.create(1, 1)} comes after {@code Priority.create(1)}, but before {@code Priority.create(2)}. In this way, + * priorities can always be inserted anywhere in a sequence. + *

+ * + *

+ * The {@link #next()} method creates a priority that is ordered immediately following the current one, and is distinct + * from all priorities obtained by any other means. This provides a convenient way to order entire segments of lists. + *

+ * + *

+ * A special priority, obtained by {@code Priority.getDefault()}, sorts before all other priorities. + *

+ * + * @author Tavian Barnes (tavianator@tavianator.com) + * @version 1.1 + * @since 1.1 + */ +public class Priority implements Comparable { + private static final Priority DEFAULT = new Priority(new int[0], 0); + private static final Comparator COMPARATOR = Ints.lexicographicalComparator(); + + private final int[] weights; + private final int seq; + + /** + * @return The default priority, which comes before all other priorities. + */ + public static Priority getDefault() { + return DEFAULT; + } + + /** + * Create a {@link Priority} with the given sequence. + * + * @param weight The first value of the weight sequence. + * @param weights An integer sequence. These sequences are sorted lexicographically, so {@code Priority.create(1)} + * sorts before {@code Priority.create(1, 1)}, which sorts before {@code Priority.create(2)}. + * @return A new {@link Priority}. + */ + public static Priority create(int weight, int... weights) { + int[] newWeights = new int[weights.length + 1]; + newWeights[0] = weight; + System.arraycopy(weights, 0, newWeights, 1, weights.length); + return new Priority(newWeights, 0); + } + + private Priority(int[] weights, int seq) { + this.weights = weights; + this.seq = seq; + } + + /** + * @return Whether this priority originated in a call to {@link #getDefault()}. + */ + public boolean isDefault() { + return weights.length == 0; + } + + /** + * @return A new {@link Priority} which immediately follows this one, and which is distinct from all other + * priorities obtained by {@link #create(int, int...)}. + */ + public Priority next() { + return new Priority(weights, seq + 1); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } else if (!(obj instanceof Priority)) { + return false; + } + + Priority other = (Priority)obj; + return Arrays.equals(weights, other.weights) + && seq == other.seq; + } + + @Override + public int hashCode() { + return Arrays.hashCode(weights) + seq; + } + + @Override + public int compareTo(Priority o) { + return ComparisonChain.start() + .compare(weights, o.weights, COMPARATOR) + .compare(seq, o.seq) + .result(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("Priority."); + if (weights.length == 0) { + builder.append("getDefault()"); + } else { + builder.append("create("); + for (int i = 0; i < weights.length; ++i) { + if (i != 0) { + builder.append(", "); + } + builder.append(weights[i]); + } + builder.append(")"); + } + if (seq != 0) { + builder.append(".next(") + .append(seq) + .append(")"); + } + return builder.toString(); + } +} diff --git a/sangria-core/src/test/java/com/tavianator/sangria/core/PriorityTest.java b/sangria-core/src/test/java/com/tavianator/sangria/core/PriorityTest.java new file mode 100644 index 0000000..7d90f57 --- /dev/null +++ b/sangria-core/src/test/java/com/tavianator/sangria/core/PriorityTest.java @@ -0,0 +1,100 @@ +/**************************************************************************** + * Sangria * + * Copyright (C) 2014 Tavian Barnes * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ****************************************************************************/ + +package com.tavianator.sangria.core; + +import java.util.*; + +import org.junit.Test; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +/** + * Tests for {@link Priority}s. + * + * @author Tavian Barnes (tavianator@tavianator.com) + * @version 1.1 + * @since 1.1 + */ +public class PriorityTest { + private final Priority defaultPriority = Priority.getDefault(); + private final Priority one = Priority.create(1); + private final Priority oneTwo = Priority.create(1, 2); + private final Priority two = Priority.create(2); + + @Test + public void testOrdering() { + List list = Arrays.asList( + defaultPriority.next(), + two, + oneTwo.next(), + oneTwo, + two.next(), + defaultPriority, + defaultPriority.next().next(), + one, + one.next()); + Collections.sort(list); + assertThat(list, contains( + defaultPriority, + defaultPriority.next(), + defaultPriority.next().next(), + one, + one.next(), + oneTwo, + oneTwo.next(), + two, + two.next())); + + assertThat(defaultPriority, equalTo(Priority.getDefault())); + assertThat(defaultPriority.next(), equalTo(Priority.getDefault().next())); + assertThat(defaultPriority, not(equalTo(Priority.getDefault().next()))); + + assertThat(one, equalTo(Priority.create(1))); + assertThat(oneTwo, equalTo(Priority.create(1, 2))); + assertThat(two, equalTo(Priority.create(2))); + + assertThat(oneTwo.hashCode(), equalTo(Priority.create(1, 2).hashCode())); + } + + @Test + public void testIsDefault() { + assertThat(defaultPriority.isDefault(), is(true)); + assertThat(defaultPriority.next().isDefault(), is(true)); + + assertThat(one.isDefault(), is(false)); + assertThat(oneTwo.isDefault(), is(false)); + assertThat(two.isDefault(), is(false)); + assertThat(two.next().isDefault(), is(false)); + } + + @Test + public void testToString() { + assertThat(Priority.getDefault().toString(), equalTo("Priority.getDefault()")); + assertThat(Priority.getDefault().next().toString(), equalTo("Priority.getDefault().next(1)")); + assertThat(Priority.getDefault().next().next().toString(), equalTo("Priority.getDefault().next(2)")); + + assertThat(Priority.create(1).toString(), equalTo("Priority.create(1)")); + assertThat(Priority.create(1).next().toString(), equalTo("Priority.create(1).next(1)")); + assertThat(Priority.create(1).next().next().toString(), equalTo("Priority.create(1).next(2)")); + + assertThat(Priority.create(1, 2).toString(), equalTo("Priority.create(1, 2)")); + assertThat(Priority.create(1, 2).next().toString(), equalTo("Priority.create(1, 2).next(1)")); + assertThat(Priority.create(1, 2).next().next().toString(), equalTo("Priority.create(1, 2).next(2)")); + } +} -- cgit v1.2.3