From d8f7347000408363c5e91a778d73eb164d71925d Mon Sep 17 00:00:00 2001
From: Juergen Hoeller
Date: Tue, 30 Aug 2016 23:56:58 +0200
Subject: [PATCH] Consistent comma splitting without regex overhead
Issue: SPR-14635
(cherry picked from commit 03609c1)
---
.../springframework/util/MimeTypeUtils.java | 2 +-
.../simp/stomp/StompHeaderAccessor.java | 6 +-
.../messaging/simp/stomp/StompHeaders.java | 25 ++---
.../htmlunit/HtmlUnitRequestBuilder.java | 20 ++--
.../org/springframework/http/HttpHeaders.java | 17 +--
.../org/springframework/http/HttpRange.java | 2 +-
.../org/springframework/http/MediaType.java | 2 +-
.../web/util/UriComponentsBuilder.java | 15 +--
.../web/socket/WebSocketExtension.java | 105 +++++++++---------
.../AbstractTyrusRequestUpgradeStrategy.java | 2 +-
10 files changed, 98 insertions(+), 98 deletions(-)
diff --git a/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java b/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java
index a0ad5cc0953..7ca497a5f59 100644
--- a/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java
+++ b/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java
@@ -277,7 +277,7 @@ public abstract class MimeTypeUtils {
if (!StringUtils.hasLength(mimeTypes)) {
return Collections.emptyList();
}
- String[] tokens = mimeTypes.split(",\\s*");
+ String[] tokens = StringUtils.tokenizeToStringArray(mimeTypes, ",");
List result = new ArrayList(tokens.length);
for (String token : tokens) {
result.add(parseMimeType(token));
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompHeaderAccessor.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompHeaderAccessor.java
index 8e38664934d..dc467a9f7b6 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompHeaderAccessor.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompHeaderAccessor.java
@@ -228,10 +228,10 @@ public class StompHeaderAccessor extends SimpMessageHeaderAccessor {
public long[] getHeartbeat() {
String rawValue = getFirstNativeHeader(STOMP_HEARTBEAT_HEADER);
- if (!StringUtils.hasText(rawValue)) {
+ String[] rawValues = StringUtils.split(rawValue, ",");
+ if (rawValues == null) {
return Arrays.copyOf(DEFAULT_HEARTBEAT, 2);
}
- String[] rawValues = StringUtils.commaDelimitedListToStringArray(rawValue);
return new long[] {Long.valueOf(rawValues[0]), Long.valueOf(rawValues[1])};
}
@@ -297,7 +297,7 @@ public class StompHeaderAccessor extends SimpMessageHeaderAccessor {
}
public void setHeartbeat(long cx, long cy) {
- setNativeHeader(STOMP_HEARTBEAT_HEADER, StringUtils.arrayToCommaDelimitedString(new Object[]{cx, cy}));
+ setNativeHeader(STOMP_HEARTBEAT_HEADER, cx + "," + cy);
}
public void setAck(String ack) {
diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompHeaders.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompHeaders.java
index c6abad4581c..4c6e252142d 100644
--- a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompHeaders.java
+++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompHeaders.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -223,9 +223,14 @@ public class StompHeaders implements MultiValueMap, Serializable
* Applies to the CONNECT and CONNECTED frames.
*/
public void setHeartbeat(long[] heartbeat) {
- Assert.notNull(heartbeat);
+ if (heartbeat == null || heartbeat.length != 2) {
+ throw new IllegalArgumentException("Heart-beat array must be of length 2, not " +
+ (heartbeat != null ? heartbeat.length : "null"));
+ }
String value = heartbeat[0] + "," + heartbeat[1];
- Assert.isTrue(heartbeat[0] >= 0 && heartbeat[1] >= 0, "Heart-beat values cannot be negative: " + value);
+ if (heartbeat[0] < 0 || heartbeat[1] < 0) {
+ throw new IllegalArgumentException("Heart-beat values cannot be negative: " + value);
+ }
set(HEARTBEAT, value);
}
@@ -234,10 +239,10 @@ public class StompHeaders implements MultiValueMap, Serializable
*/
public long[] getHeartbeat() {
String rawValue = getFirst(HEARTBEAT);
- if (!StringUtils.hasText(rawValue)) {
+ String[] rawValues = StringUtils.split(rawValue, ",");
+ if (rawValues == null) {
return null;
}
- String[] rawValues = StringUtils.commaDelimitedListToStringArray(rawValue);
return new long[] {Long.valueOf(rawValues[0]), Long.valueOf(rawValues[1])};
}
@@ -497,14 +502,8 @@ public class StompHeaders implements MultiValueMap, Serializable
@Override
public boolean equals(Object other) {
- if (this == other) {
- return true;
- }
- if (!(other instanceof StompHeaders)) {
- return false;
- }
- StompHeaders otherHeaders = (StompHeaders) other;
- return this.headers.equals(otherHeaders.headers);
+ return (this == other || (other instanceof StompHeaders &&
+ this.headers.equals(((StompHeaders) other).headers)));
}
@Override
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilder.java
index 142fceb1423..7bab3e442f6 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilder.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilder.java
@@ -50,6 +50,7 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
@@ -198,7 +199,8 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable {
* {@link HttpServletRequest#getContextPath()} which states it can be
* an empty string, or it must start with a "/" and not end with a "/".
* @param contextPath a valid contextPath
- * @throws IllegalArgumentException if the contextPath is not a valid {@link HttpServletRequest#getContextPath()}
+ * @throws IllegalArgumentException if the contextPath is not a valid
+ * {@link HttpServletRequest#getContextPath()}
*/
public void setContextPath(String contextPath) {
MockMvcWebConnection.validateContextPath(contextPath);
@@ -211,9 +213,9 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable {
private void authType(MockHttpServletRequest request) {
String authorization = header("Authorization");
- if (authorization != null) {
- String[] authzParts = authorization.split(": ");
- request.setAuthType(authzParts[0]);
+ String[] authSplit = StringUtils.split(authorization, ": ");
+ if (authSplit != null) {
+ request.setAuthType(authSplit[0]);
}
}
@@ -263,8 +265,8 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable {
while (tokens.hasMoreTokens()) {
String cookieName = tokens.nextToken().trim();
if (!tokens.hasMoreTokens()) {
- throw new IllegalArgumentException("Expected value for cookie name '" + cookieName
- + "'. Full cookie was " + cookieHeaderValue);
+ throw new IllegalArgumentException("Expected value for cookie name '" + cookieName +
+ "'. Full cookie was " + cookieHeaderValue);
}
String cookieValue = tokens.nextToken().trim();
processCookie(request, cookies, new Cookie(cookieName, cookieValue));
@@ -352,9 +354,9 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable {
request.addPreferredLocale(Locale.getDefault());
}
else {
- String[] locales = locale.split(", ");
- for (int i = locales.length - 1; i >= 0; i--) {
- request.addPreferredLocale(parseLocale(locales[i]));
+ String[] tokens = StringUtils.tokenizeToStringArray(locale, ",");
+ for (int i = tokens.length - 1; i >= 0; i--) {
+ request.addPreferredLocale(parseLocale(tokens[i]));
}
}
}
diff --git a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java
index 9145aecfcd3..a13b9bb6965 100644
--- a/spring-web/src/main/java/org/springframework/http/HttpHeaders.java
+++ b/spring-web/src/main/java/org/springframework/http/HttpHeaders.java
@@ -474,7 +474,7 @@ public class HttpHeaders implements MultiValueMap, Serializable
List result = new ArrayList();
String value = getFirst(ACCESS_CONTROL_ALLOW_METHODS);
if (value != null) {
- String[] tokens = StringUtils.tokenizeToStringArray(value, ",", true, true);
+ String[] tokens = StringUtils.tokenizeToStringArray(value, ",");
for (String token : tokens) {
HttpMethod resolved = HttpMethod.resolve(token);
if (resolved != null) {
@@ -578,10 +578,10 @@ public class HttpHeaders implements MultiValueMap, Serializable
* as specified by the {@code Accept-Charset} header.
*/
public List getAcceptCharset() {
- List result = new ArrayList();
String value = getFirst(ACCEPT_CHARSET);
if (value != null) {
- String[] tokens = value.split(",\\s*");
+ String[] tokens = StringUtils.tokenizeToStringArray(value, ",");
+ List result = new ArrayList(tokens.length);
for (String token : tokens) {
int paramIdx = token.indexOf(';');
String charsetName;
@@ -595,8 +595,11 @@ public class HttpHeaders implements MultiValueMap, Serializable
result.add(Charset.forName(charsetName));
}
}
+ return result;
+ }
+ else {
+ return Collections.emptyList();
}
- return result;
}
/**
@@ -615,8 +618,8 @@ public class HttpHeaders implements MultiValueMap, Serializable
public Set getAllow() {
String value = getFirst(ALLOW);
if (!StringUtils.isEmpty(value)) {
- List result = new LinkedList();
- String[] tokens = value.split(",\\s*");
+ String[] tokens = StringUtils.tokenizeToStringArray(value, ",");
+ List result = new ArrayList(tokens.length);
for (String token : tokens) {
HttpMethod resolved = HttpMethod.resolve(token);
if (resolved != null) {
@@ -691,7 +694,7 @@ public class HttpHeaders implements MultiValueMap, Serializable
StringBuilder builder = new StringBuilder("form-data; name=\"");
builder.append(name).append('\"');
if (filename != null) {
- if(charset == null || Charset.forName("US-ASCII").equals(charset)) {
+ if (charset == null || charset.name().equals("US-ASCII")) {
builder.append("; filename=\"");
builder.append(filename).append('\"');
}
diff --git a/spring-web/src/main/java/org/springframework/http/HttpRange.java b/spring-web/src/main/java/org/springframework/http/HttpRange.java
index a6ba6dea651..e3dbb8ca16a 100644
--- a/spring-web/src/main/java/org/springframework/http/HttpRange.java
+++ b/spring-web/src/main/java/org/springframework/http/HttpRange.java
@@ -132,7 +132,7 @@ public abstract class HttpRange {
}
ranges = ranges.substring(BYTE_RANGE_PREFIX.length());
- String[] tokens = ranges.split(",\\s*");
+ String[] tokens = StringUtils.tokenizeToStringArray(ranges, ",");
List result = new ArrayList(tokens.length);
for (String token : tokens) {
result.add(parseRange(token));
diff --git a/spring-web/src/main/java/org/springframework/http/MediaType.java b/spring-web/src/main/java/org/springframework/http/MediaType.java
index 924c1e4cb1d..3fad7788bb4 100644
--- a/spring-web/src/main/java/org/springframework/http/MediaType.java
+++ b/spring-web/src/main/java/org/springframework/http/MediaType.java
@@ -438,7 +438,7 @@ public class MediaType extends MimeType implements Serializable {
if (!StringUtils.hasLength(mediaTypes)) {
return Collections.emptyList();
}
- String[] tokens = mediaTypes.split(",\\s*");
+ String[] tokens = StringUtils.tokenizeToStringArray(mediaTypes, ",");
List result = new ArrayList(tokens.length);
for (String token : tokens) {
result.add(parseMediaType(token));
diff --git a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java
index 44eb0a07e49..b86889a3eff 100644
--- a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java
+++ b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java
@@ -673,7 +673,7 @@ public class UriComponentsBuilder implements Cloneable {
UriComponentsBuilder adaptFromForwardedHeaders(HttpHeaders headers) {
String forwardedHeader = headers.getFirst("Forwarded");
if (StringUtils.hasText(forwardedHeader)) {
- String forwardedToUse = StringUtils.commaDelimitedListToStringArray(forwardedHeader)[0];
+ String forwardedToUse = StringUtils.tokenizeToStringArray(forwardedHeader, ",")[0];
Matcher matcher = FORWARDED_HOST_PATTERN.matcher(forwardedToUse);
if (matcher.find()) {
host(matcher.group(1).trim());
@@ -686,10 +686,9 @@ public class UriComponentsBuilder implements Cloneable {
else {
String hostHeader = headers.getFirst("X-Forwarded-Host");
if (StringUtils.hasText(hostHeader)) {
- String[] hosts = StringUtils.commaDelimitedListToStringArray(hostHeader);
- String hostToUse = hosts[0];
- if (hostToUse.contains(":")) {
- String[] hostAndPort = StringUtils.split(hostToUse, ":");
+ String hostToUse = StringUtils.tokenizeToStringArray(hostHeader, ",")[0];
+ String[] hostAndPort = StringUtils.split(hostToUse, ":");
+ if (hostAndPort != null) {
host(hostAndPort[0]);
port(Integer.parseInt(hostAndPort[1]));
}
@@ -701,14 +700,12 @@ public class UriComponentsBuilder implements Cloneable {
String portHeader = headers.getFirst("X-Forwarded-Port");
if (StringUtils.hasText(portHeader)) {
- String[] ports = StringUtils.commaDelimitedListToStringArray(portHeader);
- port(Integer.parseInt(ports[0]));
+ port(Integer.parseInt(StringUtils.tokenizeToStringArray(portHeader, ",")[0]));
}
String protocolHeader = headers.getFirst("X-Forwarded-Proto");
if (StringUtils.hasText(protocolHeader)) {
- String[] protocols = StringUtils.commaDelimitedListToStringArray(protocolHeader);
- scheme(protocols[0]);
+ scheme(StringUtils.tokenizeToStringArray(protocolHeader, ",")[0]);
}
}
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketExtension.java b/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketExtension.java
index 4ec0fe803b3..817371c1638 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketExtension.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketExtension.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -44,6 +44,7 @@ import org.springframework.util.StringUtils;
* e.g. extensions "foo, bar" will be executed as "bar(foo(message))".
*
* @author Brian Clozel
+ * @author Juergen Hoeller
* @since 4.0
* @see WebSocket Protocol Extensions, RFC 6455 - Section 9
*/
@@ -68,54 +69,90 @@ public class WebSocketExtension {
* @param parameters the parameters
*/
public WebSocketExtension(String name, Map parameters) {
- Assert.hasLength(name, "extension name must not be empty");
+ Assert.hasLength(name, "Extension name must not be empty");
this.name = name;
if (!CollectionUtils.isEmpty(parameters)) {
- Map m = new LinkedCaseInsensitiveMap(parameters.size(), Locale.ENGLISH);
- m.putAll(parameters);
- this.parameters = Collections.unmodifiableMap(m);
+ Map map = new LinkedCaseInsensitiveMap(parameters.size(), Locale.ENGLISH);
+ map.putAll(parameters);
+ this.parameters = Collections.unmodifiableMap(map);
}
else {
this.parameters = Collections.emptyMap();
}
}
+
/**
- * @return the name of the extension
+ * Return the name of the extension (never {@code null) or empty}.
*/
public String getName() {
return this.name;
}
/**
- * @return the parameters of the extension, never {@code null}
+ * Return the parameters of the extension (never {@code null}).
*/
public Map getParameters() {
return this.parameters;
}
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (other == null || getClass() != other.getClass()) {
+ return false;
+ }
+ WebSocketExtension otherExt = (WebSocketExtension) other;
+ return (this.name.equals(otherExt.name) && this.parameters.equals(otherExt.parameters));
+ }
+
+ @Override
+ public int hashCode() {
+ return this.name.hashCode() * 31 + this.parameters.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder str = new StringBuilder();
+ str.append(this.name);
+ for (Map.Entry entry : this.parameters.entrySet()) {
+ str.append(';');
+ str.append(entry.getKey());
+ str.append('=');
+ str.append(entry.getValue());
+ }
+ return str.toString();
+ }
+
+
/**
* Parse the given, comma-separated string into a list of {@code WebSocketExtension} objects.
- * This method can be used to parse a "Sec-WebSocket-Extension" extensions.
+ *
This method can be used to parse a "Sec-WebSocket-Extension" header.
* @param extensions the string to parse
* @return the list of extensions
* @throws IllegalArgumentException if the string cannot be parsed
*/
public static List parseExtensions(String extensions) {
- if (extensions == null || !StringUtils.hasText(extensions)) {
- return Collections.emptyList();
- }
- else {
- List result = new ArrayList();
- for (String token : extensions.split(",")) {
+ if (StringUtils.hasText(extensions)) {
+ String[] tokens = StringUtils.tokenizeToStringArray(extensions, ",");
+ List result = new ArrayList(tokens.length);
+ for (String token : StringUtils.tokenizeToStringArray(extensions, ",")) {
result.add(parseExtension(token));
}
return result;
}
+ else {
+ return Collections.emptyList();
+ }
}
private static WebSocketExtension parseExtension(String extension) {
- Assert.doesNotContain(extension, ",", "Expected a single extension value: " + extension);
+ if (extension.contains(",")) {
+ throw new IllegalArgumentException("Expected single extension value: [" + extension + "]");
+ }
String[] parts = StringUtils.tokenizeToStringArray(extension, ";");
String name = parts[0].trim();
@@ -136,42 +173,4 @@ public class WebSocketExtension {
return new WebSocketExtension(name, parameters);
}
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if ((o == null) || (getClass() != o.getClass())) {
- return false;
- }
- WebSocketExtension that = (WebSocketExtension) o;
- if (!name.equals(that.name)) {
- return false;
- }
- if (!parameters.equals(that.parameters)) {
- return false;
- }
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = name.hashCode();
- result = 31 * result + parameters.hashCode();
- return result;
- }
-
- @Override
- public String toString() {
- StringBuilder str = new StringBuilder();
- str.append(this.name);
- for (String param : parameters.keySet()) {
- str.append(';');
- str.append(param);
- str.append('=');
- str.append(this.parameters.get(param));
- }
- return str.toString();
- }
-
}
diff --git a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractTyrusRequestUpgradeStrategy.java b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractTyrusRequestUpgradeStrategy.java
index 35886efc724..9d62b4c573d 100644
--- a/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractTyrusRequestUpgradeStrategy.java
+++ b/spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractTyrusRequestUpgradeStrategy.java
@@ -76,7 +76,7 @@ public abstract class AbstractTyrusRequestUpgradeStrategy extends AbstractStanda
@Override
public String[] getSupportedVersions() {
- return StringUtils.commaDelimitedListToStringArray(Version.getSupportedWireProtocolVersions());
+ return StringUtils.tokenizeToStringArray(Version.getSupportedWireProtocolVersions(), ",");
}
protected List getInstalledExtensions(WebSocketContainer container) {