package queue; import base.ExtendedRandom; import java.lang.reflect.Field; import java.util.ArrayDeque; import java.util.List; import java.util.Objects; import java.util.function.BiFunction; import java.util.function.Predicate; import java.util.function.Supplier; /** * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) */ @SuppressWarnings("UnusedReturnValue") public final class Queues { private Queues() {} protected interface QueueModel { @ReflectionTest.Ignore ArrayDeque model(); default Object dequeue() { return model().removeFirst(); } default int size() { return model().size(); } default boolean isEmpty() { return model().isEmpty(); } default void clear() { model().clear(); } default void enqueue(final Object element) { model().addLast(element); } default Object element() { return model().getFirst(); } } protected interface QueueChecker { M wrap(ArrayDeque reference); default List linearTest(final M queue, final ExtendedRandom random) { // Do nothing by default return List.of(); } default void check(final M queue, final ExtendedRandom random) { queue.element(); } default void add( final M queue, final Object element, final ExtendedRandom random ) { queue.enqueue(element); } default Object randomElement(final ExtendedRandom random) { return ArrayQueueTester .ELEMENTS[random.nextInt(ArrayQueueTester.ELEMENTS.length)]; } default void remove(final M queue, final ExtendedRandom random) { queue.dequeue(); } @SuppressWarnings("unchecked") default M cast(final QueueModel model) { return (M) model; } } @FunctionalInterface protected interface Splitter { List split( final QueueChecker tester, final M queue, final ExtendedRandom random ); } @FunctionalInterface /* package-private */ interface LinearTester< M extends QueueModel > extends Splitter { void test( final QueueChecker tester, final M queue, final ExtendedRandom random ); @Override default List split( final QueueChecker tester, final M queue, final ExtendedRandom random ) { test(tester, queue, random); return List.of(); } } // === 3637 (ArrayQueue) /* package-private */ interface CountModel extends ReflectionModel { default int count(final Object element) { return reduce(0, element, (v, i) -> v + 1); } } /* package-private */ static final Queues.LinearTester COUNT = ( tester, queue, random ) -> queue.count(tester.randomElement(random)); /* package-private */ interface DequeCountModel extends DequeModel, CountModel {} /* package-private */ static final Queues.LinearTester< DequeCountModel > DEQUE_COUNT = COUNT::test; // === 3839 (ArrayQueue) /* package-private */ interface CountIfModel extends ReflectionModel { default int countIf(final Predicate p) { return reduce(0, p, (v, i) -> v + 1); } } /* package-private */ static final Queues.LinearTester< CountIfModel > COUNT_IF = (t, q, r) -> q.countIf(randomPredicate(t, r)); /* package-private */ interface DequeCountIfModel extends DequeModel, CountIfModel {} /* package-private */ static final Queues.LinearTester< DequeCountIfModel > DEQUE_COUNT_IF = COUNT_IF::test; record NamedPredicate( String name, Predicate predicate ) implements Predicate { @Override public String toString() { return name; } @Override public boolean test(final T t) { return predicate.test(t); } } /* package-private */ static Predicate randomPredicate( final Queues.QueueChecker tester, final ExtendedRandom random ) { final Object item = tester.randomElement(random); return random .>>randomItem( () -> new NamedPredicate<>("item == ", o -> item == o), () -> new NamedPredicate<>(item + ".equals", item::equals), () -> new NamedPredicate<>("null == ", Objects::isNull) ) .get(); } @SafeVarargs private static Queues.LinearTester< M > randomOf(final Queues.LinearTester... variants) { return (tester, queue, random) -> random.randomItem(variants).test(tester, queue, random); } // === 3435 (ArrayQueue) /* package-private */ interface IndexIfModel extends ReflectionModel { default int indexIf(final Predicate p) { return reduce(-1, p, (v, i) -> v == -1 ? i : v); } default int lastIndexIf(final Predicate p) { return reduce(-1, p, (v, i) -> i); } } /* package-private */ static final Queues.LinearTester< IndexIfModel > INDEX_IF = randomOf( (t, q, r) -> q.indexIf(randomPredicate(t, r)), (t, q, r) -> q.lastIndexIf(randomPredicate(t, r)) ); // === 3233 (ArrayQueue) /* package-private */ interface IndexModel extends ReflectionModel { default int indexOf(final Object element) { return reduce(-1, element, (v, i) -> v == -1 ? i : v); } default int lastIndexOf(final Object element) { return reduce(-1, element, (v, i) -> i); } } /* package-private */ static final Queues.LinearTester INDEX = randomOf( (t, q, r) -> q.indexOf(t.randomElement(r)), (t, q, r) -> q.lastIndexOf(t.randomElement(r)) ); // === 3637 (Queues) /* package-private */ interface ContainsModel extends Queues.QueueModel { default boolean contains(final Object element) { return model().contains(element); } @SuppressWarnings("UnusedReturnValue") default boolean removeFirst(final Object element) { return model().removeFirstOccurrence(element); } } /* package-private */ static final Queues.LinearTester< ContainsModel > CONTAINS = (tester, queue, random) -> { final Object element = random.nextBoolean() ? tester.randomElement(random) : random.nextInt(); if (random.nextBoolean()) { queue.contains(element); } else { queue.removeFirst(element); } }; // === 3839 (Queues) /* package-private */ interface NthModel extends Queues.QueueModel { // Deliberately ugly implementation @ReflectionTest.Wrap default NthModel getNth(final int n) { final ArrayDeque deque = new ArrayDeque<>(); final int[] index = { 0 }; model().forEach(e -> { if (++index[0] % n == 0) { deque.add(e); } }); return () -> deque; } // Deliberately ugly implementation @ReflectionTest.Wrap default NthModel removeNth(final int n) { final ArrayDeque deque = new ArrayDeque<>(); final int[] index = { 0 }; model().removeIf(e -> { if (++index[0] % n == 0) { deque.add(e); return true; } else { return false; } }); return () -> deque; } default void dropNth(final int n) { final int[] index = { 0 }; model().removeIf(e -> ++index[0] % n == 0); } } /* package-private */ static final Queues.Splitter NTH = ( tester, queue, random ) -> { final int n = random.nextInt(5) + 1; return switch (random.nextInt(3)) { case 0 -> { final NthModel model = queue.removeNth(n); yield List.of(tester.cast(model)); } case 1 -> { queue.dropNth(n); yield List.of(); } case 2 -> List.of(tester.cast(queue.getNth(n))); default -> throw new AssertionError(); }; }; // === 3435 (Queues) /* package-private */ interface RemoveIfModel extends Queues.QueueModel { default void removeIf(final Predicate p) { model().removeIf(p); } default void retainIf(final Predicate p) { model().removeIf(Predicate.not(p)); } } private static final Queues.LinearTester REMOVE_IF = ( tester, queue, random ) -> queue.removeIf(randomPredicate(tester, random)); private static final Queues.LinearTester RETAIN_IF = ( tester, queue, random ) -> queue.retainIf(randomPredicate(tester, random)); /* package-private */ static final Queues.LinearTester< RemoveIfModel > REMOVE_IF_TEST = randomOf(REMOVE_IF, RETAIN_IF); // 3233 (Queues) /* package-private */ interface RemoveEqModel extends Queues.QueueModel { default void removeAll(final Object element) { model().removeIf(Predicate.isEqual(element)); } default void retainAll(final Object element) { model().removeIf(Predicate.not(Predicate.isEqual(element))); } } private static final Queues.LinearTester REMOVE_ALL = ( tester, queue, random ) -> queue.removeAll(tester.randomElement(random)); private static final Queues.LinearTester RETAIL_ALL = ( tester, queue, random ) -> queue.retainAll(tester.randomElement(random)); /* package-private */ static final Queues.LinearTester< RemoveEqModel > REMOVE_EQ_TEST = randomOf(REMOVE_ALL, RETAIL_ALL); // === Reflection /* package-private */ interface ReflectionModel extends QueueModel { Field ELEMENTS = getField("elements"); Field HEAD = getField("head"); @SuppressWarnings("unchecked") private Z get(final Field field) { try { return (Z) field.get(model()); } catch (final IllegalAccessException e) { throw new AssertionError( "Cannot access field " + field.getName() + ": " + e.getMessage(), e ); } } private static Field getField(final String name) { try { final Field field = ArrayDeque.class.getDeclaredField(name); field.setAccessible(true); return field; } catch (final NoSuchFieldException e) { throw new AssertionError( "Reflection error: " + e.getMessage(), e ); } } @ReflectionTest.Ignore default int head() { return get(HEAD); } @ReflectionTest.Ignore default Object[] elements() { return get(ELEMENTS); } @ReflectionTest.Ignore default R reduce( final R zero, final Predicate p, final BiFunction f ) { final int size = size(); final Object[] elements = elements(); final int head = head(); R result = zero; for (int i = 0; i < size; i++) { if (p.test(elements[(head + i) % elements.length])) { result = f.apply(result, i); } } return result; } @ReflectionTest.Ignore @SuppressWarnings("unused") default R reduce( final R zero, final Object v, final BiFunction f ) { return reduce(zero, o -> Objects.equals(v, o), f); } } // === Deque /* package-private */ interface DequeModel extends QueueModel { default void push(final Object element) { model().addFirst(element); } @SuppressWarnings("UnusedReturnValue") default Object peek() { return model().getLast(); } default Object remove() { return model().removeLast(); } } /* package-private */ interface DequeChecker< M extends DequeModel > extends QueueChecker { @Override default void add( final M queue, final Object element, final ExtendedRandom random ) { if (random.nextBoolean()) { QueueChecker.super.add(queue, element, random); } else { queue.push(element); } } @Override default void check(final M queue, final ExtendedRandom random) { if (random.nextBoolean()) { QueueChecker.super.check(queue, random); } else { queue.peek(); } } @Override default void remove(final M queue, final ExtendedRandom random) { if (random.nextBoolean()) { QueueChecker.super.remove(queue, random); } else { queue.remove(); } } } }