Browse Source

Consistent comma splitting without regex overhead

Issue: SPR-14635
pull/1143/merge
Juergen Hoeller 10 years ago
parent
commit
03609c1518
  1. 2
      spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java
  2. 6
      spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompHeaderAccessor.java
  3. 23
      spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompHeaders.java
  4. 10
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilder.java
  5. 15
      spring-web/src/main/java/org/springframework/http/HttpHeaders.java
  6. 2
      spring-web/src/main/java/org/springframework/http/HttpRange.java
  7. 2
      spring-web/src/main/java/org/springframework/http/MediaType.java
  8. 15
      spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java
  9. 94
      spring-websocket/src/main/java/org/springframework/web/socket/WebSocketExtension.java
  10. 2
      spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractTyrusRequestUpgradeStrategy.java

2
spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java

@ -274,7 +274,7 @@ public abstract class MimeTypeUtils {
if (!StringUtils.hasLength(mimeTypes)) { if (!StringUtils.hasLength(mimeTypes)) {
return Collections.emptyList(); return Collections.emptyList();
} }
String[] tokens = mimeTypes.split(",\\s*"); String[] tokens = StringUtils.tokenizeToStringArray(mimeTypes, ",");
List<MimeType> result = new ArrayList<>(tokens.length); List<MimeType> result = new ArrayList<>(tokens.length);
for (String token : tokens) { for (String token : tokens) {
result.add(parseMimeType(token)); result.add(parseMimeType(token));

6
spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompHeaderAccessor.java

@ -229,10 +229,10 @@ public class StompHeaderAccessor extends SimpMessageHeaderAccessor {
public long[] getHeartbeat() { public long[] getHeartbeat() {
String rawValue = getFirstNativeHeader(STOMP_HEARTBEAT_HEADER); String rawValue = getFirstNativeHeader(STOMP_HEARTBEAT_HEADER);
if (!StringUtils.hasText(rawValue)) { String[] rawValues = StringUtils.split(rawValue, ",");
if (rawValues == null) {
return Arrays.copyOf(DEFAULT_HEARTBEAT, 2); return Arrays.copyOf(DEFAULT_HEARTBEAT, 2);
} }
String[] rawValues = StringUtils.commaDelimitedListToStringArray(rawValue);
return new long[] {Long.valueOf(rawValues[0]), Long.valueOf(rawValues[1])}; return new long[] {Long.valueOf(rawValues[0]), Long.valueOf(rawValues[1])};
} }
@ -298,7 +298,7 @@ public class StompHeaderAccessor extends SimpMessageHeaderAccessor {
} }
public void setHeartbeat(long cx, long cy) { 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) { public void setAck(String ack) {

23
spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompHeaders.java

@ -223,9 +223,14 @@ public class StompHeaders implements MultiValueMap<String, String>, Serializable
* Applies to the CONNECT and CONNECTED frames. * Applies to the CONNECT and CONNECTED frames.
*/ */
public void setHeartbeat(long[] heartbeat) { 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]; 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); set(HEARTBEAT, value);
} }
@ -234,10 +239,10 @@ public class StompHeaders implements MultiValueMap<String, String>, Serializable
*/ */
public long[] getHeartbeat() { public long[] getHeartbeat() {
String rawValue = getFirst(HEARTBEAT); String rawValue = getFirst(HEARTBEAT);
if (!StringUtils.hasText(rawValue)) { String[] rawValues = StringUtils.split(rawValue, ",");
if (rawValues == null) {
return null; return null;
} }
String[] rawValues = StringUtils.commaDelimitedListToStringArray(rawValue);
return new long[] {Long.valueOf(rawValues[0]), Long.valueOf(rawValues[1])}; return new long[] {Long.valueOf(rawValues[0]), Long.valueOf(rawValues[1])};
} }
@ -497,14 +502,8 @@ public class StompHeaders implements MultiValueMap<String, String>, Serializable
@Override @Override
public boolean equals(Object other) { public boolean equals(Object other) {
if (this == other) { return (this == other || (other instanceof StompHeaders &&
return true; this.headers.equals(((StompHeaders) other).headers)));
}
if (!(other instanceof StompHeaders)) {
return false;
}
StompHeaders otherHeaders = (StompHeaders) other;
return this.headers.equals(otherHeaders.headers);
} }
@Override @Override

10
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.test.web.servlet.request.RequestPostProcessor;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
@ -213,8 +214,7 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable {
private void authType(MockHttpServletRequest request) { private void authType(MockHttpServletRequest request) {
String authorization = header("Authorization"); String authorization = header("Authorization");
if (authorization != null) { if (authorization != null) {
String[] authzParts = authorization.split(": "); request.setAuthType(StringUtils.split(authorization, ": ")[0]);
request.setAuthType(authzParts[0]);
} }
} }
@ -349,9 +349,9 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable {
request.addPreferredLocale(Locale.getDefault()); request.addPreferredLocale(Locale.getDefault());
} }
else { else {
String[] locales = locale.split(", "); String[] tokens = StringUtils.tokenizeToStringArray(locale, ",");
for (int i = locales.length - 1; i >= 0; i--) { for (int i = tokens.length - 1; i >= 0; i--) {
request.addPreferredLocale(parseLocale(locales[i])); request.addPreferredLocale(parseLocale(tokens[i]));
} }
} }
} }

15
spring-web/src/main/java/org/springframework/http/HttpHeaders.java

@ -476,7 +476,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
List<HttpMethod> result = new ArrayList<>(); List<HttpMethod> result = new ArrayList<>();
String value = getFirst(ACCESS_CONTROL_ALLOW_METHODS); String value = getFirst(ACCESS_CONTROL_ALLOW_METHODS);
if (value != null) { if (value != null) {
String[] tokens = StringUtils.tokenizeToStringArray(value, ",", true, true); String[] tokens = StringUtils.tokenizeToStringArray(value, ",");
for (String token : tokens) { for (String token : tokens) {
HttpMethod resolved = HttpMethod.resolve(token); HttpMethod resolved = HttpMethod.resolve(token);
if (resolved != null) { if (resolved != null) {
@ -580,10 +580,10 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
* as specified by the {@code Accept-Charset} header. * as specified by the {@code Accept-Charset} header.
*/ */
public List<Charset> getAcceptCharset() { public List<Charset> getAcceptCharset() {
List<Charset> result = new ArrayList<>();
String value = getFirst(ACCEPT_CHARSET); String value = getFirst(ACCEPT_CHARSET);
if (value != null) { if (value != null) {
String[] tokens = value.split(",\\s*"); String[] tokens = StringUtils.tokenizeToStringArray(value, ",");
List<Charset> result = new ArrayList<>(tokens.length);
for (String token : tokens) { for (String token : tokens) {
int paramIdx = token.indexOf(';'); int paramIdx = token.indexOf(';');
String charsetName; String charsetName;
@ -597,8 +597,11 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
result.add(Charset.forName(charsetName)); result.add(Charset.forName(charsetName));
} }
} }
return result;
}
else {
return Collections.emptyList();
} }
return result;
} }
/** /**
@ -617,8 +620,8 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
public Set<HttpMethod> getAllow() { public Set<HttpMethod> getAllow() {
String value = getFirst(ALLOW); String value = getFirst(ALLOW);
if (!StringUtils.isEmpty(value)) { if (!StringUtils.isEmpty(value)) {
List<HttpMethod> result = new LinkedList<>(); String[] tokens = StringUtils.tokenizeToStringArray(value, ",");
String[] tokens = value.split(",\\s*"); List<HttpMethod> result = new ArrayList<>(tokens.length);
for (String token : tokens) { for (String token : tokens) {
HttpMethod resolved = HttpMethod.resolve(token); HttpMethod resolved = HttpMethod.resolve(token);
if (resolved != null) { if (resolved != null) {

2
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()); ranges = ranges.substring(BYTE_RANGE_PREFIX.length());
String[] tokens = ranges.split(",\\s*"); String[] tokens = StringUtils.tokenizeToStringArray(ranges, ",");
List<HttpRange> result = new ArrayList<>(tokens.length); List<HttpRange> result = new ArrayList<>(tokens.length);
for (String token : tokens) { for (String token : tokens) {
result.add(parseRange(token)); result.add(parseRange(token));

2
spring-web/src/main/java/org/springframework/http/MediaType.java

@ -439,7 +439,7 @@ public class MediaType extends MimeType implements Serializable {
if (!StringUtils.hasLength(mediaTypes)) { if (!StringUtils.hasLength(mediaTypes)) {
return Collections.emptyList(); return Collections.emptyList();
} }
String[] tokens = mediaTypes.split(",\\s*"); String[] tokens = StringUtils.tokenizeToStringArray(mediaTypes, ",");
List<MediaType> result = new ArrayList<>(tokens.length); List<MediaType> result = new ArrayList<>(tokens.length);
for (String token : tokens) { for (String token : tokens) {
result.add(parseMediaType(token)); result.add(parseMediaType(token));

15
spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java

@ -673,7 +673,7 @@ public class UriComponentsBuilder implements Cloneable {
UriComponentsBuilder adaptFromForwardedHeaders(HttpHeaders headers) { UriComponentsBuilder adaptFromForwardedHeaders(HttpHeaders headers) {
String forwardedHeader = headers.getFirst("Forwarded"); String forwardedHeader = headers.getFirst("Forwarded");
if (StringUtils.hasText(forwardedHeader)) { if (StringUtils.hasText(forwardedHeader)) {
String forwardedToUse = StringUtils.commaDelimitedListToStringArray(forwardedHeader)[0]; String forwardedToUse = StringUtils.tokenizeToStringArray(forwardedHeader, ",")[0];
Matcher matcher = FORWARDED_HOST_PATTERN.matcher(forwardedToUse); Matcher matcher = FORWARDED_HOST_PATTERN.matcher(forwardedToUse);
if (matcher.find()) { if (matcher.find()) {
host(matcher.group(1).trim()); host(matcher.group(1).trim());
@ -686,10 +686,9 @@ public class UriComponentsBuilder implements Cloneable {
else { else {
String hostHeader = headers.getFirst("X-Forwarded-Host"); String hostHeader = headers.getFirst("X-Forwarded-Host");
if (StringUtils.hasText(hostHeader)) { if (StringUtils.hasText(hostHeader)) {
String[] hosts = StringUtils.commaDelimitedListToStringArray(hostHeader); String hostToUse = StringUtils.tokenizeToStringArray(hostHeader, ",")[0];
String hostToUse = hosts[0]; String[] hostAndPort = StringUtils.split(hostToUse, ":");
if (hostToUse.contains(":")) { if (hostAndPort != null) {
String[] hostAndPort = StringUtils.split(hostToUse, ":");
host(hostAndPort[0]); host(hostAndPort[0]);
port(Integer.parseInt(hostAndPort[1])); port(Integer.parseInt(hostAndPort[1]));
} }
@ -701,14 +700,12 @@ public class UriComponentsBuilder implements Cloneable {
String portHeader = headers.getFirst("X-Forwarded-Port"); String portHeader = headers.getFirst("X-Forwarded-Port");
if (StringUtils.hasText(portHeader)) { if (StringUtils.hasText(portHeader)) {
String[] ports = StringUtils.commaDelimitedListToStringArray(portHeader); port(Integer.parseInt(StringUtils.tokenizeToStringArray(portHeader, ",")[0]));
port(Integer.parseInt(ports[0]));
} }
String protocolHeader = headers.getFirst("X-Forwarded-Proto"); String protocolHeader = headers.getFirst("X-Forwarded-Proto");
if (StringUtils.hasText(protocolHeader)) { if (StringUtils.hasText(protocolHeader)) {
String[] protocols = StringUtils.commaDelimitedListToStringArray(protocolHeader); scheme(StringUtils.tokenizeToStringArray(protocolHeader, ",")[0]);
scheme(protocols[0]);
} }
} }

94
spring-websocket/src/main/java/org/springframework/web/socket/WebSocketExtension.java

@ -44,6 +44,7 @@ import org.springframework.util.StringUtils;
* e.g. extensions "foo, bar" will be executed as "bar(foo(message))".</p> * e.g. extensions "foo, bar" will be executed as "bar(foo(message))".</p>
* *
* @author Brian Clozel * @author Brian Clozel
* @author Juergen Hoeller
* @since 4.0 * @since 4.0
* @see <a href="https://tools.ietf.org/html/rfc6455#section-9">WebSocket Protocol Extensions, RFC 6455 - Section 9</a> * @see <a href="https://tools.ietf.org/html/rfc6455#section-9">WebSocket Protocol Extensions, RFC 6455 - Section 9</a>
*/ */
@ -68,46 +69,79 @@ public class WebSocketExtension {
* @param parameters the parameters * @param parameters the parameters
*/ */
public WebSocketExtension(String name, Map<String, String> parameters) { public WebSocketExtension(String name, Map<String, String> parameters) {
Assert.hasLength(name, "extension name must not be empty"); Assert.hasLength(name, "Extension name must not be empty");
this.name = name; this.name = name;
if (!CollectionUtils.isEmpty(parameters)) { if (!CollectionUtils.isEmpty(parameters)) {
Map<String, String> m = new LinkedCaseInsensitiveMap<>(parameters.size(), Locale.ENGLISH); Map<String, String> map = new LinkedCaseInsensitiveMap<>(parameters.size(), Locale.ENGLISH);
m.putAll(parameters); map.putAll(parameters);
this.parameters = Collections.unmodifiableMap(m); this.parameters = Collections.unmodifiableMap(map);
} }
else { else {
this.parameters = Collections.emptyMap(); this.parameters = Collections.emptyMap();
} }
} }
/** /**
* @return the name of the extension * Return the name of the extension (never {@code null) or empty}.
*/ */
public String getName() { public String getName() {
return this.name; return this.name;
} }
/** /**
* @return the parameters of the extension, never {@code null} * Return the parameters of the extension (never {@code null}).
*/ */
public Map<String, String> getParameters() { public Map<String, String> getParameters() {
return this.parameters; 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 (String param : parameters.keySet()) {
str.append(';');
str.append(param);
str.append('=');
str.append(this.parameters.get(param));
}
return str.toString();
}
/** /**
* Parse the given, comma-separated string into a list of {@code WebSocketExtension} objects. * Parse the given, comma-separated string into a list of {@code WebSocketExtension} objects.
* <p>This method can be used to parse a "Sec-WebSocket-Extension" extensions. * <p>This method can be used to parse a "Sec-WebSocket-Extension" header.
* @param extensions the string to parse * @param extensions the string to parse
* @return the list of extensions * @return the list of extensions
* @throws IllegalArgumentException if the string cannot be parsed * @throws IllegalArgumentException if the string cannot be parsed
*/ */
public static List<WebSocketExtension> parseExtensions(String extensions) { public static List<WebSocketExtension> parseExtensions(String extensions) {
if (extensions == null || !StringUtils.hasText(extensions)) { if (!StringUtils.hasText(extensions)) {
return Collections.emptyList(); return Collections.emptyList();
} }
else { else {
List<WebSocketExtension> result = new ArrayList<>(); List<WebSocketExtension> result = new ArrayList<>();
for (String token : extensions.split(",")) { for (String token : StringUtils.tokenizeToStringArray(extensions, ",")) {
result.add(parseExtension(token)); result.add(parseExtension(token));
} }
return result; return result;
@ -115,7 +149,9 @@ public class WebSocketExtension {
} }
private static WebSocketExtension parseExtension(String extension) { 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[] parts = StringUtils.tokenizeToStringArray(extension, ";");
String name = parts[0].trim(); String name = parts[0].trim();
@ -136,42 +172,4 @@ public class WebSocketExtension {
return new WebSocketExtension(name, parameters); 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();
}
} }

2
spring-websocket/src/main/java/org/springframework/web/socket/server/standard/AbstractTyrusRequestUpgradeStrategy.java

@ -110,7 +110,7 @@ public abstract class AbstractTyrusRequestUpgradeStrategy extends AbstractStanda
@Override @Override
public String[] getSupportedVersions() { public String[] getSupportedVersions() {
return StringUtils.commaDelimitedListToStringArray(Version.getSupportedWireProtocolVersions()); return StringUtils.tokenizeToStringArray(Version.getSupportedWireProtocolVersions(), ",");
} }
protected List<WebSocketExtension> getInstalledExtensions(WebSocketContainer container) { protected List<WebSocketExtension> getInstalledExtensions(WebSocketContainer container) {

Loading…
Cancel
Save