From 5a0da3b40b5b4cc06fcdbfbd24a8620eb011b477 Mon Sep 17 00:00:00 2001 From: Sumanth Rajkumar Date: Wed, 3 Aug 2022 10:50:16 -0400 Subject: [PATCH 1/3] NUMBERS-186 add complex arrays module --- commons-numbers-complex-arrays/pom.xml | 76 ++++ .../numbers/complex/arrays/ComplexList.java | 357 ++++++++++++++++++ .../numbers/complex/arrays/package-info.java | 20 + .../src/site/resources/profile.jacoco | 17 + .../src/site/resources/profile.japicmp | 17 + .../src/site/site.xml | 37 ++ .../src/site/xdoc/index.xml | 40 ++ .../complex/arrays/ComplexListTest.java | 222 +++++++++++ pom.xml | 6 + 9 files changed, 792 insertions(+) create mode 100644 commons-numbers-complex-arrays/pom.xml create mode 100644 commons-numbers-complex-arrays/src/main/java/org/apache/commons/numbers/complex/arrays/ComplexList.java create mode 100644 commons-numbers-complex-arrays/src/main/java/org/apache/commons/numbers/complex/arrays/package-info.java create mode 100644 commons-numbers-complex-arrays/src/site/resources/profile.jacoco create mode 100644 commons-numbers-complex-arrays/src/site/resources/profile.japicmp create mode 100644 commons-numbers-complex-arrays/src/site/site.xml create mode 100644 commons-numbers-complex-arrays/src/site/xdoc/index.xml create mode 100644 commons-numbers-complex-arrays/src/test/java/org/apache/commons/numbers/complex/arrays/ComplexListTest.java diff --git a/commons-numbers-complex-arrays/pom.xml b/commons-numbers-complex-arrays/pom.xml new file mode 100644 index 000000000..1d4ea853c --- /dev/null +++ b/commons-numbers-complex-arrays/pom.xml @@ -0,0 +1,76 @@ + + + + + commons-numbers-parent + org.apache.commons + 1.1-SNAPSHOT + + 4.0.0 + + commons-numbers-complex-arrays + Apache Commons Numbers Complex Arrays + + + + + org.apache.commons + commons-numbers-complex + + + org.apache.commons + commons-rng-client-api + 1.4 + test + + + org.apache.commons + commons-rng-simple + test + + + org.apache.commons + commons-numbers-core + test + + + + + + org.apache.commons.numbers.complex.arrays + + org.apache.commons.numbers.complex.arrays + + org.apache.commons.numbers.complex.arrays + + ${basedir}/.. + + + + + com.github.siom79.japicmp + japicmp-maven-plugin + + true + + + + + \ No newline at end of file diff --git a/commons-numbers-complex-arrays/src/main/java/org/apache/commons/numbers/complex/arrays/ComplexList.java b/commons-numbers-complex-arrays/src/main/java/org/apache/commons/numbers/complex/arrays/ComplexList.java new file mode 100644 index 000000000..bc58b86e4 --- /dev/null +++ b/commons-numbers-complex-arrays/src/main/java/org/apache/commons/numbers/complex/arrays/ComplexList.java @@ -0,0 +1,357 @@ +/* + * 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.apache.commons.numbers.complex.arrays; + +import org.apache.commons.numbers.complex.Complex; + +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; + +/** + * Resizable-double array implementation of the List interface. Implements all optional list operations, + * and permits all elements. In addition to implementing the List interface, + * this class provides methods to manipulate the size of the array that is used internally to store the list. + * + *

Each ComplexList instance has a capacity. The capacity is half the size of the double array used to store the elements + * in the list. It is always at least twice as large as the list size. As elements are added to an ComplexList, + * its capacity grows automatically.

+ * + *

An application can increase the capacity of an ComplexList instance before adding a large number of elements + * using the ensureCapacity operation. This may reduce the amount of incremental reallocation.

+ */ +public class ComplexList extends AbstractList { + + /** The maximum size of array to allocate. + * Ensuring Max capacity is even with additional space for vm array headers. + */ + private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 9; + + /** Max capacity for size of complex numbers in the list. */ + public static final int MAX_CAPACITY = MAX_ARRAY_SIZE / 2; + + /** error in case of allocation above max capacity. */ + private static final String OOM_ERROR_STRING = "cannot allocate capacity %s greater than max " + MAX_CAPACITY; + + /** Default initial capacity. */ + private static final int DEFAULT_CAPACITY = 8; + + /** Size label message. */ + private static final String SIZE_MSG = ", Size: "; + /** Index position label message. */ + private static final String INDEX_MSG = "Index: "; + + /** + * The double array buffer into which the elements of the ComplexList are stored. + */ + protected double[] realAndImagParts; + + /** + * Size of ComplexList. + */ + private int size; + + /** + * Constructs an empty list with the specified capacity, if it's + * greater than the default capacity of 8. + * + * @param capacity - Capacity of list. + * @throws OutOfMemoryError - if the {@code capacity} is greater than {@code MAX_CAPACITY}. + */ + public ComplexList(int capacity) { + if (capacity > MAX_CAPACITY) { + throw new OutOfMemoryError(String.format(OOM_ERROR_STRING, capacity)); + } + final int arrayLength = Math.max(DEFAULT_CAPACITY, capacity) * 2; + realAndImagParts = new double[arrayLength]; + } + + /** + * Constructs an empty list with the default capacity of 8. + */ + public ComplexList() { + realAndImagParts = new double[DEFAULT_CAPACITY * 2]; + } + + /** + * Gives the size of this ComplexList. + * + * @return the number of elements it contains. + */ + @Override + public int size() { + return size; + } + + /** + * Checks if the given index is in range. + * + * @param index - Index of the element to range check. + * @throws IndexOutOfBoundsException - if index isn't within the range. + */ + private void rangeCheck(int index) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); + } + } + + /** + * A version of rangeCheck used by add and addAll. + * + * @param index - Index of the element to range check. + * @throws IndexOutOfBoundsException - if index isn't within the range of list. + */ + private void rangeCheckForInsert(int index) { + if (index < 0 || index > size) { + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); + } + } + + /** + * Gets the complex number \( (a + i b) \) at the indexed position of the list. + * + * @param index - Index of the element to get. + * @return the complex number. + */ + @Override + public Complex get(int index) { + rangeCheck(index); + final int i = index << 1; + return Complex.ofCartesian(realAndImagParts[i], realAndImagParts[i + 1]); + } + + /** + * Replaces the element at the specified position in this list with the specified element's + * real and imaginary parts. No range checks are done. + * + * @param index - Index of the element to replace. + * @param real - Real part \( a \) of the complex number \( (a +ib) \). + * @param imaginary - Imaginary part \( b \) of the complex number \( (a +ib) \). + */ + private void setNoRangeCheck(int index, double real, double imaginary) { + final int i = index << 1; + realAndImagParts[i] = real; + realAndImagParts[i + 1] = imaginary; + } + + /** + * Replaces the element at the specified position in this list with the specified element. + + * @param index - Index of the element to replace. + * @param element - Element to be stored at the specified position. + * @return the element previously at the specified position. + * @throws IndexOutOfBoundsException - if index isn't within the range of list. + */ + @Override + public Complex set(int index, Complex element) { + rangeCheck(index); + final int i = index << 1; + final Complex oldValue = Complex.ofCartesian(realAndImagParts[i], realAndImagParts[i + 1]); + setNoRangeCheck(index, element.getReal(), element.getImaginary()); + return oldValue; + } + + /** + * Increases the capacity of this ComplexList instance, if necessary, to ensure that it can hold at + * least the number of elements specified by the minimum capacity argument. + * + * @param minCapacity – Desired minimum capacity. + * @throws OutOfMemoryError - if the {@code minCapacity} is greater than {@code MAX_ARRAY_SIZE}. + */ + public void ensureCapacity(int minCapacity) { + ensureCapacityInternal(minCapacity); + } + + /** + * Increases the capacity of this ComplexList instance, if necessary, to ensure that it can hold at + * least the number of elements specified by the minimum capacity argument. + * + * @param minCapacity – Desired minimum capacity. + * @return the backing double array. + * @throws OutOfMemoryError - if the {@code minCapacity} is greater than {@code MAX_ARRAY_SIZE}. + */ + private double[] ensureCapacityInternal(int minCapacity) { + modCount++; + final long minArrayCapacity = minCapacity * 2L; + if (minArrayCapacity > MAX_ARRAY_SIZE) { + throw new OutOfMemoryError(String.format(OOM_ERROR_STRING, minArrayCapacity)); + } + final long oldArrayCapacity = realAndImagParts.length; + if (minArrayCapacity > oldArrayCapacity) { + long newArrayCapacity = oldArrayCapacity + (oldArrayCapacity >> 1); + if (newArrayCapacity % 2 != 0) { + ++newArrayCapacity; + } + if (newArrayCapacity > MAX_ARRAY_SIZE) { + newArrayCapacity = MAX_ARRAY_SIZE; + } else if (newArrayCapacity < minArrayCapacity) { + newArrayCapacity = minArrayCapacity; + } + realAndImagParts = Arrays.copyOf(realAndImagParts, (int)newArrayCapacity); + } + return realAndImagParts; + } + + /** + * Increases the capacity of this ComplexList instance, if necessary, to ensure that it can hold at + * least an additional number of elements specified by the capacity argument. + * + * @param capacity - Desired capacity. + */ + private void expand(int capacity) { + ensureCapacityInternal(size + capacity); + } + + /** + * Appends the specified complex element to the end of this list. + * + * @param element - Complex element to be appended to this list. + * @return true after element has been added and size has been updated. + */ + @Override + public boolean add(Complex element) { + double[] e = realAndImagParts; + if (size == (e.length >>> 1)) { + e = ensureCapacityInternal(size + 1); + } + final int i = size << 1; + e[i] = element.real(); + e[i + 1] = element.imag(); + size++; + return true; + } + + /** + * Inserts the specified element at the specified position in this list. Shifts the element currently at that + * position (if any) and any subsequent elements to the right (adds one to their indices). + * + * @param index – Index at which the specified element is to be inserted. + * @param element – Complex element to be inserted. + * @throws IndexOutOfBoundsException – if index isn't within the range of list. + */ + @Override + public void add(int index, Complex element) { + rangeCheckForInsert(index); + double[] e = realAndImagParts; + if (size == e.length >>> 1) { + e = ensureCapacityInternal(size + 1); + } + final int i = index << 1; + System.arraycopy(e, 2 * index, e, i + 2, (size * 2) - i); + e[i] = element.real(); + e[i + 1] = element.imag(); + size++; + } + + /** + * Appends all the elements in the specified collection to the end of this list, in the order that they are + * returned by the specified collection's Iterator. The behavior of this operation is undefined if the + * specified collection is modified while the operation is in progress. + * (This implies that the behavior of this call is undefined if the specified collection is this list, + * and this list is nonempty.) + * + * @param c – Collection containing elements to be added to this list. + * @return true if this list changed as a result of the call. + * @throws NullPointerException – if the specified collection is null. + */ + @Override + public boolean addAll(Collection c) { + final int numNew = c.size(); + expand(numNew * 2); + double[] realAndImgData = new double[c.size() * 2]; + int i = 0; + for (final Complex val : c) { + final int i2 = i << 1; + realAndImgData[i2] = val.getReal(); + realAndImgData[i2 + 1] = val.getImaginary(); + i++; + } + System.arraycopy(realAndImgData, 0, realAndImagParts, size * 2, realAndImgData.length); + size += numNew; + return numNew != 0; + } + + /** + * Inserts all the elements in the specified collection into this list, starting at the specified position. + * Shifts the element currently at that position (if any) and any subsequent elements to the right (increases their indices). + * The new elements will appear in the list in the order that they are returned by the specified collection's iterator. + * + * @param index – Index at which to insert the first element from the specified collection. + * @param c – Collection containing elements to be added to this list. + * @return true if this list changed as a result of the call. + * @throws IndexOutOfBoundsException – if index isn't within the range of list. + * @throws NullPointerException – if the specified collection is null. + */ + @Override + public boolean addAll(int index, Collection c) { + rangeCheckForInsert(index); + final int numNew = c.size(); + final int numNew2 = numNew << 1; + expand(numNew * 2); + final double[] realAndImgData = new double[c.size() * 2]; + int i = 0; + for (final Complex val : c) { + final int i2 = i << 1; + realAndImgData[i2] = val.getReal(); + realAndImgData[i2 + 1] = val.getImaginary(); + i++; + } + final int numMoved = (size - index) * 2; + final int index2 = index << 1; + System.arraycopy(realAndImagParts, index2, realAndImagParts, index2 + numNew2, numMoved); + System.arraycopy(realAndImgData, 0, realAndImagParts, index2, realAndImgData.length); + size += numNew; + return numNew != 0; + } + + /** + * Removes the element at the specified position in this list. + * Shifts any subsequent elements to the left (subtracts one from their indices). + * + * @param index – Index of the element to be removed. + * @return the element that was removed from the list. + * @throws IndexOutOfBoundsException – if index isn't within the range of list. + */ + @Override + public Complex remove(int index) { + rangeCheck(index); + final int i = index << 1; + final int s = size << 1; + final Complex oldValue = Complex.ofCartesian(realAndImagParts[i], realAndImagParts[i + 1]); + final int numMoved = s - i - 2; + if (numMoved > 0) { + System.arraycopy(realAndImagParts, i + 2, realAndImagParts, i, numMoved); + } + size--; + realAndImagParts[s] = 0; + realAndImagParts[s + 1] = 0; + + return oldValue; + } + + /** + * Constructs an IndexOutOfBoundsException detail message. + * + * @param index – Index of the element. + * @return message detailing the exception. + */ + private String outOfBoundsMsg(int index) { + return INDEX_MSG + index + SIZE_MSG + size; + } + +} diff --git a/commons-numbers-complex-arrays/src/main/java/org/apache/commons/numbers/complex/arrays/package-info.java b/commons-numbers-complex-arrays/src/main/java/org/apache/commons/numbers/complex/arrays/package-info.java new file mode 100644 index 000000000..5897abaed --- /dev/null +++ b/commons-numbers-complex-arrays/src/main/java/org/apache/commons/numbers/complex/arrays/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ +/** + * Complex numbers. + */ +package org.apache.commons.numbers.complex.arrays; diff --git a/commons-numbers-complex-arrays/src/site/resources/profile.jacoco b/commons-numbers-complex-arrays/src/site/resources/profile.jacoco new file mode 100644 index 000000000..a12755f3b --- /dev/null +++ b/commons-numbers-complex-arrays/src/site/resources/profile.jacoco @@ -0,0 +1,17 @@ +# 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. +# ----------------------------------------------------------------------------- +# +# Empty file used to automatically trigger JaCoCo profile from commons parent pom diff --git a/commons-numbers-complex-arrays/src/site/resources/profile.japicmp b/commons-numbers-complex-arrays/src/site/resources/profile.japicmp new file mode 100644 index 000000000..6fe28ff34 --- /dev/null +++ b/commons-numbers-complex-arrays/src/site/resources/profile.japicmp @@ -0,0 +1,17 @@ +# 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. +# ----------------------------------------------------------------------------- +# +# Empty file used to automatically trigger profile from commons parent pom diff --git a/commons-numbers-complex-arrays/src/site/site.xml b/commons-numbers-complex-arrays/src/site/site.xml new file mode 100644 index 000000000..f84e9508b --- /dev/null +++ b/commons-numbers-complex-arrays/src/site/site.xml @@ -0,0 +1,37 @@ + + + + + Apache Commons Numbers + /images/commons_numbers.small.png + /index.html + + + + + + + + + + + + diff --git a/commons-numbers-complex-arrays/src/site/xdoc/index.xml b/commons-numbers-complex-arrays/src/site/xdoc/index.xml new file mode 100644 index 000000000..ac6848f95 --- /dev/null +++ b/commons-numbers-complex-arrays/src/site/xdoc/index.xml @@ -0,0 +1,40 @@ + + + + + + + + Commons Numbers Complex Arrays + + + + +
+

+ Commons Numbers provides number types and utilities. +

+ +

+ The "complex arrays" module contains utilities for working with complex number arrays and lists. +

+
+ + + +
diff --git a/commons-numbers-complex-arrays/src/test/java/org/apache/commons/numbers/complex/arrays/ComplexListTest.java b/commons-numbers-complex-arrays/src/test/java/org/apache/commons/numbers/complex/arrays/ComplexListTest.java new file mode 100644 index 000000000..8eb940eaf --- /dev/null +++ b/commons-numbers-complex-arrays/src/test/java/org/apache/commons/numbers/complex/arrays/ComplexListTest.java @@ -0,0 +1,222 @@ +/* + * 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.apache.commons.numbers.complex.arrays; + +import org.apache.commons.numbers.complex.Complex; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +public class ComplexListTest { + + @Test + void testGetAndSetMethod() { + + assertListOperation(list -> { + list.add(Complex.ofCartesian(42, 13)); + list.addAll(1, list); + list.addAll(list); + list.set(2, Complex.ofCartesian(200, 1)); + return list.get(2); + }); + + } + + @Test + void testAddAndAddAll() { + assertListOperation(list -> { + list.add(Complex.ofCartesian(42, 13)); + list.add(1, Complex.ofCartesian(11, 12)); + list.add(Complex.ofCartesian(13, 14)); + list.add(Complex.ofCartesian(15, 16)); + list.add(Complex.ofCartesian(17, 18)); + list.addAll(1, list); + list.add(Complex.ofCartesian(18, 19)); + list.add(Complex.ofCartesian(11, 12)); + list.add(Complex.ofCartesian(13, 14)); + list.add(Complex.ofCartesian(15, 16)); + list.add(Complex.ofCartesian(17, 18)); + return list.add(Complex.ofCartesian(11, 12)); + }); + + assertListOperation(list -> { + list.add(Complex.ofCartesian(42, 13)); + list.add(1, Complex.ofCartesian(11, 12)); + list.add(Complex.ofCartesian(13, 14)); + list.add(Complex.ofCartesian(15, 16)); + list.add(Complex.ofCartesian(17, 18)); + list.addAll(1, list); + list.add(Complex.ofCartesian(18, 19)); + list.add(Complex.ofCartesian(11, 12)); + list.add(Complex.ofCartesian(13, 14)); + list.add(Complex.ofCartesian(15, 16)); + list.add(Complex.ofCartesian(17, 18)); + list.add(1, Complex.ofCartesian(11, 12)); + }); + + ComplexList list = new ComplexList(); + assertListOperation(l -> { + l.addAll(list); + return l.addAll(0, list); + }); + } + + @Test + void testRemove() { + assertListOperation(list -> { + list.add(Complex.ofCartesian(42, 13)); + list.addAll(list); + list.remove(0); + return list.remove(0); + }); + } + + @Test + void testGetAndSetIndexOutOfBoundExceptions() { + ComplexList list = new ComplexList(); + list.add(Complex.ofCartesian(42, 13)); + list.addAll(1, list); + list.addAll(list); + list.set(2, Complex.ofCartesian(200, 1)); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.get(-1)); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.get(4)); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.get(5)); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.set(-2, Complex.ofCartesian(200, 1))); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.set(4, Complex.ofCartesian(200, 1))); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.set(5, Complex.ofCartesian(200, 1))); + } + + @Test + void testAddIndexOutOfBoundExceptions() { + ComplexList list = new ComplexList(); + list.add(Complex.ofCartesian(42, 13)); + list.addAll(1, list); + list.addAll(list); + list.set(2, Complex.ofCartesian(200, 1)); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> + list.add(-1, Complex.ofCartesian(42, 13))); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> + list.add(5, Complex.ofCartesian(42, 13))); + } + + @Test + void testRemoveIndexOutOfBoundExceptions() { + ComplexList list = new ComplexList(); + list.add(Complex.ofCartesian(42, 13)); + list.addAll(list); + list.remove(0); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.remove(1)); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.remove(-1)); + } + + @ParameterizedTest + @ValueSource(ints = {0, 1, 10}) + void testConstructor(int size) { + ComplexList list = new ComplexList(size); + list.add(Complex.ofCartesian(20, 12)); + list.addAll(list); + Assertions.assertEquals(Complex.ofCartesian(20, 12), list.get(0)); + Assertions.assertEquals(Complex.ofCartesian(20, 12), list.get(1)); + } + + @Test + void testCapacityExceptions() { + Assertions.assertDoesNotThrow(() -> new ComplexList().ensureCapacity(64), () -> "unexpected exception for ensureCapacity(64)"); + + assertOutOfMemoryErrorOnConstructor(() -> new ComplexList(ComplexList.MAX_CAPACITY + 1)); + + assertOutOfMemoryErrorOnEnsureCapacity(() -> new ComplexList().ensureCapacity(ComplexList.MAX_CAPACITY + 1)); + + int oldCapacity = ComplexList.MAX_CAPACITY * 2 / 3; + assertOutOfMemoryErrorOnArrayCopy(() -> new ComplexList().ensureCapacity((int) (oldCapacity * 1.4)), () -> "unexpected exception for ensureCapacity(" + (oldCapacity * 1.4) + ")"); + assertOutOfMemoryErrorOnArrayCopy(() -> new ComplexList().ensureCapacity((int) (oldCapacity * 1.5)), () -> "unexpected exception for ensureCapacity(" + (oldCapacity * 1.5) + ")"); + + assertOutOfMemoryErrorOnEnsureCapacity(() -> new ComplexList().ensureCapacity((int) (oldCapacity * 1.6))); + + } + + private static void assertOutOfMemoryErrorOnArrayCopy(Executable executable, Supplier msgSupplier) { + try { + executable.execute(); + } catch (Throwable oom) { + Assertions.assertSame(oom.getClass(), OutOfMemoryError.class, msgSupplier); + Assertions.assertTrue(oom.getStackTrace().length > 1, msgSupplier); + StackTraceElement firstStackElement = oom.getStackTrace()[0]; + Assertions.assertEquals("copyOf", firstStackElement.getMethodName(), msgSupplier); + Assertions.assertEquals(Arrays.class.getName(), firstStackElement.getClassName(), msgSupplier); + } + } + + private static void assertOutOfMemoryErrorOnEnsureCapacity(Executable executable) { + try { + executable.execute(); + Assertions.assertTrue(false, "failed to throw OutOfMemory Error"); + } catch (Throwable oom) { + Assertions.assertSame(oom.getClass(), OutOfMemoryError.class); + Assertions.assertTrue(oom.getStackTrace().length > 1); + StackTraceElement firstStackElement = oom.getStackTrace()[0]; + Assertions.assertEquals(ComplexList.class.getName(), firstStackElement.getClassName()); + Assertions.assertEquals("ensureCapacityInternal", firstStackElement.getMethodName()); + } + } + + private static void assertOutOfMemoryErrorOnConstructor(Executable executable) { + try { + executable.execute(); + Assertions.assertTrue(false, "failed to throw OutOfMemory Error"); + } catch (Throwable oom) { + Assertions.assertSame(oom.getClass(), OutOfMemoryError.class); + Assertions.assertTrue(oom.getStackTrace().length > 1); + StackTraceElement firstStackElement = oom.getStackTrace()[0]; + Assertions.assertEquals(ComplexList.class.getName(), firstStackElement.getClassName()); + Assertions.assertEquals("", firstStackElement.getMethodName()); + } + } + + private static void assertListOperation(Function, T> operation, + List l1, List l2) { + T t1 = operation.apply(l1); + T t2 = operation.apply(l2); + Assertions.assertEquals(t1, t2); + Assertions.assertEquals(l1, l2); + } + + private static void assertListOperation(Function, T> operation) { + assertListOperation(operation, new ArrayList<>(), new ComplexList()); + } + + private static void assertListOperation(Consumer> operation) { + assertListOperation(operation, new ArrayList<>(), new ComplexList()); + } + + private static void assertListOperation(Consumer> operation, + List l1, List l2) { + operation.accept(l1); + operation.accept(l2); + Assertions.assertEquals(l1, l2); + } +} diff --git a/pom.xml b/pom.xml index cce4ce398..5bb4f3666 100644 --- a/pom.xml +++ b/pom.xml @@ -109,6 +109,7 @@ commons-numbers-core commons-numbers-complex + commons-numbers-complex-arrays commons-numbers-primes commons-numbers-quaternion commons-numbers-fraction @@ -150,6 +151,11 @@ commons-numbers-complex ${project.version} + + org.apache.commons + commons-numbers-complex-arrays + ${project.version} + org.apache.commons commons-numbers-fraction From 630d2f0584c36f74651646637f5349b0635e6c5a Mon Sep 17 00:00:00 2001 From: Sumanth Rajkumar Date: Tue, 30 Aug 2022 18:02:05 -0400 Subject: [PATCH 2/3] NUMBERS-186 updated test class and logics in ComplexList --- commons-numbers-complex-arrays/pom.xml | 6 +- .../numbers/complex/arrays/ComplexList.java | 156 ++++++------- .../src/site/site.xml | 5 - .../src/site/xdoc/index.xml | 8 - .../complex/arrays/ComplexListTest.java | 206 +++++++++--------- 5 files changed, 175 insertions(+), 206 deletions(-) diff --git a/commons-numbers-complex-arrays/pom.xml b/commons-numbers-complex-arrays/pom.xml index 1d4ea853c..ce65989ff 100644 --- a/commons-numbers-complex-arrays/pom.xml +++ b/commons-numbers-complex-arrays/pom.xml @@ -62,6 +62,10 @@ ${basedir}/.. + + @@ -73,4 +77,4 @@ - \ No newline at end of file + diff --git a/commons-numbers-complex-arrays/src/main/java/org/apache/commons/numbers/complex/arrays/ComplexList.java b/commons-numbers-complex-arrays/src/main/java/org/apache/commons/numbers/complex/arrays/ComplexList.java index bc58b86e4..82e4e3d20 100644 --- a/commons-numbers-complex-arrays/src/main/java/org/apache/commons/numbers/complex/arrays/ComplexList.java +++ b/commons-numbers-complex-arrays/src/main/java/org/apache/commons/numbers/complex/arrays/ComplexList.java @@ -29,8 +29,11 @@ * this class provides methods to manipulate the size of the array that is used internally to store the list. * *

Each ComplexList instance has a capacity. The capacity is half the size of the double array used to store the elements - * in the list. It is always at least twice as large as the list size. As elements are added to an ComplexList, - * its capacity grows automatically.

+ * in the list. As elements are added to an ComplexList, its capacity grows automatically. + * The complex number is stored using an interleaved format and so the maximum number of elements that may be added is + * approximately 2^30. This is half the maximum capacity of java.util.ArrayList. + * The memory usage is more efficient than using a List of Complex objects as the underlying numbers are not stored + * using instances of Complex.

* *

An application can increase the capacity of an ComplexList instance before adding a large number of elements * using the ensureCapacity operation. This may reduce the amount of incremental reallocation.

@@ -43,7 +46,7 @@ public class ComplexList extends AbstractList { private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 9; /** Max capacity for size of complex numbers in the list. */ - public static final int MAX_CAPACITY = MAX_ARRAY_SIZE / 2; + protected static final int MAX_CAPACITY = MAX_ARRAY_SIZE / 2; /** error in case of allocation above max capacity. */ private static final String OOM_ERROR_STRING = "cannot allocate capacity %s greater than max " + MAX_CAPACITY; @@ -70,12 +73,12 @@ public class ComplexList extends AbstractList { * Constructs an empty list with the specified capacity, if it's * greater than the default capacity of 8. * - * @param capacity - Capacity of list. - * @throws OutOfMemoryError - if the {@code capacity} is greater than {@code MAX_CAPACITY}. + * @param capacity Capacity of list. + * @throws IllegalArgumentException if the {@code capacity} is greater than {@code MAX_CAPACITY}. */ public ComplexList(int capacity) { if (capacity > MAX_CAPACITY) { - throw new OutOfMemoryError(String.format(OOM_ERROR_STRING, capacity)); + throw new IllegalArgumentException(String.format(OOM_ERROR_STRING, capacity)); } final int arrayLength = Math.max(DEFAULT_CAPACITY, capacity) * 2; realAndImagParts = new double[arrayLength]; @@ -89,9 +92,7 @@ public ComplexList() { } /** - * Gives the size of this ComplexList. - * - * @return the number of elements it contains. + * {@inheritDoc} */ @Override public int size() { @@ -101,8 +102,8 @@ public int size() { /** * Checks if the given index is in range. * - * @param index - Index of the element to range check. - * @throws IndexOutOfBoundsException - if index isn't within the range. + * @param index Index of the element to range check. + * @throws IndexOutOfBoundsException if index isn't within the range. */ private void rangeCheck(int index) { if (index < 0 || index >= size) { @@ -113,8 +114,8 @@ private void rangeCheck(int index) { /** * A version of rangeCheck used by add and addAll. * - * @param index - Index of the element to range check. - * @throws IndexOutOfBoundsException - if index isn't within the range of list. + * @param index Index of the element to range check. + * @throws IndexOutOfBoundsException if index isn't within the range of list. */ private void rangeCheckForInsert(int index) { if (index < 0 || index > size) { @@ -125,7 +126,7 @@ private void rangeCheckForInsert(int index) { /** * Gets the complex number \( (a + i b) \) at the indexed position of the list. * - * @param index - Index of the element to get. + * @param index {@inheritDoc} * @return the complex number. */ @Override @@ -139,9 +140,9 @@ public Complex get(int index) { * Replaces the element at the specified position in this list with the specified element's * real and imaginary parts. No range checks are done. * - * @param index - Index of the element to replace. - * @param real - Real part \( a \) of the complex number \( (a +ib) \). - * @param imaginary - Imaginary part \( b \) of the complex number \( (a +ib) \). + * @param index Index of the element to replace. + * @param real Real part \( a \) of the complex number \( (a +ib) \). + * @param imaginary Imaginary part \( b \) of the complex number \( (a +ib) \). */ private void setNoRangeCheck(int index, double real, double imaginary) { final int i = index << 1; @@ -150,12 +151,12 @@ private void setNoRangeCheck(int index, double real, double imaginary) { } /** - * Replaces the element at the specified position in this list with the specified element. - - * @param index - Index of the element to replace. - * @param element - Element to be stored at the specified position. - * @return the element previously at the specified position. - * @throws IndexOutOfBoundsException - if index isn't within the range of list. + * {@inheritDoc} + * + * @param index {@inheritDoc} + * @param element Complex element to be set. + * @return {@inheritDoc} + * @throws IndexOutOfBoundsException {@inheritDoc} */ @Override public Complex set(int index, Complex element) { @@ -170,33 +171,24 @@ public Complex set(int index, Complex element) { * Increases the capacity of this ComplexList instance, if necessary, to ensure that it can hold at * least the number of elements specified by the minimum capacity argument. * - * @param minCapacity – Desired minimum capacity. - * @throws OutOfMemoryError - if the {@code minCapacity} is greater than {@code MAX_ARRAY_SIZE}. - */ - public void ensureCapacity(int minCapacity) { - ensureCapacityInternal(minCapacity); - } - - /** - * Increases the capacity of this ComplexList instance, if necessary, to ensure that it can hold at - * least the number of elements specified by the minimum capacity argument. - * - * @param minCapacity – Desired minimum capacity. + * @param minCapacity Desired minimum capacity. * @return the backing double array. - * @throws OutOfMemoryError - if the {@code minCapacity} is greater than {@code MAX_ARRAY_SIZE}. + * @throws OutOfMemoryError if the {@code minCapacity} is greater than {@code MAX_ARRAY_SIZE}. */ private double[] ensureCapacityInternal(int minCapacity) { modCount++; - final long minArrayCapacity = minCapacity * 2L; + final long minArrayCapacity = Integer.toUnsignedLong(minCapacity) << 1; if (minArrayCapacity > MAX_ARRAY_SIZE) { throw new OutOfMemoryError(String.format(OOM_ERROR_STRING, minArrayCapacity)); } final long oldArrayCapacity = realAndImagParts.length; if (minArrayCapacity > oldArrayCapacity) { long newArrayCapacity = oldArrayCapacity + (oldArrayCapacity >> 1); - if (newArrayCapacity % 2 != 0) { - ++newArrayCapacity; - } + // Round-odd up to even + newArrayCapacity += newArrayCapacity & 1; + + // Ensure minArrayCapacity <= newArrayCapacity <= MAX_ARRAY_SIZE + // Note: At this point minArrayCapacity <= MAX_ARRAY_SIZE if (newArrayCapacity > MAX_ARRAY_SIZE) { newArrayCapacity = MAX_ARRAY_SIZE; } else if (newArrayCapacity < minArrayCapacity) { @@ -211,17 +203,17 @@ private double[] ensureCapacityInternal(int minCapacity) { * Increases the capacity of this ComplexList instance, if necessary, to ensure that it can hold at * least an additional number of elements specified by the capacity argument. * - * @param capacity - Desired capacity. + * @param capacity Desired capacity. */ private void expand(int capacity) { ensureCapacityInternal(size + capacity); } /** - * Appends the specified complex element to the end of this list. + * {@inheritDoc} * - * @param element - Complex element to be appended to this list. - * @return true after element has been added and size has been updated. + * @param element Complex element to be appended to this list. + * @return {@inheritDoc} */ @Override public boolean add(Complex element) { @@ -237,12 +229,11 @@ public boolean add(Complex element) { } /** - * Inserts the specified element at the specified position in this list. Shifts the element currently at that - * position (if any) and any subsequent elements to the right (adds one to their indices). + * {@inheritDoc} * - * @param index – Index at which the specified element is to be inserted. - * @param element – Complex element to be inserted. - * @throws IndexOutOfBoundsException – if index isn't within the range of list. + * @param index {@inheritDoc} + * @param element Complex element to be inserted. + * @throws IndexOutOfBoundsException {@inheritDoc} */ @Override public void add(int index, Complex element) { @@ -252,64 +243,56 @@ public void add(int index, Complex element) { e = ensureCapacityInternal(size + 1); } final int i = index << 1; - System.arraycopy(e, 2 * index, e, i + 2, (size * 2) - i); + final int s = size << 1; + System.arraycopy(e, i, e, i + 2, s - i); e[i] = element.real(); e[i + 1] = element.imag(); size++; } /** - * Appends all the elements in the specified collection to the end of this list, in the order that they are - * returned by the specified collection's Iterator. The behavior of this operation is undefined if the - * specified collection is modified while the operation is in progress. - * (This implies that the behavior of this call is undefined if the specified collection is this list, - * and this list is nonempty.) + * {@inheritDoc} * - * @param c – Collection containing elements to be added to this list. - * @return true if this list changed as a result of the call. - * @throws NullPointerException – if the specified collection is null. + * @param c {@inheritDoc} + * @return {@inheritDoc} + * @throws NullPointerException {@inheritDoc} */ @Override public boolean addAll(Collection c) { final int numNew = c.size(); - expand(numNew * 2); - double[] realAndImgData = new double[c.size() * 2]; + expand(numNew); + double[] realAndImgData = new double[numNew * 2]; int i = 0; for (final Complex val : c) { - final int i2 = i << 1; - realAndImgData[i2] = val.getReal(); - realAndImgData[i2 + 1] = val.getImaginary(); - i++; + realAndImgData[i++] = val.getReal(); + realAndImgData[i++] = val.getImaginary(); } - System.arraycopy(realAndImgData, 0, realAndImagParts, size * 2, realAndImgData.length); + final int s = size << 1; + System.arraycopy(realAndImgData, 0, realAndImagParts, s, realAndImgData.length); size += numNew; return numNew != 0; } /** - * Inserts all the elements in the specified collection into this list, starting at the specified position. - * Shifts the element currently at that position (if any) and any subsequent elements to the right (increases their indices). - * The new elements will appear in the list in the order that they are returned by the specified collection's iterator. + * {@inheritDoc} * - * @param index – Index at which to insert the first element from the specified collection. - * @param c – Collection containing elements to be added to this list. - * @return true if this list changed as a result of the call. - * @throws IndexOutOfBoundsException – if index isn't within the range of list. - * @throws NullPointerException – if the specified collection is null. + * @param index {@inheritDoc} + * @param c {@inheritDoc} + * @return {@inheritDoc} + * @throws IndexOutOfBoundsException {@inheritDoc} + * @throws NullPointerException {@inheritDoc} */ @Override public boolean addAll(int index, Collection c) { rangeCheckForInsert(index); final int numNew = c.size(); final int numNew2 = numNew << 1; - expand(numNew * 2); - final double[] realAndImgData = new double[c.size() * 2]; + expand(numNew); + final double[] realAndImgData = new double[numNew * 2]; int i = 0; for (final Complex val : c) { - final int i2 = i << 1; - realAndImgData[i2] = val.getReal(); - realAndImgData[i2 + 1] = val.getImaginary(); - i++; + realAndImgData[i++] = val.getReal(); + realAndImgData[i++] = val.getImaginary(); } final int numMoved = (size - index) * 2; final int index2 = index << 1; @@ -320,16 +303,16 @@ public boolean addAll(int index, Collection c) { } /** - * Removes the element at the specified position in this list. - * Shifts any subsequent elements to the left (subtracts one from their indices). + * {@inheritDoc} * - * @param index – Index of the element to be removed. - * @return the element that was removed from the list. - * @throws IndexOutOfBoundsException – if index isn't within the range of list. + * @param index {@inheritDoc} + * @return {@inheritDoc} + * @throws IndexOutOfBoundsException {@inheritDoc} */ @Override public Complex remove(int index) { rangeCheck(index); + modCount++; final int i = index << 1; final int s = size << 1; final Complex oldValue = Complex.ofCartesian(realAndImagParts[i], realAndImagParts[i + 1]); @@ -338,8 +321,6 @@ public Complex remove(int index) { System.arraycopy(realAndImagParts, i + 2, realAndImagParts, i, numMoved); } size--; - realAndImagParts[s] = 0; - realAndImagParts[s + 1] = 0; return oldValue; } @@ -347,11 +328,10 @@ public Complex remove(int index) { /** * Constructs an IndexOutOfBoundsException detail message. * - * @param index – Index of the element. + * @param index Index of the element. * @return message detailing the exception. */ private String outOfBoundsMsg(int index) { return INDEX_MSG + index + SIZE_MSG + size; } - } diff --git a/commons-numbers-complex-arrays/src/site/site.xml b/commons-numbers-complex-arrays/src/site/site.xml index f84e9508b..f6746edee 100644 --- a/commons-numbers-complex-arrays/src/site/site.xml +++ b/commons-numbers-complex-arrays/src/site/site.xml @@ -27,11 +27,6 @@ - - - diff --git a/commons-numbers-complex-arrays/src/site/xdoc/index.xml b/commons-numbers-complex-arrays/src/site/xdoc/index.xml index ac6848f95..dc008f9da 100644 --- a/commons-numbers-complex-arrays/src/site/xdoc/index.xml +++ b/commons-numbers-complex-arrays/src/site/xdoc/index.xml @@ -24,17 +24,9 @@ - -
-

- Commons Numbers provides number types and utilities. -

-

The "complex arrays" module contains utilities for working with complex number arrays and lists.

-
- diff --git a/commons-numbers-complex-arrays/src/test/java/org/apache/commons/numbers/complex/arrays/ComplexListTest.java b/commons-numbers-complex-arrays/src/test/java/org/apache/commons/numbers/complex/arrays/ComplexListTest.java index 8eb940eaf..47370d9ba 100644 --- a/commons-numbers-complex-arrays/src/test/java/org/apache/commons/numbers/complex/arrays/ComplexListTest.java +++ b/commons-numbers-complex-arrays/src/test/java/org/apache/commons/numbers/complex/arrays/ComplexListTest.java @@ -20,22 +20,18 @@ import org.apache.commons.numbers.complex.Complex; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.function.Consumer; import java.util.function.Function; -import java.util.function.Supplier; +import java.util.stream.IntStream; public class ComplexListTest { @Test void testGetAndSetMethod() { - assertListOperation(list -> { list.add(Complex.ofCartesian(42, 13)); list.addAll(1, list); @@ -43,41 +39,75 @@ void testGetAndSetMethod() { list.set(2, Complex.ofCartesian(200, 1)); return list.get(2); }); - } @Test void testAddAndAddAll() { + List l1 = new ArrayList<>(); + List l2 = new ComplexList(); + assertListOperation(list -> list.add(Complex.ofCartesian(1, 2)), l1, l2); + assertListOperation(list -> { + list.add(1, Complex.ofCartesian(10, 20)); + return Boolean.TRUE; + }, l1, l2); + assertListOperation(list -> list.add(Complex.ofCartesian(13, 14)), l1, l2); + assertListOperation(list -> list.add(Complex.ofCartesian(15, 16)), l1, l2); + assertListOperation(list -> list.add(Complex.ofCartesian(17, 18)), l1, l2); assertListOperation(list -> { - list.add(Complex.ofCartesian(42, 13)); - list.add(1, Complex.ofCartesian(11, 12)); - list.add(Complex.ofCartesian(13, 14)); - list.add(Complex.ofCartesian(15, 16)); - list.add(Complex.ofCartesian(17, 18)); list.addAll(1, list); - list.add(Complex.ofCartesian(18, 19)); - list.add(Complex.ofCartesian(11, 12)); - list.add(Complex.ofCartesian(13, 14)); - list.add(Complex.ofCartesian(15, 16)); - list.add(Complex.ofCartesian(17, 18)); - return list.add(Complex.ofCartesian(11, 12)); - }); - + return Boolean.TRUE; + }, l1, l2); + assertListOperation(list -> list.add(Complex.ofCartesian(19, 20)), l1, l2); + assertListOperation(list -> list.add(Complex.ofCartesian(21, 22)), l1, l2); + assertListOperation(list -> list.add(Complex.ofCartesian(23, 24)), l1, l2); + + List l3 = new ArrayList<>(); + List l4 = new ComplexList(); + assertListOperation(list -> list.add(Complex.ofCartesian(1, 2)), l3, l4); + assertListOperation(list -> { + list.add(1, Complex.ofCartesian(10, 20)); + return Boolean.TRUE; + }, l3, l4); + assertListOperation(list -> list.add(Complex.ofCartesian(13, 14)), l3, l4); + assertListOperation(list -> list.add(Complex.ofCartesian(15, 16)), l3, l4); + assertListOperation(list -> list.add(Complex.ofCartesian(17, 18)), l3, l4); assertListOperation(list -> { - list.add(Complex.ofCartesian(42, 13)); - list.add(1, Complex.ofCartesian(11, 12)); - list.add(Complex.ofCartesian(13, 14)); - list.add(Complex.ofCartesian(15, 16)); - list.add(Complex.ofCartesian(17, 18)); list.addAll(1, list); - list.add(Complex.ofCartesian(18, 19)); - list.add(Complex.ofCartesian(11, 12)); - list.add(Complex.ofCartesian(13, 14)); - list.add(Complex.ofCartesian(15, 16)); - list.add(Complex.ofCartesian(17, 18)); - list.add(1, Complex.ofCartesian(11, 12)); - }); + return Boolean.TRUE; + }, l3, l4); + assertListOperation(list -> list.add(Complex.ofCartesian(19, 20)), l3, l4); + assertListOperation(list -> list.add(Complex.ofCartesian(21, 22)), l3, l4); + assertListOperation(list -> { + list.add(1, Complex.ofCartesian(10, 20)); + return Boolean.TRUE; + }, l3, l4); + + //Testing branch condition (newArrayCapacity < minArrayCapacity) in ensureCapacity + ComplexList list1 = new ComplexList(); + int size = 5; + IntStream.range(0, size).mapToObj(i -> Complex.ofCartesian(i, -i)).forEach(list1::add); + + List l5 = new ArrayList<>(); + List l6 = new ComplexList(); + assertListOperation(list -> list.add(Complex.ofCartesian(1, 2)), l5, l6); + assertListOperation(list -> { + list.addAll(list); + return Boolean.TRUE; + }, l5, l6); + assertListOperation(list -> { + list.addAll(list); + return Boolean.TRUE; + }, l5, l6); + assertListOperation(list -> { + list.addAll(list); + return Boolean.TRUE; + }, l5, l6); + assertListOperation(list -> { + list.addAll(list1); + return Boolean.TRUE; + }, l5, l6); + //Test for adding an empty list to an empty list ComplexList list = new ComplexList(); assertListOperation(l -> { l.addAll(list); @@ -98,29 +128,30 @@ void testRemove() { @Test void testGetAndSetIndexOutOfBoundExceptions() { ComplexList list = new ComplexList(); - list.add(Complex.ofCartesian(42, 13)); - list.addAll(1, list); - list.addAll(list); - list.set(2, Complex.ofCartesian(200, 1)); + // Empty list throws + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.get(0)); + int size = 5; + IntStream.range(0, size).mapToObj(i -> Complex.ofCartesian(i, -i)).forEach(list::add); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.get(-1)); - Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.get(4)); - Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.get(5)); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.get(size)); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.get(size + 1)); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.set(-2, Complex.ofCartesian(200, 1))); - Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.set(4, Complex.ofCartesian(200, 1))); - Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.set(5, Complex.ofCartesian(200, 1))); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.set(size, Complex.ofCartesian(200, 1))); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.set(size + 1, Complex.ofCartesian(200, 1))); } @Test void testAddIndexOutOfBoundExceptions() { ComplexList list = new ComplexList(); - list.add(Complex.ofCartesian(42, 13)); - list.addAll(1, list); - list.addAll(list); - list.set(2, Complex.ofCartesian(200, 1)); + int size = 5; + IntStream.range(0, size).mapToObj(i -> Complex.ofCartesian(i, -i)).forEach(list::add); + Assertions.assertThrows(IndexOutOfBoundsException.class, () -> list.add(-1, Complex.ofCartesian(42, 13))); Assertions.assertThrows(IndexOutOfBoundsException.class, () -> - list.add(5, Complex.ofCartesian(42, 13))); + list.add(size + 1, Complex.ofCartesian(42, 13))); } @Test @@ -136,65 +167,29 @@ void testRemoveIndexOutOfBoundExceptions() { @ParameterizedTest @ValueSource(ints = {0, 1, 10}) void testConstructor(int size) { - ComplexList list = new ComplexList(size); - list.add(Complex.ofCartesian(20, 12)); - list.addAll(list); - Assertions.assertEquals(Complex.ofCartesian(20, 12), list.get(0)); - Assertions.assertEquals(Complex.ofCartesian(20, 12), list.get(1)); + List l1 = new ArrayList<>(size); + List l2 = new ComplexList(size); + Assertions.assertEquals(l1, l2); + assertListOperation(l -> l.add(Complex.ofCartesian(10, 20)), l1, l2); + assertListOperation(l -> { + l.add(1, Complex.ofCartesian(10, 20)); + return Boolean.TRUE; + }, l1, l2); + assertListOperation(l -> l.addAll(1, l), l1, l2); } @Test void testCapacityExceptions() { - Assertions.assertDoesNotThrow(() -> new ComplexList().ensureCapacity(64), () -> "unexpected exception for ensureCapacity(64)"); - - assertOutOfMemoryErrorOnConstructor(() -> new ComplexList(ComplexList.MAX_CAPACITY + 1)); - - assertOutOfMemoryErrorOnEnsureCapacity(() -> new ComplexList().ensureCapacity(ComplexList.MAX_CAPACITY + 1)); - int oldCapacity = ComplexList.MAX_CAPACITY * 2 / 3; - assertOutOfMemoryErrorOnArrayCopy(() -> new ComplexList().ensureCapacity((int) (oldCapacity * 1.4)), () -> "unexpected exception for ensureCapacity(" + (oldCapacity * 1.4) + ")"); - assertOutOfMemoryErrorOnArrayCopy(() -> new ComplexList().ensureCapacity((int) (oldCapacity * 1.5)), () -> "unexpected exception for ensureCapacity(" + (oldCapacity * 1.5) + ")"); - - assertOutOfMemoryErrorOnEnsureCapacity(() -> new ComplexList().ensureCapacity((int) (oldCapacity * 1.6))); - - } - - private static void assertOutOfMemoryErrorOnArrayCopy(Executable executable, Supplier msgSupplier) { - try { - executable.execute(); - } catch (Throwable oom) { - Assertions.assertSame(oom.getClass(), OutOfMemoryError.class, msgSupplier); - Assertions.assertTrue(oom.getStackTrace().length > 1, msgSupplier); - StackTraceElement firstStackElement = oom.getStackTrace()[0]; - Assertions.assertEquals("copyOf", firstStackElement.getMethodName(), msgSupplier); - Assertions.assertEquals(Arrays.class.getName(), firstStackElement.getClassName(), msgSupplier); - } - } + Assertions.assertThrows(IllegalArgumentException.class, () -> new ComplexList(ComplexList.MAX_CAPACITY + 1)); - private static void assertOutOfMemoryErrorOnEnsureCapacity(Executable executable) { - try { - executable.execute(); - Assertions.assertTrue(false, "failed to throw OutOfMemory Error"); - } catch (Throwable oom) { - Assertions.assertSame(oom.getClass(), OutOfMemoryError.class); - Assertions.assertTrue(oom.getStackTrace().length > 1); - StackTraceElement firstStackElement = oom.getStackTrace()[0]; - Assertions.assertEquals(ComplexList.class.getName(), firstStackElement.getClassName()); - Assertions.assertEquals("ensureCapacityInternal", firstStackElement.getMethodName()); - } - } + // Set-up required sizes + ComplexList list = new ComplexList(); + List l = new SizedList(Integer.MAX_VALUE); + Assertions.assertThrows(OutOfMemoryError.class, () -> list.addAll(l)); - private static void assertOutOfMemoryErrorOnConstructor(Executable executable) { - try { - executable.execute(); - Assertions.assertTrue(false, "failed to throw OutOfMemory Error"); - } catch (Throwable oom) { - Assertions.assertSame(oom.getClass(), OutOfMemoryError.class); - Assertions.assertTrue(oom.getStackTrace().length > 1); - StackTraceElement firstStackElement = oom.getStackTrace()[0]; - Assertions.assertEquals(ComplexList.class.getName(), firstStackElement.getClassName()); - Assertions.assertEquals("", firstStackElement.getMethodName()); - } + List l2 = new SizedList(ComplexList.MAX_CAPACITY + 1); + Assertions.assertThrows(OutOfMemoryError.class, () -> list.addAll(l2)); } private static void assertListOperation(Function, T> operation, @@ -209,14 +204,17 @@ private static void assertListOperation(Function, T> operation assertListOperation(operation, new ArrayList<>(), new ComplexList()); } - private static void assertListOperation(Consumer> operation) { - assertListOperation(operation, new ArrayList<>(), new ComplexList()); - } + private static class SizedList extends ArrayList { + private final int fixedSize; - private static void assertListOperation(Consumer> operation, - List l1, List l2) { - operation.accept(l1); - operation.accept(l2); - Assertions.assertEquals(l1, l2); + SizedList(int fixedSize) { + super(); + this.fixedSize = fixedSize; + } + + @Override + public int size() { + return fixedSize; + } } } From 836f877eff88a9acbe397616f1ae0333421325d5 Mon Sep 17 00:00:00 2001 From: Sumanth Rajkumar Date: Thu, 1 Sep 2022 13:01:12 -0400 Subject: [PATCH 3/3] NUMBERS-186 more updates in test class --- commons-numbers-complex-arrays/pom.xml | 16 ----- .../numbers/complex/arrays/ComplexList.java | 64 ++++++++----------- .../src/site/resources/profile.japicmp | 2 +- .../src/site/xdoc/index.xml | 11 +++- .../complex/arrays/ComplexListTest.java | 33 +++++----- 5 files changed, 50 insertions(+), 76 deletions(-) diff --git a/commons-numbers-complex-arrays/pom.xml b/commons-numbers-complex-arrays/pom.xml index ce65989ff..1f2f2f4e5 100644 --- a/commons-numbers-complex-arrays/pom.xml +++ b/commons-numbers-complex-arrays/pom.xml @@ -34,22 +34,6 @@ org.apache.commons commons-numbers-complex
- - org.apache.commons - commons-rng-client-api - 1.4 - test - - - org.apache.commons - commons-rng-simple - test - - - org.apache.commons - commons-numbers-core - test - diff --git a/commons-numbers-complex-arrays/src/main/java/org/apache/commons/numbers/complex/arrays/ComplexList.java b/commons-numbers-complex-arrays/src/main/java/org/apache/commons/numbers/complex/arrays/ComplexList.java index 82e4e3d20..969832dcc 100644 --- a/commons-numbers-complex-arrays/src/main/java/org/apache/commons/numbers/complex/arrays/ComplexList.java +++ b/commons-numbers-complex-arrays/src/main/java/org/apache/commons/numbers/complex/arrays/ComplexList.java @@ -40,29 +40,40 @@ */ public class ComplexList extends AbstractList { - /** The maximum size of array to allocate. + /** + * The maximum size of array to allocate. * Ensuring Max capacity is even with additional space for vm array headers. */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 9; - /** Max capacity for size of complex numbers in the list. */ - protected static final int MAX_CAPACITY = MAX_ARRAY_SIZE / 2; + /** + * Max capacity for size of complex numbers in the list. + */ + private static final int MAX_CAPACITY = MAX_ARRAY_SIZE / 2; - /** error in case of allocation above max capacity. */ + /** + * error in case of allocation above max capacity. + */ private static final String OOM_ERROR_STRING = "cannot allocate capacity %s greater than max " + MAX_CAPACITY; - /** Default initial capacity. */ + /** + * Default initial capacity. + */ private static final int DEFAULT_CAPACITY = 8; - /** Size label message. */ + /** + * Size label message. + */ private static final String SIZE_MSG = ", Size: "; - /** Index position label message. */ + /** + * Index position label message. + */ private static final String INDEX_MSG = "Index: "; /** * The double array buffer into which the elements of the ComplexList are stored. */ - protected double[] realAndImagParts; + private double[] realAndImagParts; /** * Size of ComplexList. @@ -70,8 +81,7 @@ public class ComplexList extends AbstractList { private int size; /** - * Constructs an empty list with the specified capacity, if it's - * greater than the default capacity of 8. + * Constructs an empty list up to the specified capacity without a memory reallocation. * * @param capacity Capacity of list. * @throws IllegalArgumentException if the {@code capacity} is greater than {@code MAX_CAPACITY}. @@ -85,7 +95,7 @@ public ComplexList(int capacity) { } /** - * Constructs an empty list with the default capacity of 8. + * Constructs an empty list. */ public ComplexList() { realAndImagParts = new double[DEFAULT_CAPACITY * 2]; @@ -106,7 +116,7 @@ public int size() { * @throws IndexOutOfBoundsException if index isn't within the range. */ private void rangeCheck(int index) { - if (index < 0 || index >= size) { + if (index >= size) { throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } } @@ -126,7 +136,7 @@ private void rangeCheckForInsert(int index) { /** * Gets the complex number \( (a + i b) \) at the indexed position of the list. * - * @param index {@inheritDoc} + * {@inheritDoc} * @return the complex number. */ @Override @@ -153,10 +163,7 @@ private void setNoRangeCheck(int index, double real, double imaginary) { /** * {@inheritDoc} * - * @param index {@inheritDoc} * @param element Complex element to be set. - * @return {@inheritDoc} - * @throws IndexOutOfBoundsException {@inheritDoc} */ @Override public Complex set(int index, Complex element) { @@ -194,7 +201,7 @@ private double[] ensureCapacityInternal(int minCapacity) { } else if (newArrayCapacity < minArrayCapacity) { newArrayCapacity = minArrayCapacity; } - realAndImagParts = Arrays.copyOf(realAndImagParts, (int)newArrayCapacity); + realAndImagParts = Arrays.copyOf(realAndImagParts, (int) newArrayCapacity); } return realAndImagParts; } @@ -212,8 +219,7 @@ private void expand(int capacity) { /** * {@inheritDoc} * - * @param element Complex element to be appended to this list. - * @return {@inheritDoc} + * @param element Complex element to be appended to this list */ @Override public boolean add(Complex element) { @@ -230,10 +236,6 @@ public boolean add(Complex element) { /** * {@inheritDoc} - * - * @param index {@inheritDoc} - * @param element Complex element to be inserted. - * @throws IndexOutOfBoundsException {@inheritDoc} */ @Override public void add(int index, Complex element) { @@ -252,10 +254,6 @@ public void add(int index, Complex element) { /** * {@inheritDoc} - * - * @param c {@inheritDoc} - * @return {@inheritDoc} - * @throws NullPointerException {@inheritDoc} */ @Override public boolean addAll(Collection c) { @@ -275,12 +273,6 @@ public boolean addAll(Collection c) { /** * {@inheritDoc} - * - * @param index {@inheritDoc} - * @param c {@inheritDoc} - * @return {@inheritDoc} - * @throws IndexOutOfBoundsException {@inheritDoc} - * @throws NullPointerException {@inheritDoc} */ @Override public boolean addAll(int index, Collection c) { @@ -304,10 +296,6 @@ public boolean addAll(int index, Collection c) { /** * {@inheritDoc} - * - * @param index {@inheritDoc} - * @return {@inheritDoc} - * @throws IndexOutOfBoundsException {@inheritDoc} */ @Override public Complex remove(int index) { @@ -321,7 +309,6 @@ public Complex remove(int index) { System.arraycopy(realAndImagParts, i + 2, realAndImagParts, i, numMoved); } size--; - return oldValue; } @@ -334,4 +321,5 @@ public Complex remove(int index) { private String outOfBoundsMsg(int index) { return INDEX_MSG + index + SIZE_MSG + size; } + } diff --git a/commons-numbers-complex-arrays/src/site/resources/profile.japicmp b/commons-numbers-complex-arrays/src/site/resources/profile.japicmp index 6fe28ff34..f7c126cb4 100644 --- a/commons-numbers-complex-arrays/src/site/resources/profile.japicmp +++ b/commons-numbers-complex-arrays/src/site/resources/profile.japicmp @@ -14,4 +14,4 @@ # limitations under the License. # ----------------------------------------------------------------------------- # -# Empty file used to automatically trigger profile from commons parent pom +# Empty file used to automatically trigger JApiCmp profile from commons parent pom diff --git a/commons-numbers-complex-arrays/src/site/xdoc/index.xml b/commons-numbers-complex-arrays/src/site/xdoc/index.xml index dc008f9da..84e4b537e 100644 --- a/commons-numbers-complex-arrays/src/site/xdoc/index.xml +++ b/commons-numbers-complex-arrays/src/site/xdoc/index.xml @@ -24,9 +24,14 @@ -

- The "complex arrays" module contains utilities for working with complex number arrays and lists. -

+
+

+ Commons Numbers provides number types and utilities. +

+

+ The "complex arrays" module contains utilities for working with complex number arrays and lists. +

+
diff --git a/commons-numbers-complex-arrays/src/test/java/org/apache/commons/numbers/complex/arrays/ComplexListTest.java b/commons-numbers-complex-arrays/src/test/java/org/apache/commons/numbers/complex/arrays/ComplexListTest.java index 47370d9ba..d25f52e5a 100644 --- a/commons-numbers-complex-arrays/src/test/java/org/apache/commons/numbers/complex/arrays/ComplexListTest.java +++ b/commons-numbers-complex-arrays/src/test/java/org/apache/commons/numbers/complex/arrays/ComplexListTest.java @@ -28,8 +28,11 @@ import java.util.function.Function; import java.util.stream.IntStream; + public class ComplexListTest { + private static final int MAX_CAPACITY = (Integer.MAX_VALUE - 9) / 2; + @Test void testGetAndSetMethod() { assertListOperation(list -> { @@ -61,6 +64,7 @@ void testAddAndAddAll() { assertListOperation(list -> list.add(Complex.ofCartesian(21, 22)), l1, l2); assertListOperation(list -> list.add(Complex.ofCartesian(23, 24)), l1, l2); + //Testing add at an index for branch condition (size == realAndImagParts.length >>> 1) List l3 = new ArrayList<>(); List l4 = new ComplexList(); assertListOperation(list -> list.add(Complex.ofCartesian(1, 2)), l3, l4); @@ -90,22 +94,11 @@ void testAddAndAddAll() { List l5 = new ArrayList<>(); List l6 = new ComplexList(); assertListOperation(list -> list.add(Complex.ofCartesian(1, 2)), l5, l6); - assertListOperation(list -> { - list.addAll(list); - return Boolean.TRUE; - }, l5, l6); - assertListOperation(list -> { - list.addAll(list); - return Boolean.TRUE; - }, l5, l6); - assertListOperation(list -> { - list.addAll(list); - return Boolean.TRUE; - }, l5, l6); - assertListOperation(list -> { - list.addAll(list1); - return Boolean.TRUE; - }, l5, l6); + // Expand the list by doubling in size until at the known minArrayCapacity + while (l5.size() < 8) { + assertListOperation(list -> list.addAll(list), l5, l6); + } + assertListOperation(list -> list.addAll(list1), l5, l6); //Test for adding an empty list to an empty list ComplexList list = new ComplexList(); @@ -181,14 +174,14 @@ void testConstructor(int size) { @Test void testCapacityExceptions() { - Assertions.assertThrows(IllegalArgumentException.class, () -> new ComplexList(ComplexList.MAX_CAPACITY + 1)); + Assertions.assertThrows(IllegalArgumentException.class, () -> new ComplexList(MAX_CAPACITY + 1)); // Set-up required sizes ComplexList list = new ComplexList(); List l = new SizedList(Integer.MAX_VALUE); Assertions.assertThrows(OutOfMemoryError.class, () -> list.addAll(l)); - List l2 = new SizedList(ComplexList.MAX_CAPACITY + 1); + List l2 = new SizedList(MAX_CAPACITY + 1); Assertions.assertThrows(OutOfMemoryError.class, () -> list.addAll(l2)); } @@ -204,6 +197,10 @@ private static void assertListOperation(Function, T> operation assertListOperation(operation, new ArrayList<>(), new ComplexList()); } + /** + * This class purposely gives a fixed size and so is a non-functional list. + * It is used to trigger capacity exceptions when adding a collection to ComplexList. + */ private static class SizedList extends ArrayList { private final int fixedSize;