diff --git a/SQ-2023-H6/pom.xml b/SQ-2023-H6/pom.xml new file mode 100644 index 0000000..acd2026 --- /dev/null +++ b/SQ-2023-H6/pom.xml @@ -0,0 +1,139 @@ + + + 4.0.0 + org.usi.sq + SQ-2023-H6 + 1.0 + + UTF-8 + 1.7.0-M1 + 5.6.2 + 5.6.2 + 4.13 + + + + + central + https://repo.maven.apache.org/maven2 + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M5 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 8 + 8 + + + + org.jacoco + jacoco-maven-plugin + 0.8.5 + + + + prepare-agent + + + + + report + test + + report + + + + + + org.pitest + pitest-maven + 1.5.2 + + + org.usi.sq.util* + + + org.usi.sq.util* + * + + + + + + org.pitest + pitest-junit5-plugin + 0.12 + + + + + + + + org.junit.jupiter + junit-jupiter-api + ${junit.jupiter.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.jupiter.version} + test + + + junit + junit + ${junit.version} + test + + + org.junit.vintage + junit-vintage-engine + ${junit.vintage.version} + test + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.5 + + + + + report + + + + + + org.apache.maven.plugins + maven-surefire-report-plugin + 3.0.0-M5 + + + + + + org.apache.maven.plugins + maven-jxr-plugin + 3.3.0 + + + + diff --git a/SQ-2023-H6/src/main/java/org/usi/sq/util/CircularFifoQueue.java b/SQ-2023-H6/src/main/java/org/usi/sq/util/CircularFifoQueue.java new file mode 100644 index 0000000..a6b2001 --- /dev/null +++ b/SQ-2023-H6/src/main/java/org/usi/sq/util/CircularFifoQueue.java @@ -0,0 +1,276 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.usi.sq.util; + +import java.io.Serializable; +import java.util.*; + +/** + * CircularFifoQueue is a first-in first-out queue with a fixed size that + * replaces its oldest element if full. + *

+ * The removal order of a {@link CircularFifoQueue} is based on the + * insertion order; elements are removed in the same order in which they + * were added. The iteration order is the same as the removal order. + *

+ *

+ * This queue prevents null objects from being added. + *

+ * + * @param the type of elements in this collection + * @since 4.0 + */ +public class CircularFifoQueue { + + /** Serialization version. */ + private static final long serialVersionUID = -8423413834657610406L; + + /** Underlying storage array. */ + private transient E[] elements; + + /** Array index of first (oldest) queue element. */ + private transient int start = 0; + + /** + * Index mod maxElements of the array position following the last queue + * element. Queue elements start at elements[start] and "wrap around" + * elements[maxElements-1], ending at elements[decrement(end)]. + * For example, elements = {c,a,b}, start=1, end=1 corresponds to + * the queue [a,b,c]. + */ + private transient int end = 0; + + /** Flag to indicate if the queue is currently full. */ + private transient boolean full = false; + + /** Capacity of the queue. */ + private final int maxElements; + + /** + * Constructor that creates a queue with the default size of 32. + */ + public CircularFifoQueue() { + this(32); + } + + /** + * Constructor that creates a queue with the specified size. + * + * @param size the size of the queue (cannot be changed) + * @throws IllegalArgumentException if the size is < 1 + */ + @SuppressWarnings("unchecked") + public CircularFifoQueue(final int size) { + if (size <= 0) { + throw new IllegalArgumentException("The size must be greater than 0"); + } + elements = (E[]) new Object[size]; + maxElements = elements.length; + } + + /** + * Returns {@code true} if the capacity limit of this queue has been reached, + * i.e. the number of elements stored in the queue equals its maximum size. + * + * @return {@code true} if the capacity limit has been reached, {@code false} otherwise + * @since 4.1 + */ + public boolean isAtFullCapacity() { + return size() == maxElements; + } + + /** + * Adds the given element to this queue. If the queue is full, the least recently added + * element is discarded so that a new element can be inserted. + * + * @param element the element to add + * @return true, always + * @throws NullPointerException if the given element is null + */ + public boolean add(final E element) { + if (null == element) { + throw new NullPointerException("Attempted to add null object to queue"); + } + + if (isAtFullCapacity()) { + remove(); + } + + elements[end++] = element; + + if (end >= maxElements) { + end = 0; + } + + if (end == start) { + full = true; + } + + return true; + } + + /** + * Adds the given element to this queue. If the queue is full, the least recently added + * element is discarded so that a new element can be inserted. + * + * @param element the element to add + * @return true, always + * @throws NullPointerException if the given element is null + */ + public boolean offer(final E element) { + return add(element); + } + + /** + * Increments the internal index. + * + * @param index the index to increment + * @return the updated index + */ + private int increment(int index) { + index++; + if (index >= maxElements) { + index = 0; + } + return index; + } + + /** + * Decrements the internal index. + * + * @param index the index to decrement + * @return the updated index + */ + private int decrement(int index) { + index--; + if (index < 0) { + index = maxElements - 1; + } + return index; + } + + /** + * Returns true if this queue is empty; false otherwise. + * + * @return true if this queue is empty + */ + public boolean isEmpty() { + return size() == 0; + } + + public E remove() { + if (isEmpty()) { + throw new NoSuchElementException("queue is empty"); + } + + final E element = elements[start]; + if (null != element) { + elements[start++] = null; + + if (start >= maxElements) { + start = 0; + } + full = false; + } + return element; + } + + /** + * Returns the number of elements stored in the queue. + * + * @return this queue's size + */ + public int size() { + int size = 0; + + if (end < start) { + size = maxElements - start + end; + } else if (end == start) { + size = full ? maxElements : 0; + } else { + size = end - start; + } + + return size; + } + + + /** + * Returns an iterator over this queue's elements. + * + * @return an iterator over this queue's elements + */ + public Iterator iterator() { + return new Iterator() { + + private int index = start; + private int lastReturnedIndex = -1; + private boolean isFirst = full; + + public boolean hasNext() { + return isFirst || index != end; + } + + public E next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + isFirst = false; + lastReturnedIndex = index; + index = increment(index); + return elements[lastReturnedIndex]; + } + + public void remove() { + if (lastReturnedIndex == -1) { + throw new IllegalStateException(); + } + + // First element can be removed quickly + if (lastReturnedIndex == start) { + CircularFifoQueue.this.remove(); + lastReturnedIndex = -1; + return; + } + + int pos = lastReturnedIndex + 1; + if (start < lastReturnedIndex && pos < end) { + // shift in one part + System.arraycopy(elements, pos, elements, lastReturnedIndex, end - pos); + } else { + // Other elements require us to shift the subsequent elements + while (pos != end) { + if (pos >= maxElements) { + elements[pos - 1] = elements[0]; + pos = 0; + } else { + elements[decrement(pos)] = elements[pos]; + pos = increment(pos); + } + } + } + + lastReturnedIndex = -1; + end = decrement(end); + elements[end] = null; + full = false; + index = decrement(index); + } + + }; + } +} diff --git a/SQ-2023-H6/src/test/java/org/usi/sq/util/CircularFifoQueueTest.java b/SQ-2023-H6/src/test/java/org/usi/sq/util/CircularFifoQueueTest.java new file mode 100644 index 0000000..264e6de --- /dev/null +++ b/SQ-2023-H6/src/test/java/org/usi/sq/util/CircularFifoQueueTest.java @@ -0,0 +1,89 @@ +package org.usi.sq.util; + +import org.junit.jupiter.api.Test; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +class CircularFifoQueueTest { + + private void testIteratorEmpty(Iterator it) { + assertFalse(it.hasNext()); + assertThrows(NoSuchElementException.class, it::next); + } + + private void testIteratorNext(Iterator it, T e) { + assertTrue(it.hasNext()); + assertEquals(e, it.next()); + } + + private void testQueueAll(int initQueueSize, List values, long removeQueueCount, List itrRemoveIndexes) { + final Set removeIndexesSet = new HashSet<>(itrRemoveIndexes); + + final CircularFifoQueue queue = new CircularFifoQueue<>(initQueueSize); + for (T e : values) assertTrue(queue.offer(e)); + + int queueSizeAfterOffers = Math.min(initQueueSize, values.size()); + for (int i = 0; i < removeQueueCount; i++) { + assertEquals(values.get(values.size() - queueSizeAfterOffers + i), queue.remove()); + } + + assertEquals(queueSizeAfterOffers - removeQueueCount, queue.size()); + assertEquals(queue.size() == initQueueSize, queue.isAtFullCapacity()); + + final Iterator it = queue.iterator(); + final List notRemovedValues = new ArrayList<>(values.size()); + + final List expected = values.subList(Math.max(0, values.size() - queue.size()), values.size()); + + // Test Iteration + for (int i = 0; i < expected.size(); i++) { + T e = expected.get(i); + testIteratorNext(it, e); + + if (removeIndexesSet.contains(i)) { + it.remove(); + assertThrows(IllegalStateException.class, it::remove); // removing again causes explosion + } else { + notRemovedValues.add(e); + } + } + testIteratorEmpty(it); + + // Test removal + assertEquals(notRemovedValues.size(), queue.size()); + final Iterator itNew = queue.iterator(); + for (T e : notRemovedValues) testIteratorNext(itNew, e); + testIteratorEmpty(itNew); + } + + + + + @Test + public void testIterator() { + assertThrows(IllegalArgumentException.class, () -> new CircularFifoQueue(0)); + assertThrows(NoSuchElementException.class, () -> new CircularFifoQueue().remove()); + assertThrows(NullPointerException.class, () -> new CircularFifoQueue().offer(null)); + + testQueueIteratorRemove(10, Arrays.asList(1, 2, 3), 0, 2); + testQueueIteratorRemove(3, Arrays.asList(1, 2, 3), 0, 2); + testQueueIteratorRemove(3, Arrays.asList(1, 2, 3), 0, 2); + testQueueIteratorRemove(2, Arrays.asList(1, 2, 3)); + testQueueIteratorRemove(4, Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9), 1, 3); + testQueueAll(7, Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13), 4, Arrays.asList(1, 3)); + + testQueueIteratorRemove(4, Arrays.asList(1, 2, 3, 4, 5), 1, 3); + testQueueIteratorRemove(10, Collections.emptyList()); + testQueueQueueRemove(4, Arrays.asList(1, 2, 3, 4, 5), 1); + } + + private void testQueueQueueRemove(int i, List asList, int i1) { + testQueueAll(i, asList, i1, Collections.emptyList()); + } + + private void testQueueIteratorRemove(int i, List asList, Integer... asList1) { + testQueueAll(i, asList, 0, Arrays.asList(asList1)); + } +}