You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2021/04/04 12:55:37 UTC
[juneau] branch master updated: Performance improvements around
HeaderList and PartList.
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 9550315 Performance improvements around HeaderList and PartList.
9550315 is described below
commit 9550315cd477fa4b926327f97479e936c83fe474
Author: JamesBognar <ja...@salesforce.com>
AuthorDate: Sun Apr 4 08:55:15 2021 -0400
Performance improvements around HeaderList and PartList.
---
.../juneau/http/header/BasicHeaderIterator.java | 104 ++++
.../apache/juneau/http/header/HeaderBeanMeta.java | 8 +-
.../org/apache/juneau/http/header/HeaderList.java | 113 ++--
.../juneau/http/header/HeaderListBuilder.java | 212 ++++++--
.../apache/juneau/http/part/BasicPartIterator.java | 97 ++++
.../HeaderBeanMeta.java => part/PartBeanMeta.java} | 85 +--
.../org/apache/juneau/http/part/PartIterator.java | 68 ++-
.../java/org/apache/juneau/http/part/PartList.java | 537 ++++++++++++++++---
.../apache/juneau/http/part/PartListBuilder.java | 490 ++++++++++++-----
.../org/apache/juneau/internal/ArrayBuilder.java | 12 +-
.../org/apache/juneau/internal/StringUtils.java | 12 +
.../org/apache/juneau/rest/client/RestClient.java | 18 +-
.../org/apache/juneau/rest/client/RestRequest.java | 51 +-
.../apache/juneau/rest/RestOperationContext.java | 8 +-
.../java/org/apache/juneau/http/PartList_Test.java | 101 ----
.../org/apache/juneau/http/header/Accept_Test.java | 3 +
.../juneau/http/header/HeaderBeanMeta_Test.java | 2 +-
.../apache/juneau/http/header/HeaderList_Test.java | 97 ++++
.../PartBeanMeta_Test.java} | 38 +-
.../org/apache/juneau/http/part/PartList_Test.java | 580 +++++++++++++++++++++
.../remote/Remote_FormDataAnnotation_Test.java | 12 +-
.../http/remote/Remote_PathAnnotation_Test.java | 16 +-
.../http/remote/Remote_QueryAnnotation_Test.java | 15 +-
.../rest/client/RestClient_FormData_Test.java | 1 -
.../org/apache/juneau/utils/ArrayBuilder_Test.java | 2 +-
25 files changed, 2124 insertions(+), 558 deletions(-)
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/BasicHeaderIterator.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/BasicHeaderIterator.java
new file mode 100644
index 0000000..4aa0ffe
--- /dev/null
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/BasicHeaderIterator.java
@@ -0,0 +1,104 @@
+// ***************************************************************************************************************************
+// * 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.http.header;
+
+import static org.apache.juneau.assertions.Assertions.*;
+
+import java.util.NoSuchElementException;
+
+import org.apache.http.Header;
+import org.apache.http.HeaderIterator;
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.internal.*;
+
+/**
+ * Basic implementation of a {@link HeaderIterator}.
+ *
+ * {@review}
+ */
+@NotThreadSafe
+public class BasicHeaderIterator implements HeaderIterator {
+
+ private final Header[] entries;
+ private final String name;
+ private final boolean caseSensitive;
+
+ private int currentIndex;
+
+ /**
+ * Creates a new header iterator.
+ *
+ * @param headers An array of headers over which to iterate.
+ * @param name The name of the headers over which to iterate, or <jk>null</jk> for all.
+ * @param caseSensitive Use case-sensitive matching for part name.
+ */
+ public BasicHeaderIterator(Header[] headers, String name, boolean caseSensitive) {
+ this.entries = assertArgNotNull("headers", headers);
+ this.name = name;
+ this.caseSensitive = caseSensitive;
+ this.currentIndex = findNext(-1);
+ }
+
+ private int findNext(int pos) {
+
+ int from = pos;
+
+ int to = entries.length - 1;
+ boolean found = false;
+ while (!found && (from < to)) {
+ from++;
+ found = filter(from);
+ }
+
+ return found ? from : -1;
+ }
+
+ private boolean filter(int index) {
+ return (name == null) || eq(name, entries[index].getName());
+ }
+
+ @Override /* HeaderIterator */
+ public boolean hasNext() {
+ return (currentIndex >= 0);
+ }
+
+ @Override /* HeaderIterator */
+ public Header nextHeader() throws NoSuchElementException {
+
+ int current = currentIndex;
+
+ if (current < 0)
+ throw new NoSuchElementException("Iteration already finished.");
+
+ currentIndex = findNext(current);
+
+ return entries[current];
+ }
+
+ @Override /* HeaderIterator */
+ public final Object next() throws NoSuchElementException {
+ return nextHeader();
+ }
+
+ /**
+ * Not supported.
+ */
+ @Override /* HeaderIterator */
+ public void remove() {
+ throw new UnsupportedOperationException("Remove is not supported.");
+ }
+
+ private boolean eq(String s1, String s2) {
+ return StringUtils.eq(!caseSensitive, s1, s2);
+ }
+}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/HeaderBeanMeta.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/HeaderBeanMeta.java
index a3978f3..7e7148b 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/HeaderBeanMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/HeaderBeanMeta.java
@@ -31,10 +31,10 @@ import org.apache.juneau.reflect.*;
* <p>
* Header beans must have one of the following public constructors:
* <ul>
- * <li><c>X(String <jv>headerValue</jv>)</c>
- * <li><c>X(Object <jv>headerValue</jv>)</c>
- * <li><c>X(String <jv>headerName</jv>, String <jv>headerValue</jv>)</c>
- * <li><c>X(String <jv>headerName</jv>, Object <jv>headerValue</jv>)</c>
+ * <li><c><jk>public</jk> X(String <jv>headerValue</jv>)</c>
+ * <li><c><jk>public</jk> X(Object <jv>headerValue</jv>)</c>
+ * <li><c><jk>public</jk> X(String <jv>headerName</jv>, String <jv>headerValue</jv>)</c>
+ * <li><c><jk>public</jk> X(String <jv>headerName</jv>, Object <jv>headerValue</jv>)</c>
* </ul>
*
* <p>
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/HeaderList.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/HeaderList.java
index 207151e..51b4194 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/HeaderList.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/HeaderList.java
@@ -20,7 +20,6 @@ import java.util.function.*;
import java.util.stream.*;
import org.apache.http.*;
-import org.apache.http.message.*;
import org.apache.http.util.*;
import org.apache.juneau.*;
import org.apache.juneau.annotation.*;
@@ -65,6 +64,28 @@ import org.apache.juneau.svl.*;
* </p>
*
* <p>
+ * The builder class supports setting default header values (i.e. add a header to the list if it isn't otherwise in the list).
+ * Note that this is different from simply setting a value twice as using default values will not overwrite existing
+ * headers.
+ * <br>The following example notes the distinction:
+ *
+ * <p class='bcode w800'>
+ * <jv>headers</jv> = HeaderList
+ * .<jsm>create</jsm>()
+ * .set(Accept.<jsf>TEXT_PLAIN</jsf>)
+ * .set(Accept.<jsf>TEXT_XML</jsf>)
+ * .build();
+ * <jsm>assertObject</jsm>(<jv>headers</jv>).isString(<js>"[Accept: text/xml]"</js>);
+ *
+ * <jv>headers</jv> = HeaderList
+ * .create()
+ * .set(Accept.<jsf>TEXT_PLAIN</jsf>)
+ * .setDefault(Accept.<jsf>TEXT_XML</jsf>)
+ * .build();
+ * <jsm>assertObject</jsm>(<jv>headers</jv>).isString(<js>"[Accept: text/plain]"</js>);
+ * </p>
+ *
+ * <p>
* Various methods are provided for iterating over the headers in this list to avoid array copies.
* <ul class='javatree'>
* <li class='jm'>{@link #forEach(Consumer)} / {@link #forEach(String,Consumer)} - Use consumers to process headers.
@@ -148,7 +169,7 @@ public class HeaderList {
/** Predefined instance. */
public static final HeaderList EMPTY = new HeaderList();
- final Header[] headers;
+ final Header[] entries;
final boolean caseSensitive;
/**
@@ -204,7 +225,7 @@ public class HeaderList {
return EMPTY;
if (pairs.length % 2 != 0)
throw new BasicRuntimeException("Odd number of parameters passed into HeaderList.ofPairs()");
- ArrayBuilder<Header> b = ArrayBuilder.create(Header.class, pairs.length / 2);
+ ArrayBuilder<Header> b = ArrayBuilder.create(Header.class, pairs.length / 2, true);
for (int i = 0; i < pairs.length; i+=2)
b.add(BasicHeader.of(stringify(pairs[i]), pairs[i+1]));
return new HeaderList(b.toArray());
@@ -216,7 +237,25 @@ public class HeaderList {
* @param builder The builder containing the settings for this bean.
*/
public HeaderList(HeaderListBuilder builder) {
- this.headers = builder.headers.toArray(new Header[builder.headers.size()]);
+ if (builder.defaultEntries == null) {
+ entries = builder.entries.toArray(new Header[builder.entries.size()]);
+ } else {
+ ArrayBuilder<Header> l = ArrayBuilder.create(Header.class, builder.entries.size() + builder.defaultEntries.size(), true);
+
+ for (int i = 0, j = builder.entries.size(); i < j; i++)
+ l.add(builder.entries.get(i));
+
+ for (int i1 = 0, j1 = builder.defaultEntries.size(); i1 < j1; i1++) {
+ Header 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);
+ }
+
+ entries = l.toArray();
+ }
this.caseSensitive = builder.caseSensitive;
}
@@ -229,10 +268,10 @@ public class HeaderList {
* <br><jk>null</jk> entries are ignored.
*/
protected HeaderList(List<Header> headers) {
- ArrayBuilder<Header> l = ArrayBuilder.create(Header.class, headers.size());
+ ArrayBuilder<Header> l = ArrayBuilder.create(Header.class, headers.size(), true);
for (int i = 0, j = headers.size(); i < j; i++)
l.add(headers.get(i));
- this.headers = l.toArray();
+ entries = l.toArray();
caseSensitive = false;
}
@@ -244,10 +283,10 @@ public class HeaderList {
* <br><jk>null</jk> entries are ignored.
*/
protected HeaderList(Header...headers) {
- ArrayBuilder<Header> l = ArrayBuilder.create(Header.class, headers.length);
+ ArrayBuilder<Header> l = ArrayBuilder.create(Header.class, headers.length, true);
for (int i = 0; i < headers.length; i++)
l.add(headers[i]);
- this.headers = l.toArray();
+ entries = l.toArray();
caseSensitive = false;
}
@@ -255,7 +294,7 @@ public class HeaderList {
* Default constructor.
*/
protected HeaderList() {
- this.headers = EMPTY_ARRAY;
+ entries = EMPTY_ARRAY;
caseSensitive = false;
}
@@ -282,8 +321,8 @@ public class HeaderList {
Header first = null;
List<Header> rest = null;
- for (int i = 0; i < headers.length; i++) {
- Header x = headers[i];
+ for (int i = 0; i < entries.length; i++) {
+ Header x = entries[i];
if (eq(x.getName(), name)) {
if (first == null)
first = x;
@@ -341,8 +380,8 @@ public class HeaderList {
Header first = null;
List<Header> rest = null;
- for (int i = 0; i < headers.length; i++) {
- Header x = headers[i];
+ for (int i = 0; i < entries.length; i++) {
+ Header x = entries[i];
if (eq(x.getName(), name)) {
if (first == null)
first = x;
@@ -388,11 +427,11 @@ public class HeaderList {
public <T> Optional<T> get(Class<T> type) {
assertArgNotNull("type", type);
- String headerName = HeaderBeanMeta.of(type).getSchema().getName();
- if (headerName == null)
+ String name = HeaderBeanMeta.of(type).getSchema().getName();
+ if (name == null)
throw new BasicIllegalArgumentException("Header name could not be found on bean type ''{0}''", type.getName());
- return get(headerName, type);
+ return get(name, type);
}
/**
@@ -410,8 +449,8 @@ public class HeaderList {
*/
public Header[] getAll(String name) {
List<Header> l = null;
- for (int i = 0; i < headers.length; i++) {
- Header x = headers[i];
+ for (int i = 0; i < entries.length; i++) {
+ Header x = entries[i];
if (eq(x.getName(), name)) {
if (l == null)
l = new ArrayList<>();
@@ -427,7 +466,7 @@ public class HeaderList {
* @return The number of headers in this list.
*/
public int size() {
- return headers.length;
+ return entries.length;
}
/**
@@ -440,8 +479,8 @@ public class HeaderList {
* @return The first matching header, or <jk>null</jk> if not found.
*/
public Optional<Header> getFirst(String name) {
- for (int i = 0; i < headers.length; i++) {
- Header x = headers[i];
+ for (int i = 0; i < entries.length; i++) {
+ Header x = entries[i];
if (eq(x.getName(), name))
return Optional.of(x);
}
@@ -458,8 +497,8 @@ public class HeaderList {
* @return The last matching header, or <jk>null</jk> if not found.
*/
public Optional<Header> getLast(String name) {
- for (int i = headers.length - 1; i >= 0; i--) {
- Header x = headers[i];
+ for (int i = entries.length - 1; i >= 0; i--) {
+ Header x = entries[i];
if (eq(x.getName(), name))
return Optional.of(x);
}
@@ -472,7 +511,7 @@ public class HeaderList {
* @return An array of all the headers in this list, or an empty array if no headers are present.
*/
public Header[] getAll() {
- return headers.length == 0 ? EMPTY_ARRAY : Arrays.copyOf(headers, headers.length);
+ return entries.length == 0 ? EMPTY_ARRAY : Arrays.copyOf(entries, entries.length);
}
/**
@@ -485,8 +524,8 @@ public class HeaderList {
* @return <jk>true</jk> if at least one header with the name is present.
*/
public boolean contains(String name) {
- for (int i = 0; i < headers.length; i++) {
- Header x = headers[i];
+ for (int i = 0; i < entries.length; i++) {
+ Header x = entries[i];
if (eq(x.getName(), name))
return true;
}
@@ -499,7 +538,7 @@ public class HeaderList {
* @return A new iterator over this list of headers.
*/
public HeaderIterator iterator() {
- return new BasicHeaderIterator(headers, null);
+ return new BasicHeaderIterator(entries, null, caseSensitive);
}
/**
@@ -510,7 +549,7 @@ public class HeaderList {
* @return A new iterator over the matching headers in this list.
*/
public HeaderIterator iterator(String name) {
- return new BasicHeaderIterator(headers, name);
+ return new BasicHeaderIterator(entries, name, caseSensitive);
}
/**
@@ -524,8 +563,8 @@ public class HeaderList {
* @return This object (for method chaining).
*/
public HeaderList forEach(Consumer<Header> c) {
- for (int i = 0; i < headers.length; i++)
- c.accept(headers[i]);
+ for (int i = 0; i < entries.length; i++)
+ c.accept(entries[i]);
return this;
}
@@ -541,9 +580,9 @@ public class HeaderList {
* @return This object (for method chaining).
*/
public HeaderList forEach(String name, Consumer<Header> c) {
- for (int i = 0; i < headers.length; i++)
- if (eq(name, headers[i].getName()))
- c.accept(headers[i]);
+ for (int i = 0; i < entries.length; i++)
+ if (eq(name, entries[i].getName()))
+ c.accept(entries[i]);
return this;
}
@@ -556,7 +595,7 @@ public class HeaderList {
* @return This object (for method chaining).
*/
public Stream<Header> stream() {
- return Arrays.stream(headers);
+ return Arrays.stream(entries);
}
/**
@@ -569,15 +608,15 @@ public class HeaderList {
* @return This object (for method chaining).
*/
public Stream<Header> stream(String name) {
- return Arrays.stream(headers).filter(x->eq(name, x.getName()));
+ return Arrays.stream(entries).filter(x->eq(name, x.getName()));
}
private boolean eq(String s1, String s2) {
- return caseSensitive ? StringUtils.eq(s1, s2) : StringUtils.eqic(s1, s2);
+ return StringUtils.eq(!caseSensitive, s1, s2);
}
@Override /* Object */
public String toString() {
- return Arrays.asList(headers).toString();
+ return Arrays.asList(entries).toString();
}
}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/HeaderListBuilder.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/HeaderListBuilder.java
index 6ce7ad3..af8390c 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/HeaderListBuilder.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/HeaderListBuilder.java
@@ -30,9 +30,10 @@ import org.apache.juneau.svl.*;
@NotThreadSafe
public class HeaderListBuilder {
- final List<Header> headers;
+ final List<Header> entries;
+ List<Header> defaultEntries;
private VarResolver varResolver;
- boolean caseSensitive = false;
+ boolean caseSensitive;
/** Predefined instance. */
private static final HeaderList EMPTY = new HeaderList();
@@ -41,7 +42,7 @@ public class HeaderListBuilder {
* Constructor.
*/
public HeaderListBuilder() {
- headers = new ArrayList<>();
+ entries = new ArrayList<>();
}
/**
@@ -50,9 +51,9 @@ public class HeaderListBuilder {
* @param copyFrom The bean to copy from.
*/
public HeaderListBuilder(HeaderList copyFrom) {
- headers = new ArrayList<>(copyFrom.headers.length);
- for (int i = 0; i < copyFrom.headers.length; i++)
- headers.add(copyFrom.headers[i]);
+ entries = new ArrayList<>(copyFrom.entries.length);
+ for (int i = 0; i < copyFrom.entries.length; i++)
+ entries.add(copyFrom.entries[i]);
caseSensitive = copyFrom.caseSensitive;
}
@@ -62,7 +63,7 @@ public class HeaderListBuilder {
* @return A new {@link HeaderList} bean.
*/
public HeaderList build() {
- return headers.isEmpty() ? EMPTY : new HeaderList(this);
+ return entries.isEmpty() && defaultEntries == null ? EMPTY : new HeaderList(this);
}
/**
@@ -77,6 +78,8 @@ public class HeaderListBuilder {
* <li class='jm'>{@link #prepend(String, Supplier) prepend(String,Supplier<?>)}
* <li class='jm'>{@link #set(String, Object) set(String,Object)}
* <li class='jm'>{@link #set(String, Supplier) set(String,Supplier<?>)}
+ * <li class='jm'>{@link #setDefault(String, Object) set(String,Object)}
+ * <li class='jm'>{@link #setDefault(String, Supplier) set(String,Supplier<?>)}
* </ul>
*
* <p>
@@ -100,6 +103,8 @@ public class HeaderListBuilder {
* <li class='jm'>{@link #prepend(String, Supplier) prepend(String,Supplier<?>)}
* <li class='jm'>{@link #set(String, Object) set(String,Object)}
* <li class='jm'>{@link #set(String, Supplier) set(String,Supplier<?>)}
+ * <li class='jm'>{@link #setDefault(String, Object) set(String,Object)}
+ * <li class='jm'>{@link #setDefault(String, Supplier) set(String,Supplier<?>)}
* </ul>
*
* @param varResolver The variable resolver to use for resolving variables.
@@ -119,7 +124,7 @@ public class HeaderListBuilder {
* @return This object (for method chaining).
*/
public HeaderListBuilder caseSensitive() {
- this.caseSensitive = true;
+ caseSensitive = true;
return this;
}
@@ -130,7 +135,7 @@ public class HeaderListBuilder {
*/
@FluentSetter
public HeaderListBuilder clear() {
- headers.clear();
+ entries.clear();
return this;
}
@@ -143,8 +148,8 @@ public class HeaderListBuilder {
@FluentSetter
public HeaderListBuilder append(HeaderList value) {
if (value != null)
- for (Header h : value.headers)
- append(h);
+ for (Header x : value.entries)
+ append(x);
return this;
}
@@ -157,7 +162,7 @@ public class HeaderListBuilder {
@FluentSetter
public HeaderListBuilder append(Header value) {
if (value != null)
- headers.add(value);
+ entries.add(value);
return this;
}
@@ -201,7 +206,7 @@ public class HeaderListBuilder {
@FluentSetter
public HeaderListBuilder append(Header...values) {
if (values != null)
- for (int i = 0; i < values.length; i++) /* See HTTPCORE-361 */
+ for (int i = 0; i < values.length; i++)
append(values[i]);
return this;
}
@@ -229,7 +234,7 @@ public class HeaderListBuilder {
@FluentSetter
public HeaderListBuilder prepend(HeaderList value) {
if (value != null)
- headers.addAll(0, Arrays.asList(value.headers));
+ entries.addAll(0, Arrays.asList(value.entries));
return this;
}
@@ -242,7 +247,7 @@ public class HeaderListBuilder {
@FluentSetter
public HeaderListBuilder prepend(Header value) {
if (value != null)
- headers.add(0, value);
+ entries.add(0, value);
return this;
}
@@ -299,7 +304,7 @@ public class HeaderListBuilder {
@FluentSetter
public HeaderListBuilder prepend(List<Header> values) {
if (values != null)
- headers.addAll(0, values);
+ entries.addAll(0, values);
return this;
}
@@ -312,8 +317,8 @@ public class HeaderListBuilder {
@FluentSetter
public HeaderListBuilder remove(HeaderList value) {
if (value != null)
- for (int i = 0; i < value.headers.length; i++) /* See HTTPCORE-361 */
- remove(value.headers[i]);
+ for (int i = 0; i < value.entries.length; i++)
+ remove(value.entries[i]);
return this;
}
@@ -326,7 +331,7 @@ public class HeaderListBuilder {
@FluentSetter
public HeaderListBuilder remove(Header value) {
if (value != null)
- headers.remove(value);
+ entries.remove(value);
return this;
}
@@ -338,7 +343,7 @@ public class HeaderListBuilder {
*/
@FluentSetter
public HeaderListBuilder remove(Header...values) {
- for (int i = 0; i < values.length; i++) /* See HTTPCORE-361 */
+ for (int i = 0; i < values.length; i++)
remove(values[i]);
return this;
}
@@ -364,9 +369,9 @@ public class HeaderListBuilder {
*/
@FluentSetter
public HeaderListBuilder remove(String name) {
- for (int i = 0; i < headers.size(); i++) /* See HTTPCORE-361 */
- if (eq(headers.get(i).getName(), name))
- headers.remove(i--);
+ for (int i = 0; i < entries.size(); i++) /* See HTTPCORE-361 */
+ if (eq(entries.get(i).getName(), name))
+ entries.remove(i--);
return this;
}
@@ -379,7 +384,7 @@ public class HeaderListBuilder {
@FluentSetter
public HeaderListBuilder remove(String...names) {
if (names != null)
- for (int i = 0; i < names.length; i++) /* See HTTPCORE-361 */
+ for (int i = 0; i < names.length; i++)
remove(names[i]);
return this;
}
@@ -397,21 +402,21 @@ public class HeaderListBuilder {
public HeaderListBuilder set(Header value) {
if (value != null) {
boolean replaced = false;
- for (int i = 0, j = headers.size(); i < j; i++) {
- Header x = headers.get(i);
+ for (int i = 0, j = entries.size(); i < j; i++) {
+ Header x = entries.get(i);
if (eq(x.getName(), value.getName())) {
if (replaced) {
- headers.remove(i);
+ entries.remove(i);
j--;
} else {
- headers.set(i, value);
+ entries.set(i, value);
replaced = true;
}
}
}
if (! replaced)
- headers.add(value);
+ entries.add(value);
}
return this;
@@ -468,22 +473,23 @@ public class HeaderListBuilder {
public HeaderListBuilder set(List<Header> values) {
if (values != null) {
- for (int k = 0, m = values.size(); k < m; k++) {
- Header h = values.get(k);
+ for (int i1 = 0, j1 = values.size(); i1 < j1; i1++) {
+ Header h = values.get(i1);
if (h != null) {
- for (int i = 0, j = headers.size(); i < j; i++) {
- Header x = headers.get(i);
+ for (int i2 = 0, j2 = entries.size(); i2 < j2; i2++) {
+ Header x = entries.get(i2);
if (eq(x.getName(), h.getName())) {
- headers.remove(i);
- j--;
+ entries.remove(i2);
+ j2--;
}
}
}
}
- for (Header v : values) {
- if (v != null) {
- headers.add(v);
+ for (int i = 0, j = values.size(); i < j; i++) {
+ Header x = values.get(i);
+ if (x != null) {
+ entries.add(x);
}
}
}
@@ -501,7 +507,135 @@ public class HeaderListBuilder {
* @return This object (for method chaining).
*/
public HeaderListBuilder set(HeaderList values) {
- return set(values.headers);
+ if (values != null)
+ set(values.entries);
+ return this;
+ }
+
+ /**
+ * Sets a default value for a header.
+ *
+ * <p>
+ * If no header with the same name is found, the given header is added to the end of the list.
+ *
+ * @param value The default header to set. <jk>null</jk> values are ignored.
+ * @return This object (for method chaining).
+ */
+ @FluentSetter
+ public HeaderListBuilder setDefault(Header value) {
+ if (value != null) {
+ boolean replaced = false;
+ if (defaultEntries == null)
+ defaultEntries = new ArrayList<>();
+ for (int i = 0, j = defaultEntries.size(); i < j; i++) {
+ Header x = defaultEntries.get(i);
+ if (eq(x.getName(), value.getName())) {
+ if (replaced) {
+ defaultEntries.remove(i);
+ j--;
+ } else {
+ defaultEntries.set(i, value);
+ replaced = true;
+ }
+ }
+ }
+
+ if (! replaced)
+ defaultEntries.add(value);
+ }
+
+ return this;
+ }
+
+ /**
+ * Sets default values for one or more headers.
+ *
+ * <p>
+ * If no header with the same name is found the given header is added to the end of the list.
+ *
+ * @param values The default headers to set. <jk>null</jk> values are ignored.
+ * @return This object (for method chaining).
+ */
+ @FluentSetter
+ public HeaderListBuilder setDefault(Header...values) {
+ if (values != null)
+ setDefault(Arrays.asList(values));
+ return this;
+ }
+
+ /**
+ * Sets a default value for a header.
+ *
+ * @param name The header name.
+ * @param value The header value.
+ * @return This object (for method chaining).
+ */
+ public HeaderListBuilder setDefault(String name, Object value) {
+ return setDefault(header(name, value));
+ }
+
+ /**
+ * Sets a default value for a header.
+ *
+ * @param name The header name.
+ * @param value The header value.
+ * @return This object (for method chaining).
+ */
+ public HeaderListBuilder setDefault(String name, Supplier<?> value) {
+ return setDefault(header(name, value));
+ }
+
+ /**
+ * Sets default values for one or more headers.
+ *
+ * <p>
+ * If no header with the same name is found the given header is added to the end of the list.
+ *
+ * @param values The default headers to set. <jk>null</jk> values are ignored.
+ * @return This object (for method chaining).
+ */
+ @FluentSetter
+ public HeaderListBuilder setDefault(List<Header> values) {
+
+ if (values != null) {
+ if (defaultEntries == null)
+ defaultEntries = new ArrayList<>();
+ for (int i1 = 0, j1 = values.size(); i1 < j1; i1++) {
+ Header h = values.get(i1);
+ if (h != null) {
+ for (int i2 = 0, j2 = defaultEntries.size(); i2 < j2; i2++) {
+ Header x = defaultEntries.get(i2);
+ if (eq(x.getName(), h.getName())) {
+ defaultEntries.remove(i2);
+ j2--;
+ }
+ }
+ }
+ }
+
+ for (Header v : values) {
+ if (v != null) {
+ defaultEntries.add(v);
+ }
+ }
+ }
+
+ return this;
+ }
+
+ /**
+ * Replaces the first occurrence of the headers with the same name.
+ *
+ * <p>
+ * If no header with the same name is found the given header is added to the end of the list.
+ *
+ * @param values The default headers to set. <jk>null</jk> values are ignored.
+ * @return This object (for method chaining).
+ */
+ public HeaderListBuilder setDefault(HeaderList values) {
+ if (values != null)
+ setDefault(values.entries);
+ return this;
}
private boolean isResolving() {
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/part/BasicPartIterator.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/part/BasicPartIterator.java
new file mode 100644
index 0000000..6e283b7
--- /dev/null
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/part/BasicPartIterator.java
@@ -0,0 +1,97 @@
+// ***************************************************************************************************************************
+// * 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.http.part;
+
+import static org.apache.juneau.assertions.Assertions.*;
+
+import java.util.NoSuchElementException;
+
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.internal.*;
+
+/**
+ * Basic implementation of a {@link PartIterator}.
+ *
+ * {@review}
+ */
+@NotThreadSafe
+public class BasicPartIterator implements PartIterator {
+
+ private final Part[] entries;
+ private final String name;
+ private final boolean caseInsensitive;
+
+ private int currentIndex;
+
+ /**
+ * Creates a new part iterator.
+ *
+ * @param parts An array of parts over which to iterate.
+ * @param name The name of the parts over which to iterate, or <jk>null</jk> for all.
+ * @param caseInsensitive Use case-insensitive matching for part name.
+ */
+ public BasicPartIterator(Part[] parts, String name, boolean caseInsensitive) {
+ this.entries = assertArgNotNull("parts", parts);
+ this.name = name;
+ this.caseInsensitive = caseInsensitive;
+ this.currentIndex = findNext(-1);
+ }
+
+ private int findNext(int pos) {
+
+ int from = pos;
+
+ int to = entries.length - 1;
+ boolean found = false;
+ while (!found && (from < to)) {
+ from++;
+ found = filter(from);
+ }
+
+ return found ? from : -1;
+ }
+
+ private boolean filter(int index) {
+ return (name == null) || eq(name, entries[index].getName());
+ }
+
+ @Override /* Iterator */
+ public boolean hasNext() {
+ return (currentIndex >= 0);
+ }
+
+ @Override /* Iterator */
+ public Part next() throws NoSuchElementException {
+
+ int current = currentIndex;
+
+ if (current < 0)
+ throw new NoSuchElementException("Iteration already finished.");
+
+ currentIndex = findNext(current);
+
+ return entries[current];
+ }
+
+ /**
+ * Not supported.
+ */
+ @Override /* Iterator */
+ public void remove() throws UnsupportedOperationException {
+ throw new UnsupportedOperationException("Remove is not supported.");
+ }
+
+ private boolean eq(String s1, String s2) {
+ return StringUtils.eq(caseInsensitive, s1, s2);
+ }
+}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/HeaderBeanMeta.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/part/PartBeanMeta.java
similarity index 60%
copy from juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/HeaderBeanMeta.java
copy to juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/part/PartBeanMeta.java
index a3978f3..2b26377 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/HeaderBeanMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/part/PartBeanMeta.java
@@ -10,77 +10,78 @@
// * "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.http.header;
+package org.apache.juneau.http.part;
import static org.apache.juneau.internal.StringUtils.*;
import java.lang.reflect.*;
import java.util.concurrent.*;
+import org.apache.juneau.http.annotation.*;
import org.apache.juneau.httppart.*;
import org.apache.juneau.reflect.*;
/**
- * Holds metadata about header beans (POJOs that get serialized as HTTP headers).
- * {@reviewed}
+ * Holds metadata about http part beans (POJOs that get serialized as HTTP parts such as form data or query parameters).
+ * {@review}
*
* <p>
- * Header beans are typically annotated with {@link org.apache.juneau.http.annotation.Header @Header} although it's not an
- * absolute requirement.
+ * HTTP part beans are typically annotated with {@link Query @Query}, {@link FormData @FormData}, or {@link Path @Path}
+ * although it's not an absolute requirement.
*
* <p>
- * Header beans must have one of the following public constructors:
+ * HTTP part beans must have one of the following public constructors:
* <ul>
- * <li><c>X(String <jv>headerValue</jv>)</c>
- * <li><c>X(Object <jv>headerValue</jv>)</c>
- * <li><c>X(String <jv>headerName</jv>, String <jv>headerValue</jv>)</c>
- * <li><c>X(String <jv>headerName</jv>, Object <jv>headerValue</jv>)</c>
+ * <li><c><jk>public</jk> X(String <jv>partValue</jv>)</c>
+ * <li><c><jk>public</jk> X(Object <jv>partValue</jv>)</c>
+ * <li><c><jk>public</jk> X(String <jv>partName</jv>, String <jv>partValue</jv>)</c>
+ * <li><c><jk>public</jk> X(String <jv>partName</jv>, Object <jv>partValue</jv>)</c>
* </ul>
*
* <p>
* <h5 class='figure'>Example</h5>
* <p class='bcode w800'>
- * <jc>// Our header bean.</jc>
- * <ja>@Header</ja>(<js>"Foo"</js>)
- * <jk>public class</jk> FooHeader <jk>extends</jk> BasicStringHeader {
+ * <jc>// Our part bean.</jc>
+ * <ja>@Query</ja>(<js>"foo"</js>)
+ * <jk>public class</jk> FooPart <jk>extends</jk> BasicStringPart {
*
- * <jk>public</jk> FooHeader(String <jv>headerValue</jv>) {
- * <jk>super</jk>(<js>"Foo"</js>, <jv>headerValue</jv>);
+ * <jk>public</jk> FooPart(String <jv>partValue</jv>) {
+ * <jk>super</jk>(<js>"foo"</js>, <jv>partValue</jv>);
* }
* }
*
- * <jc>// Code to retrieve a header bean from a header list in a request.</jc>
- * HeaderList <jv>headers</jv> = <jv>httpRequest</jv>.getHeaders();
- * FooHeader <jv>foo</jv> = <jv>headers</jv>.get(FooHeader.<jk>class</jk>);
+ * <jc>// Code to retrieve a part bean from a part list in a request.</jc>
+ * PartList <jv>parts</jv> = <jv>httpRequest</jv>.getFormDataList();
+ * FooPart <jv>foo</jv> = <jv>parts</jv>.get(FooPart.<jk>class</jk>);
* </p>
*
- * @param <T> The header bean type.
+ * @param <T> The HTTP part bean type.
*/
-public class HeaderBeanMeta<T> {
+public class PartBeanMeta<T> {
- private static final ConcurrentHashMap<Class<?>,HeaderBeanMeta<?>> CACHE = new ConcurrentHashMap<>();
+ private static final ConcurrentHashMap<Class<?>,PartBeanMeta<?>> CACHE = new ConcurrentHashMap<>();
private final Class<T> type;
private final Constructor<T> constructor;
private final HttpPartSchema schema;
/**
- * Finds the header bean meta for the specified type.
+ * Finds the part bean meta for the specified type.
*
- * @param type The header bean type.
+ * @param type The part bean type.
* @return The metadata, or <jk>null</jk> if a valid constructor could not be found.
*/
@SuppressWarnings("unchecked")
- public static <T> HeaderBeanMeta<T> of(Class<T> type) {
- HeaderBeanMeta<?> m = CACHE.get(type);
+ public static <T> PartBeanMeta<T> of(Class<T> type) {
+ PartBeanMeta<?> m = CACHE.get(type);
if (m == null) {
- m = new HeaderBeanMeta<>(type);
+ m = new PartBeanMeta<>(type);
CACHE.put(type, m);
}
- return (HeaderBeanMeta<T>)m;
+ return (PartBeanMeta<T>)m;
}
- private HeaderBeanMeta(Class<T> type) {
+ private PartBeanMeta(Class<T> type) {
this.type = type;
ClassInfo ci = ClassInfo.of(type);
@@ -94,14 +95,24 @@ public class HeaderBeanMeta<T> {
cci = ci.getPublicConstructor(String.class, Object.class);
constructor = cci == null ? null : cci.inner();
- this.schema = HttpPartSchema.create(org.apache.juneau.http.annotation.Header.class, type);
+ if (ci.hasAnnotation(Query.class))
+ this.schema = HttpPartSchema.create(Query.class, type);
+ else if (ci.hasAnnotation(FormData.class))
+ this.schema = HttpPartSchema.create(FormData.class, type);
+ else if (ci.hasAnnotation(Path.class))
+ this.schema = HttpPartSchema.create(Path.class, type);
+ else if (ci.hasAnnotation(ResponseHeader.class))
+ this.schema = HttpPartSchema.create(ResponseHeader.class, type);
+ else
+ this.schema = HttpPartSchema.create(org.apache.juneau.http.annotation.Header.class, type);
}
/**
- * Returns schema information about this header.
+ * Returns schema information about this part.
*
* <p>
- * This is information pulled from {@link org.apache.juneau.http.annotation.Header @Header} annotation on the class.
+ * This is information pulled from {@link Query @Query}, {@link FormData @FormData}, or {@link Path @Path} annotations
+ * on the class.
*
* @return The schema information.
*/
@@ -110,13 +121,13 @@ public class HeaderBeanMeta<T> {
}
/**
- * Constructs a header bean with the specified name or value.
+ * Constructs a part bean with the specified name or value.
*
* <p>
- * Can only be used on beans where the header name is known.
+ * Can only be used on beans where the part name is known.
*
* @param value
- * The header value.
+ * The part value.
* @return A newly constructed bean.
*/
public T construct(Object value) {
@@ -124,14 +135,14 @@ public class HeaderBeanMeta<T> {
}
/**
- * Constructs a header bean with the specified name or value.
+ * Constructs a part bean with the specified name or value.
*
* @param name
- * The header name.
+ * The part name.
* <br>If <jk>null</jk>, uses the value pulled from the {@link org.apache.juneau.http.annotation.Header#name() @Header(name)} or
* {@link org.apache.juneau.http.annotation.Header#value() @Header(value)} annotations.
* @param value
- * The header value.
+ * The part value.
* @return A newly constructed bean.
* @throws UnsupportedOperationException If bean could not be constructed (e.g. couldn't find a constructor).
*/
diff --git a/juneau-utest/src/test/java/org/apache/juneau/utils/ArrayBuilder_Test.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/part/PartIterator.java
similarity index 67%
copy from juneau-utest/src/test/java/org/apache/juneau/utils/ArrayBuilder_Test.java
copy to juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/part/PartIterator.java
index 6ca5880..9ce65a7 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/utils/ArrayBuilder_Test.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/part/PartIterator.java
@@ -1,37 +1,31 @@
-// ***************************************************************************************************************************
-// * 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.utils;
-
-import static org.apache.juneau.assertions.Assertions.*;
-import static org.junit.runners.MethodSorters.*;
-
-import org.apache.juneau.internal.*;
-import org.junit.*;
-
-@FixMethodOrder(NAME_ASCENDING)
-public class ArrayBuilder_Test {
-
- @Test
- public void a01_basic() {
- ArrayBuilder<String> x = ArrayBuilder.create(String.class, 2);
- assertObject(x.toArray()).asJson().is("[]");
- x.add(null);
- assertObject(x.toArray()).asJson().is("[]");
- x.add("a");
- assertObject(x.toArray()).asJson().is("['a']");
- x.add("b");
- x.add(null);
- assertObject(x.toArray()).asJson().is("['a','b']");
- assertThrown(()->x.add("c")).isType(ArrayIndexOutOfBoundsException.class);
- }
-}
+// ***************************************************************************************************************************
+// * 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.http.part;
+
+import java.util.Iterator;
+
+/**
+ * A type-safe iterator for {@link Part} objects.
+ *
+ * {@review}
+ */
+public interface PartIterator extends Iterator<Part> {
+
+ /**
+ * Indicates whether there is another part in this iteration.
+ *
+ * @return <jk>true</jk> if there is another part.
+ */
+ @Override /* Iterator */
+ boolean hasNext();
+}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/part/PartList.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/part/PartList.java
index 75dde84..0ca3416 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/part/PartList.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/part/PartList.java
@@ -12,26 +12,153 @@
// ***************************************************************************************************************************
package org.apache.juneau.http.part;
-import static java.util.Collections.*;
+import static org.apache.juneau.assertions.Assertions.*;
import static org.apache.juneau.internal.StringUtils.*;
-import static java.util.Arrays.*;
import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
+import org.apache.http.*;
+import org.apache.http.util.*;
import org.apache.juneau.*;
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.http.HttpParts;
+import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.svl.*;
/**
- * An unmodifiable list of HTTP parts.
+ * An immutable list of HTTP parts (form-data, query-parameters, path-parameters).
+ * {@review}
+ *
+ * <h5 class='figure'>Example</h5>
+ * <p class='bcode w800'>
+ * PartList <jv>parts</jv> = PartList
+ * .<jsm>create</jsm>()
+ * .append(MyPart.<jsm>of</jsm>("foo"))
+ * .append(<js>"Bar"</js>, ()-><jsm>getDynamicValueFromSomewhere</jsm>())
+ * .build();
+ * </p>
+ *
+ * <p>
+ * Convenience creators are provided for creating lists with minimal code:
+ * <p class='bcode w800'>
+ * PartList <jv>parts</jv> = PartList.<jsm>of</jsm>(BasicIntegerPart.of(<js>"foo"</js>, 1));
+ * </p>
+ *
+ * <p>
+ * Part lists are immutable, but can be appended to using the {@link #copy()} method:
+ * <p class='bcode w800'>
+ * parts = parts
+ * .copy()
+ * .append(BasicIntegerPart.<jsm>of</jsm>(<js>"foo"</js>, 1))
+ * .build();
+ * </p>
+ *
+ * <p>
+ * Static methods are provided on {@link HttpParts} to further simplify creation of part lists.
+ * <p class='bcode w800'>
+ * <jk>import static</jk> org.apache.juneau.http.HttpParts.*;
+ *
+ * PartList <jv>parts</jv> = <jsm>partList</jsm>(<jsm>integerPart</jsm>(<js>"foo"</js>, 1), <jsm>booleanPart</jsm>(<js>"bar"<js>, <jk>false</jk>));
+ * </p>
+ *
+ * <p>
+ * The builder class supports setting default part values (i.e. add a part to the list if it isn't otherwise in the list).
+ * Note that this is different from simply setting a value twice as using default values will not overwrite existing
+ * parts.
+ * <br>The following example notes the distinction:
+ *
+ * <p class='bcode w800'>
+ * <jv>parts</jv> = PartList
+ * .<jsm>create</jsm>()
+ * .set(<js>"Foo"</js>, <js>"bar"</js>)
+ * .set(<js>"Foo"</js>, <js>"baz"</js>)
+ * .build();
+ * <jsm>assertObject</jsm>(<jv>parts</jv>).isString(<js>"foo=baz"</js>);
+ *
+ * <jv>parts</jv> = PartList
+ * .create()
+ * .set(<js>"Foo"</js>, <js>"bar"</js>)
+ * .setDefault(<js>"Foo"</js>, <js>"baz"</js>)
+ * .build();
+ * <jsm>assertObject</jsm>(<jv>parts</jv>).isString(<js>"foo=bar"</js>);
+ * </p>
+ *
+ * <p>
+ * Various methods are provided for iterating over the parts in this list to avoid array copies.
+ * <ul class='javatree'>
+ * <li class='jm'>{@link #forEach(Consumer)} / {@link #forEach(String,Consumer)} - Use consumers to process parts.
+ * <li class='jm'>{@link #iterator()} / {@link #iterator(String)} - Use an {@link PartIterator} to process parts.
+ * <li class='jm'>{@link #stream()} / {@link #stream(String)} - Use a stream.
+ * </ul>
+ * <p>
+ * In general, try to use these over the {@link #getAll()}/{@link #getAll(String)} methods that require array copies.
+ *
+ * <p>
+ * The {@link #get(String)} method is special in that it will collapse multiple parts with the same name into
+ * a single comma-delimited list (see <a href='https://tools.ietf.org/html/rfc2616#section-4.2'>RFC 2616 Section 4.2</a> for rules).
+ *
+ * <p>
+ * The {@link #get(Class)} and {@link #get(String, Class)} methods are provided for working with {@link FormData}/{@link Query}/{@link Path}/{@link FormData}-annotated
+ * beans.
+ *
+ * <h5 class='figure'>Example</h5>
+ * <p class='bcode w800'>
+ * MyQueryBean <jv>foo</jv> = <jv>parts</jv>.get(MyQueryBean.<jk>class</jk>);
+ * </p>
+ *
+ * <p>
+ * By default, part names are treated as case-sensitive. This can be changed using the {@link PartListBuilder#caseInsensitive()}
+ * method.
+ *
+ * <p>
+ * A {@link VarResolver} can be associated with this builder to create part values with embedded variables that
+ * are resolved at runtime.
+ *
+ * <h5 class='figure'>Example</h5>
+ * <p class='bcode w800'>
+ * <jc>// Create a part list with dynamically-resolving values pulled from a system property.</jc>
+ *
+ * System.<jsm>setProperty</jsm>(<js>"foo"</js>, <js>"bar"</js>);
+ *
+ * PartList <jv>parts</jv> = PartList
+ * .<jsm>create</jsm>()
+ * .resolving()
+ * .append(<js>"X1"</js>, <js>"$S{foo}"</js>)
+ * .append(<js>"X2"</js>, ()-><js>"$S{foo}"</js>)
+ * .build();
+ *
+ * <jsm>assertObject</jsm>(<jv>parts</jv>).isString(<js>"X1=bar&X2=bar"</js>);
+ * </p>
+ *
+ * <p>
+ * The {@link PartList} object can be extended to defined pre-packaged lists of parts which can be used in various
+ * annotations throughout the framework.
+ *
+ * <h5 class='figure'>Example</h5>
+ * <p class='bcode w800'>
+ * <jc>// A predefined list of parts.</jc>
+ * <jk>public class</jk> MyPartList <jk>extends</jk> PartList {
+ * <jk>public</jk> MyPartList() {
+ * <jk>super</jk>(BasicIntegerPart.<jsm>of</jsm>(<js>"foo"</js>,1), BasicBooleanPart.<jsm>of</jsm>(<js>"bar"</js>,<jk>false</jk>);
+ * }
+ * }
*/
-public class PartList implements Iterable<Part> {
+@ThreadSafe
+public class PartList {
+
+ private static final Part[] EMPTY_ARRAY = new Part[0];
/** Represents no part supplier in annotations. */
public static final class Null extends PartList {}
/** Predefined instance. */
- public static final PartList EMPTY = create().build();
+ public static final PartList EMPTY = new PartList();
- final List<Part> parts;
+ final Part[] entries;
+ final boolean caseInsensitive;
/**
* Instantiates a new builder for this bean.
@@ -45,7 +172,10 @@ public class PartList implements Iterable<Part> {
/**
* Creates a new {@link PartList} initialized with the specified parts.
*
- * @param parts The parts to add to the list. Can be <jk>null</jk>. <jk>null</jk> entries are ignored.
+ * @param parts
+ * The parts to add to the list.
+ * <br>Can be <jk>null</jk>.
+ * <br><jk>null</jk> entries are ignored.
* @return A new unmodifiable instance, never <jk>null</jk>.
*/
public static PartList of(List<Part> parts) {
@@ -55,16 +185,23 @@ public class PartList implements Iterable<Part> {
/**
* Creates a new {@link PartList} initialized with the specified parts.
*
- * @param parts The parts to add to the list. <jk>null</jk> entries are ignored.
+ * @param parts
+ * The parts to add to the list.
+ * <br><jk>null</jk> entries are ignored.
* @return A new unmodifiable instance, never <jk>null</jk>.
*/
public static PartList of(Part...parts) {
- return parts == null || parts.length == 0 ? EMPTY : new PartList(asList(parts));
+ return parts == null || parts.length == 0 ? EMPTY : new PartList(parts);
}
/**
* Creates a new {@link PartList} initialized with the specified name/value pairs.
*
+ * <h5 class='figure'>Example</h5>
+ * <p class='bcode w800'>
+ * PartList <jv>parts</jv> = PartList.<jsm>ofPairs</jsm>(<js>"foo"</js>, 1, <js>"bar"</js>, <jk>true</jk>);
+ * </p>
+ *
* @param pairs
* Initial list of pairs.
* <br>Must be an even number of parameters representing key/value pairs.
@@ -72,14 +209,14 @@ public class PartList implements Iterable<Part> {
* @return A new instance.
*/
public static PartList ofPairs(Object...pairs) {
- if (pairs.length == 0)
+ if (pairs == null || pairs.length == 0)
return EMPTY;
if (pairs.length % 2 != 0)
throw new BasicRuntimeException("Odd number of parameters passed into PartList.ofPairs()");
- PartListBuilder b = create();
+ ArrayBuilder<Part> b = ArrayBuilder.create(Part.class, pairs.length / 2, true);
for (int i = 0; i < pairs.length; i+=2)
- b.add(stringify(pairs[i]), pairs[i+1]);
- return new PartList(b);
+ b.add(BasicPart.of(stringify(pairs[i]), pairs[i+1]));
+ return new PartList(b.toArray());
}
/**
@@ -88,33 +225,65 @@ public class PartList implements Iterable<Part> {
* @param builder The builder containing the settings for this bean.
*/
public PartList(PartListBuilder builder) {
- this.parts = new ArrayList<>(builder.parts);
+ if (builder.defaultEntries == null) {
+ entries = builder.entries.toArray(new Part[builder.entries.size()]);
+ } else {
+ ArrayBuilder<Part> l = ArrayBuilder.create(Part.class, builder.entries.size() + builder.defaultEntries.size(), true);
+
+ for (int i = 0, j = builder.entries.size(); i < j; i++)
+ l.add(builder.entries.get(i));
+
+ for (int i1 = 0, j1 = builder.defaultEntries.size(); i1 < j1; i1++) {
+ Part 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);
+ }
+
+ entries = l.toArray();
+ }
+ this.caseInsensitive = builder.caseInsensitive;
}
/**
* Constructor.
*
- * @param parts The initial list of parts. <jk>null</jk> entries are ignored.
+ * @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<Part> parts) {
- if (parts == null || parts.isEmpty())
- this.parts = emptyList();
- else {
- List<Part> l = new ArrayList<>();
- for (int i = 0; i < parts.size(); i++) {
- Part x = parts.get(i);
- if (x != null)
- l.add(x);
- }
- this.parts = unmodifiableList(l);
- }
+ ArrayBuilder<Part> l = ArrayBuilder.create(Part.class, parts.size(), true);
+ for (int i = 0, j = parts.size(); i < j; i++)
+ l.add(parts.get(i));
+ entries = l.toArray();
+ caseInsensitive = false;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param parts
+ * The parts to add to the list.
+ * <br><jk>null</jk> entries are ignored.
+ */
+ protected PartList(Part...parts) {
+ ArrayBuilder<Part> l = ArrayBuilder.create(Part.class, parts.length, true);
+ for (int i = 0; i < parts.length; i++)
+ l.add(parts[i]);
+ entries = l.toArray();
+ caseInsensitive = false;
}
/**
* Default constructor.
*/
protected PartList() {
- this.parts = emptyList();
+ entries = EMPTY_ARRAY;
+ caseInsensitive = false;
}
/**
@@ -127,97 +296,319 @@ public class PartList implements Iterable<Part> {
}
/**
- * Gets all of the headers with the given name.
+ * Gets a part representing all of the part values with the given name.
+ *
+ * <p>
+ * If more that one part with the given name exists the values will be combined with <js>", "</js> as per
+ * <a href='https://tools.ietf.org/html/rfc2616#section-4.2'>RFC 2616 Section 4.2</a>.
+ *
+ * @param name The part name.
+ * @return A part with a condensed value, or {@link Optional#empty()} if no parts by the given name are present
+ */
+ public Optional<Part> get(String name) {
+
+ Part first = null;
+ List<Part> rest = null;
+ for (int i = 0; i < entries.length; i++) {
+ Part x = entries[i];
+ if (eq(x.getName(), name)) {
+ if (first == null)
+ first = x;
+ else {
+ if (rest == null)
+ rest = new ArrayList<>();
+ rest.add(x);
+ }
+ }
+ }
+
+ if (first == null)
+ return Optional.empty();
+
+ if (rest == null)
+ return Optional.of(first);
+
+ CharArrayBuffer sb = new CharArrayBuffer(128);
+ sb.append(first.getValue());
+ for (int i = 0; i < rest.size(); i++) {
+ sb.append(',');
+ sb.append(rest.get(i).getValue());
+ }
+
+ return Optional.of(new BasicPart(name, sb.toString()));
+ }
+
+ /**
+ * Gets a part representing all of the part values with the given name.
+ *
+ * <p>
+ * If more that one part with the given name exists the values will be combined with <js>", "</js> as per
+ * <a href='https://tools.ietf.org/html/rfc2616#section-4.2'>RFC 2616 Section 4.2</a>.
+ *
+ * <p>
+ * The implementation class must have a public constructor taking in one of the following argument lists:
+ * <ul>
+ * <li><c>X(String <jv>value</jv>)</c>
+ * <li><c>X(Object <jv>value</jv>)</c>
+ * <li><c>X(String <jv>name</jv>, String <jv>value</jv>)</c>
+ * <li><c>X(String <jv>name</jv>, Object <jv>value</jv>)</c>
+ * </ul>
+ *
+ * <h5 class='figure'>Example</h5>
+ * <p class='bcode w800'>
+ * BasicIntegerPart <jv>age</jv> = partList.get(<js>"age"</js>, BasicIntegerPart.<jk>class</jk>);
+ * </p>
+ *
+ * @param name The part name.
+ * @param type The part implementation class.
+
+ * @return A part with a condensed value or <jk>null</jk> if no parts by the given name are present
+ */
+ public <T> Optional<T> get(String name, Class<T> type) {
+
+ Part first = null;
+ List<Part> rest = null;
+ for (int i = 0; i < entries.length; i++) {
+ Part x = entries[i];
+ if (eq(x.getName(), name)) {
+ if (first == null)
+ first = x;
+ else {
+ if (rest == null)
+ rest = new ArrayList<>();
+ rest.add(x);
+ }
+ }
+ }
+
+ if (first == null)
+ return Optional.empty();
+
+ if (rest == null)
+ return Optional.of(PartBeanMeta.of(type).construct(name, first.getValue()));
+
+ CharArrayBuffer sb = new CharArrayBuffer(128);
+ sb.append(first.getValue());
+ for (int i = 0; i < rest.size(); i++) {
+ sb.append(',');
+ sb.append(rest.get(i).getValue());
+ }
+
+ return Optional.of(PartBeanMeta.of(type).construct(name, sb.toString()));
+ }
+
+ /**
+ * Gets a part representing all of the part values with the given name.
+ *
+ * <p>
+ * Same as {@link #get(String, Class)} but the part name is pulled from the name or value attribute of the {@link FormData}/{@link Query}/{@link Path} annotation.
+ *
+ * <h5 class='figure'>Example</h5>
+ * <p class='bcode w800'>
+ * Age <jv>age</jv> = partList.get(Age.<jk>class</jk>);
+ * </p>
+ *
+ * @param type The part implementation class.
+ * @return A part with a condensed value or <jk>null</jk> if no parts by the given name are present
+ */
+ public <T> Optional<T> get(Class<T> type) {
+ assertArgNotNull("type", type);
+
+ String name = PartBeanMeta.of(type).getSchema().getName();
+ if (name == null)
+ throw new BasicIllegalArgumentException("Part name could not be found on bean type ''{0}''", type.getName());
+
+ return get(name, type);
+ }
+
+ /**
+ * Gets all of the parts with the given name.
*
* <p>
- * The returned array maintains the relative order in which the headers were added.
+ * The returned array maintains the relative order in which the parts were added.
*
* <p>
- * Part name comparison is case sensitive.
+ * Part name comparison is case insensitive.
*
- * @param name The header name.
+ * @param name The part name.
*
- * @return An array containing all matching headers, or an empty array if none are found.
+ * @return An array containing all matching parts, or an empty array if none are found.
*/
- public List<Part> get(String name) {
+ public Part[] getAll(String name) {
List<Part> l = null;
- for (int i = 0; i < parts.size(); i++) { // See HTTPCORE-361
- Part x = parts.get(i);
- if (x.getName().equals(name)) {
+ for (int i = 0; i < entries.length; i++) {
+ Part x = entries[i];
+ if (eq(x.getName(), name)) {
if (l == null)
l = new ArrayList<>();
l.add(x);
}
}
- return l == null ? emptyList() : l;
+ return l == null ? EMPTY_ARRAY : l.toArray(new Part[l.size()]);
+ }
+
+ /**
+ * 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 header with the given name.
+ * Gets the first part with the given name.
*
* <p>
- * Part name comparison is case sensitive.
+ * Part name comparison is case insensitive.
*
- * @param name The header name.
- * @return The first matching header, or <jk>null</jk> if not found.
+ * @param name The part name.
+ * @return The first matching part, or <jk>null</jk> if not found.
*/
- public Part getFirst(String name) {
- for (int i = 0; i < parts.size(); i++) { // See HTTPCORE-361
- Part x = parts.get(i);
- if (x.getName().equals(name))
- return x;
+ public Optional<Part> getFirst(String name) {
+ for (int i = 0; i < entries.length; i++) {
+ Part x = entries[i];
+ if (eq(x.getName(), name))
+ return Optional.of(x);
}
- return null;
+ return Optional.empty();
}
/**
- * Gets the last header with the given name.
+ * Gets the last part with the given name.
*
* <p>
- * Part name comparison is case sensitive.
+ * Part name comparison is case insensitive.
*
- * @param name The header name.
- * @return The last matching header, or <jk>null</jk> if not found.
+ * @param name The part name.
+ * @return The last matching part, or <jk>null</jk> if not found.
*/
- public Part getLast(String name) {
- for (int i = parts.size() - 1; i >= 0; i--) {
- Part x = parts.get(i);
- if (x.getName().equals(name))
- return x;
+ public Optional<Part> getLast(String name) {
+ for (int i = entries.length - 1; i >= 0; i--) {
+ Part x = entries[i];
+ if (eq(x.getName(), name))
+ return Optional.of(x);
}
- return null;
+ return Optional.empty();
}
/**
- * Gets all of the headers contained within this list.
+ * Gets all of the parts contained within this list.
*
- * @return An array containing all the parts within this list, or an empty list if no parts are present.
+ * @return An array of all the parts in this list, or an empty array if no parts are present.
*/
- public List<Part> getAll() {
- return unmodifiableList(parts);
+ public Part[] getAll() {
+ return entries.length == 0 ? EMPTY_ARRAY : Arrays.copyOf(entries, entries.length);
}
/**
- * Tests if headers with the given name are contained within this list.
+ * Tests if parts with the given name are contained within this list.
*
* <p>
- * Part name comparison is case sensitive.
+ * Part name comparison is case insensitive.
*
- * @param name The header name.
- * @return <jk>true</jk> if at least one header with the name is present.
+ * @param name The part name.
+ * @return <jk>true</jk> if at least one part with the name is present.
*/
public boolean contains(String name) {
- for (int i = 0; i < parts.size(); i++) { // See HTTPCORE-361
- Part x = parts.get(i);
- if (x.getName().equals(name))
+ for (int i = 0; i < entries.length; i++) {
+ Part x = entries[i];
+ if (eq(x.getName(), name))
return true;
}
return false;
}
- @Override /* Iterable */
- public Iterator<Part> iterator() {
- return getAll().iterator();
+ /**
+ * Returns an iterator over this list of parts.
+ *
+ * @return A new iterator over this list of parts.
+ */
+ public PartIterator iterator() {
+ return new BasicPartIterator(entries, null, caseInsensitive);
+ }
+
+ /**
+ * Returns an iterator over the parts with a given name in this list.
+ *
+ * @param name The name of the parts over which to iterate, or <jk>null</jk> for all parts
+ *
+ * @return A new iterator over the matching parts in this list.
+ */
+ public PartIterator iterator(String name) {
+ return new BasicPartIterator(entries, name, caseInsensitive);
+ }
+
+ /**
+ * Performs an operation on the parts of this list.
+ *
+ * <p>
+ * This is the preferred method for iterating over parts as it does not involve
+ * creation or copy of lists/arrays.
+ *
+ * @param c The consumer.
+ * @return This object (for method chaining).
+ */
+ public PartList forEach(Consumer<Part> c) {
+ for (int i = 0; i < entries.length; i++)
+ c.accept(entries[i]);
+ return this;
+ }
+
+ /**
+ * Performs an operation on the parts with the specified name 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 name The part name.
+ * @param c The consumer.
+ * @return This object (for method chaining).
+ */
+ public PartList forEach(String name, Consumer<Part> c) {
+ for (int i = 0; i < entries.length; i++)
+ if (eq(name, entries[i].getName()))
+ c.accept(entries[i]);
+ return this;
+ }
+
+ /**
+ * Returns a stream of the parts in this list.
+ *
+ * <p>
+ * This does not involve a copy of the underlying array of <c>Part</c> objects so should perform well.
+ *
+ * @return This object (for method chaining).
+ */
+ public Stream<Part> stream() {
+ return Arrays.stream(entries);
+ }
+
+ /**
+ * Returns a stream of the parts in this list with the specified name.
+ *
+ * <p>
+ * This does not involve a copy of the underlying array of <c>Part</c> objects so should perform well.
+ *
+ * @param name The part name.
+ * @return This object (for method chaining).
+ */
+ public Stream<Part> 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> asNameValuePairs() {
+ return Collections.unmodifiableList(Arrays.asList(entries));
+ }
+
+ private boolean eq(String s1, String s2) {
+ return StringUtils.eq(caseInsensitive, s1, s2);
}
/**
@@ -226,8 +617,8 @@ public class PartList implements Iterable<Part> {
@Override /* Object */
public String toString() {
StringBuilder sb = new StringBuilder();
- for (int i = 0; i < parts.size(); i++) {
- Part p = parts.get(i);
+ for (int i = 0; i < entries.length; i++) {
+ Part p = entries[i];
String v = p.getValue();
if (v != null) {
if (sb.length() > 0)
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/part/PartListBuilder.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/part/PartListBuilder.java
index f37513f..d275e90 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/part/PartListBuilder.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/part/PartListBuilder.java
@@ -17,19 +17,22 @@ import static org.apache.juneau.internal.StringUtils.*;
import java.util.*;
import java.util.function.*;
-import org.apache.juneau.httppart.*;
+import org.apache.juneau.annotation.*;
import org.apache.juneau.internal.*;
-import org.apache.juneau.oapi.*;
import org.apache.juneau.svl.*;
/**
* Builder for {@link PartList} objects.
+ * {@review}
*/
@FluentSetters
+@NotThreadSafe
public class PartListBuilder {
- final List<Part> parts;
- private volatile VarResolver varResolver;
+ final List<Part> entries;
+ List<Part> defaultEntries;
+ private VarResolver varResolver;
+ boolean caseInsensitive = false;
/** Predefined instance. */
private static final PartList EMPTY = new PartList();
@@ -38,7 +41,7 @@ public class PartListBuilder {
* Constructor.
*/
public PartListBuilder() {
- parts = new ArrayList<>();
+ entries = new ArrayList<>();
}
/**
@@ -47,8 +50,10 @@ public class PartListBuilder {
* @param copyFrom The bean to copy from.
*/
public PartListBuilder(PartList copyFrom) {
- parts = new ArrayList<>(copyFrom.parts);
-
+ entries = new ArrayList<>(copyFrom.entries.length);
+ for (int i = 0; i < copyFrom.entries.length; i++)
+ entries.add(copyFrom.entries[i]);
+ caseInsensitive = copyFrom.caseInsensitive;
}
/**
@@ -57,7 +62,7 @@ public class PartListBuilder {
* @return A new {@link PartList} bean.
*/
public PartList build() {
- return parts.isEmpty() ? EMPTY : new PartList(this);
+ return entries.isEmpty() && defaultEntries == null ? EMPTY : new PartList(this);
}
/**
@@ -66,9 +71,14 @@ public class PartListBuilder {
* <p>
* Resolves variables in part values when using the following methods:
* <ul>
- * <li class='jm'>{@link #add(String, Object) add(String,Object)}
- * <li class='jm'>{@link #add(String, Supplier) add(String,Supplier<?>)}
- * <li class='jm'>{@link #add(String, Object, HttpPartType, HttpPartSerializerSession, HttpPartSchema, boolean) add(String,Object,HttpPartType,HttpPartSerializerSession,HttpPartSchema,boolean)}
+ * <li class='jm'>{@link #append(String, Object) append(String,Object)}
+ * <li class='jm'>{@link #append(String, Supplier) append(String,Supplier<?>)}
+ * <li class='jm'>{@link #prepend(String, Object) prepend(String,Object)}
+ * <li class='jm'>{@link #prepend(String, Supplier) prepend(String,Supplier<?>)}
+ * <li class='jm'>{@link #set(String, Object) set(String,Object)}
+ * <li class='jm'>{@link #set(String, Supplier) set(String,Supplier<?>)}
+ * <li class='jm'>{@link #setDefault(String, Object) set(String,Object)}
+ * <li class='jm'>{@link #setDefault(String, Supplier) set(String,Supplier<?>)}
* </ul>
*
* <p>
@@ -86,9 +96,14 @@ public class PartListBuilder {
* <p>
* Resolves variables in part values when using the following methods:
* <ul>
- * <li class='jm'>{@link #add(String, Object) add(String,Object)}
- * <li class='jm'>{@link #add(String, Supplier) add(String,Supplier<?>)}
- * <li class='jm'>{@link #add(String, Object, HttpPartType, HttpPartSerializerSession, HttpPartSchema, boolean) add(String,Object,HttpPartType,HttpPartSerializerSession,HttpPartSchema,boolean)}
+ * <li class='jm'>{@link #append(String, Object) append(String,Object)}
+ * <li class='jm'>{@link #append(String, Supplier) append(String,Supplier<?>)}
+ * <li class='jm'>{@link #prepend(String, Object) prepend(String,Object)}
+ * <li class='jm'>{@link #prepend(String, Supplier) prepend(String,Supplier<?>)}
+ * <li class='jm'>{@link #set(String, Object) set(String,Object)}
+ * <li class='jm'>{@link #set(String, Supplier) set(String,Supplier<?>)}
+ * <li class='jm'>{@link #setDefault(String, Object) set(String,Object)}
+ * <li class='jm'>{@link #setDefault(String, Supplier) set(String,Supplier<?>)}
* </ul>
*
* @param varResolver The variable resolver to use for resolving variables.
@@ -100,59 +115,72 @@ public class PartListBuilder {
}
/**
+ * Specifies that the part in this builder should be treated as case-insensitive.
+ *
+ * <p>
+ * The default behavior is case-sensitive.
+ *
+ * @return This object (for method chaining).
+ */
+ public PartListBuilder caseInsensitive() {
+ caseInsensitive = true;
+ return this;
+ }
+
+ /**
* Removes any parts already in this builder.
*
* @return This object (for method chaining).
*/
@FluentSetter
public PartListBuilder clear() {
- parts.clear();
+ entries.clear();
return this;
}
/**
- * Adds the specified part to the end of the parts in this builder.
+ * Adds the specified parts to the end of the parts in this builder.
*
- * @param value The part to add. <jk>null</jk> values are ignored.
+ * @param value The parts to add. <jk>null</jk> values are ignored.
* @return This object (for method chaining).
*/
@FluentSetter
- public PartListBuilder add(Part value) {
+ public PartListBuilder append(PartList value) {
if (value != null)
- parts.add(value);
+ for (Part x : value.entries)
+ append(x);
return this;
}
/**
- * Adds the specified part to the end of the parts in this builder.
+ * Adds the specified parts to the end of the parts in this builder.
*
- * @param name The part name.
- * @param value The part value.
+ * @param value The parts to add. <jk>null</jk> values are ignored.
* @return This object (for method chaining).
*/
@FluentSetter
- public PartListBuilder add(String name, String value) {
- Part x = isResolving() ? new BasicPart(name, resolver(value)) : new BasicPart(name, value);
- return add(x);
+ public PartListBuilder append(Part value) {
+ if (value != null)
+ entries.add(value);
+ return this;
}
/**
- * Appends the specified part to the end of this list.
+ * Appends the specified parts to the end of this builder.
*
* <p>
- * The part is added as a {@link BasicPart}.
+ * The parts is added as a {@link BasicPart}.
*
* @param name The part name.
* @param value The part value.
* @return This object (for method chaining).
*/
- public PartListBuilder add(String name, Object value) {
- Part x = isResolving() ? new BasicPart(name, resolver(value)) : new BasicPart(name, value);
- return add(x);
+ public PartListBuilder append(String name, Object value) {
+ return append(part(name, value));
}
/**
- * Appends the specified part to the end of this list using a value supplier.
+ * Appends the specified part to the end of this builder using a value supplier.
*
* <p>
* The part is added as a {@link BasicPart}.
@@ -164,30 +192,22 @@ public class PartListBuilder {
* @param value The part value supplier.
* @return This object (for method chaining).
*/
- public PartListBuilder add(String name, Supplier<?> value) {
- Part x = isResolving() ? new BasicPart(name, resolver(value)) : new BasicPart(name, value);
- return add(x);
+ public PartListBuilder append(String name, Supplier<?> value) {
+ return append(part(name, value));
}
/**
- * Appends the specified part to the end of this list.
+ * Adds the specified parts to the end of the parts in this builder.
*
- * @param name The part name.
- * @param value The part value.
- * @param type The HTTP part type.
- * @param serializer
- * The serializer to use for serializing the value to a string value.
- * @param schema
- * The schema object that defines the format of the output.
- * <br>If <jk>null</jk>, defaults to the schema defined on the parser.
- * <br>If that's also <jk>null</jk>, defaults to {@link HttpPartSchema#DEFAULT}.
- * <br>Only used if serializer is schema-aware (e.g. {@link OpenApiSerializer}).
- * @param skipIfEmpty If value is a blank string, the value should return as <jk>null</jk>.
+ * @param values The parts to add. <jk>null</jk> values are ignored.
* @return This object (for method chaining).
*/
- public PartListBuilder add(String name, Object value, HttpPartType type, HttpPartSerializerSession serializer, HttpPartSchema schema, boolean skipIfEmpty) {
- Part x = isResolving() ? new SerializedPart(name, resolver(value), type, serializer, schema, skipIfEmpty) : new SerializedPart(name, value, type, serializer, schema, skipIfEmpty);
- return add(x);
+ @FluentSetter
+ public PartListBuilder append(Part...values) {
+ if (values != null)
+ for (int i = 0; i < values.length; i++)
+ append(values[i]);
+ return this;
}
/**
@@ -197,47 +217,120 @@ public class PartListBuilder {
* @return This object (for method chaining).
*/
@FluentSetter
- public PartListBuilder add(Part...values) {
- for (int i = 0; i < values.length; i++) /* See HTTPCORE-361 */
- add(values[i]);
+ public PartListBuilder append(List<Part> values) {
+ if (values != null)
+ for (int i = 0, j = values.size(); i < j; i++)
+ append(values.get(i));
return this;
}
/**
- * Adds the specified parts to the end of the parts in this builder.
+ * Adds the specified part to the beginning of the parts in this builder.
+ *
+ * @param value The part to add. <jk>null</jk> values are ignored.
+ * @return This object (for method chaining).
+ */
+ @FluentSetter
+ public PartListBuilder prepend(PartList value) {
+ if (value != null)
+ entries.addAll(0, Arrays.asList(value.entries));
+ return this;
+ }
+
+ /**
+ * Adds the specified part to the beginning of the parts in this builder.
+ *
+ * @param value The part to add. <jk>null</jk> values are ignored.
+ * @return This object (for method chaining).
+ */
+ @FluentSetter
+ public PartListBuilder prepend(Part value) {
+ if (value != null)
+ entries.add(0, value);
+ return this;
+ }
+
+ /**
+ * Appends the specified part to the beginning of this builder.
+ *
+ * <p>
+ * The part is added as a {@link BasicPart}.
+ *
+ * @param name The part name.
+ * @param value The part value.
+ * @return This object (for method chaining).
+ */
+ public PartListBuilder prepend(String name, Object value) {
+ return prepend(part(name, value));
+ }
+
+ /**
+ * Appends the specified part to the beginning of this builder using a value supplier.
+ *
+ * <p>
+ * The part is added as a {@link BasicPart}.
+ *
+ * <p>
+ * Value is re-evaluated on each call to {@link BasicPart#getValue()}.
+ *
+ * @param name The part name.
+ * @param value The part value supplier.
+ * @return This object (for method chaining).
+ */
+ public PartListBuilder prepend(String name, Supplier<?> value) {
+ return prepend(part(name, value));
+ }
+
+ /**
+ * Adds the specified parts to the beginning of the parts in this builder.
*
* @param values The parts to add. <jk>null</jk> values are ignored.
* @return This object (for method chaining).
*/
@FluentSetter
- public PartListBuilder add(List<Part> values) {
- for (int i = 0; i < values.size(); i++) /* See HTTPCORE-361 */
- add(values.get(i));
+ public PartListBuilder prepend(Part...values) {
+ if (values != null)
+ prepend(Arrays.asList(values));
return this;
}
/**
- * Adds the specified parts to the end of the parts in this builder.
+ * Adds the specified parts to the beginning of the parts in this builder.
*
- * @param values The part to add. <jk>null</jk> values are ignored.
+ * @param values The parts to add. <jk>null</jk> values are ignored.
* @return This object (for method chaining).
*/
- public PartListBuilder add(PartList values) {
+ @FluentSetter
+ public PartListBuilder prepend(List<Part> values) {
if (values != null)
- add(values.getAll());
+ entries.addAll(0, values);
return this;
}
/**
- * Removes the specified parts from this builder.
+ * Removes the specified part from this builder.
+ *
+ * @param value The part to remove. <jk>null</jk> values are ignored.
+ * @return This object (for method chaining).
+ */
+ @FluentSetter
+ public PartListBuilder 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.
*
- * @param value The parts to remove. <jk>null</jk> values are ignored.
+ * @param value The part to remove. <jk>null</jk> values are ignored.
* @return This object (for method chaining).
*/
@FluentSetter
public PartListBuilder remove(Part value) {
if (value != null)
- parts.remove(value);
+ entries.remove(value);
return this;
}
@@ -249,7 +342,7 @@ public class PartListBuilder {
*/
@FluentSetter
public PartListBuilder remove(Part...values) {
- for (int i = 0; i < values.length; i++) /* See HTTPCORE-361 */
+ for (int i = 0; i < values.length; i++)
remove(values[i]);
return this;
}
@@ -262,7 +355,7 @@ public class PartListBuilder {
*/
@FluentSetter
public PartListBuilder remove(List<Part> values) {
- for (int i = 0; i < values.size(); i++) /* See HTTPCORE-361 */
+ for (int i = 0, j = values.size(); i < j; i++) /* See HTTPCORE-361 */
remove(values.get(i));
return this;
}
@@ -275,14 +368,28 @@ public class PartListBuilder {
*/
@FluentSetter
public PartListBuilder remove(String name) {
- for (int i = 0; i < parts.size(); i++) /* See HTTPCORE-361 */
- if (parts.get(i).getName().equals(name))
- parts.remove(i--);
- return null;
+ for (int i = 0; i < entries.size(); i++) /* See HTTPCORE-361 */
+ if (eq(entries.get(i).getName(), name))
+ entries.remove(i--);
+ return this;
+ }
+
+ /**
+ * Removes the part with the specified name from this builder.
+ *
+ * @param names The part name.
+ * @return This object (for method chaining).
+ */
+ @FluentSetter
+ public PartListBuilder remove(String...names) {
+ if (names != null)
+ for (int i = 0; i < names.length; i++)
+ remove(names[i]);
+ return this;
}
/**
- * Replaces the first occurrence of the part with the same name.
+ * Adds or replaces the part(s) 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.
@@ -291,24 +398,31 @@ public class PartListBuilder {
* @return This object (for method chaining).
*/
@FluentSetter
- public PartListBuilder update(Part value) {
- if (value == null)
- return this;
-
- for (int i = 0; i < parts.size(); i++) {
- Part x = parts.get(i);
- if (x.getName().equals(value.getName())) {
- parts.set(i, value);
- return this;
+ public PartListBuilder set(Part value) {
+ if (value != null) {
+ boolean replaced = false;
+ for (int i = 0, j = entries.size(); i < j; i++) {
+ Part x = entries.get(i);
+ if (eq(x.getName(), value.getName())) {
+ if (replaced) {
+ entries.remove(i);
+ j--;
+ } else {
+ entries.set(i, value);
+ replaced = true;
+ }
+ }
}
+
+ if (! replaced)
+ entries.add(value);
}
- parts.add(value);
return this;
}
/**
- * Replaces the first occurrence of the parts with the same name.
+ * Adds or replaces the part(s) 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.
@@ -317,112 +431,209 @@ public class PartListBuilder {
* @return This object (for method chaining).
*/
@FluentSetter
- public PartListBuilder update(Part...values) {
- for (int i = 0; i < values.length; i++) /* See HTTPCORE-361 */
- update(values[i]);
+ public PartListBuilder set(Part...values) {
+ if (values != null)
+ set(Arrays.asList(values));
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 name The part name.
+ * @param value The part value.
+ * @return This object (for method chaining).
+ */
+ public PartListBuilder set(String name, Object value) {
+ return set(part(name, value));
+ }
+
+ /**
+ * Replaces the first occurrence of the parts with the same name.
*
- * @param values The parts to replace. <jk>null</jk> values are ignored.
+ * @param name The part name.
+ * @param value The part value.
* @return This object (for method chaining).
*/
- @FluentSetter
- public PartListBuilder update(List<Part> values) {
- for (int i = 0; i < values.size(); i++) /* See HTTPCORE-361 */
- update(values.get(i));
- return this;
+ public PartListBuilder set(String name, Supplier<?> value) {
+ return set(part(name, value));
}
/**
- * Sets all of the parts contained within this list overriding any existing parts.
+ * Replaces the first occurrence of the parts with the same name.
*
* <p>
- * The parts are added in the order in which they appear in the array.
+ * 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 set
+ * @param values The parts to replace. <jk>null</jk> values are ignored.
* @return This object (for method chaining).
*/
@FluentSetter
- public PartListBuilder set(Part...values) {
- clear();
- Collections.addAll(parts, values);
+ public PartListBuilder set(List<Part> values) {
+
+ if (values != null) {
+ for (int i1 = 0, j1 = values.size(); i1 < j1; i1++) {
+ Part p = values.get(i1);
+ if (p != null) {
+ for (int i2 = 0, j2 = entries.size(); i2 < j2; i2++) {
+ Part x = entries.get(i2);
+ if (eq(x.getName(), p.getName())) {
+ entries.remove(i2);
+ j2--;
+ }
+ }
+ }
+ }
+
+ for (int i = 0, j = values.size(); i < j; i++) {
+ Part x = values.get(i);
+ if (x != null) {
+ entries.add(x);
+ }
+ }
+ }
+
return this;
}
/**
- * Sets all of the parts contained within this list overriding any existing parts.
+ * Replaces the first occurrence of the parts with the same name.
*
* <p>
- * The parts are added in the order in which they appear in the list.
+ * 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 set
+ * @param values The parts to replace. <jk>null</jk> values are ignored.
* @return This object (for method chaining).
*/
- @FluentSetter
- public PartListBuilder set(List<Part> values) {
- clear();
- parts.addAll(values);
+ public PartListBuilder set(PartList values) {
+ if (values != null)
+ set(values.entries);
return this;
}
/**
- * Appends or replaces the part values in this list.
+ * Sets a default value for a part.
*
* <p>
- * If the part already exists in this list, it will be replaced with the new value.
- * Otherwise it will be appended to the end of this list.
+ * If no part with the same name is found, the given part is added to the end of the list.
*
- * @param values The values to append or replace in this list.
+ * @param value The default part to set. <jk>null</jk> values are ignored.
* @return This object (for method chaining).
*/
- public PartListBuilder appendUnique(Part...values) {
- for (Part h : values) {
+ @FluentSetter
+ public PartListBuilder setDefault(Part value) {
+ if (value != null) {
boolean replaced = false;
- for (ListIterator<Part> li = parts.listIterator(); li.hasNext();) {
- Part h2 = li.next();
- if (h2.getName().equals(h.getName())) {
- li.set(h);
- replaced = true;
- break;
+ if (defaultEntries == null)
+ defaultEntries = new ArrayList<>();
+ for (int i = 0, j = defaultEntries.size(); i < j; i++) {
+ Part x = defaultEntries.get(i);
+ if (eq(x.getName(), value.getName())) {
+ if (replaced) {
+ defaultEntries.remove(i);
+ j--;
+ } else {
+ defaultEntries.set(i, value);
+ replaced = true;
+ }
}
}
+
if (! replaced)
- add(h);
+ defaultEntries.add(value);
}
+
+ return this;
+ }
+
+ /**
+ * Sets default values for one or more parts.
+ *
+ * <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 (for method chaining).
+ */
+ @FluentSetter
+ public PartListBuilder setDefault(Part...values) {
+ if (values != null)
+ setDefault(Arrays.asList(values));
return this;
}
+ /**
+ * Sets a default value for a part.
+ *
+ * @param name The part name.
+ * @param value The part value.
+ * @return This object (for method chaining).
+ */
+ public PartListBuilder setDefault(String name, Object value) {
+ return setDefault(part(name, value));
+ }
+
+ /**
+ * Sets a default value for a part.
+ *
+ * @param name The part name.
+ * @param value The part value.
+ * @return This object (for method chaining).
+ */
+ public PartListBuilder setDefault(String name, Supplier<?> value) {
+ return setDefault(part(name, value));
+ }
/**
- * Appends or replaces the part values in this list.
+ * Sets default values for one or more parts.
*
* <p>
- * If the part already exists in this list, it will be replaced with the new value.
- * Otherwise it will be appended to the end of this list.
+ * If no part with the same name is found the given part is added to the end of the list.
*
- * @param values The values to append or replace in this list.
+ * @param values The default parts to set. <jk>null</jk> values are ignored.
* @return This object (for method chaining).
*/
- public PartListBuilder appendUnique(Collection<Part> values) {
- for (Part h : values) {
- boolean replaced = false;
- for (ListIterator<Part> li = parts.listIterator(); li.hasNext();) {
- Part h2 = li.next();
- if (h2.getName().equals(h.getName())) {
- li.set(h);
- replaced = true;
- break;
+ @FluentSetter
+ public PartListBuilder setDefault(List<Part> values) {
+
+ if (values != null) {
+ if (defaultEntries == null)
+ defaultEntries = new ArrayList<>();
+ for (int i1 = 0, j1 = values.size(); i1 < j1; i1++) {
+ Part p = values.get(i1);
+ if (p != null) {
+ for (int i2 = 0, j2 = defaultEntries.size(); i2 < j2; i2++) {
+ Part x = defaultEntries.get(i2);
+ if (eq(x.getName(), p.getName())) {
+ defaultEntries.remove(i2);
+ j2--;
+ }
+ }
+ }
+ }
+
+ for (Part v : values) {
+ if (v != null) {
+ defaultEntries.add(v);
}
}
- if (! replaced)
- add(h);
}
+
+ 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 (for method chaining).
+ */
+ public PartListBuilder setDefault(PartList values) {
+ if (values != null)
+ setDefault(values.entries);
return this;
}
@@ -431,7 +642,7 @@ public class PartListBuilder {
}
private Supplier<Object> resolver(Object input) {
- return ()->(varResolver == null ? unwrap(input) : varResolver.resolve(stringify(unwrap(input))));
+ return ()->varResolver.resolve(stringify(unwrap(input)));
}
private Object unwrap(Object o) {
@@ -440,6 +651,17 @@ public class PartListBuilder {
return o;
}
+ private Part part(String name, Object value) {
+ return isResolving() ? new BasicPart(name, resolver(value)) : new BasicPart(name, value);
+ }
+
+ private Part part(String name, Supplier<?> value) {
+ return isResolving() ? new BasicPart(name, resolver(value)) : new BasicPart(name, value);
+ }
+
+ private boolean eq(String s1, String s2) {
+ return caseInsensitive ? StringUtils.eq(s1, s2) : StringUtils.eqic(s1, s2);
+ }
// <FluentSetters>
// </FluentSetters>
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ArrayBuilder.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ArrayBuilder.java
index 9586424..5e1cb23 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ArrayBuilder.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ArrayBuilder.java
@@ -28,16 +28,18 @@ public class ArrayBuilder<T> {
private final T[] array;
private int i = 0;
+ private final boolean skipNulls;
/**
* Creator.
*
* @param elementType The element type.
* @param size The array size.
+ * @param skipNulls If <jk>true</jk>, <jk>null</jk> values will be ignored.
* @return A new builder object.
*/
- public static <T> ArrayBuilder<T> create(Class<T> elementType, int size) {
- return new ArrayBuilder<>(elementType, size);
+ public static <T> ArrayBuilder<T> create(Class<T> elementType, int size, boolean skipNulls) {
+ return new ArrayBuilder<>(elementType, size, skipNulls);
}
/**
@@ -45,10 +47,12 @@ public class ArrayBuilder<T> {
*
* @param elementType The element type.
* @param capacity The array size.
+ * @param skipNulls If <jk>true</jk>, <jk>null</jk> values will be ignored.
*/
@SuppressWarnings("unchecked")
- public ArrayBuilder(Class<T> elementType, int capacity) {
+ public ArrayBuilder(Class<T> elementType, int capacity, boolean skipNulls) {
array = (T[])Array.newInstance(elementType, capacity);
+ this.skipNulls = skipNulls;
}
/**
@@ -59,7 +63,7 @@ public class ArrayBuilder<T> {
* @throws ArrayIndexOutOfBoundsException if size is exceeded.
*/
public ArrayBuilder<T> add(T t) {
- if (t != null)
+ if (!(skipNulls && t == null))
array[i++] = t;
return this;
}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/StringUtils.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/StringUtils.java
index 1af5b3f..95b6e74 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/StringUtils.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/StringUtils.java
@@ -1259,6 +1259,18 @@ public final class StringUtils {
}
/**
+ * Tests two strings for equality, but gracefully handles nulls.
+ *
+ * @param caseInsensitive Use case-insensitive matching.
+ * @param s1 String 1.
+ * @param s2 String 2.
+ * @return <jk>true</jk> if the strings are equal.
+ */
+ public static boolean eq(boolean caseInsensitive, String s1, String s2) {
+ return caseInsensitive ? eqic(s1, s2) : eq(s1, s2);
+ }
+
+ /**
* Finds the position where the two strings differ.
*
* @param s1 The first string.
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 5e84e16..3f15530 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
@@ -2100,9 +2100,9 @@ public class RestClient extends BeanContext implements HttpClient, Closeable, Re
for (Object o : cp.getList(RESTCLIENT_query, Object.class).orElse(emptyList())) {
o = buildBuilders(o, partSerializerSession);
if (o instanceof PartList)
- query.add((PartList)o);
+ query.append((PartList)o);
else
- query.add(BasicPart.cast(o));
+ query.append(BasicPart.cast(o));
}
this.query = query.build();
@@ -2110,9 +2110,9 @@ public class RestClient extends BeanContext implements HttpClient, Closeable, Re
for (Object o : cp.getList(RESTCLIENT_formData, Object.class).orElse(emptyList())) {
o = buildBuilders(o, partSerializerSession);
if (o instanceof PartList)
- formData.add((PartList)o);
+ formData.append((PartList)o);
else
- formData.add(BasicPart.cast(o));
+ formData.append(BasicPart.cast(o));
}
this.formData = formData.build();
@@ -2530,7 +2530,7 @@ public class RestClient extends BeanContext implements HttpClient, Closeable, Re
if (body instanceof NameValuePair[])
return req.body(new UrlEncodedFormEntity(Arrays.asList((NameValuePair[])body)));
if (body instanceof PartList)
- return req.body(new UrlEncodedFormEntity((PartList)body));
+ return req.body(new UrlEncodedFormEntity(((PartList)body).asNameValuePairs()));
if (body instanceof HttpResource)
((HttpResource)body).getHeaders().forEach(x-> req.header(x));
if (body instanceof HttpEntity) {
@@ -2896,13 +2896,11 @@ public class RestClient extends BeanContext implements HttpClient, Closeable, Re
RestRequest req = createRequest(toURI(op.getUri(), rootUri), op.getMethod(), op.hasBody());
- headers.forEach(x -> req.header(APPEND, BasicHeader.cast(x)));
+ headers.forEach(x -> req.header(APPEND, x));
- for (Object o : query)
- req.query(BasicPart.cast(o));
+ query.forEach(x -> req.query(APPEND, x.getName(), x.getValue()));
- for (Object o : formData)
- req.formData(BasicPart.cast(o));
+ formData.forEach(x -> req.formData(APPEND, x.getName(), x.getValue()));
onInit(req);
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
index e14fe4e..c1aa656 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
@@ -987,16 +987,14 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur
* <li>A collection or array of anything on this list.
* </ul>
* @return This object (for method chaining).
- * @throws RestCallException Invalid input.
*/
@SuppressWarnings("rawtypes")
- public RestRequest paths(Object...params) throws RestCallException {
+ public RestRequest paths(Object...params) {
for (Object o : params) {
if (BasicPart.canCast(o)) {
innerPath(BasicPart.cast(o));
} else if (o instanceof PartList) {
- for (NameValuePair p : (PartList)o)
- innerPath(p);
+ ((PartList)o).forEach(x -> innerPath(x));
} else if (o instanceof Collection) {
for (Object o2 : (Collection<?>)o)
innerPath(BasicPart.cast(o2));
@@ -1010,7 +1008,7 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur
for (Map.Entry<String,Object> e : toBeanMap(o).entrySet())
innerPath(serializedPart(e.getKey(), e.getValue(), PATH, partSerializer, null, null));
} else if (o != null) {
- throw new RestCallException(null, null, "Invalid type passed to paths(): {0}", className(o));
+ throw new BasicRuntimeException("Invalid type passed to paths(): {0}", className(o));
}
}
return this;
@@ -1056,8 +1054,7 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur
if (BasicPart.canCast(value)) {
innerPath(BasicPart.cast(value));
} else if (value instanceof PartList) {
- for (Part o : (PartList)value)
- innerPath(o);
+ ((PartList)value).forEach(x -> innerPath(x));
} else if (value instanceof Collection) {
for (Object o : (Collection<?>)value)
innerPath(BasicPart.cast(o));
@@ -1076,12 +1073,12 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur
return this;
}
- private RestRequest innerPath(NameValuePair param) throws RestCallException {
+ private RestRequest innerPath(NameValuePair param) {
String path = uriBuilder.getPath();
String name = param.getName(), value = param.getValue();
String var = "{" + name + "}";
if (path.indexOf(var) == -1 && ! name.equals("/*"))
- throw new RestCallException(null, null, "Path variable {"+name+"} was not found in path.");
+ throw new RuntimeException("Path variable {"+name+"} was not found in path.");
String p = null;
if (name.equals("/*"))
p = path.replaceAll("\\/\\*$", "/" + value);
@@ -1174,9 +1171,8 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur
*
* @param pair The parameter.
* @return This object (for method chaining).
- * @throws RestCallException Invalid input.
*/
- public RestRequest query(NameValuePair pair) throws RestCallException {
+ public RestRequest query(NameValuePair pair) {
return queries(pair);
}
@@ -1240,9 +1236,8 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur
* <li>Value converted to a string using the configured part serializer.
* </ul>
* @return This object (for method chaining).
- * @throws RestCallException Invalid input.
*/
- public RestRequest query(AddFlag flag, String name, Object value) throws RestCallException {
+ public RestRequest query(AddFlag flag, String name, Object value) {
return queries(flag, serializedPart(name, value, QUERY, partSerializer, null, EnumSet.of(flag)));
}
@@ -1278,9 +1273,8 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur
* <li>A collection or array of anything on this list.
* </ul>
* @return This object (for method chaining).
- * @throws RestCallException Invalid input.
*/
- public RestRequest queries(Object...params) throws RestCallException {
+ public RestRequest queries(Object...params) {
return queries(APPEND, params);
}
@@ -1323,16 +1317,14 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur
* <li>A collection or array of anything on this list.
* </ul>
* @return This object (for method chaining).
- * @throws RestCallException Invalid input.
*/
- public RestRequest queries(AddFlag flag, Object...params) throws RestCallException {
+ public RestRequest queries(AddFlag flag, Object...params) {
List<NameValuePair> l = new ArrayList<>();
for (Object o : params) {
if (BasicPart.canCast(o)) {
l.add(BasicPart.cast(o));
} else if (o instanceof PartList) {
- for (NameValuePair p : (PartList)o)
- l.add(p);
+ ((PartList)o).forEach(x -> l.add(x));
} else if (o instanceof Collection) {
for (Object o2 : (Collection<?>)o)
l.add(BasicPart.cast(o2));
@@ -1346,7 +1338,7 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur
for (Map.Entry<String,Object> e : toBeanMap(o).entrySet())
l.add(serializedPart(e.getKey(), e.getValue(), QUERY, partSerializer, null, EnumSet.of(flag)));
} else if (o != null) {
- throw new RestCallException(null, null, "Invalid type passed to queries(): {0}", className(o));
+ throw new BasicRuntimeException("Invalid type passed to queries(): {0}", className(o));
}
}
return innerQuery(EnumSet.of(flag), l);
@@ -1435,8 +1427,7 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur
if (BasicPart.canCast(value)) {
l.add(BasicPart.cast(value));
} else if (value instanceof PartList) {
- for (Object o : (PartList)value)
- l.add(BasicPart.cast(o));
+ ((PartList)value).forEach(x -> l.add(x));
} else if (value instanceof Collection) {
for (Object o : (Collection<?>)value)
l.add(BasicPart.cast(o));
@@ -1627,9 +1618,8 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur
* <li>Value converted to a string using the configured part serializer.
* </ul>
* @return This object (for method chaining).
- * @throws RestCallException Invalid input.
*/
- public RestRequest formData(AddFlag flag, String name, Object value) throws RestCallException {
+ public RestRequest formData(AddFlag flag, String name, Object value) {
return formDatas(flag, serializedPart(name, value, FORMDATA, partSerializer, null, EnumSet.of(flag)));
}
@@ -1710,16 +1700,14 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur
* <li>A collection or array of anything on this list.
* </ul>
* @return This object (for method chaining).
- * @throws RestCallException Invalid input.
*/
- public RestRequest formDatas(AddFlag flag, Object...params) throws RestCallException {
+ public RestRequest formDatas(AddFlag flag, Object...params) {
List<NameValuePair> l = new ArrayList<>();
for (Object o : params) {
if (BasicPart.canCast(o)) {
l.add(BasicPart.cast(o));
} else if (o instanceof PartList) {
- for (NameValuePair p : (PartList)o)
- l.add(p);
+ ((PartList)o).forEach(x -> l.add(x));
} else if (o instanceof Collection) {
for (Object o2 : (Collection<?>)o)
l.add(BasicPart.cast(o2));
@@ -1733,7 +1721,7 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur
for (Map.Entry<String,Object> e : toBeanMap(o).entrySet())
l.add(serializedPart(e.getKey(), e.getValue(), FORMDATA, partSerializer, null, EnumSet.of(flag)));
} else if (o != null) {
- throw new RestCallException(null, null, "Invalid type passed to formDatas(): {0}", className(o));
+ throw new BasicRuntimeException("Invalid type passed to formDatas(): {0}", className(o));
}
}
return innerFormData(EnumSet.of(flag), l);
@@ -1831,8 +1819,7 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur
if (BasicPart.canCast(value)) {
l.add(BasicPart.cast(value));
} else if (value instanceof PartList) {
- for (Part o : (PartList)value)
- l.add(o);
+ ((PartList)value).forEach(x -> l.add(x));
} else if (value instanceof Collection) {
for (Object o : (Collection<?>)value)
l.add(BasicPart.cast(o));
@@ -2955,7 +2942,7 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur
if (formData != null)
entity = new UrlEncodedFormEntity(formData);
else if (input2 instanceof PartList)
- entity = new UrlEncodedFormEntity((PartList)input2);
+ entity = new UrlEncodedFormEntity(((PartList)input2).asNameValuePairs());
else if (input2 instanceof HttpResource) {
HttpResource r = (HttpResource)input2;
r.getHeaders().forEach(x -> addHeader(x));
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOperationContext.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOperationContext.java
index aa17db6..e7ec3d0 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOperationContext.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOperationContext.java
@@ -1457,7 +1457,7 @@ public class RestOperationContext extends BeanContext implements Comparable<Rest
PartListBuilder x = PartList.create();
- x.appendUnique(properties.getInstanceArray(RESTOP_defaultQuery, Part.class, beanStore).orElse(new Part[0]));
+ x.setDefault(properties.getInstanceArray(RESTOP_defaultQuery, Part.class, beanStore).orElse(new Part[0]));
for (Annotation[] aa : method.getParameterAnnotations()) {
for (Annotation a : aa) {
@@ -1466,7 +1466,7 @@ public class RestOperationContext extends BeanContext implements Comparable<Rest
String def = joinnlFirstNonEmptyArray(h._default(), h.df());
if (def != null) {
try {
- x.appendUnique(basicPart(firstNonEmpty(h.name(), h.n(), h.value()), parseAnything(def)));
+ x.setDefault(basicPart(firstNonEmpty(h.name(), h.n(), h.value()), parseAnything(def)));
} catch (ParseException e) {
throw new ConfigException(e, "Malformed @Query annotation");
}
@@ -1501,7 +1501,7 @@ public class RestOperationContext extends BeanContext implements Comparable<Rest
PartListBuilder x = PartList.create();
- x.appendUnique(properties.getInstanceArray(RESTOP_defaultFormData, Part.class, beanStore).orElse(new Part[0]));
+ x.setDefault(properties.getInstanceArray(RESTOP_defaultFormData, Part.class, beanStore).orElse(new Part[0]));
for (Annotation[] aa : method.getParameterAnnotations()) {
for (Annotation a : aa) {
@@ -1510,7 +1510,7 @@ public class RestOperationContext extends BeanContext implements Comparable<Rest
String def = joinnlFirstNonEmptyArray(h._default(), h.df());
if (def != null) {
try {
- x.appendUnique(basicPart(firstNonEmpty(h.name(), h.n(), h.value()), parseAnything(def)));
+ x.setDefault(basicPart(firstNonEmpty(h.name(), h.n(), h.value()), parseAnything(def)));
} catch (ParseException e) {
throw new ConfigException(e, "Malformed @FormData annotation");
}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/http/PartList_Test.java b/juneau-utest/src/test/java/org/apache/juneau/http/PartList_Test.java
deleted file mode 100644
index 6de157c..0000000
--- a/juneau-utest/src/test/java/org/apache/juneau/http/PartList_Test.java
+++ /dev/null
@@ -1,101 +0,0 @@
-// ***************************************************************************************************************************
-// * 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.http;
-
-import static org.apache.juneau.assertions.Assertions.*;
-import static org.junit.runners.MethodSorters.*;
-import static org.apache.juneau.http.HttpParts.*;
-
-import org.apache.juneau.collections.*;
-import org.apache.juneau.http.part.*;
-import org.apache.juneau.httppart.*;
-import org.apache.juneau.oapi.*;
-import org.junit.*;
-
-@FixMethodOrder(NAME_ASCENDING)
-public class PartList_Test {
-
- @Test
- public void a01_basic() {
- PartListBuilder x = PartList.create();
-
- assertObject(x.build().iterator()).asJson().is("[]");
- x.add(part("Foo","bar"));
- assertObject(x.build().iterator()).asJson().is("['Foo=bar']");
- x.add(part("Foo","baz"));
- assertObject(x.build().iterator()).asJson().is("['Foo=bar','Foo=baz']");
- x.add(PartList.of());
- assertObject(x.build().iterator()).asJson().is("['Foo=bar','Foo=baz']");
- x.add(PartList.of(part("Foo","qux")));
- assertObject(x.build().iterator()).asJson().is("['Foo=bar','Foo=baz','Foo=qux']");
- x.add(PartList.of(part("Foo","q2x"), part("Foo","q3x")));
- assertObject(x.build().iterator()).asJson().is("['Foo=bar','Foo=baz','Foo=qux','Foo=q2x','Foo=q3x']");
- x.add(PartList.of(part("Foo","q4x"),part("Foo","q5x")));
- assertObject(x.build().iterator()).asJson().is("['Foo=bar','Foo=baz','Foo=qux','Foo=q2x','Foo=q3x','Foo=q4x','Foo=q5x']");
- x.add((PartList)null);
- assertObject(x.build().iterator()).asJson().is("['Foo=bar','Foo=baz','Foo=qux','Foo=q2x','Foo=q3x','Foo=q4x','Foo=q5x']");
-
- assertObject(new PartList.Null().iterator()).asJson().is("[]");
- }
-
- @Test
- public void a02_creators() {
- PartList x;
-
- x = partList(part("Foo","bar"), part("Foo","baz"), null);
- assertObject(x.iterator()).asJson().is("['Foo=bar','Foo=baz']");
-
- x = partList(AList.of(part("Foo","bar"), part("Foo","baz"), null));
- assertObject(x.iterator()).asJson().is("['Foo=bar','Foo=baz']");
-
- x = partList("Foo","bar","Foo","baz");
- assertObject(x.iterator()).asJson().is("['Foo=bar','Foo=baz']");
-
- assertThrown(()->partList("Foo")).is("Odd number of parameters passed into PartList.ofPairs()");
- }
-
- @Test
- public void a03_addMethods() {
- String pname = "NameValuePairSupplierTest.x";
-
- PartListBuilder x = PartList.create().resolving();
- System.setProperty(pname, "y");
-
- x.add("X1","bar");
- x.add("X2","$S{"+pname+"}");
- x.add("X3","bar");
- x.add("X4",()->"$S{"+pname+"}");
- x.add("X5","bar",HttpPartType.QUERY,openApiSession(),null,false);
- x.add("X6","$S{"+pname+"}",HttpPartType.QUERY,openApiSession(),null,false);
-
- assertString(x.build().toString()).is("X1=bar&X2=y&X3=bar&X4=y&X5=bar&X6=y");
-
- System.setProperty(pname, "z");
-
- assertString(x.build().toString()).is("X1=bar&X2=z&X3=bar&X4=z&X5=bar&X6=z");
-
- System.clearProperty(pname);
- }
-
- //-----------------------------------------------------------------------------------------------------------------
- // Utility methods
- //-----------------------------------------------------------------------------------------------------------------
-
- private static Part part(String name, Object val) {
- return basicPart(name, val);
- }
-
- private static HttpPartSerializerSession openApiSession() {
- return OpenApiSerializer.DEFAULT.createPartSession(null);
- }
-}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/http/header/Accept_Test.java b/juneau-utest/src/test/java/org/apache/juneau/http/header/Accept_Test.java
index 5069da7..2f39874 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/http/header/Accept_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/http/header/Accept_Test.java
@@ -27,6 +27,9 @@ import org.apache.juneau.rest.client.*;
import org.apache.juneau.rest.mock.*;
import org.junit.*;
+/**
+ * Tests: {@link Accept}
+ */
@FixMethodOrder(NAME_ASCENDING)
public class Accept_Test {
diff --git a/juneau-utest/src/test/java/org/apache/juneau/http/header/HeaderBeanMeta_Test.java b/juneau-utest/src/test/java/org/apache/juneau/http/header/HeaderBeanMeta_Test.java
index fa69bd8..26d6633 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/http/header/HeaderBeanMeta_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/http/header/HeaderBeanMeta_Test.java
@@ -95,7 +95,7 @@ public class HeaderBeanMeta_Test {
HeaderBeanMeta<A5> a5 = HeaderBeanMeta.of(A5.class);
assertThrown(()->a5.construct("foo")).contains("Constructor for type "+TNAME+"$A5 could not be found.");
- assertString(a4.getSchema().getName()).is("A4");
+ assertString(a5.getSchema().getName()).is("A5");
HeaderBeanMeta<A6> a6 = HeaderBeanMeta.of(A6.class);
assertThrown(()->a6.construct("X", "foo")).contains("oops");
diff --git a/juneau-utest/src/test/java/org/apache/juneau/http/header/HeaderList_Test.java b/juneau-utest/src/test/java/org/apache/juneau/http/header/HeaderList_Test.java
index bc64ad0..733c189 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/http/header/HeaderList_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/http/header/HeaderList_Test.java
@@ -25,6 +25,9 @@ import org.apache.juneau.httppart.*;
import org.apache.juneau.oapi.*;
import org.junit.*;
+/**
+ * Tests: {@link HeaderList}, {@link HeaderListBuilder}, {@link BasicHeaderIterator}
+ */
@FixMethodOrder(NAME_ASCENDING)
public class HeaderList_Test {
@@ -377,6 +380,7 @@ public class HeaderList_Test {
.set(FOO_3)
.set(BAR_1)
.set((Header)null)
+ .set((HeaderList)null)
.build();
assertObject(x).isString("[Foo: 3, Bar: 1]");
@@ -434,6 +438,99 @@ public class HeaderList_Test {
}
//-----------------------------------------------------------------------------------------------------------------
+ // BasicHeaderIterator
+ //-----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void c01_iterators() {
+ HeaderList x = HeaderList.of(Accept.TEXT_XML,ContentType.TEXT_XML);
+
+ HeaderIterator i1 = x.iterator();
+ assertObject(i1.nextHeader()).isString("Accept: text/xml");
+ assertObject(i1.nextHeader()).isString("Content-Type: text/xml");
+ assertThrown(()->i1.nextHeader()).contains("Iteration already finished.");
+
+ HeaderIterator i2 = x.iterator();
+ assertObject(i2.next()).isString("Accept: text/xml");
+ assertObject(i2.nextHeader()).isString("Content-Type: text/xml");
+ assertThrown(()->i2.next()).contains("Iteration already finished.");
+
+ HeaderIterator i3 = x.iterator("accept");
+ assertObject(i3.nextHeader()).isString("Accept: text/xml");
+ assertThrown(()->i3.nextHeader()).contains("Iteration already finished.");
+
+ HeaderList x2 = HeaderList.create().append(Accept.TEXT_XML,ContentType.TEXT_XML).caseSensitive().build();
+
+ HeaderIterator i4 = x2.iterator("Accept");
+ assertObject(i4.nextHeader()).isString("Accept: text/xml");
+ assertThrown(()->i4.nextHeader()).contains("Iteration already finished.");
+
+ HeaderIterator i5 = x2.iterator("accept");
+ assertThrown(()->i5.nextHeader()).contains("Iteration already finished.");
+
+ assertThrown(()->i5.remove()).contains("Remove is not supported.");
+ }
+
+ //-----------------------------------------------------------------------------------------------------------------
+ // Default headers
+ //-----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void d01_defaultHeaders() {
+ HeaderList x1 = HeaderList.create().setDefault(Accept.TEXT_XML).build();
+ assertObject(x1).isString("[Accept: text/xml]");
+
+ HeaderList x2 = HeaderList.create().set(Accept.TEXT_PLAIN).setDefault(Accept.TEXT_XML).build();
+ assertObject(x2).isString("[Accept: text/plain]");
+
+ HeaderList x3 = HeaderList.create().set(ContentType.TEXT_XML,Accept.TEXT_PLAIN,ContentType.TEXT_XML).setDefault(Accept.TEXT_XML).build();
+ assertObject(x3).isString("[Content-Type: text/xml, Accept: text/plain, Content-Type: text/xml]");
+
+ HeaderList x4 = HeaderList.create().set(ContentType.TEXT_XML,ContentType.TEXT_XML).setDefault(Accept.TEXT_XML).build();
+ assertObject(x4).isString("[Content-Type: text/xml, Content-Type: text/xml, Accept: text/xml]");
+
+ HeaderList x5 = HeaderList.create().set(ContentType.TEXT_XML,ContentType.TEXT_XML).setDefault(Accept.TEXT_XML).setDefault(ContentType.TEXT_HTML).build();
+ assertObject(x5).isString("[Content-Type: text/xml, Content-Type: text/xml, Accept: text/xml]");
+
+ HeaderList x6 = HeaderList.create().setDefault(Accept.TEXT_XML,Accept.TEXT_PLAIN).build();
+ assertObject(x6).isString("[Accept: text/xml, Accept: text/plain]");
+
+ HeaderList x7 = HeaderList.create().setDefault(Accept.TEXT_XML).setDefault(Accept.TEXT_PLAIN).build();
+ assertObject(x7).isString("[Accept: text/plain]");
+
+ HeaderList x8 = HeaderList.create().setDefault(Accept.TEXT_XML,Accept.TEXT_HTML).setDefault(Accept.TEXT_PLAIN).build();
+ assertObject(x8).isString("[Accept: text/plain]");
+
+ HeaderList x9 = HeaderList
+ .create()
+ .setDefault((Header)null)
+ .setDefault((HeaderList)null)
+ .setDefault((Header[])null)
+ .setDefault((List<Header>)null)
+ .build();
+ assertObject(x9).isString("[]");
+
+ HeaderList x10 = HeaderList.create().setDefault("Accept","text/xml").build();
+ assertObject(x10).isString("[Accept: text/xml]");
+
+ HeaderList x11 = HeaderList.create().setDefault("Accept",()->"text/xml").build();
+ assertObject(x11).isString("[Accept: text/xml]");
+
+ HeaderList x12 = HeaderList.create().set(ContentType.TEXT_XML,ContentType.TEXT_PLAIN).setDefault(AList.of(Accept.TEXT_XML,ContentType.TEXT_HTML,null)).build();
+ assertObject(x12).isString("[Content-Type: text/xml, Content-Type: text/plain, Accept: text/xml]");
+
+ HeaderList x13 = HeaderList.create().set(ContentType.TEXT_XML,ContentType.TEXT_PLAIN).setDefault(HeaderList.of(Accept.TEXT_XML,ContentType.TEXT_HTML,null)).build();
+ assertObject(x13).isString("[Content-Type: text/xml, Content-Type: text/plain, Accept: text/xml]");
+
+ HeaderList x14 = HeaderList.create().set(ContentType.TEXT_XML,ContentType.TEXT_PLAIN)
+ .setDefault(AList.of(Accept.TEXT_XML,ContentType.TEXT_HTML,null))
+ .setDefault(AList.of(Accept.TEXT_HTML,ContentType.TEXT_XML,null))
+ .setDefault(AList.of(Age.of(1)))
+ .build();
+ assertObject(x14).isString("[Content-Type: text/xml, Content-Type: text/plain, Accept: text/html, Age: 1]");
+ }
+
+ //-----------------------------------------------------------------------------------------------------------------
// Utility methods
//-----------------------------------------------------------------------------------------------------------------
diff --git a/juneau-utest/src/test/java/org/apache/juneau/http/header/HeaderBeanMeta_Test.java b/juneau-utest/src/test/java/org/apache/juneau/http/part/PartBeanMeta_Test.java
similarity index 79%
copy from juneau-utest/src/test/java/org/apache/juneau/http/header/HeaderBeanMeta_Test.java
copy to juneau-utest/src/test/java/org/apache/juneau/http/part/PartBeanMeta_Test.java
index fa69bd8..1b0c8b8 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/http/header/HeaderBeanMeta_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/http/part/PartBeanMeta_Test.java
@@ -10,7 +10,7 @@
// * "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.http.header;
+package org.apache.juneau.http.part;
import static org.junit.Assert.*;
import static org.junit.runners.MethodSorters.*;
@@ -20,10 +20,13 @@ import static org.apache.juneau.internal.StringUtils.*;
import org.apache.juneau.http.annotation.*;
import org.junit.*;
+/**
+ * Tests: {@link PartBeanMeta}
+ */
@FixMethodOrder(NAME_ASCENDING)
-public class HeaderBeanMeta_Test {
+public class PartBeanMeta_Test {
- private static final String TNAME = HeaderBeanMeta_Test.class.getName();
+ private static final String TNAME = PartBeanMeta_Test.class.getName();
public static class A1 {
public String name, value;
@@ -43,7 +46,7 @@ public class HeaderBeanMeta_Test {
}
}
- @Header(name="A3")
+ @Query(name="A3")
public static class A3 {
public String value;
@@ -52,7 +55,7 @@ public class HeaderBeanMeta_Test {
}
}
- @Header("A4")
+ @FormData("A4")
public static class A4 {
public String value;
@@ -61,7 +64,7 @@ public class HeaderBeanMeta_Test {
}
}
- @Header(name="A5")
+ @Path(name="A5")
public static class A5 {}
public static class A6 {
@@ -70,34 +73,41 @@ public class HeaderBeanMeta_Test {
}
}
+ @ResponseHeader(name="A7")
+ public static class A7 {}
+
@Test
public void a01_basic() {
- HeaderBeanMeta<A1> a1 = HeaderBeanMeta.of(A1.class);
- assertTrue(a1 == HeaderBeanMeta.of(A1.class));
+ PartBeanMeta<A1> a1 = PartBeanMeta.of(A1.class);
+ assertTrue(a1 == PartBeanMeta.of(A1.class));
assertObject(a1.construct("X", "foo")).isJson("{name:'X',value:'foo'}");
assertThrown(()->a1.construct("foo")).contains("Constructor for type "+TNAME+"$A1 requires a name as the first argument.");
assertString(a1.getSchema().getName()).isNull();
- HeaderBeanMeta<A2> a2 = HeaderBeanMeta.of(A2.class);
+ PartBeanMeta<A2> a2 = PartBeanMeta.of(A2.class);
assertObject(a2.construct("X", "foo")).isJson("{name:'X',value:'foo'}");
assertThrown(()->a2.construct("foo")).contains("Constructor for type "+TNAME+"$A2 requires a name as the first argument.");
assertString(a2.getSchema().getName()).isNull();
- HeaderBeanMeta<A3> a3 = HeaderBeanMeta.of(A3.class);
+ PartBeanMeta<A3> a3 = PartBeanMeta.of(A3.class);
assertObject(a3.construct("X", "foo")).isJson("{value:'foo'}");
assertObject(a3.construct("foo")).isJson("{value:'foo'}");
assertString(a3.getSchema().getName()).is("A3");
- HeaderBeanMeta<A4> a4 = HeaderBeanMeta.of(A4.class);
+ PartBeanMeta<A4> a4 = PartBeanMeta.of(A4.class);
assertObject(a4.construct("X", "foo")).isJson("{value:'foo'}");
assertObject(a4.construct("foo")).isJson("{value:'foo'}");
assertString(a4.getSchema().getName()).is("A4");
- HeaderBeanMeta<A5> a5 = HeaderBeanMeta.of(A5.class);
+ PartBeanMeta<A5> a5 = PartBeanMeta.of(A5.class);
assertThrown(()->a5.construct("foo")).contains("Constructor for type "+TNAME+"$A5 could not be found.");
- assertString(a4.getSchema().getName()).is("A4");
+ assertString(a5.getSchema().getName()).is("A5");
- HeaderBeanMeta<A6> a6 = HeaderBeanMeta.of(A6.class);
+ PartBeanMeta<A6> a6 = PartBeanMeta.of(A6.class);
assertThrown(()->a6.construct("X", "foo")).contains("oops");
+
+ PartBeanMeta<A7> a7 = PartBeanMeta.of(A7.class);
+ assertThrown(()->a7.construct("foo")).contains("Constructor for type "+TNAME+"$A7 could not be found.");
+ assertString(a7.getSchema().getName()).is("A7");
}
}
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
new file mode 100644
index 0000000..6c8128c
--- /dev/null
+++ b/juneau-utest/src/test/java/org/apache/juneau/http/part/PartList_Test.java
@@ -0,0 +1,580 @@
+// ***************************************************************************************************************************
+// * 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.http.part;
+
+import static org.apache.juneau.assertions.Assertions.*;
+import static org.apache.juneau.http.HttpParts.*;
+import static org.junit.runners.MethodSorters.*;
+
+import java.util.*;
+import java.util.concurrent.atomic.*;
+
+import org.apache.juneau.collections.*;
+import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.httppart.*;
+import org.apache.juneau.oapi.*;
+import org.junit.*;
+
+/**
+ * Tests: {@link PartList}, {@link PartListBuilder}, {@link BasicPartIterator}
+ */
+@FixMethodOrder(NAME_ASCENDING)
+public class PartList_Test {
+
+ private static final Part
+ FOO_1 = part("Foo","1"),
+ FOO_2 = part("Foo","2"),
+ FOO_3 = part("Foo","3"),
+ FOO_4 = part("Foo","4"),
+ FOO_5 = part("Foo","5"),
+ FOO_6 = part("Foo","6"),
+ FOO_7 = part("Foo","7"),
+ BAR_1 = part("Bar","1"),
+ BAR_2 = part("Bar","2"),
+
+ X_x = part("X", "x");
+
+ @Query("a")
+ public static class APart extends BasicStringPart {
+ public static APart X = new APart("x"), Y = new APart("y"), Z = new APart("z");
+ public APart(Object value) {
+ super("a", value);
+ }
+ }
+
+ @FormData("b")
+ public static class BPart extends BasicStringPart {
+ public static BPart X = new BPart("x"), Y = new BPart("y"), Z = new BPart("z");
+ public BPart(Object value) {
+ super("b", value);
+ }
+ }
+
+ @Path("c")
+ public static class CPart extends BasicStringPart {
+ public static CPart X = new CPart("x");
+ public CPart(Object value) {
+ super("c", value);
+ }
+ }
+
+ @Test
+ public void a01_basic() {
+ PartListBuilder x = PartList.create();
+
+ assertObject(x.build()).isString("");
+ x.append(FOO_1);
+ assertObject(x.build()).isString("Foo=1");
+ x.append(FOO_2);
+ assertObject(x.build()).isString("Foo=1&Foo=2");
+ x.append(PartList.of().getAll());
+ assertObject(x.build()).isString("Foo=1&Foo=2");
+ x.append(PartList.of(FOO_3).getAll());
+ assertObject(x.build()).isString("Foo=1&Foo=2&Foo=3");
+ x.append(PartList.of(FOO_4, FOO_5).getAll());
+ assertObject(x.build()).isString("Foo=1&Foo=2&Foo=3&Foo=4&Foo=5");
+ x.append(PartList.of(FOO_6, FOO_7).getAll());
+ assertObject(x.build()).isString("Foo=1&Foo=2&Foo=3&Foo=4&Foo=5&Foo=6&Foo=7");
+ x.append((Part)null);
+ assertObject(x.build()).isString("Foo=1&Foo=2&Foo=3&Foo=4&Foo=5&Foo=6&Foo=7");
+ x.append((List<Part>)null);
+ assertObject(x.build()).isString("Foo=1&Foo=2&Foo=3&Foo=4&Foo=5&Foo=6&Foo=7");
+
+ assertObject(new PartList.Null()).isString("");
+ }
+
+ @Test
+ public void a02_creators() {
+ PartList x;
+
+ x = partList(FOO_1, FOO_2, null);
+ assertObject(x).isString("Foo=1&Foo=2");
+
+ x = partList(AList.of(FOO_1, FOO_2, null));
+ assertObject(x).isString("Foo=1&Foo=2");
+
+ x = partList("Foo","1","Foo","2");
+ assertObject(x).isString("Foo=1&Foo=2");
+
+ assertThrown(()->partList("Foo")).is("Odd number of parameters passed into PartList.ofPairs()");
+
+ x = PartList.of((List<Part>)null);
+ assertObject(x).isString("");
+
+ x = PartList.of(Collections.emptyList());
+ assertObject(x).isString("");
+
+ x = PartList.of(AList.of(FOO_1));
+ assertObject(x).isString("Foo=1");
+
+ x = PartList.of((Part[])null);
+ assertObject(x).isString("");
+
+ x = PartList.of();
+ assertObject(x).isString("");
+
+ x = PartList.of(FOO_1);
+ assertObject(x).isString("Foo=1");
+
+ x = PartList.ofPairs((Object[])null);
+ assertObject(x).isString("");
+
+ x = PartList.ofPairs();
+ assertObject(x).isString("");
+ }
+
+ @Test
+ public void a03_addMethods() {
+ String pname = "PartSupplierTest.x";
+
+ PartListBuilder x = PartList.create().resolving();
+ System.setProperty(pname, "y");
+
+ x.append("X1","bar");
+ x.append("X2","$S{"+pname+"}");
+ x.append("X3","bar");
+ x.append("X4",()->"$S{"+pname+"}");
+ x.append(new SerializedPart("X5","bar",HttpPartType.QUERY,openApiSession(),null,false));
+
+ assertObject(x.build()).isString("X1=bar&X2=y&X3=bar&X4=y&X5=bar");
+
+ System.setProperty(pname, "z");
+
+ assertObject(x.build()).isString("X1=bar&X2=z&X3=bar&X4=z&X5=bar");
+
+ System.clearProperty(pname);
+ }
+
+ @Test
+ public void a04_toArrayMethods() {
+ PartListBuilder x = PartList
+ .create()
+ .append("X1","1")
+ .append(partList("X2","2").getAll());
+ assertObject(x.build()).isString("X1=1&X2=2");
+ }
+
+ @Test
+ public void a05_copy() {
+ PartList x = PartList.of(FOO_1).copy().build();
+ assertObject(x).isString("Foo=1");
+ }
+
+ @Test
+ public void a06_getCondensed() {
+ PartList x = PartList.of(FOO_1);
+ assertObject(x.get((String)null)).isNull();
+ assertObject(x.get("Foo")).isString("Foo=1");
+ assertObject(x.get("Bar")).isNull();
+ x = PartList.of(FOO_1, FOO_2, FOO_3, X_x);
+ assertObject(x.get("Foo")).isString("Foo=1%2C2%2C3");
+ assertObject(x.get("Bar")).isNull();
+ }
+
+ @Query("Foo")
+ static class Foo extends BasicStringPart {
+ public Foo(Object value) {
+ super("Foo", value);
+ }
+ }
+
+ @Test
+ public void a07_getCondensed_asType() {
+ PartList x = PartList.of(FOO_1);
+ assertObject(x.get(null, APart.class)).isNull();
+ assertObject(x.get("Foo", APart.class)).isString("a=1");
+ assertObject(x.get("Bar", APart.class)).isNull();
+ x = PartList.of(FOO_1, FOO_2, FOO_3, X_x);
+ assertObject(x.get("Foo", APart.class)).isString("a=1%2C2%2C3");
+ assertObject(x.get("Bar", APart.class)).isNull();
+ assertObject(x.get(Foo.class)).isString("Foo=1%2C2%2C3");
+ final PartList x2 = x;
+ assertThrown(()->x2.get(String.class)).contains("Part name could not be found on bean type 'java.lang.String'");
+ }
+
+ @Test
+ public void a08_get() {
+ PartList x = PartList.of(FOO_1, FOO_2, X_x);
+ assertArray(x.getAll(null)).isString("[]");
+ assertArray(x.getAll("Foo")).isString("[Foo=1, Foo=2]");
+ assertArray(x.getAll("FOO")).isString("[]");
+ assertArray(x.getAll("Bar")).isString("[]");
+ }
+
+ @Test
+ public void a09_getFirst() {
+ PartList x = PartList.of(FOO_1, FOO_2, X_x);
+ assertObject(x.getFirst(null)).isNull();
+ assertObject(x.getFirst("Foo")).isString("Foo=1");
+ assertObject(x.getFirst("FOO")).isNull();
+ assertObject(x.getFirst("Bar")).isNull();
+ }
+
+ @Test
+ public void a10_getLast() {
+ PartList x = PartList.of(FOO_1, FOO_2, X_x);
+ assertObject(x.getLast(null)).isNull();
+ assertObject(x.getLast("Foo")).isString("Foo=2");
+ assertObject(x.getLast("FOO")).isNull();
+ assertObject(x.getLast("Bar")).isNull();
+ }
+
+ @Test
+ public void a11_contains() {
+ PartList x = PartList.of(FOO_1, FOO_2, X_x);
+ assertBoolean(x.contains(null)).isFalse();
+ assertBoolean(x.contains("Foo")).isTrue();
+ assertBoolean(x.contains("FOO")).isFalse();
+ assertBoolean(x.contains("Bar")).isFalse();
+ }
+
+ @Test
+ public void a12_headerIterator_all() {
+ PartList x = PartList.of();
+ assertBoolean(x.iterator().hasNext()).isFalse();
+ x = PartList.of(FOO_1);
+ assertBoolean(x.iterator().hasNext()).isTrue();
+ }
+
+ @Test
+ public void a13_headerIterator_single() {
+ PartList x = PartList.of();
+ assertBoolean(x.iterator("Foo").hasNext()).isFalse();
+ x = PartList.of(FOO_1);
+ assertBoolean(x.iterator("Foo").hasNext()).isTrue();
+ assertBoolean(x.iterator("FOO").hasNext()).isFalse();
+ }
+
+ @Test
+ public void a14_forEach_all() {
+ PartList x = PartList.of();
+
+ final AtomicInteger i1 = new AtomicInteger();
+ x.forEach(h -> i1.incrementAndGet());
+ assertInteger(i1.get()).is(0);
+
+ x = PartList.of(FOO_1, FOO_2);
+ final AtomicInteger i2 = new AtomicInteger();
+ x.forEach(h -> i2.incrementAndGet());
+ assertInteger(i2.get()).is(2);
+ }
+
+ @Test
+ public void a15_forEach_single() {
+ PartList x = PartList.of();
+
+ final AtomicInteger i1 = new AtomicInteger();
+ x.forEach("Foo", h -> i1.incrementAndGet());
+ assertInteger(i1.get()).is(0);
+
+ x = PartList.of(FOO_1, FOO_2, X_x);
+ final AtomicInteger i2 = new AtomicInteger();
+ x.forEach("Foo", h -> i2.incrementAndGet());
+ assertInteger(i2.get()).is(2);
+ }
+
+ @Test
+ public void a16_stream_all() {
+ PartList x = PartList.of();
+
+ final AtomicInteger i1 = new AtomicInteger();
+ x.stream().forEach(h -> i1.incrementAndGet());
+ assertInteger(i1.get()).is(0);
+
+ x = PartList.of(FOO_1, FOO_2);
+ final AtomicInteger i2 = new AtomicInteger();
+ x.stream().forEach(h -> i2.incrementAndGet());
+ assertInteger(i2.get()).is(2);
+ }
+
+ @Test
+ public void a17_stream_single() {
+ PartList x = PartList.of();
+
+ final AtomicInteger i1 = new AtomicInteger();
+ x.stream("Foo").forEach(h -> i1.incrementAndGet());
+ assertInteger(i1.get()).is(0);
+
+ x = PartList.of(FOO_1, FOO_2, X_x);
+ final AtomicInteger i2 = new AtomicInteger();
+ x.stream("Foo").forEach(h -> i2.incrementAndGet());
+ assertInteger(i2.get()).is(2);
+ }
+
+ @Test
+ public void a18_caseSensitive() {
+ PartList x1 = PartList.create().append(FOO_1, FOO_2, X_x).build();
+ assertArray(x1.getAll("Foo")).isString("[Foo=1, Foo=2]");
+ assertArray(x1.getAll("FOO")).isString("[]");
+
+ PartList x2 = x1.copy().caseInsensitive().build();
+ assertArray(x2.getAll("Foo")).isString("[Foo=1, Foo=2]");
+ assertArray(x2.getAll("FOO")).isString("[Foo=1, Foo=2]");
+ }
+
+ @Test
+ public void a19_size() {
+ PartList x = PartList.of(FOO_1);
+ assertInteger(x.size()).is(1);
+ }
+
+ //-----------------------------------------------------------------------------------------------------------------
+ // Builder methods
+ //-----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void b01_builder_clear() {
+ PartListBuilder x = PartList.create();
+ x.append(FOO_1);
+ x.clear();
+ assertObject(x.build()).isString("");
+ }
+
+ @Test
+ public void b02_builder_append() {
+ PartList x1 = PartList.create().append(FOO_1).build();
+ PartList x2 = PartList
+ .create()
+ .append()
+ .append((PartList)null)
+ .append((Part)null)
+ .append((Part[])null)
+ .append(x1)
+ .append(FOO_2, FOO_3)
+ .append("Bar", "b1")
+ .append("Bar", ()->"b2")
+ .append((List<Part>)null)
+ .append(AList.of(FOO_4))
+ .build();
+ assertObject(x2).isString("Foo=1&Foo=2&Foo=3&Bar=b1&Bar=b2&Foo=4");
+ }
+
+ @Test
+ public void b03_builder_prepend() {
+ PartList x1 = PartList.create().append(FOO_1).build();
+ PartList x2 = PartList
+ .create()
+ .prepend()
+ .prepend((PartList)null)
+ .prepend((Part)null)
+ .prepend((Part[])null)
+ .prepend(x1)
+ .prepend(FOO_2, FOO_3)
+ .prepend("Bar", "b1")
+ .prepend("Bar", ()->"b2")
+ .prepend((List<Part>)null)
+ .prepend(AList.of(FOO_4))
+ .build();
+ assertObject(x2).isString("Foo=4&Bar=b2&Bar=b1&Foo=2&Foo=3&Foo=1");
+ }
+
+ @Test
+ public void b04_builder_remove() {
+ PartList x = PartList
+ .create()
+ .append(FOO_1,FOO_2,FOO_3,FOO_4,FOO_5,FOO_6,FOO_7)
+ .remove((PartList)null)
+ .remove((Part)null)
+ .remove(PartList.of(FOO_1))
+ .remove(FOO_2)
+ .remove(FOO_3, FOO_4)
+ .remove(AList.of(FOO_5))
+ .build();
+ assertObject(x).isString("Foo=6&Foo=7");
+
+ x = PartList.create().append(FOO_1,FOO_2).remove((String[])null).remove("Bar","Foo").build();
+ assertObject(x).isString("");
+ }
+
+ @Test
+ public void b05_builder_set() {
+ PartList x = null;
+
+ x = PartList
+ .create()
+ .append(FOO_1,FOO_2)
+ .set(FOO_3)
+ .set(BAR_1)
+ .set((Part)null)
+ .set((PartList)null)
+ .build();
+ assertObject(x).isString("Foo=3&Bar=1");
+
+ x = PartList
+ .create()
+ .append(BAR_1,FOO_1,FOO_2,BAR_2)
+ .set(FOO_3)
+ .build();
+ assertObject(x).isString("Bar=1&Foo=3&Bar=2");
+
+ x = PartList
+ .create()
+ .append(BAR_1,FOO_1,FOO_2,BAR_2)
+ .set((Part[])null)
+ .set(null,FOO_3,FOO_4,FOO_5)
+ .build();
+ assertObject(x).isString("Bar=1&Bar=2&Foo=3&Foo=4&Foo=5");
+
+ x = PartList
+ .create()
+ .append(BAR_1,FOO_1,FOO_2,BAR_2)
+ .set((List<Part>)null)
+ .set(AList.of(null,FOO_3,FOO_4,FOO_5))
+ .build();
+ assertObject(x).isString("Bar=1&Bar=2&Foo=3&Foo=4&Foo=5");
+
+ x = PartList
+ .create()
+ .append(BAR_1,FOO_1,FOO_2,BAR_2)
+ .set("FOO", "x")
+ .build();
+ assertObject(x).isString("Bar=1&FOO=x&Bar=2");
+
+ x = PartList
+ .create()
+ .append(BAR_1,FOO_1,FOO_2,BAR_2)
+ .set("FOO", ()->"x")
+ .build();
+ assertObject(x).isString("Bar=1&FOO=x&Bar=2");
+
+ x = PartList
+ .create()
+ .caseInsensitive()
+ .append(BAR_1,FOO_1,FOO_2,BAR_2)
+ .set("FOO", ()->"x")
+ .build();
+ assertObject(x).isString("Bar=1&Foo=1&Foo=2&Bar=2&FOO=x");
+
+ x = PartList
+ .create()
+ .append(BAR_1,FOO_1,FOO_2,BAR_2)
+ .set(PartList.of(FOO_3,FOO_4))
+ .build();
+ assertObject(x).isString("Bar=1&Bar=2&Foo=3&Foo=4");
+ }
+
+ //-----------------------------------------------------------------------------------------------------------------
+ // BasicPartIterator
+ //-----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void c01_iterators() {
+ PartList x = PartList.of(APart.X, BPart.X);
+
+ PartIterator i1 = x.iterator();
+ assertObject(i1.next()).isString("a=x");
+ assertObject(i1.next()).isString("b=x");
+ assertThrown(()->i1.next()).contains("Iteration already finished.");
+
+ PartIterator i2 = x.iterator();
+ assertObject(i2.next()).isString("a=x");
+ assertObject(i2.next()).isString("b=x");
+ assertThrown(()->i2.next()).contains("Iteration already finished.");
+
+ PartIterator i3 = x.iterator("a");
+ assertObject(i3.next()).isString("a=x");
+ assertThrown(()->i3.next()).contains("Iteration already finished.");
+
+ PartIterator i4 = x.iterator("A");
+ assertThrown(()->i4.next()).contains("Iteration already finished.");
+
+ PartList x2 = PartList.create().append(APart.X,BPart.X).caseInsensitive().build();
+
+ PartIterator i5 = x2.iterator("A");
+ assertObject(i5.next()).isString("a=x");
+ assertThrown(()->i5.next()).contains("Iteration already finished.");
+
+ assertThrown(()->i5.remove()).contains("Remove is not supported.");
+ }
+
+ //-----------------------------------------------------------------------------------------------------------------
+ // Default headers
+ //-----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void d01_defaultParts() {
+ PartList x1 = PartList.create().setDefault(APart.X).build();
+ assertObject(x1).isString("a=x");
+
+ PartList x2 = PartList.create().set(APart.X).setDefault(APart.Y).build();
+ assertObject(x2).isString("a=x");
+
+ PartList x3 = PartList.create().set(BPart.X,APart.X,BPart.Y).setDefault(APart.Y).build();
+ assertObject(x3).isString("b=x&a=x&b=y");
+
+ PartList x4 = PartList.create().set(BPart.X,BPart.Y).setDefault(APart.X).build();
+ assertObject(x4).isString("b=x&b=y&a=x");
+
+ PartList x5 = PartList.create().set(BPart.X,BPart.Y).setDefault(APart.X).setDefault(BPart.X).build();
+ assertObject(x5).isString("b=x&b=y&a=x");
+
+ PartList x6 = PartList.create().setDefault(APart.X,APart.Y).build();
+ assertObject(x6).isString("a=x&a=y");
+
+ PartList x7 = PartList.create().setDefault(APart.X).setDefault(APart.Y).build();
+ assertObject(x7).isString("a=y");
+
+ PartList x8 = PartList.create().setDefault(APart.X,APart.Y).setDefault(APart.Z).build();
+ assertObject(x8).isString("a=z");
+
+ PartList x9 = PartList
+ .create()
+ .setDefault((Part)null)
+ .setDefault((PartList)null)
+ .setDefault((Part[])null)
+ .setDefault((List<Part>)null)
+ .build();
+ assertObject(x9).isString("");
+
+ PartList x10 = PartList.create().setDefault("a","x").build();
+ assertObject(x10).isString("a=x");
+
+ PartList x11 = PartList.create().setDefault("a",()->"x").build();
+ assertObject(x11).isString("a=x");
+
+ PartList x12 = PartList.create().set(BPart.X,BPart.Y).setDefault(AList.of(APart.X,BPart.Z,null)).build();
+ assertObject(x12).isString("b=x&b=y&a=x");
+
+ PartList x13 = PartList.create().set(BPart.X,BPart.Y).setDefault(PartList.of(APart.X,BPart.Z,null)).build();
+ assertObject(x13).isString("b=x&b=y&a=x");
+
+ PartList x14 = PartList.create().set(BPart.X,BPart.Y)
+ .setDefault(AList.of(APart.X,BPart.X,null))
+ .setDefault(AList.of(APart.Y,BPart.Y,null))
+ .setDefault(AList.of(CPart.X))
+ .build();
+ 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.asNameValuePairs()).isString("[a=x]");
+ }
+
+ //-----------------------------------------------------------------------------------------------------------------
+ // Utility methods
+ //-----------------------------------------------------------------------------------------------------------------
+
+ private static Part part(String name, Object val) {
+ return basicPart(name, val);
+ }
+
+ private static HttpPartSerializerSession openApiSession() {
+ return OpenApiSerializer.DEFAULT.createPartSession(null);
+ }
+}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_FormDataAnnotation_Test.java b/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_FormDataAnnotation_Test.java
index d3ea94d..fa992e1 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_FormDataAnnotation_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_FormDataAnnotation_Test.java
@@ -848,11 +848,7 @@ public class Remote_FormDataAnnotation_Test {
}
@FormData
public NameValuePair[] getE() {
- return parts("e1","v1","e2",123,"e3",null,"e4","").getAll().toArray(new NameValuePair[0]);
- }
- @FormData
- public BasicPart[] getF() {
- return parts("f1","v1","f2",123,"f3",null,"f4","").getAll().toArray(new BasicPart[0]);
+ return parts("e1","v1","e2",123,"e3",null,"e4","").getAll();
}
}
@@ -860,9 +856,9 @@ public class Remote_FormDataAnnotation_Test {
public void k03_requestBean_nameValuePairs() throws Exception {
K3 x1 = remote(K.class,K3.class);
K3 x2 = client(K.class).partSerializer(UonSerializer.class).build().getRemote(K3.class);
- assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:'',e1:'v1',e2:'123',e4:'',f1:'v1',f2:'123',f4:''}",x1.postX1(new K3a()));
- assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:'',e1:'v1',e2:'123',e4:'',f1:'v1',f2:'123',f4:''}",x2.postX1(new K3a()));
- assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:'',e1:'v1',e2:'123',e4:'',f1:'v1',f2:'123',f4:''}",x2.postX2(new K3a()));
+ assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:'',e1:'v1',e2:'123',e4:''}",x1.postX1(new K3a()));
+ assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:'',e1:'v1',e2:'123',e4:''}",x2.postX1(new K3a()));
+ assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:'',e1:'v1',e2:'123',e4:''}",x2.postX2(new K3a()));
}
//-----------------------------------------------------------------------------------------------------------------
diff --git a/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_PathAnnotation_Test.java b/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_PathAnnotation_Test.java
index 993b945..6a9f831 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_PathAnnotation_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_PathAnnotation_Test.java
@@ -681,8 +681,8 @@ public class Remote_PathAnnotation_Test {
@Remote(path="/")
public static interface K3 {
- @RemoteOp(path="/{a1}/{a2}/{a3}/{a4}/{b1}/{b2}/{b3}/{c1}/{c2}/{c3}/{c4}/{e1}/{e2}/{e3}/{e4}/{f1}/{f2}/{f3}/{f4}") String getX1(@Request K3a rb);
- @RemoteOp(path="/{a1}/{a2}/{a3}/{a4}/{b1}/{b2}/{b3}/{c1}/{c2}/{c3}/{c4}/{e1}/{e2}/{e3}/{e4}/{f1}/{f2}/{f3}/{f4}") String getX2(@Request(serializer=XSerializer.class) K3a rb);
+ @RemoteOp(path="/{a1}/{a2}/{a3}/{a4}/{b1}/{b2}/{b3}/{c1}/{c2}/{c3}/{c4}/{e1}/{e2}/{e3}/{e4}") String getX1(@Request K3a rb);
+ @RemoteOp(path="/{a1}/{a2}/{a3}/{a4}/{b1}/{b2}/{b3}/{c1}/{c2}/{c3}/{c4}/{e1}/{e2}/{e3}/{e4}") String getX2(@Request(serializer=XSerializer.class) K3a rb);
}
public static class K3a {
@@ -704,11 +704,7 @@ public class Remote_PathAnnotation_Test {
}
@Path(aev=true)
public NameValuePair[] getE() {
- return parts("e1","v1","e2",123,"e3",null,"e4","").getAll().toArray(new Part[0]);
- }
- @Path(aev=true)
- public BasicPart[] getF() {
- return parts("f1","v1","f2",123,"f3",null,"f4","").getAll().toArray(new BasicPart[0]);
+ return parts("e1","v1","e2",123,"e3",null,"e4","").getAll();
}
}
@@ -716,9 +712,9 @@ public class Remote_PathAnnotation_Test {
public void k03_requestBean_nameValuePairs() throws Exception {
K3 x1 = remote(K.class,K3.class);
K3 x2 = client(K.class).partSerializer(UonSerializer.class).build().getRemote(K3.class);
- assertEquals("v1/123/null//true/123/null/v1/123/null//v1/123/null//v1/123/null/",x1.getX1(new K3a()));
- assertEquals("v1/123/null//true/123/null/v1/123/null//v1/123/null//v1/123/null/",x2.getX1(new K3a()));
- assertEquals("v1/123/null//true/123/null/v1/123/null//v1/123/null//v1/123/null/",x2.getX2(new K3a()));
+ assertEquals("v1/123/null//true/123/null/v1/123/null//v1/123/null/",x1.getX1(new K3a()));
+ assertEquals("v1/123/null//true/123/null/v1/123/null//v1/123/null/",x2.getX1(new K3a()));
+ assertEquals("v1/123/null//true/123/null/v1/123/null//v1/123/null/",x2.getX2(new K3a()));
}
//-----------------------------------------------------------------------------------------------------------------
diff --git a/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_QueryAnnotation_Test.java b/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_QueryAnnotation_Test.java
index fed8c1b..f7fd2e5 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_QueryAnnotation_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_QueryAnnotation_Test.java
@@ -87,7 +87,6 @@ public class Remote_QueryAnnotation_Test {
@RemoteOp(path="a") String getX20(@Query PartList b);
@RemoteOp(path="a") String getX21(@Query NameValuePair b);
@RemoteOp(path="a") String getX22(@Query NameValuePair[] b);
- @RemoteOp(path="a") String getX23(@Query BasicPart[] b);
@RemoteOp(path="a") String getX24(@Query String b);
@RemoteOp(path="a") String getX25(@Query List<NameValuePair> b);
}
@@ -116,8 +115,7 @@ public class Remote_QueryAnnotation_Test {
assertEquals("{foo:'bar'}",x.getX19(parts("foo","bar")));
assertEquals("{foo:'bar'}",x.getX20(parts("foo","bar")));
assertEquals("{foo:'bar'}",x.getX21(part("foo","bar")));
- assertEquals("{foo:'bar'}",x.getX22(parts("foo","bar").getAll().toArray(new Part[0])));
- assertEquals("{foo:'bar'}",x.getX23(parts("foo","bar").getAll().toArray(new BasicPart[0])));
+ assertEquals("{foo:'bar'}",x.getX22(parts("foo","bar").getAll()));
assertEquals("{foo:'bar'}",x.getX24("foo=bar"));
assertEquals("{}",x.getX24(null));
assertEquals("{foo:'bar'}",x.getX25(AList.of(part("foo","bar"))));
@@ -815,11 +813,7 @@ public class Remote_QueryAnnotation_Test {
}
@Query(aev=true)
public NameValuePair[] getE() {
- return parts("e1","v1","e2",123,"e3",null,"e4","").getAll().toArray(new Part[0]);
- }
- @Query(aev=true)
- public BasicPart[] getF() {
- return parts("f1","v1","f2",123,"f3",null,"f4","").getAll().toArray(new BasicPart[0]);
+ return parts("e1","v1","e2",123,"e3",null,"e4","").getAll();
}
}
@@ -827,9 +821,8 @@ public class Remote_QueryAnnotation_Test {
public void k03_requestBean_nameValuePairs() throws Exception {
K3 x1 = remote(K.class,K3.class);
K3 x2 = client(K.class).partSerializer(UonSerializer.class).build().getRemote(K3.class);
- assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:'',e1:'v1',e2:'123',e4:'',f1:'v1',f2:'123',f4:''}",x1.getX1(new K3a()));
- assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:'',e1:'v1',e2:'123',e4:'',f1:'v1',f2:'123',f4:''}",x2.getX1(new K3a()));
- assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:'',e1:'v1',e2:'123',e4:'',f1:'v1',f2:'123',f4:''}",x2.getX2(new K3a()));
+ assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:'',e1:'v1',e2:'123',e4:''}",x1.getX1(new K3a()));
+ assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:'',e1:'v1',e2:'123',e4:''}",x2.getX1(new K3a()));
}
//-----------------------------------------------------------------------------------------------------------------
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_FormData_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_FormData_Test.java
index 1f81a33..0884200 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_FormData_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_FormData_Test.java
@@ -95,7 +95,6 @@ public class RestClient_FormData_Test {
client().formDatas(part("foo","bar"),null).build().post("/formData").run().assertBody().is("foo=bar");
client().build().post("/formData").formDatas(part("foo","bar"),null).run().assertBody().is("foo=bar");
client().formDatas(part("foo",null)).build().post("/formData").run().assertBody().is("");
- client().formDatas(part(null,"foo")).build().post("/formData").run().assertBody().is("null=foo");
client().formDatas(part(null,null)).build().post("/formData").run().assertBody().is("");
client().build().post("/formData").formDatas(part("foo",null)).run().assertBody().is("");
diff --git a/juneau-utest/src/test/java/org/apache/juneau/utils/ArrayBuilder_Test.java b/juneau-utest/src/test/java/org/apache/juneau/utils/ArrayBuilder_Test.java
index 6ca5880..988f41b 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/utils/ArrayBuilder_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/utils/ArrayBuilder_Test.java
@@ -23,7 +23,7 @@ public class ArrayBuilder_Test {
@Test
public void a01_basic() {
- ArrayBuilder<String> x = ArrayBuilder.create(String.class, 2);
+ ArrayBuilder<String> x = ArrayBuilder.create(String.class, 2, true);
assertObject(x.toArray()).asJson().is("[]");
x.add(null);
assertObject(x.toArray()).asJson().is("[]");