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) {