Skip to content

Commit d27c170

Browse files
committed
Optimize query and URI encoding/validation
Switch to masks instead of arrays, and write directly to StringBuilder when building up a query string.
1 parent f72d701 commit d27c170

3 files changed

Lines changed: 212 additions & 129 deletions

File tree

io/src/main/java/software/amazon/smithy/java/io/uri/QueryStringBuilder.java

Lines changed: 52 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -5,95 +5,101 @@
55

66
package software.amazon.smithy.java.io.uri;
77

8-
import java.util.ArrayList;
98
import java.util.HashSet;
109
import java.util.List;
1110
import java.util.Map;
12-
import java.util.Set;
1311

1412
/**
1513
* Used to build a query string from key value pair parameters.
1614
*/
1715
public final class QueryStringBuilder {
1816

19-
private final List<String> values = new ArrayList<>();
20-
private final Set<String> keysFromHttpQuery = new HashSet<>();
17+
private final StringBuilder builder = new StringBuilder();
18+
private final HashSet<String> httpQueryKeys = new HashSet<>();
19+
private boolean empty = true;
2120

2221
/**
23-
* Clears the contents of the query string builder.
22+
* Clears the contents of the query string builder so it can be reused.
2423
*/
2524
public void clear() {
26-
values.clear();
25+
builder.setLength(0);
26+
empty = true;
27+
httpQueryKeys.clear();
2728
}
2829

2930
/**
3031
* Add a query string parameter and value to the query string.
31-
* <p>
32-
* The given key and value will be percent-encoded. If the value is already percent-encoded, it will be
33-
* double percent-encoded.
32+
*
33+
* <p>The given key and value are percent-encoded. If the value is already
34+
* percent-encoded, it will be double percent-encoded.
3435
*
3536
* @param key Key of the parameter.
36-
* @param value Value of the parameter (or null).
37+
* @param value Value of the parameter (or null, which appends {@code key=}).
3738
*/
3839
public void add(String key, String value) {
39-
values.add(key);
40-
keysFromHttpQuery.add(key);
41-
values.add(value);
40+
append(key, value);
41+
httpQueryKeys.add(key);
4242
}
4343

4444
/**
45-
* Add a query string parameter and value to the query string comes from httpQueryParams trait.
46-
* <p>
47-
* The given key and value will be percent-encoded. If the value is already percent-encoded, it will be
48-
* double percent-encoded. Query string parameters from httpQuery should take precedence if there are
49-
* duplicate keys from @httpQuery.
45+
* Add multiple values for the same key.
5046
*
51-
* @param key Key of the parameter.
52-
* @param value Value of the parameter (or null).
47+
* @param key Key of the parameter.
48+
* @param values Values to add (each is added as a separate {@code key=value} pair).
5349
*/
54-
public void addForQueryParams(String key, String value) {
55-
if (!keysFromHttpQuery.contains(key)) {
56-
values.add(key);
57-
values.add(value);
50+
public void add(String key, List<String> values) {
51+
for (String v : values) {
52+
add(key, v);
5853
}
5954
}
6055

6156
/**
62-
* Add a query string parameter and values to the query string.
63-
* <p>
64-
* The given key and values will be percent-encoded. If the values are already percent-encoded, it will be
65-
* double percent-encoded.
57+
* Add all entries from a map of {@code key -> [value, ...]} to the query string.
6658
*
67-
* @param key Key of the parameter.
68-
* @param values List of values
59+
* @param values Map of keys to lists of values.
6960
*/
70-
public void add(String key, List<String> values) {
71-
for (String value : values) {
72-
add(key, value);
61+
public void add(Map<String, List<String>> values) {
62+
for (var entry : values.entrySet()) {
63+
add(entry.getKey(), entry.getValue());
7364
}
7465
}
7566

7667
/**
77-
* Add a query string parameter and values to the query string.
78-
* <p>
79-
* The given key and values will be percent-encoded. If the values are already percent-encoded, it will be
80-
* double percent-encoded.
68+
* Add a query string parameter and value from a {@code @httpQueryParams} member.
69+
*
70+
* <p>Skips the key if it was already added via {@link #add} so {@code @httpQuery}
71+
* members take precedence over {@code @httpQueryParams} entries with the same key.
8172
*
82-
* @param values List of values
73+
* @param key Key of the parameter.
74+
* @param value Value of the parameter (or null).
8375
*/
84-
public void add(Map<String, List<String>> values) {
85-
for (var entry : values.entrySet()) {
86-
add(entry.getKey(), entry.getValue());
76+
public void addForQueryParams(String key, String value) {
77+
if (!httpQueryKeys.contains(key)) {
78+
append(key, value);
79+
}
80+
}
81+
82+
private void append(String key, String value) {
83+
if (!empty) {
84+
builder.append('&');
85+
} else {
86+
empty = false;
87+
}
88+
89+
URLEncoding.encodeUnreserved(key, builder, false);
90+
builder.append('=');
91+
if (value != null) {
92+
URLEncoding.encodeUnreserved(value, builder, false);
8793
}
8894
}
8995

9096
/**
9197
* Check if the query string is empty.
9298
*
93-
* @return Returns true if empty.
99+
* @return Returns true if no parameters have been added.
94100
*/
95101
public boolean isEmpty() {
96-
return values.isEmpty();
102+
return empty;
97103
}
98104

99105
/**
@@ -103,28 +109,15 @@ public boolean isEmpty() {
103109
*/
104110
@Override
105111
public String toString() {
106-
StringBuilder result = new StringBuilder();
107-
write(result);
108-
return result.toString();
112+
return builder.toString();
109113
}
110114

111115
/**
112-
* Write the query string directly to a string builder.
116+
* Append the query string directly to a string builder.
113117
*
114118
* @param sink Where to write.
115119
*/
116120
public void write(StringBuilder sink) {
117-
for (int i = 0; i < values.size(); i += 2) {
118-
if (i > 0) {
119-
sink.append('&');
120-
}
121-
encode(values.get(i), sink);
122-
sink.append('=');
123-
encode(values.get(i + 1), sink);
124-
}
125-
}
126-
127-
private void encode(String raw, StringBuilder builder) {
128-
URLEncoding.encodeUnreserved(raw, builder, false);
121+
sink.append(builder);
129122
}
130123
}

0 commit comments

Comments
 (0)