This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new 39d7b4ee4c org.apache.juneau.common.reflect API improvements
new f14cff088d Merge branch 'master' of
https://gitbox.apache.org/repos/asf/juneau
39d7b4ee4c is described below
commit 39d7b4ee4c763498a652f6c830cf76a801a1f7e1
Author: James Bognar <[email protected]>
AuthorDate: Fri Nov 21 11:23:45 2025 -0500
org.apache.juneau.common.reflect API improvements
---
.../juneau/common/collections/ReversedList.java | 283 ++++++++++++++
.../juneau/common/reflect/AnnotationProvider.java | 121 ++++--
.../common/collections/ReversedList_Test.java | 433 +++++++++++++++++++++
3 files changed, 802 insertions(+), 35 deletions(-)
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/ReversedList.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/ReversedList.java
new file mode 100644
index 0000000000..ad9cd4def5
--- /dev/null
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/ReversedList.java
@@ -0,0 +1,283 @@
+/*
+ * 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.juneau.common.collections;
+
+import java.util.*;
+
+/**
+ * A reversed view of a list that does not modify the underlying list.
+ *
+ * <p>
+ * This class provides a read-only reversed view of a list, where element
access is transparently
+ * reversed without copying or modifying the original list. All read
operations (get, iterator, etc.)
+ * operate on the underlying list in reverse order.
+ *
+ * <h5 class='section'>Features:</h5>
+ * <ul class='spaced-list'>
+ * <li>Zero-copy reverse view - no data duplication
+ * <li>Efficient random access via index translation
+ * <li>Reflects changes in the underlying list automatically
+ * <li>Read-only - modification operations throw {@link
UnsupportedOperationException}
+ * <li>Iterator and ListIterator support in reversed order
+ * </ul>
+ *
+ * <h5 class='section'>Usage:</h5>
+ * <p class='bjava'>
+ * <jc>// Create a list</jc>
+ * List<String> <jv>original</jv> = List.<jsm>of</jsm>(<js>"A"</js>,
<js>"B"</js>, <js>"C"</js>);
+ *
+ * <jc>// Create reversed view</jc>
+ * List<String> <jv>reversed</jv> = <jk>new</jk>
ReversedList<>(<jv>original</jv>);
+ *
+ * <jc>// Access in reverse order</jc>
+ * <jv>reversed</jv>.get(0); <jc>// Returns "C"</jc>
+ * <jv>reversed</jv>.get(1); <jc>// Returns "B"</jc>
+ * <jv>reversed</jv>.get(2); <jc>// Returns "A"</jc>
+ *
+ * <jc>// Iterate in reverse</jc>
+ * <jk>for</jk> (String <jv>s</jv> : <jv>reversed</jv>) {
+ * <jc>// Iterates: "C", "B", "A"</jc>
+ * }
+ * </p>
+ *
+ * <h5 class='section'>Notes:</h5>
+ * <ul class='spaced-list'>
+ * <li>The underlying list must not be null
+ * <li>Changes to the underlying list are immediately visible in the
reversed view
+ * <li>All modification operations (add, remove, set, clear) throw {@link
UnsupportedOperationException}
+ * <li>Size changes in the underlying list are reflected in this view
+ * </ul>
+ *
+ * @param <E> The element type.
+ */
+public class ReversedList<E> extends AbstractList<E> implements RandomAccess {
+
+ private final List<E> list;
+
+ /**
+ * Creates a new reversed view of the specified list.
+ *
+ * @param list The list to reverse. Must not be <jk>null</jk>.
+ * @throws IllegalArgumentException if list is <jk>null</jk>.
+ */
+ public ReversedList(List<E> list) {
+ if (list == null)
+ throw new IllegalArgumentException("List cannot be
null");
+ this.list = list;
+ }
+
+ /**
+ * Returns the element at the specified position in this reversed view.
+ *
+ * <p>
+ * The position is translated to access the underlying list in reverse
order.
+ * For example, index 0 returns the last element of the underlying list.
+ *
+ * @param index The index of the element to return (0-based, in
reversed order).
+ * @return The element at the specified position in the reversed view.
+ * @throws IndexOutOfBoundsException if the index is out of range.
+ */
+ @Override
+ public E get(int index) {
+ return list.get(list.size() - 1 - index);
+ }
+
+ /**
+ * Returns the number of elements in this reversed view.
+ *
+ * <p>
+ * This is always equal to the size of the underlying list.
+ *
+ * @return The number of elements in this reversed view.
+ */
+ @Override
+ public int size() {
+ return list.size();
+ }
+
+ /**
+ * Returns an iterator over the elements in this reversed view in
proper sequence.
+ *
+ * <p>
+ * The iterator traverses the underlying list in reverse order.
+ *
+ * @return An iterator over the elements in reversed order.
+ */
+ @Override
+ public Iterator<E> iterator() {
+ return new Iterator<E>() {
+ private final ListIterator<E> it =
list.listIterator(list.size());
+
+ @Override
+ public boolean hasNext() {
+ return it.hasPrevious();
+ }
+
+ @Override
+ public E next() {
+ return it.previous();
+ }
+
+ @Override
+ public void remove() {
+ throw new
UnsupportedOperationException("ReversedList is read-only");
+ }
+ };
+ }
+
+ /**
+ * Returns a list iterator over the elements in this reversed view.
+ *
+ * <p>
+ * The iterator traverses the underlying list in reverse order.
+ *
+ * @return A list iterator over the elements in reversed order.
+ */
+ @Override
+ public ListIterator<E> listIterator() {
+ return listIterator(0);
+ }
+
+ /**
+ * Returns a list iterator over the elements in this reversed view,
starting at the specified position.
+ *
+ * <p>
+ * The iterator traverses the underlying list in reverse order,
starting from the translated position.
+ *
+ * @param index The index of the first element to be returned from the
list iterator (in reversed order).
+ * @return A list iterator over the elements in reversed order.
+ * @throws IndexOutOfBoundsException if the index is out of range.
+ */
+ @Override
+ public ListIterator<E> listIterator(final int index) {
+ if (index < 0 || index > size())
+ throw new IndexOutOfBoundsException("Index: " + index +
", Size: " + size());
+
+ return new ListIterator<E>() {
+ private final ListIterator<E> it =
list.listIterator(list.size() - index);
+
+ @Override
+ public boolean hasNext() {
+ return it.hasPrevious();
+ }
+
+ @Override
+ public E next() {
+ return it.previous();
+ }
+
+ @Override
+ public boolean hasPrevious() {
+ return it.hasNext();
+ }
+
+ @Override
+ public E previous() {
+ return it.next();
+ }
+
+ @Override
+ public int nextIndex() {
+ return list.size() - it.previousIndex() - 1;
+ }
+
+ @Override
+ public int previousIndex() {
+ return list.size() - it.nextIndex() - 1;
+ }
+
+ @Override
+ public void remove() {
+ throw new
UnsupportedOperationException("ReversedList is read-only");
+ }
+
+ @Override
+ public void set(E e) {
+ throw new
UnsupportedOperationException("ReversedList is read-only");
+ }
+
+ @Override
+ public void add(E e) {
+ throw new
UnsupportedOperationException("ReversedList is read-only");
+ }
+ };
+ }
+
+ /**
+ * Returns a view of the portion of this reversed list between the
specified indices.
+ *
+ * <p>
+ * The returned sublist is also a reversed view and reflects changes in
the underlying list.
+ *
+ * @param fromIndex Low endpoint (inclusive) of the subList.
+ * @param toIndex High endpoint (exclusive) of the subList.
+ * @return A view of the specified range within this reversed list.
+ * @throws IndexOutOfBoundsException if the indices are out of range.
+ */
+ @Override
+ public List<E> subList(int fromIndex, int toIndex) {
+ if (fromIndex < 0 || toIndex > size() || fromIndex > toIndex)
+ throw new IndexOutOfBoundsException("fromIndex: " +
fromIndex + ", toIndex: " + toIndex + ", size: " + size());
+
+ // Translate indices to the underlying list
+ int translatedFrom = list.size() - toIndex;
+ int translatedTo = list.size() - fromIndex;
+
+ return new ReversedList<>(list.subList(translatedFrom,
translatedTo));
+ }
+
+ /**
+ * Not supported - this is a read-only view.
+ *
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public void add(int index, E element) {
+ throw new UnsupportedOperationException("ReversedList is
read-only");
+ }
+
+ /**
+ * Not supported - this is a read-only view.
+ *
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public E remove(int index) {
+ throw new UnsupportedOperationException("ReversedList is
read-only");
+ }
+
+ /**
+ * Not supported - this is a read-only view.
+ *
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public E set(int index, E element) {
+ throw new UnsupportedOperationException("ReversedList is
read-only");
+ }
+
+ /**
+ * Not supported - this is a read-only view.
+ *
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException("ReversedList is
read-only");
+ }
+}
+
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider.java
index 4b1a356fc1..bb2405e533 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider.java
@@ -26,6 +26,7 @@ import static java.util.stream.Stream.*;
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
+import java.util.function.*;
import java.util.stream.*;
import org.apache.juneau.common.collections.*;
@@ -195,7 +196,8 @@ public class AnnotationProvider {
private static final Map<String, Long> methodCallCounts = new
java.util.concurrent.ConcurrentHashMap<>();
private static final boolean ENABLE_INSTRUMENTATION =
Boolean.getBoolean("juneau.instrumentAnnotationProvider");
- private static final boolean ENABLE_NEW_CODE =
Boolean.getBoolean("juneau.annotationProvider.enableNewCode");
+// private static final boolean ENABLE_NEW_CODE =
Boolean.getBoolean("juneau.annotationProvider.enableNewCode");
+ private static final boolean ENABLE_NEW_CODE = true;
static {
if (ENABLE_INSTRUMENTATION) {
@@ -205,11 +207,11 @@ public class AnnotationProvider {
pw.println("\n=== AnnotationProvider
Method Call Statistics ===");
pw.println(String.format("%-65s %12s",
"Method", "Calls"));
pw.println("=".repeat(80));
-
+
methodCallCounts.entrySet().stream()
.sorted(Map.Entry.<String,
Long>comparingByValue().reversed())
.forEach(e ->
pw.println(String.format("%-65s %,12d", e.getKey(), e.getValue())));
-
+
long totalCalls =
methodCallCounts.values().stream().mapToLong(Long::longValue).sum();
pw.println("=".repeat(80));
pw.println(String.format("%-65s %,12d",
"TOTAL", totalCalls));
@@ -441,10 +443,11 @@ public class AnnotationProvider {
return new Builder();
}
- private final Cache<Class<?>,List<AnnotationInfo<Annotation>>>
classAnnnotations;
- private final Cache<Method,List<AnnotationInfo<Annotation>>>
methodAnnotations;
- private final Cache<Field,List<AnnotationInfo<Annotation>>>
fieldAnnotations;
- private final Cache<Constructor<?>,List<AnnotationInfo<Annotation>>>
constructorAnnotations;
+ private final Cache<Class<?>,List<AnnotationInfo<Annotation>>>
classRuntimeAnnotations;
+ private final Cache<Method,List<AnnotationInfo<Annotation>>>
methodRuntimeAnnotations;
+ private final Cache<Field,List<AnnotationInfo<Annotation>>>
fieldRuntimeAnnotations;
+ private final Cache<Constructor<?>,List<AnnotationInfo<Annotation>>>
constructorRuntimeAnnotations;
+ private final Cache3<Class<?>,ElementInfo,AnnotationTraversal[],List>
cache;
private final ReflectionMap<Annotation> annotationMap;
/**
@@ -453,20 +456,24 @@ public class AnnotationProvider {
* @param builder The builder containing configuration settings.
*/
protected AnnotationProvider(Builder builder) {
- this.classAnnnotations =
Cache.<Class<?>,List<AnnotationInfo<Annotation>>>create()
- .supplier(this::findClassAnnotations)
+ this.classRuntimeAnnotations =
Cache.<Class<?>,List<AnnotationInfo<Annotation>>>create()
+ .supplier(this::findClassRuntimeAnnotations)
+ .disableCaching(builder.disableCaching)
+ .build();
+ this.methodRuntimeAnnotations =
Cache.<Method,List<AnnotationInfo<Annotation>>>create()
+ .supplier(this::findMethodRuntimeAnnotations)
.disableCaching(builder.disableCaching)
.build();
- this.methodAnnotations =
Cache.<Method,List<AnnotationInfo<Annotation>>>create()
- .supplier(this::findMethodAnnotations)
+ this.fieldRuntimeAnnotations =
Cache.<Field,List<AnnotationInfo<Annotation>>>create()
+ .supplier(this::findFieldRuntimeAnnotations)
.disableCaching(builder.disableCaching)
.build();
- this.fieldAnnotations =
Cache.<Field,List<AnnotationInfo<Annotation>>>create()
- .supplier(this::findFieldAnnotations)
+ this.constructorRuntimeAnnotations =
Cache.<Constructor<?>,List<AnnotationInfo<Annotation>>>create()
+ .supplier(this::findConstructorRuntimeAnnotations)
.disableCaching(builder.disableCaching)
.build();
- this.constructorAnnotations =
Cache.<Constructor<?>,List<AnnotationInfo<Annotation>>>create()
- .supplier(this::findConstructorAnnotations)
+ this.cache =
Cache3.<Class<?>,ElementInfo,AnnotationTraversal[],List>create()
+ .supplier(this::findCached)
.disableCaching(builder.disableCaching)
.build();
this.annotationMap = builder.runtimeAnnotations.build();
@@ -476,22 +483,22 @@ public class AnnotationProvider {
// Private implementation
//-----------------------------------------------------------------------------------------------------------------
- private List<AnnotationInfo<Annotation>> findClassAnnotations(Class<?>
forClass) {
+ private List<AnnotationInfo<Annotation>>
findClassRuntimeAnnotations(Class<?> forClass) {
var ci = ClassInfo.of(forClass);
return annotationMap.find(forClass).map(a -> ai(ci,
a)).toList();
}
- private List<AnnotationInfo<Annotation>> findMethodAnnotations(Method
forMethod) {
+ private List<AnnotationInfo<Annotation>>
findMethodRuntimeAnnotations(Method forMethod) {
var mi = MethodInfo.of(forMethod);
return annotationMap.find(forMethod).map(a -> ai(mi,
a)).toList();
}
- private List<AnnotationInfo<Annotation>> findFieldAnnotations(Field
forField) {
+ private List<AnnotationInfo<Annotation>>
findFieldRuntimeAnnotations(Field forField) {
var fi = FieldInfo.of(forField);
return annotationMap.find(forField).map(a -> ai(fi,
a)).toList();
}
- private List<AnnotationInfo<Annotation>>
findConstructorAnnotations(Constructor<?> forConstructor) {
+ private List<AnnotationInfo<Annotation>>
findConstructorRuntimeAnnotations(Constructor<?> forConstructor) {
var ci = ConstructorInfo.of(forConstructor);
return annotationMap.find(forConstructor).map(a -> ai(ci,
a)).toList();
}
@@ -533,7 +540,7 @@ public class AnnotationProvider {
trackCall("find(Class, ClassInfo, AnnotationTraversal...)");
if (ENABLE_NEW_CODE)
return findNew(type, clazz, traversals).stream();
-
+
assertArgNotNull("type", type);
assertArgNotNull("clazz", clazz);
if (traversals.length == 0)
@@ -544,14 +551,14 @@ public class AnnotationProvider {
.flatMap(traversal -> {
if (traversal == SELF) {
return concat(
-
classAnnnotations.get(clazz.inner()).stream(),
+
classRuntimeAnnotations.get(clazz.inner()).stream(),
clazz.getDeclaredAnnotations().stream()
)
.filter(a -> a.isType(type)).map(a ->
(AnnotationInfo<A>)a);
} else if (traversal == PARENTS) {
return
clazz.getParentsAndInterfaces().stream().flatMap(x ->
concat(
-
classAnnnotations.get(x.inner()).stream(),
+
classRuntimeAnnotations.get(x.inner()).stream(),
x.getDeclaredAnnotations().stream()
).filter(a ->
a.isType(type)).map(a -> (AnnotationInfo<A>)a)
);
@@ -569,10 +576,42 @@ public class AnnotationProvider {
*/
@SuppressWarnings("unchecked")
private <A extends Annotation> List<AnnotationInfo<A>> findNew(Class<A>
type, ClassInfo clazz, AnnotationTraversal... traversals) {
- // TODO: Implement optimized version
- throw new UnsupportedOperationException("New implementation not
yet available");
+ return (List<AnnotationInfo<A>>)cache.get(type, clazz,
traversals);
}
+ /**
+ * Computes and caches the complete list of annotations for a given
type, class, and traversal combination.
+ * This is the supplier function for the findCache.
+ */
+ @SuppressWarnings("unchecked")
+ private List findCached(Class<?> type, ElementInfo element,
AnnotationTraversal[] traversals) {
+ var l = new ArrayList();
+ var filter = isType((Class<? extends Annotation>)type);
+
+ if (element instanceof ClassInfo ci) {
+ if (traversals.length == 0)
+ traversals = a(PARENTS, PACKAGE);
+ var t = Arrays.asList(traversals);
+ if (t.contains(SELF)) {
+
classRuntimeAnnotations.get(ci.inner()).stream().filter(filter).forEach(l::add);
+
ci.getDeclaredAnnotations().stream().filter(filter).forEach(l::add);
+ }
+ if (t.contains(PARENTS)) {
+ for (var p : ci.getParentsAndInterfaces()) {
+
classRuntimeAnnotations.get(p.inner()).stream().filter(filter).forEach(l::add);
+
p.getDeclaredAnnotations().stream().filter(filter).forEach(l::add);
+ }
+ }
+ if (t.contains(PACKAGE)) {
+ if (ci.getPackage() != null)
+
ci.getPackage().getAnnotations().stream().filter(filter).forEach(l::add);
+ }
+ }
+
+ return l;
+ }
+
+
/**
* Streams all annotations from a class using configurable traversal
options, without filtering by annotation type.
*
@@ -607,13 +646,13 @@ public class AnnotationProvider {
.flatMap(traversal -> {
if (traversal == SELF) {
return concat(
-
classAnnnotations.get(clazz.inner()).stream(),
+
classRuntimeAnnotations.get(clazz.inner()).stream(),
clazz.getDeclaredAnnotations().stream().map(a -> (AnnotationInfo<Annotation>)a)
);
} else if (traversal == PARENTS) {
return
clazz.getParentsAndInterfaces().stream().flatMap(x -> {
return concat(
-
classAnnnotations.get(x.inner()).stream(),
+
classRuntimeAnnotations.get(x.inner()).stream(),
x.getDeclaredAnnotations().stream().map(a -> (AnnotationInfo<Annotation>)a)
);
});
@@ -736,13 +775,13 @@ public class AnnotationProvider {
.flatMap(traversal -> {
if (traversal == SELF) {
return concat(
-
methodAnnotations.get(method.inner()).stream(),
+
methodRuntimeAnnotations.get(method.inner()).stream(),
method.getDeclaredAnnotations().stream()
).filter(a -> a.isType(type)).map(a ->
(AnnotationInfo<A>)a);
} else if (traversal == MATCHING_METHODS) {
return
method.getMatchingMethods().stream().skip(1).flatMap(m ->
concat(
-
methodAnnotations.get(m.inner()).stream(),
+
methodRuntimeAnnotations.get(m.inner()).stream(),
m.getDeclaredAnnotations().stream()
).filter(a ->
a.isType(type)).map(a -> (AnnotationInfo<A>)a)
);
@@ -790,13 +829,13 @@ public class AnnotationProvider {
.flatMap(traversal -> {
if (traversal == SELF) {
return concat(
-
methodAnnotations.get(method.inner()).stream(),
+
methodRuntimeAnnotations.get(method.inner()).stream(),
method.getDeclaredAnnotations().stream()
);
} else if (traversal == MATCHING_METHODS) {
return
method.getMatchingMethods().stream().skip(1).flatMap(m -> {
return concat(
-
methodAnnotations.get(m.inner()).stream(),
+
methodRuntimeAnnotations.get(m.inner()).stream(),
m.getDeclaredAnnotations().stream()
);
});
@@ -1132,7 +1171,7 @@ public class AnnotationProvider {
.flatMap(traversal -> {
if (traversal == SELF) {
return concat(
-
fieldAnnotations.get(field.inner()).stream(),
+
fieldRuntimeAnnotations.get(field.inner()).stream(),
field.getAnnotations().stream()
).filter(a -> a.isType(type)).map(a ->
(AnnotationInfo<A>)a);
}
@@ -1169,7 +1208,7 @@ public class AnnotationProvider {
.flatMap(traversal -> {
if (traversal == SELF) {
return concat(
-
fieldAnnotations.get(field.inner()).stream(),
+
fieldRuntimeAnnotations.get(field.inner()).stream(),
field.getAnnotations().stream()
);
}
@@ -1279,7 +1318,7 @@ public class AnnotationProvider {
.flatMap(traversal -> {
if (traversal == SELF) {
return concat(
-
constructorAnnotations.get(constructor.inner()).stream(),
+
constructorRuntimeAnnotations.get(constructor.inner()).stream(),
constructor.getDeclaredAnnotations().stream()
).filter(a -> a.isType(type)).map(a ->
(AnnotationInfo<A>)a);
}
@@ -1316,7 +1355,7 @@ public class AnnotationProvider {
.flatMap(traversal -> {
if (traversal == SELF) {
return concat(
-
constructorAnnotations.get(constructor.inner()).stream(),
+
constructorRuntimeAnnotations.get(constructor.inner()).stream(),
constructor.getDeclaredAnnotations().stream()
);
}
@@ -1393,11 +1432,23 @@ public class AnnotationProvider {
trackCall("has(Class, ConstructorInfo,
AnnotationTraversal...)");
return find(type, constructor,
traversals).findFirst().isPresent();
}
-
+
//-----------------------------------------------------------------------------------------------------------------
// Helper methods
//-----------------------------------------------------------------------------------------------------------------
+ /**
+ * Creates a predicate that filters annotations by type.
+ * If type is null, all annotations pass; otherwise only annotations of
the specified type pass.
+ *
+ * @param type The annotation type to filter by, or null to accept all
annotations.
+ * @return A predicate that tests if an annotation matches the type.
+ */
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private static Predicate isType(Class<? extends Annotation> type) {
+ return type == null ? x -> true : x ->
((AnnotationInfo)x).isType(type);
+ }
+
private <A extends Annotation> AnnotationInfo<A> ai(Annotatable on, A
value) {
return AnnotationInfo.of(on, value);
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/common/collections/ReversedList_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/common/collections/ReversedList_Test.java
new file mode 100644
index 0000000000..cb520ea74b
--- /dev/null
+++
b/juneau-utest/src/test/java/org/apache/juneau/common/collections/ReversedList_Test.java
@@ -0,0 +1,433 @@
+/*
+ * 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.juneau.common.collections;
+
+import static org.apache.juneau.junit.bct.BctAssertions.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.junit.jupiter.api.*;
+
+class ReversedList_Test extends TestBase {
+
+
//====================================================================================================
+ // Basic functionality
+
//====================================================================================================
+
+ @Test
+ void a01_basicGet() {
+ var original = List.of("A", "B", "C");
+ var reversed = new ReversedList<>(original);
+
+ assertEquals("C", reversed.get(0));
+ assertEquals("B", reversed.get(1));
+ assertEquals("A", reversed.get(2));
+ assertSize(3, reversed);
+ }
+
+ @Test
+ void a02_basicIteration() {
+ var original = List.of("A", "B", "C");
+ var reversed = new ReversedList<>(original);
+
+ var result = new ArrayList<String>();
+ for (String s : reversed) {
+ result.add(s);
+ }
+
+ assertEquals(List.of("C", "B", "A"), result);
+ }
+
+ @Test
+ void a03_emptyList() {
+ var original = List.<String>of();
+ var reversed = new ReversedList<>(original);
+
+ assertEmpty(reversed);
+ assertFalse(reversed.iterator().hasNext());
+ }
+
+ @Test
+ void a04_singleElement() {
+ var original = List.of("A");
+ var reversed = new ReversedList<>(original);
+
+ assertSize(1, reversed);
+ assertEquals("A", reversed.get(0));
+ }
+
+
//====================================================================================================
+ // Null handling
+
//====================================================================================================
+
+ @Test
+ void b01_nullList_throwsException() {
+ assertThrows(IllegalArgumentException.class, () -> new
ReversedList<>(null));
+ }
+
+ @Test
+ void b02_listWithNulls() {
+ var original = Arrays.asList("A", null, "C");
+ var reversed = new ReversedList<>(original);
+
+ assertEquals("C", reversed.get(0));
+ assertNull(reversed.get(1));
+ assertEquals("A", reversed.get(2));
+ }
+
+
//====================================================================================================
+ // Index bounds
+
//====================================================================================================
+
+ @Test
+ void c01_outOfBounds_negative() {
+ var original = List.of("A", "B", "C");
+ var reversed = new ReversedList<>(original);
+
+ assertThrows(IndexOutOfBoundsException.class, () ->
reversed.get(-1));
+ }
+
+ @Test
+ void c02_outOfBounds_tooLarge() {
+ var original = List.of("A", "B", "C");
+ var reversed = new ReversedList<>(original);
+
+ assertThrows(IndexOutOfBoundsException.class, () ->
reversed.get(3));
+ }
+
+
//====================================================================================================
+ // Reflection of underlying list changes
+
//====================================================================================================
+
+ @Test
+ void d01_reflectsUnderlyingChanges() {
+ var original = new ArrayList<>(Arrays.asList("A", "B", "C"));
+ var reversed = new ReversedList<>(original);
+
+ assertEquals("C", reversed.get(0));
+
+ original.add("D");
+
+ assertSize(4, reversed);
+ assertEquals("D", reversed.get(0));
+ assertEquals("C", reversed.get(1));
+ }
+
+ @Test
+ void d02_reflectsUnderlyingRemoval() {
+ var original = new ArrayList<>(Arrays.asList("A", "B", "C"));
+ var reversed = new ReversedList<>(original);
+
+ original.remove(2); // Remove "C"
+
+ assertSize(2, reversed);
+ assertEquals("B", reversed.get(0));
+ assertEquals("A", reversed.get(1));
+ }
+
+
//====================================================================================================
+ // Read-only enforcement
+
//====================================================================================================
+
+ @Test
+ void e01_add_throwsException() {
+ var original = List.of("A", "B", "C");
+ var reversed = new ReversedList<>(original);
+
+ assertThrows(UnsupportedOperationException.class, () ->
reversed.add("D"));
+ }
+
+ @Test
+ void e02_addAtIndex_throwsException() {
+ var original = List.of("A", "B", "C");
+ var reversed = new ReversedList<>(original);
+
+ assertThrows(UnsupportedOperationException.class, () ->
reversed.add(0, "D"));
+ }
+
+ @Test
+ void e03_remove_throwsException() {
+ var original = List.of("A", "B", "C");
+ var reversed = new ReversedList<>(original);
+
+ assertThrows(UnsupportedOperationException.class, () ->
reversed.remove(0));
+ }
+
+ @Test
+ void e04_set_throwsException() {
+ var original = List.of("A", "B", "C");
+ var reversed = new ReversedList<>(original);
+
+ assertThrows(UnsupportedOperationException.class, () ->
reversed.set(0, "D"));
+ }
+
+ @Test
+ void e05_clear_throwsException() {
+ var original = List.of("A", "B", "C");
+ var reversed = new ReversedList<>(original);
+
+ assertThrows(UnsupportedOperationException.class, () ->
reversed.clear());
+ }
+
+ @Test
+ void e06_iteratorRemove_throwsException() {
+ var original = List.of("A", "B", "C");
+ var reversed = new ReversedList<>(original);
+
+ var it = reversed.iterator();
+ it.next();
+ assertThrows(UnsupportedOperationException.class, () ->
it.remove());
+ }
+
+
//====================================================================================================
+ // Iterator functionality
+
//====================================================================================================
+
+ @Test
+ void f01_iterator_traversal() {
+ var original = List.of("A", "B", "C", "D");
+ var reversed = new ReversedList<>(original);
+
+ var it = reversed.iterator();
+ assertTrue(it.hasNext());
+ assertEquals("D", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("C", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("B", it.next());
+ assertTrue(it.hasNext());
+ assertEquals("A", it.next());
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ void f02_listIterator_forward() {
+ var original = List.of("A", "B", "C");
+ var reversed = new ReversedList<>(original);
+
+ var it = reversed.listIterator();
+ assertEquals("C", it.next());
+ assertEquals("B", it.next());
+ assertEquals("A", it.next());
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ void f03_listIterator_backward() {
+ var original = List.of("A", "B", "C");
+ var reversed = new ReversedList<>(original);
+
+ var it = reversed.listIterator(3);
+ assertEquals("A", it.previous());
+ assertEquals("B", it.previous());
+ assertEquals("C", it.previous());
+ assertFalse(it.hasPrevious());
+ }
+
+ @Test
+ void f04_listIterator_bidirectional() {
+ var original = List.of("A", "B", "C");
+ var reversed = new ReversedList<>(original);
+
+ var it = reversed.listIterator(1);
+ assertEquals("B", it.next());
+ assertEquals("B", it.previous());
+ assertEquals("C", it.previous());
+ assertEquals("C", it.next());
+ }
+
+ @Test
+ void f05_listIterator_indices() {
+ var original = List.of("A", "B", "C");
+ var reversed = new ReversedList<>(original);
+
+ var it = reversed.listIterator();
+ assertEquals(-1, it.previousIndex());
+ assertEquals(0, it.nextIndex());
+
+ it.next();
+ assertEquals(0, it.previousIndex());
+ assertEquals(1, it.nextIndex());
+
+ it.next();
+ assertEquals(1, it.previousIndex());
+ assertEquals(2, it.nextIndex());
+
+ it.next();
+ assertEquals(2, it.previousIndex());
+ assertEquals(3, it.nextIndex());
+ }
+
+ @Test
+ void f06_listIterator_modificationThrows() {
+ var original = List.of("A", "B", "C");
+ var reversed = new ReversedList<>(original);
+
+ var it = reversed.listIterator();
+ it.next();
+
+ assertThrows(UnsupportedOperationException.class, () ->
it.remove());
+ assertThrows(UnsupportedOperationException.class, () ->
it.set("X"));
+ assertThrows(UnsupportedOperationException.class, () ->
it.add("X"));
+ }
+
+
//====================================================================================================
+ // SubList functionality
+
//====================================================================================================
+
+ @Test
+ void g01_subList_basic() {
+ var original = List.of("A", "B", "C", "D", "E");
+ var reversed = new ReversedList<>(original);
+
+ var subList = reversed.subList(1, 4);
+
+ assertSize(3, subList);
+ assertEquals("D", subList.get(0));
+ assertEquals("C", subList.get(1));
+ assertEquals("B", subList.get(2));
+ }
+
+ @Test
+ void g02_subList_empty() {
+ var original = List.of("A", "B", "C");
+ var reversed = new ReversedList<>(original);
+
+ var subList = reversed.subList(1, 1);
+
+ assertEmpty(subList);
+ }
+
+ @Test
+ void g03_subList_full() {
+ var original = List.of("A", "B", "C");
+ var reversed = new ReversedList<>(original);
+
+ var subList = reversed.subList(0, 3);
+
+ assertSize(3, subList);
+ assertEquals("C", subList.get(0));
+ assertEquals("B", subList.get(1));
+ assertEquals("A", subList.get(2));
+ }
+
+ @Test
+ void g04_subList_outOfBounds() {
+ var original = List.of("A", "B", "C");
+ var reversed = new ReversedList<>(original);
+
+ assertThrows(IndexOutOfBoundsException.class, () ->
reversed.subList(-1, 2));
+ assertThrows(IndexOutOfBoundsException.class, () ->
reversed.subList(0, 4));
+ assertThrows(IndexOutOfBoundsException.class, () ->
reversed.subList(2, 1));
+ }
+
+ @Test
+ void g05_subList_reflectsChanges() {
+ var original = new ArrayList<>(Arrays.asList("A", "B", "C",
"D", "E"));
+ var reversed = new ReversedList<>(original);
+ var subList = reversed.subList(1, 4);
+
+ original.set(3, "X"); // Changes "D" to "X" in original
+
+ assertEquals("X", subList.get(0));
+ }
+
+
//====================================================================================================
+ // Contains and indexOf
+
//====================================================================================================
+
+ @Test
+ void h01_contains() {
+ var original = List.of("A", "B", "C");
+ var reversed = new ReversedList<>(original);
+
+ assertTrue(reversed.contains("A"));
+ assertTrue(reversed.contains("B"));
+ assertTrue(reversed.contains("C"));
+ assertFalse(reversed.contains("D"));
+ }
+
+ @Test
+ void h02_indexOf() {
+ var original = List.of("A", "B", "C");
+ var reversed = new ReversedList<>(original);
+
+ assertEquals(2, reversed.indexOf("A"));
+ assertEquals(1, reversed.indexOf("B"));
+ assertEquals(0, reversed.indexOf("C"));
+ assertEquals(-1, reversed.indexOf("D"));
+ }
+
+ @Test
+ void h03_lastIndexOf() {
+ var original = List.of("A", "B", "A", "C");
+ var reversed = new ReversedList<>(original);
+
+ // Original: ["A", "B", "A", "C"]
+ // Reversed: ["C", "A", "B", "A"]
+ // First "A" in reversed is at index 1, last "A" is at index 3
+ assertEquals(3, reversed.lastIndexOf("A"));
+ assertEquals(0, reversed.lastIndexOf("C"));
+ }
+
+
//====================================================================================================
+ // Edge cases
+
//====================================================================================================
+
+ @Test
+ void i01_largeList() {
+ var original = new ArrayList<Integer>();
+ for (int i = 0; i < 1000; i++) {
+ original.add(i);
+ }
+
+ var reversed = new ReversedList<>(original);
+
+ assertSize(1000, reversed);
+ assertEquals(999, reversed.get(0));
+ assertEquals(0, reversed.get(999));
+ }
+
+ @Test
+ void i02_toArray() {
+ var original = List.of("A", "B", "C");
+ var reversed = new ReversedList<>(original);
+
+ var array = reversed.toArray();
+
+ assertEquals(3, array.length);
+ assertEquals("C", array[0]);
+ assertEquals("B", array[1]);
+ assertEquals("A", array[2]);
+ }
+
+ @Test
+ void i03_toArrayTyped() {
+ var original = List.of("A", "B", "C");
+ var reversed = new ReversedList<>(original);
+
+ var array = reversed.toArray(new String[0]);
+
+ assertEquals(3, array.length);
+ assertEquals("C", array[0]);
+ assertEquals("B", array[1]);
+ assertEquals("A", array[2]);
+ }
+}
+