This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch jbFixRestNpe
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/jbFixRestNpe by this push:
     new c00f8fe89 PartList should be an ArrayList.
c00f8fe89 is described below

commit c00f8fe897dc13c40d57b49a5afc8a9f2d9f7f6d
Author: JamesBognar <[email protected]>
AuthorDate: Fri Aug 12 17:34:49 2022 -0400

    PartList should be an ArrayList.
---
 .../juneau/collections/ControlledArrayList.java    | 391 +++++++++++++++++++++
 .../org/apache/juneau/rest/client/RestClient.java  |   2 +-
 .../java/org/apache/juneau/http/part/PartList.java | 267 ++++----------
 .../ArgsTest.java => collections/Args_Test.java}   |  11 +-
 .../collections/ControlledArrayList_Test.java      | 191 ++++++++++
 .../org/apache/juneau/http/part/PartList_Test.java |  12 +-
 6 files changed, 656 insertions(+), 218 deletions(-)

diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/ControlledArrayList.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/ControlledArrayList.java
new file mode 100644
index 000000000..0b38c3613
--- /dev/null
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/ControlledArrayList.java
@@ -0,0 +1,391 @@
+// 
***************************************************************************************************************************
+// * 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.collections;
+
+import java.util.*;
+import java.util.function.*;
+
+/**
+ * An array list that allows you to control whether it's read-only via a 
constructor parameter.
+ *
+ * <p>
+ * Override methods such as {@link #overrideAdd(int, Object)} are provided 
that bypass the unmodifiable restriction
+ * on the list.  They allow you to manipulate the list while not exposing the 
ability to manipulate the list through
+ * any of the methods provided by the {@link List} interface (meaning you can 
pass the object around as an unmodifiable List).
+ *
+ * @param <E> The element type.
+ */
+public class ControlledArrayList<E> extends ArrayList<E> {
+
+       private static final long serialVersionUID = -1L;
+
+       private final boolean modifiable;
+
+       /**
+        * Constructor.
+        *
+        * @param modifiable If <jk>true</jk>, this list can be modified 
through normal list operation methods on the {@link List} interface.
+        */
+       public ControlledArrayList(boolean modifiable) {
+               this.modifiable = modifiable;
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param modifiable If <jk>true</jk>, this list can be modified 
through normal list operation methods on the {@link List} interface.
+        * @param list The initial contents of this list.
+        */
+       public ControlledArrayList(boolean modifiable, List<? extends E> list) {
+               super(list);
+               this.modifiable = modifiable;
+       }
+
+       void checkModifiable() {
+               if (! modifiable)
+                       throw new UnsupportedOperationException("List is 
read-only.");
+       }
+
+       /**
+        * Returns <jk>true</jk> if this list is modifiable.
+        *
+        * @return <jk>true</jk> if this list is modifiable.
+        */
+       public boolean isModifiable() {
+               return modifiable;
+       }
+
+       @Override
+       public E set(int index, E element) {
+               checkModifiable();
+               return overrideSet(index, element);
+       }
+
+       /**
+        * Same as {@link #set(int, Object)} but bypasses the modifiable flag.
+        *
+        * @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.
+        */
+       public E overrideSet(int index, E element) {
+               return super.set(index, element);
+       }
+
+       @Override
+       public void add(int index, E element) {
+               checkModifiable();
+               overrideAdd(index, element);
+       }
+
+       /**
+        * Same as {@link #add(int, Object)} but bypasses the modifiable flag.
+        *
+        * @param index Index of the element to replace.
+        * @param element Element to be stored at the specified position.
+        */
+       public void overrideAdd(int index, E element) {
+               super.add(index, element);
+       }
+
+       @Override
+       public E remove(int index) {
+               checkModifiable();
+               return overrideRemove(index);
+       }
+
+       /**
+        * Same as {@link #remove(int)} but bypasses the modifiable flag.
+        *
+        * @param index Index of the element to remove.
+        * @return The element that was removed from the list.
+        */
+       public E overrideRemove(int index) {
+               return super.remove(index);
+       }
+
+       @Override
+       public boolean addAll(int index, Collection<? extends E> c) {
+               checkModifiable();
+               return overrideAddAll(index, c);
+       }
+
+       /**
+        * Same as {@link #addAll(int,Collection)} but bypasses the modifiable 
flag.
+        *
+        * @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 <jk>true</jk> if this list changed as a result of the call.
+        */
+       public boolean overrideAddAll(int index, Collection<? extends E> c) {
+               return super.addAll(index, c);
+       }
+
+       @Override
+       public void replaceAll(UnaryOperator<E> operator) {
+               checkModifiable();
+               overrideReplaceAll(operator);
+       }
+
+       /**
+        * Same as {@link #replaceAll(UnaryOperator)} but bypasses the 
modifiable flag.
+        *
+        * @param operator The operator to apply to each element.
+        */
+       public void overrideReplaceAll(UnaryOperator<E> operator) {
+               super.replaceAll(operator);
+       }
+
+       @Override
+       public void sort(Comparator<? super E> c) {
+               checkModifiable();
+               overrideSort(c);
+       }
+
+       /**
+        * Same as {@link #overrideSort(Comparator)} but bypasses the 
modifiable flag.
+        *
+        * @param c The Comparator used to compare list elements. A null value 
indicates that the elements' natural ordering should be used.
+        */
+       public void overrideSort(Comparator<? super E> c) {
+               super.sort(c);
+       }
+
+       @Override
+       public boolean add(E element) {
+               checkModifiable();
+               return overrideAdd(element);
+       }
+
+       /**
+        * Same as {@link #add(Object)} but bypasses the modifiable flag.
+        *
+        * @param element Element to be stored at the specified position.
+        * @return <jk>true</jk>.
+        */
+       public boolean overrideAdd(E element) {
+               return super.add(element);
+       }
+
+       @Override
+       public boolean remove(Object o) {
+               checkModifiable();
+               return overrideRemove(o);
+       }
+
+       /**
+        * Same as {@link #remove(Object)} but bypasses the modifiable flag.
+        *
+        * @param o Element to be removed from this list, if present.
+        * @return <jk>true</jk> if this list contained the specified element.
+        */
+       public boolean overrideRemove(Object o) {
+               return super.remove(o);
+       }
+
+       @Override
+       public boolean addAll(Collection<? extends E> c) {
+               checkModifiable();
+               return overrideAddAll(c);
+       }
+
+       /**
+        * Same as {@link #addAll(Collection)} but bypasses the modifiable flag.
+        *
+        * @param c Collection containing elements to be added to this list.
+        * @return <jk>true</jk> if this list changed as a result of the call.
+        */
+       public boolean overrideAddAll(Collection<? extends E> c) {
+               return super.addAll(c);
+       }
+
+       @Override
+       public boolean removeAll(Collection<?> coll) {
+               checkModifiable();
+               return overrideRemoveAll(coll);
+       }
+
+       /**
+        * Same as {@link #removeAll(Collection)} but bypasses the modifiable 
flag.
+        *
+        * @param c Collection containing elements to be removed from this list.
+        * @return <jk>true</jk> if this list changed as a result of the call.
+        */
+       public boolean overrideRemoveAll(Collection<?> c) {
+               return super.removeAll(c);
+       }
+
+       @Override
+       public boolean retainAll(Collection<?> c) {
+               checkModifiable();
+               return overrideRetainAll(c);
+       }
+
+       /**
+        * Same as {@link #retainAll(Collection)} but bypasses the modifiable 
flag.
+        *
+        * @param c Collection containing elements to be retained in this list.
+        * @return <jk>true</jk> if this list changed as a result of the call.
+        */
+       public boolean overrideRetainAll(Collection<?> c) {
+               return super.retainAll(c);
+       }
+
+       @Override
+       public void clear() {
+               checkModifiable();
+               overrideClear();
+       }
+
+       /**
+        * Same as {@link #clear()} but bypasses the modifiable flag.
+        */
+       public void overrideClear() {
+               super.clear();
+       }
+
+       @Override
+       public boolean removeIf(Predicate<? super E> filter) {
+               checkModifiable();
+               return overrideRemoveIf(filter);
+       }
+
+       /**
+        * Same as {@link #removeIf(Predicate)} but bypasses the modifiable 
flag.
+        *
+        * @param filter A predicate which returns true for elements to be 
removed.
+        * @return <jk>true</jk> if any elements were removed.
+        */
+       public boolean overrideRemoveIf(Predicate<? super E> filter) {
+               return super.removeIf(filter);
+       }
+
+       @Override
+       public List<E> subList(int fromIndex, int toIndex) {
+               return new ControlledArrayList<>(modifiable, 
super.subList(fromIndex, toIndex));
+       }
+
+       @Override
+       public ListIterator<E> listIterator() {
+               return listIterator(0);
+       }
+
+       @Override
+       public ListIterator<E> listIterator(final int index) {
+               if (modifiable)
+                       return overrideListIterator(index);
+
+               return new ListIterator<E>() {
+                       private final ListIterator<? extends E> i = 
overrideListIterator(index);
+
+                       @Override
+                       public boolean hasNext() {
+                               return i.hasNext();
+                       }
+
+                       @Override
+                       public E next() {
+                               return i.next();
+                       }
+
+                       @Override
+                       public boolean hasPrevious() {
+                               return i.hasPrevious();
+                       }
+
+                       @Override
+                       public E previous() {
+                               return i.previous();
+                       }
+
+                       @Override
+                       public int nextIndex() {
+                               return i.nextIndex();
+                       }
+
+                       @Override
+                       public int previousIndex() {
+                               return i.previousIndex();
+                       }
+
+                       @Override
+                       public void remove() {
+                               throw new UnsupportedOperationException();
+                       }
+
+                       @Override
+                       public void set(E e) {
+                               throw new UnsupportedOperationException();
+                       }
+
+                       @Override
+                       public void add(E e) {
+                               throw new UnsupportedOperationException();
+                       }
+
+                       @Override
+                       public void forEachRemaining(Consumer<? super E> 
action) {
+                               i.forEachRemaining(action);
+                       }
+               };
+       }
+
+       /**
+        * Same as {@link #listIterator()} but bypasses the modifiable flag.
+        *
+        * @param index Index of the first element to be returned from the list 
iterator.
+        * @return A list iterator over the elements in this list (in proper 
sequence), starting at the specified position in the list.
+        */
+       public ListIterator<E> overrideListIterator(final int index) {
+               return super.listIterator(index);
+       }
+
+       @Override
+       public Iterator<E> iterator() {
+               if (modifiable)
+                       return overrideIterator();
+
+               return new Iterator<E>() {
+                       private final Iterator<? extends E> i = 
overrideIterator();
+
+                       @Override
+                       public boolean hasNext() {
+                               return i.hasNext();
+                       }
+
+                       @Override
+                       public E next() {
+                               return i.next();
+                       }
+
+                       @Override
+                       public void remove() {
+                               throw new UnsupportedOperationException();
+                       }
+
+                       @Override
+                       public void forEachRemaining(Consumer<? super E> 
action) {
+                               i.forEachRemaining(action);
+                       }
+               };
+       }
+
+       /**
+        * Same as {@link #iterator()} but bypasses the modifiable flag.
+        *
+        * @return An iterator over the elements in this list in proper 
sequence.
+        */
+       public Iterator<E> overrideIterator() {
+               return super.iterator();
+       }
+}
diff --git 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
index f26e9a472..b4c1a3493 100644
--- 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
+++ 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
@@ -6777,7 +6777,7 @@ public class RestClient extends BeanContextable 
implements HttpClient, Closeable
                        if (body instanceof NameValuePair[])
                                return req.content(new 
UrlEncodedFormEntity(alist((NameValuePair[])body)));
                        if (body instanceof PartList)
-                               return req.content(new 
UrlEncodedFormEntity(((PartList)body).toNameValuePairs()));
+                               return req.content(new 
UrlEncodedFormEntity(((PartList)body)));
                        if (body instanceof HttpResource)
                                ((HttpResource)body).getHeaders().forEach(x-> 
req.header(x));
                        if (body instanceof HttpEntity) {
diff --git 
a/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/http/part/PartList.java
 
b/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/http/part/PartList.java
index f0ca32b4b..e57bc67c3 100644
--- 
a/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/http/part/PartList.java
+++ 
b/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/http/part/PartList.java
@@ -24,6 +24,7 @@ import java.util.stream.*;
 import org.apache.http.*;
 import org.apache.http.util.*;
 import org.apache.juneau.*;
+import org.apache.juneau.collections.*;
 import org.apache.juneau.http.HttpParts;
 import org.apache.juneau.http.annotation.*;
 import org.apache.juneau.internal.*;
@@ -156,21 +157,27 @@ import org.apache.juneau.svl.*;
  *     <li class='extlink'>{@source}
  * </ul>
  */
-public class PartList {
+public class PartList extends ControlledArrayList<NameValuePair> {
 
        
//-----------------------------------------------------------------------------------------------------------------
        // Static
        
//-----------------------------------------------------------------------------------------------------------------
 
+       private static final long serialVersionUID = 1L;
        private static final NameValuePair[] EMPTY_ARRAY = new NameValuePair[0];
        private static final String[] EMPTY_STRING_ARRAY = new String[0];
        private static final Predicate<NameValuePair> NOT_NULL = x -> x != null;
 
        /** Represents no part supplier in annotations. */
-       public static final class Null extends PartList {}
+       public static final class Null extends PartList {
+               Null(boolean modifiable) {
+                       super(false);
+               }
+               private static final long serialVersionUID = 1L;
+       }
 
        /** Predefined instance. */
-       public static final PartList EMPTY = new PartList();
+       public static final PartList EMPTY = new PartList(false);
 
        /**
         * Instantiates a new builder for this bean.
@@ -191,7 +198,7 @@ public class PartList {
         * @return A new unmodifiable instance, never <jk>null</jk>.
         */
        public static PartList of(List<NameValuePair> parts) {
-               return parts == null || parts.isEmpty() ? EMPTY : new 
PartList(parts);
+               return parts == null || parts.isEmpty() ? EMPTY : new 
PartList(true, parts);
        }
 
        /**
@@ -203,7 +210,7 @@ public class PartList {
         * @return A new unmodifiable instance, never <jk>null</jk>.
         */
        public static PartList of(NameValuePair...parts) {
-               return parts == null || parts.length == 0 ? EMPTY : new 
PartList(parts);
+               return parts == null || parts.length == 0 ? EMPTY : new 
PartList(true, parts);
        }
 
        /**
@@ -228,7 +235,7 @@ public class PartList {
                ArrayBuilder<NameValuePair> b = 
ArrayBuilder.of(NameValuePair.class).filter(NOT_NULL).size(pairs.length / 2);
                for (int i = 0; i < pairs.length; i+=2)
                        b.add(BasicPart.of(stringify(pairs[i]), pairs[i+1]));
-               return new PartList(b.orElse(EMPTY_ARRAY));
+               return new PartList(true, b.orElse(EMPTY_ARRAY));
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -244,7 +251,7 @@ public class PartList {
                final List<NameValuePair> entries;
                List<NameValuePair> defaultEntries;
                private VarResolver varResolver;
-               boolean caseInsensitive = false;
+               boolean caseInsensitive = false, unmodifiable = false;
 
                /**
                 * Constructor.
@@ -261,8 +268,9 @@ public class PartList {
                 */
                protected Builder(PartList copyFrom) {
                        super(copyFrom.getClass());
-                       entries = list(copyFrom.entries);
+                       entries = copyOf(copyFrom);
                        caseInsensitive = copyFrom.caseInsensitive;
+                       unmodifiable = false;
                }
 
                /**
@@ -276,6 +284,7 @@ public class PartList {
                        defaultEntries = copyOf(copyFrom.defaultEntries);
                        varResolver = copyFrom.varResolver;
                        caseInsensitive = copyFrom.caseInsensitive;
+                       unmodifiable = copyFrom.unmodifiable;
                }
 
                @Override /* BeanBuilder */
@@ -359,27 +368,26 @@ public class PartList {
                }
 
                /**
-                * Removes any parts already in this builder.
+                * Specifies that the resulting list should be unmodifiable.
+                *
+                * <p>
+                * The default behavior is modifiable.
                 *
                 * @return This object.
                 */
-               @FluentSetter
-               public Builder clear() {
-                       entries.clear();
+               public Builder unmodifiable() {
+                       unmodifiable = true;
                        return this;
                }
 
                /**
-                * Adds the specified parts to the end of the parts in this 
builder.
+                * Removes any parts already in this builder.
                 *
-                * @param value The parts to add.  <jk>null</jk> values are 
ignored.
                 * @return This object.
                 */
                @FluentSetter
-               public Builder append(PartList value) {
-                       if (value != null)
-                               for (NameValuePair x : value.entries)
-                                       append(x);
+               public Builder clear() {
+                       entries.clear();
                        return this;
                }
 
@@ -450,8 +458,7 @@ public class PartList {
                @FluentSetter
                public Builder append(List<? extends NameValuePair> values) {
                        if (values != null)
-                               for (int i = 0, j = values.size(); i < j; i++)
-                                       append(values.get(i));
+                               values.forEach(x -> append(x));
                        return this;
                }
 
@@ -464,7 +471,7 @@ public class PartList {
                @FluentSetter
                public Builder prepend(PartList value) {
                        if (value != null)
-                               prependAll(entries, value.entries);
+                               prependAll(entries, value.getAll());
                        return this;
                }
 
@@ -538,20 +545,6 @@ public class PartList {
                        return this;
                }
 
-               /**
-                * Removes the specified part from this builder.
-                *
-                * @param value The part to remove.  <jk>null</jk> values are 
ignored.
-                * @return This object.
-                */
-               @FluentSetter
-               public Builder remove(PartList value) {
-                       if (value != null)
-                               for (int i = 0; i < value.entries.length; i++)
-                                       remove(value.entries[i]);
-                       return this;
-               }
-
                /**
                 * Removes the specified part from this builder.
                 *
@@ -586,8 +579,8 @@ public class PartList {
                 */
                @FluentSetter
                public Builder remove(List<? extends NameValuePair> values) {
-                       for (int i = 0, j = values.size(); i < j; i++) /* See 
HTTPCORE-361 */
-                               remove(values.get(i));
+                       if (values != null)
+                               values.forEach(x -> remove(x));
                        return this;
                }
 
@@ -727,21 +720,6 @@ public class PartList {
                        return this;
                }
 
-               /**
-                * Adds or replaces the parts with the specified names.
-                *
-                * <p>
-                * If no part with the same name is found the given part is 
added to the end of the list.
-                *
-                * @param values The parts to replace.  <jk>null</jk> values 
are ignored.
-                * @return This object.
-                */
-               public Builder set(PartList values) {
-                       if (values != null)
-                               set(values.entries);
-                       return this;
-               }
-
                /**
                 * Sets a default value for a part.
                 *
@@ -852,21 +830,6 @@ public class PartList {
                        return this;
                }
 
-               /**
-                * Replaces the first occurrence of the parts with the same 
name.
-                *
-                * <p>
-                * If no part with the same name is found the given part is 
added to the end of the list.
-                *
-                * @param values The default parts to set.  <jk>null</jk> 
values are ignored.
-                * @return This object.
-                */
-               public Builder setDefault(PartList values) {
-                       if (values != null)
-                               setDefault(values.entries);
-                       return this;
-               }
-
                /**
                 * Adds the specify part to this list.
                 *
@@ -1007,33 +970,6 @@ public class PartList {
                        throw new BasicRuntimeException("Invalid value 
specified for flag parameter on add(flag,values) method: {0}", flag);
                }
 
-               /**
-                * Adds the specified parts to this list.
-                *
-                * @param flag
-                *      What to do with the part.
-                *      <br>Possible values:
-                *      <ul>
-                *              <li>{@link ListOperation#APPEND APPEND} - Calls 
{@link #append(PartList)}.
-                *              <li>{@link ListOperation#PREPEND PREEND} - 
Calls {@link #prepend(PartList)}.
-                *              <li>{@link ListOperation#SET REPLACE} - Calls 
{@link #set(PartList)}.
-                *              <li>{@link ListOperation#DEFAULT DEFAULT} - 
Calls {@link #setDefault(PartList)}.
-                *      </ul>
-                * @param values The parts to add.
-                * @return This object.
-                */
-               public Builder add(ListOperation flag, PartList values) {
-                       if (flag == ListOperation.APPEND)
-                               return append(values);
-                       if (flag == ListOperation.PREPEND)
-                               return prepend(values);
-                       if (flag == ListOperation.SET)
-                               return set(values);
-                       if (flag == ListOperation.DEFAULT)
-                               return setDefault(values);
-                       throw new BasicRuntimeException("Invalid value 
specified for flag parameter on add(flag,values) method: {0}", flag);
-               }
-
                /**
                 * Performs an action on all the parts in this list.
                 *
@@ -1177,7 +1113,6 @@ public class PartList {
        // Instance
        
//-----------------------------------------------------------------------------------------------------------------
 
-       final NameValuePair[] entries;
        final boolean caseInsensitive;
 
        /**
@@ -1186,24 +1121,17 @@ public class PartList {
         * @param builder The builder containing the settings for this bean.
         */
        public PartList(Builder builder) {
-               if (builder.defaultEntries == null) {
-                       entries = builder.entries.toArray(new 
NameValuePair[builder.entries.size()]);
-               } else {
-                       ArrayBuilder<NameValuePair> l = 
ArrayBuilder.of(NameValuePair.class).filter(NOT_NULL).size(builder.entries.size()
 + builder.defaultEntries.size());
-
-                       for (int i = 0, j = builder.entries.size(); i < j; i++)
-                               l.add(builder.entries.get(i));
+               super(! builder.unmodifiable, builder.entries);
 
+               if (builder.defaultEntries != null) {
                        for (int i1 = 0, j1 = builder.defaultEntries.size(); i1 
< j1; i1++) {
                                NameValuePair x = 
builder.defaultEntries.get(i1);
                                boolean exists = false;
                                for (int i2 = 0, j2 = builder.entries.size(); 
i2 < j2 && ! exists; i2++)
                                        exists = 
eq(builder.entries.get(i2).getName(), x.getName());
                                if (! exists)
-                                       l.add(x);
+                                       overrideAdd(x);
                        }
-
-                       entries = l.orElse(EMPTY_ARRAY);
                }
                this.caseInsensitive = builder.caseInsensitive;
        }
@@ -1211,39 +1139,37 @@ public class PartList {
        /**
         * Constructor.
         *
+        * @param modifiable Whether this list should be modifiable.
         * @param parts
         *      The parts to add to the list.
         *      <br>Can be <jk>null</jk>.
         *      <br><jk>null</jk> entries are ignored.
         */
-       protected PartList(List<NameValuePair> parts) {
-               ArrayBuilder<NameValuePair> l = 
ArrayBuilder.of(NameValuePair.class).filter(NOT_NULL).size(parts.size());
-               for (int i = 0, j = parts.size(); i < j; i++)
-                       l.add(parts.get(i));
-               entries = l.orElse(EMPTY_ARRAY);
+       protected PartList(boolean modifiable, List<NameValuePair> parts) {
+               super(modifiable, parts);
                caseInsensitive = false;
        }
 
        /**
         * Constructor.
         *
+        * @param modifiable Whether this list should be modifiable.
         * @param parts
         *      The parts to add to the list.
         *      <br><jk>null</jk> entries are ignored.
         */
-       protected PartList(NameValuePair...parts) {
-               ArrayBuilder<NameValuePair> l = 
ArrayBuilder.of(NameValuePair.class).filter(NOT_NULL).size(parts.length);
-               for (int i = 0; i < parts.length; i++)
-                       l.add(parts[i]);
-               entries = l.orElse(EMPTY_ARRAY);
+       protected PartList(boolean modifiable, NameValuePair...parts) {
+               super(modifiable, Arrays.asList(parts));
                caseInsensitive = false;
        }
 
        /**
         * Default constructor.
+        *
+        * @param modifiable Whether this list should be modifiable.
         */
-       protected PartList() {
-               entries = EMPTY_ARRAY;
+       protected PartList(boolean modifiable) {
+               super(modifiable);
                caseInsensitive = false;
        }
 
@@ -1270,8 +1196,7 @@ public class PartList {
 
                NameValuePair first = null;
                List<NameValuePair> rest = null;
-               for (int i = 0; i < entries.length; i++) {
-                       NameValuePair x = entries[i];
+               for (NameValuePair x : this) {
                        if (eq(x.getName(), name)) {
                                if (first == null)
                                        first = x;
@@ -1329,8 +1254,7 @@ public class PartList {
 
                NameValuePair first = null;
                List<NameValuePair> rest = null;
-               for (int i = 0; i < entries.length; i++) {
-                       NameValuePair x = entries[i];
+               for (NameValuePair x : this) {
                        if (eq(x.getName(), name)) {
                                if (first == null)
                                        first = x;
@@ -1393,7 +1317,7 @@ public class PartList {
         *      An array of all the parts in this list, or an empty array if no 
parts are present.
         */
        public NameValuePair[] getAll() {
-               return entries.length == 0 ? EMPTY_ARRAY : 
Arrays.copyOf(entries, entries.length);
+               return size() == 0 ? EMPTY_ARRAY : toArray(new 
NameValuePair[size()]);
        }
 
        /**
@@ -1410,21 +1334,12 @@ public class PartList {
         */
        public NameValuePair[] getAll(String name) {
                ArrayBuilder<NameValuePair> b = 
ArrayBuilder.of(NameValuePair.class).filter(NOT_NULL);
-               for (int i = 0; i < entries.length; i++)
-                       if (eq(entries[i].getName(), name))
-                               b.add(entries[i]);
+               for (NameValuePair x : this)
+                       if (eq(x.getName(), name))
+                               b.add(x);
                return b.orElse(EMPTY_ARRAY);
        }
 
-       /**
-        * Returns the number of parts in this list.
-        *
-        * @return The number of parts in this list.
-        */
-       public int size() {
-               return entries.length;
-       }
-
        /**
         * Gets the first part with the given name.
         *
@@ -1435,8 +1350,7 @@ public class PartList {
         * @return The first matching part, or <jk>null</jk> if not found.
         */
        public Optional<NameValuePair> getFirst(String name) {
-               for (int i = 0; i < entries.length; i++) {
-                       NameValuePair x = entries[i];
+               for (NameValuePair x : this) {
                        if (eq(x.getName(), name))
                                return optional(x);
                }
@@ -1453,8 +1367,8 @@ public class PartList {
         * @return The last matching part, or <jk>null</jk> if not found.
         */
        public Optional<NameValuePair> getLast(String name) {
-               for (int i = entries.length - 1; i >= 0; i--) {
-                       NameValuePair x = entries[i];
+               for (int i = size() - 1; i >= 0; i--) {
+                       NameValuePair x = get(i);
                        if (eq(x.getName(), name))
                                return optional(x);
                }
@@ -1505,8 +1419,7 @@ public class PartList {
         * @return <jk>true</jk> if at least one part with the name is present.
         */
        public boolean contains(String name) {
-               for (int i = 0; i < entries.length; i++) {
-                       NameValuePair x = entries[i];
+               for (NameValuePair x : this) {
                        if (eq(x.getName(), name))
                                return true;
                }
@@ -1518,8 +1431,9 @@ public class PartList {
         *
         * @return A new iterator over this list of parts.
         */
+       @Override
        public PartIterator iterator() {
-               return new BasicPartIterator(entries, null, caseInsensitive);
+               return new BasicPartIterator(getAll(), null, caseInsensitive);
        }
 
        /**
@@ -1530,21 +1444,7 @@ public class PartList {
         * @return A new iterator over the matching parts in this list.
         */
        public PartIterator iterator(String name) {
-               return new BasicPartIterator(entries, name, caseInsensitive);
-       }
-
-       /**
-        * Performs an action on all parts in this list.
-        *
-        * <p>
-        * This is the preferred method for iterating over parts as it does not 
involve
-        * creation or copy of lists/arrays.
-        *
-        * @param action An action to perform on each element.
-        * @return This object.
-        */
-       public PartList forEach(Consumer<NameValuePair> action) {
-               return forEach(x -> true, action);
+               return new BasicPartIterator(getAll(), name, caseInsensitive);
        }
 
        /**
@@ -1574,23 +1474,10 @@ public class PartList {
         * @return This object.
         */
        public PartList forEach(Predicate<NameValuePair> filter, 
Consumer<NameValuePair> action) {
-               for (int i = 0; i < entries.length; i++)
-                       consume(filter, action, entries[i]);
+               forEach(x -> consume(filter, action, x));
                return this;
        }
 
-       /**
-        * Returns a stream of the parts in this list.
-        *
-        * <p>
-        * This does not involve a copy of the underlying array of 
<c>NameValuePair</c> objects so should perform well.
-        *
-        * @return This object.
-        */
-       public Stream<NameValuePair> stream() {
-               return Arrays.stream(entries);
-       }
-
        /**
         * Returns a stream of the parts in this list with the specified name.
         *
@@ -1601,28 +1488,7 @@ public class PartList {
         * @return This object.
         */
        public Stream<NameValuePair> stream(String name) {
-               return Arrays.stream(entries).filter(x->eq(name, x.getName()));
-       }
-
-       /**
-        * Returns the contents of this list as an unmodifiable list of {@link 
NameValuePair} objects.
-        *
-        * @return The contents of this list as an unmodifiable list of {@link 
NameValuePair} objects.
-        */
-       public List<NameValuePair> toNameValuePairs() {
-               return ulist(entries);
-       }
-
-       /**
-        * Performs an action on the contents of this list.
-        *
-        * @param action The action to perform.
-        * @return This object.
-        */
-       public PartList forEachNameValuePair(Consumer<NameValuePair> action) {
-               for (NameValuePair p : entries)
-                       action.accept(p);
-               return this;
+               return Arrays.stream(getAll(name)).filter(x->eq(name, 
x.getName()));
        }
 
        private boolean eq(String s1, String s2) {
@@ -1635,15 +1501,16 @@ public class PartList {
        @Override /* Object */
        public String toString() {
                StringBuilder sb = new StringBuilder();
-               for (int i = 0; i < entries.length; i++) {
-                       NameValuePair p = entries[i];
-                       String v = p.getValue();
-                       if (v != null) {
-                               if (sb.length() > 0)
-                                       sb.append("&");
-                               
sb.append(urlEncode(p.getName())).append('=').append(urlEncode(p.getValue()));
+               forEach(p -> {
+                       if (p != null) {
+                               String v = p.getValue();
+                               if (v != null) {
+                                       if (sb.length() > 0)
+                                               sb.append("&");
+                                       
sb.append(urlEncode(p.getName())).append('=').append(urlEncode(p.getValue()));
+                               }
                        }
-               }
+               });
                return sb.toString();
        }
 }
diff --git a/juneau-utest/src/test/java/org/apache/juneau/utils/ArgsTest.java 
b/juneau-utest/src/test/java/org/apache/juneau/collections/Args_Test.java
similarity index 88%
rename from juneau-utest/src/test/java/org/apache/juneau/utils/ArgsTest.java
rename to 
juneau-utest/src/test/java/org/apache/juneau/collections/Args_Test.java
index ace209ace..0a9dd9865 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/utils/ArgsTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/collections/Args_Test.java
@@ -10,22 +10,21 @@
 // * "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.utils;
+package org.apache.juneau.collections;
 
 import static org.junit.Assert.*;
 import static org.junit.runners.MethodSorters.*;
 
-import org.apache.juneau.collections.*;
 import org.junit.*;
 
 @FixMethodOrder(NAME_ASCENDING)
-public class ArgsTest {
+public class Args_Test {
 
-       
//====================================================================================================
+       
//-----------------------------------------------------------------------------------------------------------------
        // test - Basic tests
-       
//====================================================================================================
+       
//-----------------------------------------------------------------------------------------------------------------
        @Test
-       public void test() throws Exception {
+       public void basic() throws Exception {
                Args a;
 
                // Empty args
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/collections/ControlledArrayList_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/collections/ControlledArrayList_Test.java
new file mode 100644
index 000000000..d568f5da0
--- /dev/null
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/collections/ControlledArrayList_Test.java
@@ -0,0 +1,191 @@
+// 
***************************************************************************************************************************
+// * 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.collections;
+
+import static org.apache.juneau.assertions.Assertions.*;
+import static org.junit.Assert.*;
+import static org.junit.runners.MethodSorters.*;
+
+import java.util.*;
+
+import org.junit.*;
+
+@FixMethodOrder(NAME_ASCENDING)
+public class ControlledArrayList_Test {
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // test - Basic tests
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Test
+       public void a01_constructors() throws Exception {
+               ControlledArrayList<Integer> x;
+
+               x = new ControlledArrayList<>(true);
+               assertTrue(x.isModifiable());
+
+               x = new ControlledArrayList<>(false);
+               assertFalse(x.isModifiable());
+
+               x = new ControlledArrayList<>(true, Arrays.asList(1));
+               assertTrue(x.isModifiable());
+
+               x = new ControlledArrayList<>(false, Arrays.asList(1));
+               assertFalse(x.isModifiable());
+       }
+
+       @Test
+       public void a02_basicMethods() throws Exception {
+               ControlledArrayList<Integer> x1 = new 
ControlledArrayList<Integer>(true, Arrays.asList(1));
+               ControlledArrayList<Integer> x2 = new 
ControlledArrayList<Integer>(false, Arrays.asList(1));
+
+               x1.set(0, 2);
+               assertThrown(() -> x2.set(0, 
2)).isType(UnsupportedOperationException.class);
+               x2.overrideSet(0, 2);
+               assertList(x1).is(x2);
+
+               x1.add(0, 2);
+               assertThrown(() -> x2.add(0, 
2)).isType(UnsupportedOperationException.class);
+               x2.overrideAdd(0, 2);
+               assertList(x1).is(x2);
+
+               x1.remove(0);
+               assertThrown(() -> 
x2.remove(0)).isType(UnsupportedOperationException.class);
+               x2.overrideRemove(0);
+               assertList(x1).is(x2);
+
+               x1.addAll(0, Arrays.asList(3));
+               assertThrown(() -> x2.addAll(0, 
Arrays.asList(3))).isType(UnsupportedOperationException.class);
+               x2.overrideAddAll(0, Arrays.asList(3));
+               assertList(x1).is(x2);
+
+               x1.replaceAll(x -> x);
+               assertThrown(() -> x2.replaceAll(x -> 
x)).isType(UnsupportedOperationException.class);
+               x2.overrideReplaceAll(x -> x);
+               assertList(x1).is(x2);
+
+               x1.sort(null);
+               assertThrown(() -> 
x2.sort(null)).isType(UnsupportedOperationException.class);
+               x2.overrideSort(null);
+               assertList(x1).is(x2);
+
+               x1.add(1);
+               assertThrown(() -> 
x2.add(1)).isType(UnsupportedOperationException.class);
+               x2.overrideAdd(1);
+               assertList(x1).is(x2);
+
+               x1.remove((Integer)1);
+               assertThrown(() -> 
x2.remove((Integer)1)).isType(UnsupportedOperationException.class);
+               x2.overrideRemove((Integer)1);
+               assertList(x1).is(x2);
+
+               x1.addAll(Arrays.asList(3));
+               assertThrown(() -> 
x2.addAll(Arrays.asList(3))).isType(UnsupportedOperationException.class);
+               x2.overrideAddAll(Arrays.asList(3));
+               assertList(x1).is(x2);
+
+               x1.removeAll(Arrays.asList(3));
+               assertThrown(() -> 
x2.removeAll(Arrays.asList(3))).isType(UnsupportedOperationException.class);
+               x2.overrideRemoveAll(Arrays.asList(3));
+               assertList(x1).is(x2);
+
+               x1.retainAll(Arrays.asList(2));
+               assertThrown(() -> 
x2.retainAll(Arrays.asList(2))).isType(UnsupportedOperationException.class);
+               x2.overrideRetainAll(Arrays.asList(2));
+               assertList(x1).is(x2);
+
+               x1.clear();
+               assertThrown(() -> 
x2.clear()).isType(UnsupportedOperationException.class);
+               x2.overrideClear();
+               assertList(x1).is(x2);
+
+               x1.add(1);
+               x2.overrideAdd(1);
+
+               x1.removeIf(x -> x == 1);
+               assertThrown(() -> x2.removeIf(x -> x == 
1)).isType(UnsupportedOperationException.class);
+               x2.overrideRemoveIf(x -> x == 1);
+               assertList(x1).is(x2);
+
+               x1.add(1);
+               x2.overrideAdd(1);
+
+               ControlledArrayList<Integer> x1a = 
(ControlledArrayList<Integer>) x1.subList(0, 0);
+               ControlledArrayList<Integer> x2a = 
(ControlledArrayList<Integer>) x2.subList(0, 0);
+               assertTrue(x1a.isModifiable());
+               assertFalse(x2a.isModifiable());
+       }
+
+       @Test
+       public void a03_iterator() throws Exception {
+               ControlledArrayList<Integer> x1 = new 
ControlledArrayList<Integer>(true, Arrays.asList(1));
+               ControlledArrayList<Integer> x2 = new 
ControlledArrayList<Integer>(false, Arrays.asList(1));
+
+               Iterator<Integer> i1 = x1.iterator();
+               Iterator<Integer> i2 = x2.iterator();
+
+               assertTrue(i1.hasNext());
+               assertTrue(i2.hasNext());
+
+               assertEquals(1, i1.next().intValue());
+               assertEquals(1, i2.next().intValue());
+
+               i1.remove();
+               assertThrown(() -> 
i2.remove()).isType(UnsupportedOperationException.class);
+
+               i1.forEachRemaining(x -> {});
+               i2.forEachRemaining(x -> {});
+       }
+
+       @Test
+       public void a04_listIterator() throws Exception {
+               ControlledArrayList<Integer> x1 = new 
ControlledArrayList<Integer>(true, Arrays.asList(1));
+               ControlledArrayList<Integer> x2 = new 
ControlledArrayList<Integer>(false, Arrays.asList(1));
+
+               ListIterator<Integer> i1a = x1.listIterator();
+               ListIterator<Integer> i2a = x2.listIterator();
+
+               assertTrue(i1a.hasNext());
+               assertTrue(i2a.hasNext());
+
+               assertEquals(1, i1a.next().intValue());
+               assertEquals(1, i2a.next().intValue());
+
+               assertTrue(i1a.hasPrevious());
+               assertTrue(i2a.hasPrevious());
+
+               assertEquals(1, i1a.nextIndex());
+               assertEquals(1, i2a.nextIndex());
+
+               assertEquals(0, i1a.previousIndex());
+               assertEquals(0, i2a.previousIndex());
+
+               i1a.previous();
+               i2a.previous();
+
+               i1a.set(1);
+               assertThrown(() -> 
i2a.set(1)).isType(UnsupportedOperationException.class);
+
+               i1a.add(1);
+               assertThrown(() -> 
i2a.add(1)).isType(UnsupportedOperationException.class);
+
+               i1a.next();
+               i2a.next();
+
+               i1a.remove();
+               assertThrown(() -> 
i2a.remove()).isType(UnsupportedOperationException.class);
+
+               i1a.forEachRemaining(x -> {});
+               i2a.forEachRemaining(x -> {});
+       }
+}
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/http/part/PartList_Test.java 
b/juneau-utest/src/test/java/org/apache/juneau/http/part/PartList_Test.java
index 34b679cbd..bb2fe44f1 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/http/part/PartList_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/http/part/PartList_Test.java
@@ -92,7 +92,7 @@ public class PartList_Test {
                x.append((List<NameValuePair>)null);
                
assertObject(x.build()).isString("Foo=1&Foo=2&Foo=3&Foo=4&Foo=5&Foo=6&Foo=7");
 
-               assertObject(new PartList.Null()).isString("");
+               assertObject(new PartList.Null(false)).isString("");
        }
 
        @Test
@@ -558,16 +558,6 @@ public class PartList_Test {
                assertObject(x14).isString("b=x&b=y&a=y&c=x");
        }
 
-       
//-----------------------------------------------------------------------------------------------------------------
-       // Other tests
-       
//-----------------------------------------------------------------------------------------------------------------
-
-       @Test
-       public void e01_asNameValuePairs() {
-               PartList x = PartList.of(APart.X);
-               assertObject(x.toNameValuePairs()).isString("[a=x]");
-       }
-
        
//-----------------------------------------------------------------------------------------------------------------
        // Utility methods
        
//-----------------------------------------------------------------------------------------------------------------

Reply via email to