diff --git a/concourse-driver-java/src/main/java/com/cinchapi/concourse/lang/sort/BuildableOrderState.java b/concourse-driver-java/src/main/java/com/cinchapi/concourse/lang/sort/BuildableOrderState.java new file mode 100644 index 0000000000..5bafa33243 --- /dev/null +++ b/concourse-driver-java/src/main/java/com/cinchapi/concourse/lang/sort/BuildableOrderState.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2013-2019 Cinchapi Inc. + * + * 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.cinchapi.concourse.lang.sort; + +/** + * The base class for a Sort Order state that can be transformed into a complete + * and well-formed {@link Order}. + */ +public abstract class BuildableOrderState extends OrderState { + + /** + * Construct a new instance. + * + * @param order + */ + protected BuildableOrderState(Order order) { + super(order); + } + + /** + * Build and return the {@link Order}. + * + * @return the built Order + */ + public final Order build() { + order.close(); + return order; + } + +} diff --git a/concourse-driver-java/src/main/java/com/cinchapi/concourse/lang/sort/Direction.java b/concourse-driver-java/src/main/java/com/cinchapi/concourse/lang/sort/Direction.java new file mode 100644 index 0000000000..a935fde7e4 --- /dev/null +++ b/concourse-driver-java/src/main/java/com/cinchapi/concourse/lang/sort/Direction.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2013-2019 Cinchapi Inc. + * + * 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.cinchapi.concourse.lang.sort; + +/** + * Sort directions. + * + * @author Jeff Nelson + */ +enum Direction { + + ASCENDING(1), DESCENDING(-1); + + /** + * Return the default {@link Direction}. + * + * @return the default + */ + static Direction $default() { + return Direction.ASCENDING; + } + + /** + * The coefficient is multiplied by the result of a {@link Comparator} to + * sort elements in forward or reverse order. + */ + private final int coefficient; + + /** + * Construct a new instance. + * + * @param coefficient + */ + Direction(int coefficient) { + this.coefficient = coefficient; + } + + /** + * Return the coefficient associated with this {@link Direction}. + * + * @return the coefficient + */ + public int coefficient() { + return coefficient; + } + +} diff --git a/concourse-driver-java/src/main/java/com/cinchapi/concourse/lang/sort/Order.java b/concourse-driver-java/src/main/java/com/cinchapi/concourse/lang/sort/Order.java new file mode 100644 index 0000000000..cdf1068fac --- /dev/null +++ b/concourse-driver-java/src/main/java/com/cinchapi/concourse/lang/sort/Order.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2013-2019 Cinchapi Inc. + * + * 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.cinchapi.concourse.lang.sort; + +import java.util.LinkedHashMap; +import java.util.Objects; + +import javax.annotation.Nullable; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; + +/** + * {@link Order} encapsulates the semantics of a result set sorting. Any given + * time, objects of this class can exist in one of two modes: {@code building} + * or {@code built}. When an Order is + * {@code built}, it is guaranteed to represent a fully and well formed sort + * order that can be processed. On the other hand, when a Order is + * {@code building} it is in an incomplete state. + *

+ * This class is the public interface to Order construction. It is meant to + * be used in a chained manner, where the caller initially calls + * {@link Order#by} and continues to construct the Order using the + * options available from each subsequently returned state. + *

+ * + */ +public final class Order { + + /** + * Start building a new {@link Order}. + * + * @return the Order builder + */ + public static OrderByState by(String key) { + Order order = new Order(); + return new OrderByState(order, key, Direction.$default()); + } + + /** + * A mapping from each key to direction ordinal (e.g. 1 for ASC and -1 for + * DESC) in the constructed {@link Order}. + */ + @VisibleForTesting + final LinkedHashMap spec; + + /** + * The last key that was {@link #add(String, Direction) added}. + */ + @Nullable + protected String lastKey; + + /** + * A flag that indicates whether this {@link Order} has been built. + */ + private boolean built = false; + + /** + * Construct a new instance. + */ + private Order() { + this.spec = Maps.newLinkedHashMap(); + } + + @Override + public boolean equals(Object obj) { + if(obj instanceof Order) { + return Objects.equals(spec, ((Order) obj).spec); + } + else { + return false; + } + } + + @Override + public int hashCode() { + return spec.hashCode(); + } + + /** + * Mark this {@link Order} as {@code built}. + */ + void close() { + built = !built ? true : built; + } + + /** + * Add to the order {@link #spec}. + * + * @param key + * @param direction + */ + final void add(String key, Direction direction) { + Preconditions.checkState(!built, "Cannot modify a built Order"); + spec.put(key, direction.coefficient()); + this.lastKey = key; + } + +} diff --git a/concourse-driver-java/src/main/java/com/cinchapi/concourse/lang/sort/OrderByState.java b/concourse-driver-java/src/main/java/com/cinchapi/concourse/lang/sort/OrderByState.java new file mode 100644 index 0000000000..f0436ed883 --- /dev/null +++ b/concourse-driver-java/src/main/java/com/cinchapi/concourse/lang/sort/OrderByState.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2013-2019 Cinchapi Inc. + * + * 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.cinchapi.concourse.lang.sort; + +/** + * The {@link OrderState} that expects the next token to be a sort order or a + * new key + * to sort by. + */ +public class OrderByState extends BuildableOrderState { + + /** + * Construct a new instance. + * + * @param order + */ + OrderByState(Order order, String key, Direction direction) { + super(order); + order.add(key, direction); + } + + /** + * Add the {@link Direction#ASCENDING} direction to the last key that + * was specified in {@link Order} that is building + * + * @return the builder + */ + public OrderDirectionState ascending() { + return new OrderDirectionState(order, order.lastKey, + Direction.ASCENDING); + } + + /** + * Alias for {@link #descending()}. + * + * @return the builder + */ + public OrderDirectionState decreasing() { + return descending(); + } + + /** + * Add the {@link Direction#DESCENDING} direction to the last key that + * was specified in {@link Order} that is building + * + * @return the builder + */ + public OrderDirectionState descending() { + return new OrderDirectionState(order, order.lastKey, + Direction.DESCENDING); + } + + /** + * Alias for {@link #ascending()}. + * + * @return the builder + */ + public OrderDirectionState increasing() { + return ascending(); + } + + /** + * Alias for {@link #descending()}. + * + * @return the builder + */ + public OrderDirectionState largestFirst() { + return descending(); + } + + /** + * Alias for {@link #ascending()}. + * + * @return the builder + */ + public OrderDirectionState largestLast() { + return ascending(); + } + + /** + * Alias for {@link #descending()}. + * + * @return the builder + */ + public OrderDirectionState reversed() { + return descending(); + } + + /** + * Alias for {@link #ascending()}. + * + * @return the builder + */ + public OrderDirectionState smallestFirst() { + return ascending(); + } + + /** + * Alias for {@link #descending()}. + * + * @return the builder + */ + public OrderDirectionState smallestLast() { + return descending(); + } + + /** + * Adds a new {@link SortOrder} to sort by to the {@link Order} that is + * building. + * + * @return the builder + */ + public OrderThenState then() { + return new OrderThenState(order); + } +} diff --git a/concourse-driver-java/src/main/java/com/cinchapi/concourse/lang/sort/OrderDirectionState.java b/concourse-driver-java/src/main/java/com/cinchapi/concourse/lang/sort/OrderDirectionState.java new file mode 100644 index 0000000000..b95aff6673 --- /dev/null +++ b/concourse-driver-java/src/main/java/com/cinchapi/concourse/lang/sort/OrderDirectionState.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2013-2019 Cinchapi Inc. + * + * 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.cinchapi.concourse.lang.sort; + +/** + * A {@link State} that represents a building {@link Order} that just had + * direction information specified for the most recently added key. + * + * @author Jeff Nelson + */ +public class OrderDirectionState extends BuildableOrderState { + + /** + * Construct a new instance. + * + * @param order + */ + OrderDirectionState(Order order, String key, Direction direction) { + super(order); + order.add(key, direction); + } + + /** + * Specify a transition to adding a new key to the order. + * + * @return the builder + */ + public OrderThenState then() { + return new OrderThenState(order); + } + +} diff --git a/concourse-driver-java/src/main/java/com/cinchapi/concourse/lang/sort/OrderState.java b/concourse-driver-java/src/main/java/com/cinchapi/concourse/lang/sort/OrderState.java new file mode 100644 index 0000000000..1602fc0a68 --- /dev/null +++ b/concourse-driver-java/src/main/java/com/cinchapi/concourse/lang/sort/OrderState.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2013-2019 Cinchapi Inc. + * + * 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.cinchapi.concourse.lang.sort; + +/** + * This is the base class and marker for any valid state in the {@link Order} + * builder. Each {@link OrderState} is passed the current {@link Order} and + * holds a reference. + *

+ * For the purposes of a builder, an {@link OrderState} typically describes what + * was most recently consumed. + *

+ */ +public abstract class OrderState { + + /** + * A reference to the {@link Order} that is being built. + */ + protected final Order order; + + /** + * Construct a new instance. + * + * @param order + */ + protected OrderState(Order order) { + this.order = order; + } + +} diff --git a/concourse-driver-java/src/main/java/com/cinchapi/concourse/lang/sort/OrderThenState.java b/concourse-driver-java/src/main/java/com/cinchapi/concourse/lang/sort/OrderThenState.java new file mode 100644 index 0000000000..90581383eb --- /dev/null +++ b/concourse-driver-java/src/main/java/com/cinchapi/concourse/lang/sort/OrderThenState.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2013-2019 Cinchapi Inc. + * + * 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.cinchapi.concourse.lang.sort; + +/** + * The {@link OrderThenState} is simply a bridge that allows a transition back + * to a {@link OrderByState}. + */ +public class OrderThenState extends OrderState { + + /** + * Construct a new instance. + * + * @param order + */ + OrderThenState(Order order) { + super(order); + } + + public OrderByState by(String key) { + return new OrderByState(order, key, Direction.$default()); + } + +} \ No newline at end of file diff --git a/concourse-driver-java/src/main/java/com/cinchapi/concourse/lang/sort/Sort.java b/concourse-driver-java/src/main/java/com/cinchapi/concourse/lang/sort/Sort.java new file mode 100644 index 0000000000..fcc3ddf84c --- /dev/null +++ b/concourse-driver-java/src/main/java/com/cinchapi/concourse/lang/sort/Sort.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013-2019 Cinchapi Inc. + * + * 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.cinchapi.concourse.lang.sort; + +/** + * Serves as an alias for {@link Order} + */ +public final class Sort { + + /** + * Start building a new {@link Order}. + * + * @return the Order builder + */ + public static OrderByState by(String key) { + return Order.by(key); + } + + private Sort() {/* no-init */} + +} diff --git a/concourse-driver-java/src/test/java/com/cinchapi/concourse/lang/sort/OrderTest.java b/concourse-driver-java/src/test/java/com/cinchapi/concourse/lang/sort/OrderTest.java new file mode 100644 index 0000000000..fabfb2c3dc --- /dev/null +++ b/concourse-driver-java/src/test/java/com/cinchapi/concourse/lang/sort/OrderTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2013-2019 Cinchapi Inc. + * + * 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.cinchapi.concourse.lang.sort; + +import java.util.Map; + +import org.junit.Assert; +import org.junit.Test; + +import com.google.common.collect.ImmutableMap; + +/** + * Unit tests for the {@link com.cinchapi.concourse.lang.sort.Order} building + * functionality. + */ +public class OrderTest { + + @Test + public void testDefaultSortOrder() { + Map expected = ImmutableMap.of("foo", 1); + Order order = Order.by("foo").build(); + Assert.assertEquals(expected, order.spec); + } + + @Test + public void testAscendingSortOrder() { + Map expected = ImmutableMap.of("foo", 1); + Order order = Order.by("foo").ascending().build(); + Assert.assertEquals(expected, order.spec); + } + + @Test + public void testDescendingSortOrder() { + Map expected = ImmutableMap.of("foo", -1); + Order order = Order.by("foo").descending().build(); + Assert.assertEquals(expected, order.spec); + } + + @Test + public void testMultipleSortKeysSortOrder() { + Map expected = ImmutableMap.of("foo", 1, "bar", 1); + Order order = Order.by("foo").then().by("bar").ascending().build(); + Assert.assertEquals(expected, order.spec); + } + + @Test + public void testMultipleSortKeysWithImplicitSortOrders() { + Map expected = ImmutableMap.of("foo", 1, "bar", 1, + "zoo", 1); + Order order = Order.by("foo").then().by("bar").then().by("zoo").build(); + Assert.assertEquals(expected, order.spec); + } + + @Test(expected = IllegalStateException.class) + public void testCannotAddSymbolToBuiltOrder() { + Order order = Order.by("foo").build(); + order.add("bar", Direction.ASCENDING); + } + + @Test + public void testAlias() { + Map expected = ImmutableMap.of("foo", 1); + Order order = Sort.by("foo").build(); + Assert.assertEquals(expected, order.spec); + } + + @Test + public void testComplexOrder() { + Order order = Sort.by("a").then().by("b").ascending().then().by("c") + .then().by("d").descending().then().by("e").largestFirst() + .build(); + Map expected = ImmutableMap.of("a", 1, "b", 1, "c", 1, + "d", -1, "e", -1); + Assert.assertEquals(expected, order.spec); + } + +}