diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java index a690180d3e..7268205488 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpHeaders.java @@ -17,7 +17,9 @@ package io.netty.handler.codec.http; import io.netty.buffer.ByteBuf; import io.netty.handler.codec.AsciiString; +import io.netty.handler.codec.DefaultHeaders.NameConverter; import io.netty.handler.codec.DefaultTextHeaders; +import io.netty.handler.codec.DefaultTextHeaders.DefaultTextValueTypeConverter; import io.netty.handler.codec.TextHeaders; import java.util.Calendar; @@ -27,20 +29,228 @@ import java.util.List; import java.util.Map; import java.util.Set; +/** + * Default implementation of {@link HttpHeaders}. + */ public class DefaultHttpHeaders extends HttpHeaders { + private static final int HIGHEST_INVALID_NAME_CHAR_MASK = ~63; + private static final int HIGHEST_INVALID_VALUE_CHAR_MASK = ~15; + + /** + * A look-up table used for checking if a character in a header name is prohibited. + */ + private static final byte[] LOOKUP_TABLE = new byte[~HIGHEST_INVALID_NAME_CHAR_MASK + 1]; + + static { + LOOKUP_TABLE['\t'] = -1; + LOOKUP_TABLE['\n'] = -1; + LOOKUP_TABLE[0x0b] = -1; + LOOKUP_TABLE['\f'] = -1; + LOOKUP_TABLE[' '] = -1; + LOOKUP_TABLE[','] = -1; + LOOKUP_TABLE[':'] = -1; + LOOKUP_TABLE[';'] = -1; + LOOKUP_TABLE['='] = -1; + } + private final TextHeaders headers; + private static final class HttpHeadersValidationConverter extends DefaultTextValueTypeConverter { + private final boolean validate; + + HttpHeadersValidationConverter(boolean validate) { + this.validate = validate; + } + + @Override + public CharSequence convertObject(Object value) { + if (value == null) { + throw new NullPointerException("value"); + } + + CharSequence seq; + if (value instanceof CharSequence) { + seq = (CharSequence) value; + } else if (value instanceof Number) { + seq = value.toString(); + } else if (value instanceof Date) { + seq = HttpHeaderDateFormat.get().format((Date) value); + } else if (value instanceof Calendar) { + seq = HttpHeaderDateFormat.get().format(((Calendar) value).getTime()); + } else { + seq = value.toString(); + } + + if (validate) { + if (value instanceof AsciiString) { + validateValue((AsciiString) seq); + } else { + validateValue(seq); + } + } + + return seq; + } + + private static void validateValue(AsciiString seq) { + int state = 0; + // Start looping through each of the character + final int start = seq.arrayOffset(); + final int end = start + seq.length(); + final byte[] array = seq.array(); + for (int index = start; index < end; index++) { + state = validateValueChar(seq, state, (char) (array[index] & 0xFF)); + } + + if (state != 0) { + throw new IllegalArgumentException("a header value must not end with '\\r' or '\\n':" + seq); + } + } + + private static void validateValue(CharSequence seq) { + int state = 0; + // Start looping through each of the character + for (int index = 0; index < seq.length(); index++) { + state = validateValueChar(seq, state, seq.charAt(index)); + } + + if (state != 0) { + throw new IllegalArgumentException("a header value must not end with '\\r' or '\\n':" + seq); + } + } + + private static int validateValueChar(CharSequence seq, int state, char character) { + /* + * State: + * 0: Previous character was neither CR nor LF + * 1: The previous character was CR + * 2: The previous character was LF + */ + if ((character & HIGHEST_INVALID_VALUE_CHAR_MASK) == 0) { + // Check the absolutely prohibited characters. + switch (character) { + case 0x0b: // Vertical tab + throw new IllegalArgumentException("a header value contains a prohibited character '\\v': " + seq); + case '\f': + throw new IllegalArgumentException("a header value contains a prohibited character '\\f': " + seq); + } + } + + // Check the CRLF (HT | SP) pattern + switch (state) { + case 0: + switch (character) { + case '\r': + state = 1; + break; + case '\n': + state = 2; + break; + } + break; + case 1: + switch (character) { + case '\n': + state = 2; + break; + default: + throw new IllegalArgumentException("only '\\n' is allowed after '\\r': " + seq); + } + break; + case 2: + switch (character) { + case '\t': + case ' ': + state = 0; + break; + default: + throw new IllegalArgumentException("only ' ' and '\\t' are allowed after '\\n': " + seq); + } + } + return state; + } + } + + static class HttpHeadersNameConverter implements NameConverter { + protected final boolean validate; + + HttpHeadersNameConverter(boolean validate) { + this.validate = validate; + } + + @Override + public CharSequence convertName(CharSequence name) { + if (validate) { + if (name instanceof AsciiString) { + validateName((AsciiString) name); + } else { + validateName(name); + } + } + + return name; + } + + private static void validateName(AsciiString name) { + // Go through each characters in the name + final int start = name.arrayOffset(); + final int end = start + name.length(); + final byte[] array = name.array(); + for (int index = start; index < end; index ++) { + byte b = array[index]; + + // Check to see if the character is not an ASCII character + if (b < 0) { + throw new IllegalArgumentException("a header name cannot contain non-ASCII characters: " + name); + } + + // Check for prohibited characters. + validateNameChar(name, b); + } + } + + private static void validateName(CharSequence name) { + // Go through each characters in the name + for (int index = 0; index < name.length(); index++) { + char character = name.charAt(index); + + // Check to see if the character is not an ASCII character + if (character > 127) { + throw new IllegalArgumentException("a header name cannot contain non-ASCII characters: " + name); + } + + // Check for prohibited characters + validateNameChar(name, character); + } + } + + private static void validateNameChar(CharSequence name, int character) { + if ((character & HIGHEST_INVALID_NAME_CHAR_MASK) == 0 && LOOKUP_TABLE[character] != 0) { + throw new IllegalArgumentException( + "a header name cannot contain the following prohibited characters: =,;: \\t\\r\\n\\v\\f: " + + name); + } + } + } + + private static final HttpHeadersValidationConverter + VALIDATE_OBJECT_CONVERTER = new HttpHeadersValidationConverter(true); + private static final HttpHeadersValidationConverter + NO_VALIDATE_OBJECT_CONVERTER = new HttpHeadersValidationConverter(false); + private static final HttpHeadersNameConverter VALIDATE_NAME_CONVERTER = new HttpHeadersNameConverter(true); + private static final HttpHeadersNameConverter NO_VALIDATE_NAME_CONVERTER = new HttpHeadersNameConverter(false); public DefaultHttpHeaders() { this(true); } public DefaultHttpHeaders(boolean validate) { - headers = validate? new ValidatingTextHeaders() : new NonValidatingTextHeaders(); + this(true, validate? VALIDATE_NAME_CONVERTER : NO_VALIDATE_NAME_CONVERTER); } - DefaultHttpHeaders(TextHeaders headers) { - this.headers = headers; + protected DefaultHttpHeaders(boolean validate, NameConverter nameConverter) { + headers = new DefaultTextHeaders(true, + validate ? VALIDATE_OBJECT_CONVERTER : NO_VALIDATE_OBJECT_CONVERTER, nameConverter); } @Override @@ -65,25 +275,25 @@ public class DefaultHttpHeaders extends HttpHeaders { @Override public HttpHeaders add(String name, Object value) { - headers.add(name, value); + headers.addObject(name, value); return this; } @Override public HttpHeaders add(CharSequence name, Object value) { - headers.add(name, value); + headers.addObject(name, value); return this; } @Override public HttpHeaders add(String name, Iterable values) { - headers.add(name, values); + headers.addObject(name, values); return this; } @Override public HttpHeaders add(CharSequence name, Iterable values) { - headers.add(name, values); + headers.addObject(name, values); return this; } @@ -101,25 +311,25 @@ public class DefaultHttpHeaders extends HttpHeaders { @Override public HttpHeaders set(String name, Object value) { - headers.set(name, value); + headers.setObject(name, value); return this; } @Override public HttpHeaders set(CharSequence name, Object value) { - headers.set(name, value); + headers.setObject(name, value); return this; } @Override public HttpHeaders set(String name, Iterable values) { - headers.set(name, values); + headers.setObject(name, values); return this; } @Override public HttpHeaders set(CharSequence name, Iterable values) { - headers.set(name, values); + headers.setObject(name, values); return this; } @@ -131,32 +341,32 @@ public class DefaultHttpHeaders extends HttpHeaders { @Override public String get(String name) { - return headers.get(name); + return headers.getAndConvert(name); } @Override public String get(CharSequence name) { - return headers.get(name); + return headers.getAndConvert(name); } @Override public List getAll(String name) { - return headers.getAll(name); + return headers.getAllAndConvert(name); } @Override public List getAll(CharSequence name) { - return headers.getAll(name); + return headers.getAllAndConvert(name); } @Override public List> entries() { - return headers.entries(); + return headers.entriesConverted(); } @Override public Iterator> iterator() { - return headers.iterator(); + return headers.iteratorConverted(); } @Override @@ -186,207 +396,10 @@ public class DefaultHttpHeaders extends HttpHeaders { @Override public Set names() { - return headers.names(); + return headers.namesAndConvert(String.CASE_INSENSITIVE_ORDER); } - void encode(ByteBuf buf) { + void encode(ByteBuf buf) throws Exception { headers.forEachEntry(new HttpHeadersEncoder(buf)); } - - static class NonValidatingTextHeaders extends DefaultTextHeaders { - @Override - protected CharSequence convertValue(Object value) { - if (value == null) { - throw new NullPointerException("value"); - } - - CharSequence seq; - if (value instanceof CharSequence) { - seq = (CharSequence) value; - } else if (value instanceof Number) { - seq = value.toString(); - } else if (value instanceof Date) { - seq = HttpHeaderDateFormat.get().format((Date) value); - } else if (value instanceof Calendar) { - seq = HttpHeaderDateFormat.get().format(((Calendar) value).getTime()); - } else { - seq = value.toString(); - } - - return seq; - } - } - - static class ValidatingTextHeaders extends NonValidatingTextHeaders { - private static final int HIGHEST_INVALID_NAME_CHAR_MASK = ~63; - private static final int HIGHEST_INVALID_VALUE_CHAR_MASK = ~15; - - /** - * A look-up table used for checking if a character in a header name is prohibited. - */ - private static final byte[] LOOKUP_TABLE = new byte[~HIGHEST_INVALID_NAME_CHAR_MASK + 1]; - - static { - LOOKUP_TABLE['\t'] = -1; - LOOKUP_TABLE['\n'] = -1; - LOOKUP_TABLE[0x0b] = -1; - LOOKUP_TABLE['\f'] = -1; - LOOKUP_TABLE[' '] = -1; - LOOKUP_TABLE[','] = -1; - LOOKUP_TABLE[':'] = -1; - LOOKUP_TABLE[';'] = -1; - LOOKUP_TABLE['='] = -1; - } - - @Override - protected CharSequence convertName(CharSequence name) { - name = super.convertName(name); - if (name instanceof AsciiString) { - validateName((AsciiString) name); - } else { - validateName(name); - } - - return name; - } - - private static void validateName(AsciiString name) { - // Go through each characters in the name - final int start = name.arrayOffset(); - final int end = start + name.length(); - final byte[] array = name.array(); - for (int index = start; index < end; index ++) { - byte b = array[index]; - - // Check to see if the character is not an ASCII character - if (b < 0) { - throw new IllegalArgumentException( - "a header name cannot contain non-ASCII characters: " + name); - } - - // Check for prohibited characters. - validateNameChar(name, b); - } - } - - private static void validateName(CharSequence name) { - // Go through each characters in the name - for (int index = 0; index < name.length(); index ++) { - char character = name.charAt(index); - - // Check to see if the character is not an ASCII character - if (character > 127) { - throw new IllegalArgumentException( - "a header name cannot contain non-ASCII characters: " + name); - } - - // Check for prohibited characters. - validateNameChar(name, character); - } - } - - private static void validateNameChar(CharSequence name, int character) { - if ((character & HIGHEST_INVALID_NAME_CHAR_MASK) == 0 && LOOKUP_TABLE[character] != 0) { - throw new IllegalArgumentException( - "a header name cannot contain the following prohibited characters: " + - "=,;: \\t\\r\\n\\v\\f: " + name); - } - } - - @Override - protected CharSequence convertValue(Object value) { - CharSequence seq = super.convertValue(value); - if (value instanceof AsciiString) { - validateValue((AsciiString) seq); - } else { - validateValue(seq); - } - - return seq; - } - - private static void validateValue(AsciiString seq) { - int state = 0; - // Start looping through each of the character - final int start = seq.arrayOffset(); - final int end = start + seq.length(); - final byte[] array = seq.array(); - for (int index = start; index < end; index ++) { - state = validateValueChar(seq, state, (char) (array[index] & 0xFF)); - } - - if (state != 0) { - throw new IllegalArgumentException( - "a header value must not end with '\\r' or '\\n':" + seq); - } - } - - private static void validateValue(CharSequence seq) { - int state = 0; - // Start looping through each of the character - for (int index = 0; index < seq.length(); index ++) { - state = validateValueChar(seq, state, seq.charAt(index)); - } - - if (state != 0) { - throw new IllegalArgumentException( - "a header value must not end with '\\r' or '\\n':" + seq); - } - } - - private static int validateValueChar(CharSequence seq, int state, char character) { - /* - * State: - * - * 0: Previous character was neither CR nor LF - * 1: The previous character was CR - * 2: The previous character was LF - */ - if ((character & HIGHEST_INVALID_VALUE_CHAR_MASK) == 0) { - // Check the absolutely prohibited characters. - switch (character) { - case 0x0b: // Vertical tab - throw new IllegalArgumentException( - "a header value contains a prohibited character '\\v': " + seq); - case '\f': - throw new IllegalArgumentException( - "a header value contains a prohibited character '\\f': " + seq); - } - } - - // Check the CRLF (HT | SP) pattern - switch (state) { - case 0: - switch (character) { - case '\r': - state = 1; - break; - case '\n': - state = 2; - break; - } - break; - case 1: - switch (character) { - case '\n': - state = 2; - break; - default: - throw new IllegalArgumentException( - "only '\\n' is allowed after '\\r': " + seq); - } - break; - case 2: - switch (character) { - case '\t': case ' ': - state = 0; - break; - default: - throw new IllegalArgumentException( - "only ' ' and '\\t' are allowed after '\\n': " + seq); - } - } - return state; - } - } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpMessage.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpMessage.java index a699ba4acd..96f15deda2 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpMessage.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultHttpMessage.java @@ -17,7 +17,7 @@ package io.netty.handler.codec.http; import io.netty.util.internal.StringUtil; -import java.util.Map; +import java.util.Map.Entry; /** * The default {@link HttpMessage} implementation. @@ -88,7 +88,11 @@ public abstract class DefaultHttpMessage extends DefaultHttpObject implements Ht } void appendHeaders(StringBuilder buf) { - for (Map.Entry e: headers()) { + appendHeaders(buf, headers()); + } + + void appendHeaders(StringBuilder buf, HttpHeaders headers) { + for (Entry e: headers) { buf.append(e.getKey()); buf.append(": "); buf.append(e.getValue()); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultLastHttpContent.java b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultLastHttpContent.java index 8583bdfd1f..67ae513a4a 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/DefaultLastHttpContent.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/DefaultLastHttpContent.java @@ -18,11 +18,11 @@ package io.netty.handler.codec.http; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.handler.codec.AsciiString; -import io.netty.handler.codec.http.DefaultHttpHeaders.NonValidatingTextHeaders; -import io.netty.handler.codec.http.DefaultHttpHeaders.ValidatingTextHeaders; import io.netty.util.internal.StringUtil; -import java.util.Map; +import java.util.Map.Entry; + +import static io.netty.handler.codec.http.HttpHeaders.Names.*; /** * The default {@link LastHttpContent} implementation. @@ -42,8 +42,7 @@ public class DefaultLastHttpContent extends DefaultHttpContent implements LastHt public DefaultLastHttpContent(ByteBuf content, boolean validateHeaders) { super(content); - trailingHeaders = new DefaultHttpHeaders( - validateHeaders? new ValidatingTrailingTextHeaders() : new NonValidatingTextHeaders()); + trailingHeaders = new TrailingHttpHeaders(validateHeaders); this.validateHeaders = validateHeaders; } @@ -102,7 +101,7 @@ public class DefaultLastHttpContent extends DefaultHttpContent implements LastHt } private void appendHeaders(StringBuilder buf) { - for (Map.Entry e: trailingHeaders()) { + for (Entry e : trailingHeaders()) { buf.append(e.getKey()); buf.append(": "); buf.append(e.getValue()); @@ -110,17 +109,33 @@ public class DefaultLastHttpContent extends DefaultHttpContent implements LastHt } } - private static final class ValidatingTrailingTextHeaders extends ValidatingTextHeaders { - @Override - protected CharSequence convertName(CharSequence name) { - name = super.convertName(name); - if (AsciiString.equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH, name) || - AsciiString.equalsIgnoreCase(HttpHeaders.Names.TRANSFER_ENCODING, name) || - AsciiString.equalsIgnoreCase(HttpHeaders.Names.TRAILER, name)) { - throw new IllegalArgumentException( - "prohibited trailing header: " + name); + private static final class TrailingHttpHeaders extends DefaultHttpHeaders { + private static final class TrailingHttpHeadersNameConverter extends HttpHeadersNameConverter { + TrailingHttpHeadersNameConverter(boolean validate) { + super(validate); } - return name; + + @Override + public CharSequence convertName(CharSequence name) { + name = super.convertName(name); + if (validate) { + if (AsciiString.equalsIgnoreCase(CONTENT_LENGTH, name) + || AsciiString.equalsIgnoreCase(TRANSFER_ENCODING, name) + || AsciiString.equalsIgnoreCase(TRAILER, name)) { + throw new IllegalArgumentException("prohibited trailing header: " + name); + } + } + return name; + } + } + + private static final TrailingHttpHeadersNameConverter + VALIDATE_NAME_CONVERTER = new TrailingHttpHeadersNameConverter(true); + private static final TrailingHttpHeadersNameConverter + NO_VALIDATE_NAME_CONVERTER = new TrailingHttpHeadersNameConverter(false); + + TrailingHttpHeaders(boolean validate) { + super(validate, validate ? VALIDATE_NAME_CONVERTER : NO_VALIDATE_NAME_CONVERTER); } } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderUtil.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderUtil.java new file mode 100644 index 0000000000..b1eb85b5ba --- /dev/null +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaderUtil.java @@ -0,0 +1,263 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project 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 io.netty.handler.codec.http; + +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.AsciiString; +import io.netty.handler.codec.http.HttpHeaders.Names; +import io.netty.handler.codec.http.HttpHeaders.Values; + +import java.util.Iterator; +import java.util.List; + +public final class HttpHeaderUtil { + + /** + * Returns {@code true} if and only if the connection can remain open and + * thus 'kept alive'. This methods respects the value of the + * {@code "Connection"} header first and then the return value of + * {@link HttpVersion#isKeepAliveDefault()}. + */ + public static boolean isKeepAlive(HttpMessage message) { + CharSequence connection = message.headers().get(Names.CONNECTION); + if (connection != null && AsciiString.equalsIgnoreCase(Values.CLOSE, connection)) { + return false; + } + + if (message.protocolVersion().isKeepAliveDefault()) { + return !AsciiString.equalsIgnoreCase(Values.CLOSE, connection); + } else { + return AsciiString.equalsIgnoreCase(Values.KEEP_ALIVE, connection); + } + } + + /** + * Sets the value of the {@code "Connection"} header depending on the + * protocol version of the specified message. This getMethod sets or removes + * the {@code "Connection"} header depending on what the default keep alive + * mode of the message's protocol version is, as specified by + * {@link HttpVersion#isKeepAliveDefault()}. + *
    + *
  • If the connection is kept alive by default: + *
      + *
    • set to {@code "close"} if {@code keepAlive} is {@code false}.
    • + *
    • remove otherwise.
    • + *
  • + *
  • If the connection is closed by default: + *
      + *
    • set to {@code "keep-alive"} if {@code keepAlive} is {@code true}.
    • + *
    • remove otherwise.
    • + *
  • + *
+ */ + public static void setKeepAlive(HttpMessage message, boolean keepAlive) { + HttpHeaders h = message.headers(); + if (message.protocolVersion().isKeepAliveDefault()) { + if (keepAlive) { + h.remove(Names.CONNECTION); + } else { + h.set(Names.CONNECTION, Values.CLOSE); + } + } else { + if (keepAlive) { + h.set(Names.CONNECTION, Values.KEEP_ALIVE); + } else { + h.remove(Names.CONNECTION); + } + } + } + + /** + * Returns the length of the content. Please note that this value is + * not retrieved from {@link HttpContent#content()} but from the + * {@code "Content-Length"} header, and thus they are independent from each + * other. + * + * @return the content length + * + * @throws NumberFormatException + * if the message does not have the {@code "Content-Length"} header + * or its value is not a number + */ + public static long getContentLength(HttpMessage message) { + String value = message.headers().get(Names.CONTENT_LENGTH); + if (value != null) { + return Long.parseLong(value); + } + + // We know the content length if it's a Web Socket message even if + // Content-Length header is missing. + long webSocketContentLength = getWebSocketContentLength(message); + if (webSocketContentLength >= 0) { + return webSocketContentLength; + } + + // Otherwise we don't. + throw new NumberFormatException("header not found: " + Names.CONTENT_LENGTH); + } + + /** + * Returns the length of the content. Please note that this value is + * not retrieved from {@link HttpContent#content()} but from the + * {@code "Content-Length"} header, and thus they are independent from each + * other. + * + * @return the content length or {@code defaultValue} if this message does + * not have the {@code "Content-Length"} header or its value is not + * a number + */ + public static long getContentLength(HttpMessage message, long defaultValue) { + String value = message.headers().get(Names.CONTENT_LENGTH); + if (value != null) { + return Long.parseLong(value); + } + + // We know the content length if it's a Web Socket message even if + // Content-Length header is missing. + long webSocketContentLength = getWebSocketContentLength(message); + if (webSocketContentLength >= 0) { + return webSocketContentLength; + } + + // Otherwise we don't. + return defaultValue; + } + + /** + * Returns the content length of the specified web socket message. If the + * specified message is not a web socket message, {@code -1} is returned. + */ + private static int getWebSocketContentLength(HttpMessage message) { + // WebSockset messages have constant content-lengths. + HttpHeaders h = message.headers(); + if (message instanceof HttpRequest) { + HttpRequest req = (HttpRequest) message; + if (HttpMethod.GET.equals(req.method()) && + h.contains(Names.SEC_WEBSOCKET_KEY1) && + h.contains(Names.SEC_WEBSOCKET_KEY2)) { + return 8; + } + } else if (message instanceof HttpResponse) { + HttpResponse res = (HttpResponse) message; + if (res.status().code() == 101 && + h.contains(Names.SEC_WEBSOCKET_ORIGIN) && + h.contains(Names.SEC_WEBSOCKET_LOCATION)) { + return 16; + } + } + + // Not a web socket message + return -1; + } + + /** + * Sets the {@code "Content-Length"} header. + */ + public static void setContentLength(HttpMessage message, long length) { + message.headers().set(Names.CONTENT_LENGTH, length); + } + + public static boolean isContentLengthSet(HttpMessage m) { + return m.headers().contains(Names.CONTENT_LENGTH); + } + + /** + * Returns {@code true} if and only if the specified message contains the + * {@code "Expect: 100-continue"} header. + */ + public static boolean is100ContinueExpected(HttpMessage message) { + // Expect: 100-continue is for requests only. + if (!(message instanceof HttpRequest)) { + return false; + } + + // It works only on HTTP/1.1 or later. + if (message.protocolVersion().compareTo(HttpVersion.HTTP_1_1) < 0) { + return false; + } + + // In most cases, there will be one or zero 'Expect' header. + CharSequence value = message.headers().get(Names.EXPECT); + if (value == null) { + return false; + } + if (AsciiString.equalsIgnoreCase(Values.CONTINUE, value)) { + return true; + } + + // Multiple 'Expect' headers. Search through them. + return message.headers().contains(Names.EXPECT, Values.CONTINUE, true); + } + + /** + * Sets or removes the {@code "Expect: 100-continue"} header to / from the + * specified message. If the specified {@code value} is {@code true}, + * the {@code "Expect: 100-continue"} header is set and all other previous + * {@code "Expect"} headers are removed. Otherwise, all {@code "Expect"} + * headers are removed completely. + */ + public static void set100ContinueExpected(HttpMessage message, boolean expected) { + if (expected) { + message.headers().set(Names.EXPECT, Values.CONTINUE); + } else { + message.headers().remove(Names.EXPECT); + } + } + + /** + * Checks to see if the transfer encoding in a specified {@link HttpMessage} is chunked + * + * @param message The message to check + * @return True if transfer encoding is chunked, otherwise false + */ + public static boolean isTransferEncodingChunked(HttpMessage message) { + return message.headers().contains(Names.TRANSFER_ENCODING, Values.CHUNKED, true); + } + + public static void setTransferEncodingChunked(HttpMessage m, boolean chunked) { + if (chunked) { + m.headers().add(Names.TRANSFER_ENCODING, Values.CHUNKED); + m.headers().remove(Names.CONTENT_LENGTH); + } else { + List values = m.headers().getAll(Names.TRANSFER_ENCODING); + if (values.isEmpty()) { + return; + } + Iterator valuesIt = values.iterator(); + while (valuesIt.hasNext()) { + CharSequence value = valuesIt.next(); + if (AsciiString.equalsIgnoreCase(value, Values.CHUNKED)) { + valuesIt.remove(); + } + } + if (values.isEmpty()) { + m.headers().remove(Names.TRANSFER_ENCODING); + } else { + m.headers().set(Names.TRANSFER_ENCODING, values); + } + } + } + + static void encodeAscii0(CharSequence seq, ByteBuf buf) { + int length = seq.length(); + for (int i = 0 ; i < length; i++) { + buf.writeByte((byte) seq.charAt(i)); + } + } + + private HttpHeaderUtil() { } +} diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java index 5dc131365b..64bff4e636 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeaders.java @@ -15,6 +15,10 @@ */ package io.netty.handler.codec.http; +import static io.netty.handler.codec.http.HttpConstants.COLON; +import static io.netty.handler.codec.http.HttpConstants.CR; +import static io.netty.handler.codec.http.HttpConstants.LF; +import static io.netty.handler.codec.http.HttpConstants.SP; import io.netty.buffer.ByteBuf; import io.netty.handler.codec.AsciiString; @@ -28,8 +32,6 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import static io.netty.handler.codec.http.HttpConstants.*; - /** * Provides the constants for the standard HTTP header names and values and * commonly used utility methods that accesses an {@link HttpMessage}. @@ -38,20 +40,20 @@ public abstract class HttpHeaders implements Iterable> private static final byte[] HEADER_SEPERATOR = { COLON, SP }; private static final byte[] CRLF = { CR, LF }; - private static final CharSequence CONTENT_LENGTH_ENTITY = newEntity(Names.CONTENT_LENGTH); - private static final CharSequence CONNECTION_ENTITY = newEntity(Names.CONNECTION); - private static final CharSequence CLOSE_ENTITY = newEntity(Values.CLOSE); - private static final CharSequence KEEP_ALIVE_ENTITY = newEntity(Values.KEEP_ALIVE); - private static final CharSequence HOST_ENTITY = newEntity(Names.HOST); - private static final CharSequence DATE_ENTITY = newEntity(Names.DATE); - private static final CharSequence EXPECT_ENTITY = newEntity(Names.EXPECT); - private static final CharSequence CONTINUE_ENTITY = newEntity(Values.CONTINUE); - private static final CharSequence TRANSFER_ENCODING_ENTITY = newEntity(Names.TRANSFER_ENCODING); - private static final CharSequence CHUNKED_ENTITY = newEntity(Values.CHUNKED); - private static final CharSequence SEC_WEBSOCKET_KEY1_ENTITY = newEntity(Names.SEC_WEBSOCKET_KEY1); - private static final CharSequence SEC_WEBSOCKET_KEY2_ENTITY = newEntity(Names.SEC_WEBSOCKET_KEY2); - private static final CharSequence SEC_WEBSOCKET_ORIGIN_ENTITY = newEntity(Names.SEC_WEBSOCKET_ORIGIN); - private static final CharSequence SEC_WEBSOCKET_LOCATION_ENTITY = newEntity(Names.SEC_WEBSOCKET_LOCATION); + private static final CharSequence CONTENT_LENGTH_ENTITY = Names.CONTENT_LENGTH; + private static final CharSequence CONNECTION_ENTITY = Names.CONNECTION; + private static final CharSequence CLOSE_ENTITY = Values.CLOSE; + private static final CharSequence KEEP_ALIVE_ENTITY = Values.KEEP_ALIVE; + private static final CharSequence HOST_ENTITY = Names.HOST; + private static final CharSequence DATE_ENTITY = Names.DATE; + private static final CharSequence EXPECT_ENTITY = Names.EXPECT; + private static final CharSequence CONTINUE_ENTITY = Values.CONTINUE; + private static final CharSequence TRANSFER_ENCODING_ENTITY = Names.TRANSFER_ENCODING; + private static final CharSequence CHUNKED_ENTITY = Values.CHUNKED; + private static final CharSequence SEC_WEBSOCKET_KEY1_ENTITY = Names.SEC_WEBSOCKET_KEY1; + private static final CharSequence SEC_WEBSOCKET_KEY2_ENTITY = Names.SEC_WEBSOCKET_KEY2; + private static final CharSequence SEC_WEBSOCKET_ORIGIN_ENTITY = Names.SEC_WEBSOCKET_ORIGIN; + private static final CharSequence SEC_WEBSOCKET_LOCATION_ENTITY = Names.SEC_WEBSOCKET_LOCATION; public static final HttpHeaders EMPTY_HEADERS = new HttpHeaders() { @Override @@ -122,300 +124,330 @@ public abstract class HttpHeaders implements Iterable> /** * Standard HTTP header names. + *

+ * These are all defined as lowercase to support HTTP/2 requirements while also not + * violating HTTP/1.x requirements. New header names should always be lowercase. */ public static final class Names { /** - * {@code "Accept"} + * {@code "accept"} */ - public static final String ACCEPT = "Accept"; + public static final AsciiString ACCEPT = new AsciiString("accept"); /** - * {@code "Accept-Charset"} + * {@code "accept-charset"} */ - public static final String ACCEPT_CHARSET = "Accept-Charset"; + public static final AsciiString ACCEPT_CHARSET = new AsciiString("accept-charset"); /** - * {@code "Accept-Encoding"} + * {@code "accept-encoding"} */ - public static final String ACCEPT_ENCODING = "Accept-Encoding"; + public static final AsciiString ACCEPT_ENCODING = new AsciiString("accept-encoding"); /** - * {@code "Accept-Language"} + * {@code "accept-language"} */ - public static final String ACCEPT_LANGUAGE = "Accept-Language"; + public static final AsciiString ACCEPT_LANGUAGE = new AsciiString("accept-language"); /** - * {@code "Accept-Ranges"} + * {@code "accept-ranges"} */ - public static final String ACCEPT_RANGES = "Accept-Ranges"; + public static final AsciiString ACCEPT_RANGES = new AsciiString("accept-ranges"); /** - * {@code "Accept-Patch"} + * {@code "accept-patch"} */ - public static final String ACCEPT_PATCH = "Accept-Patch"; + public static final AsciiString ACCEPT_PATCH = new AsciiString("accept-patch"); /** - * {@code "Access-Control-Allow-Credentials"} + * {@code "access-control-allow-credentials"} */ - public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials"; + public static final AsciiString ACCESS_CONTROL_ALLOW_CREDENTIALS = + new AsciiString("access-control-allow-credentials"); /** - * {@code "Access-Control-Allow-Headers"} + * {@code "access-control-allow-headers"} */ - public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; + public static final AsciiString ACCESS_CONTROL_ALLOW_HEADERS = + new AsciiString("access-control-allow-headers"); /** - * {@code "Access-Control-Allow-Methods"} + * {@code "access-control-allow-methods"} */ - public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; + public static final AsciiString ACCESS_CONTROL_ALLOW_METHODS = + new AsciiString("access-control-allow-methods"); /** - * {@code "Access-Control-Allow-Origin"} + * {@code "access-control-allow-origin"} */ - public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; + public static final AsciiString ACCESS_CONTROL_ALLOW_ORIGIN = + new AsciiString("access-control-allow-origin"); /** - * {@code "Access-Control-Expose-Headers"} + * {@code "access-control-expose-headers"} */ - public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers"; + public static final AsciiString ACCESS_CONTROL_EXPOSE_HEADERS = + new AsciiString("access-control-expose-headers"); /** - * {@code "Access-Control-Max-Age"} + * {@code "access-control-max-age"} */ - public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age"; + public static final AsciiString ACCESS_CONTROL_MAX_AGE = new AsciiString("access-control-max-age"); /** - * {@code "Access-Control-Request-Headers"} + * {@code "access-control-request-headers"} */ - public static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; + public static final AsciiString ACCESS_CONTROL_REQUEST_HEADERS = + new AsciiString("access-control-request-headers"); /** - * {@code "Access-Control-Request-Method"} + * {@code "access-control-request-method"} */ - public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method"; + public static final AsciiString ACCESS_CONTROL_REQUEST_METHOD = + new AsciiString("access-control-request-method"); /** - * {@code "Age"} + * {@code "age"} */ - public static final String AGE = "Age"; + public static final AsciiString AGE = new AsciiString("age"); /** - * {@code "Allow"} + * {@code "allow"} */ - public static final String ALLOW = "Allow"; + public static final AsciiString ALLOW = new AsciiString("allow"); /** - * {@code "Authorization"} + * {@code "authorization"} */ - public static final String AUTHORIZATION = "Authorization"; + public static final AsciiString AUTHORIZATION = new AsciiString("authorization"); /** - * {@code "Cache-Control"} + * {@code "cache-control"} */ - public static final String CACHE_CONTROL = "Cache-Control"; + public static final AsciiString CACHE_CONTROL = new AsciiString("cache-control"); /** - * {@code "Connection"} + * {@code "connection"} */ - public static final String CONNECTION = "Connection"; + public static final AsciiString CONNECTION = new AsciiString("connection"); /** - * {@code "Content-Base"} + * {@code "content-base"} */ - public static final String CONTENT_BASE = "Content-Base"; + public static final AsciiString CONTENT_BASE = new AsciiString("content-base"); /** - * {@code "Content-Encoding"} + * {@code "content-encoding"} */ - public static final String CONTENT_ENCODING = "Content-Encoding"; + public static final AsciiString CONTENT_ENCODING = new AsciiString("content-encoding"); /** - * {@code "Content-Language"} + * {@code "content-language"} */ - public static final String CONTENT_LANGUAGE = "Content-Language"; + public static final AsciiString CONTENT_LANGUAGE = new AsciiString("content-language"); /** - * {@code "Content-Length"} + * {@code "content-length"} */ - public static final String CONTENT_LENGTH = "Content-Length"; + public static final AsciiString CONTENT_LENGTH = new AsciiString("content-length"); /** - * {@code "Content-Location"} + * {@code "content-location"} */ - public static final String CONTENT_LOCATION = "Content-Location"; + public static final AsciiString CONTENT_LOCATION = new AsciiString("content-location"); /** - * {@code "Content-Transfer-Encoding"} + * {@code "content-transfer-encoding"} */ - public static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding"; + public static final AsciiString CONTENT_TRANSFER_ENCODING = new AsciiString("content-transfer-encoding"); /** - * {@code "Content-MD5"} + * {@code "content-disposition"} */ - public static final String CONTENT_MD5 = "Content-MD5"; + public static final AsciiString CONTENT_DISPOSITION = new AsciiString("content-disposition"); /** - * {@code "Content-Range"} + * {@code "content-md5"} */ - public static final String CONTENT_RANGE = "Content-Range"; + public static final AsciiString CONTENT_MD5 = new AsciiString("content-md5"); /** - * {@code "Content-Type"} + * {@code "content-range"} */ - public static final String CONTENT_TYPE = "Content-Type"; + public static final AsciiString CONTENT_RANGE = new AsciiString("content-range"); /** - * {@code "Cookie"} + * {@code "content-type"} */ - public static final String COOKIE = "Cookie"; + public static final AsciiString CONTENT_TYPE = new AsciiString("content-type"); /** - * {@code "Date"} + * {@code "cookie"} */ - public static final String DATE = "Date"; + public static final AsciiString COOKIE = new AsciiString("cookie"); /** - * {@code "ETag"} + * {@code "date"} */ - public static final String ETAG = "ETag"; + public static final AsciiString DATE = new AsciiString("date"); /** - * {@code "Expect"} + * {@code "etag"} */ - public static final String EXPECT = "Expect"; + public static final AsciiString ETAG = new AsciiString("etag"); /** - * {@code "Expires"} + * {@code "expect"} */ - public static final String EXPIRES = "Expires"; + public static final AsciiString EXPECT = new AsciiString("expect"); /** - * {@code "From"} + * {@code "expires"} */ - public static final String FROM = "From"; + public static final AsciiString EXPIRES = new AsciiString("expires"); /** - * {@code "Host"} + * {@code "from"} */ - public static final String HOST = "Host"; + public static final AsciiString FROM = new AsciiString("from"); /** - * {@code "If-Match"} + * {@code "host"} */ - public static final String IF_MATCH = "If-Match"; + public static final AsciiString HOST = new AsciiString("host"); /** - * {@code "If-Modified-Since"} + * {@code "if-match"} */ - public static final String IF_MODIFIED_SINCE = "If-Modified-Since"; + public static final AsciiString IF_MATCH = new AsciiString("if-match"); /** - * {@code "If-None-Match"} + * {@code "if-modified-since"} */ - public static final String IF_NONE_MATCH = "If-None-Match"; + public static final AsciiString IF_MODIFIED_SINCE = new AsciiString("if-modified-since"); /** - * {@code "If-Range"} + * {@code "if-none-match"} */ - public static final String IF_RANGE = "If-Range"; + public static final AsciiString IF_NONE_MATCH = new AsciiString("if-none-match"); /** - * {@code "If-Unmodified-Since"} + * {@code "if-range"} */ - public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; + public static final AsciiString IF_RANGE = new AsciiString("if-range"); /** - * {@code "Last-Modified"} + * {@code "if-unmodified-since"} */ - public static final String LAST_MODIFIED = "Last-Modified"; + public static final AsciiString IF_UNMODIFIED_SINCE = new AsciiString("if-unmodified-since"); /** - * {@code "Location"} + * {@code "last-modified"} */ - public static final String LOCATION = "Location"; + public static final AsciiString LAST_MODIFIED = new AsciiString("last-modified"); /** - * {@code "Max-Forwards"} + * {@code "location"} */ - public static final String MAX_FORWARDS = "Max-Forwards"; + public static final AsciiString LOCATION = new AsciiString("location"); /** - * {@code "Origin"} + * {@code "max-forwards"} */ - public static final String ORIGIN = "Origin"; + public static final AsciiString MAX_FORWARDS = new AsciiString("max-forwards"); /** - * {@code "Pragma"} + * {@code "origin"} */ - public static final String PRAGMA = "Pragma"; + public static final AsciiString ORIGIN = new AsciiString("origin"); /** - * {@code "Proxy-Authenticate"} + * {@code "pragma"} */ - public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate"; + public static final AsciiString PRAGMA = new AsciiString("pragma"); /** - * {@code "Proxy-Authorization"} + * {@code "proxy-authenticate"} */ - public static final String PROXY_AUTHORIZATION = "Proxy-Authorization"; + public static final AsciiString PROXY_AUTHENTICATE = new AsciiString("proxy-authenticate"); /** - * {@code "Range"} + * {@code "proxy-authorization"} */ - public static final String RANGE = "Range"; + public static final AsciiString PROXY_AUTHORIZATION = new AsciiString("proxy-authorization"); /** - * {@code "Referer"} + * {@code "range"} */ - public static final String REFERER = "Referer"; + public static final AsciiString RANGE = new AsciiString("range"); /** - * {@code "Retry-After"} + * {@code "referer"} */ - public static final String RETRY_AFTER = "Retry-After"; + public static final AsciiString REFERER = new AsciiString("referer"); /** - * {@code "Sec-WebSocket-Key1"} + * {@code "retry-after"} */ - public static final String SEC_WEBSOCKET_KEY1 = "Sec-WebSocket-Key1"; + public static final AsciiString RETRY_AFTER = new AsciiString("retry-after"); /** - * {@code "Sec-WebSocket-Key2"} + * {@code "sec-websocket-key1"} */ - public static final String SEC_WEBSOCKET_KEY2 = "Sec-WebSocket-Key2"; + public static final AsciiString SEC_WEBSOCKET_KEY1 = new AsciiString("sec-websocket-key1"); /** - * {@code "Sec-WebSocket-Location"} + * {@code "sec-websocket-key2"} */ - public static final String SEC_WEBSOCKET_LOCATION = "Sec-WebSocket-Location"; + public static final AsciiString SEC_WEBSOCKET_KEY2 = new AsciiString("sec-websocket-key2"); /** - * {@code "Sec-WebSocket-Origin"} + * {@code "sec-websocket-location"} */ - public static final String SEC_WEBSOCKET_ORIGIN = "Sec-WebSocket-Origin"; + public static final AsciiString SEC_WEBSOCKET_LOCATION = new AsciiString("sec-websocket-location"); /** - * {@code "Sec-WebSocket-Protocol"} + * {@code "sec-websocket-origin"} */ - public static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol"; + public static final AsciiString SEC_WEBSOCKET_ORIGIN = new AsciiString("sec-websocket-origin"); /** - * {@code "Sec-WebSocket-Version"} + * {@code "sec-websocket-protocol"} */ - public static final String SEC_WEBSOCKET_VERSION = "Sec-WebSocket-Version"; + public static final AsciiString SEC_WEBSOCKET_PROTOCOL = new AsciiString("sec-websocket-protocol"); /** - * {@code "Sec-WebSocket-Key"} + * {@code "sec-websocket-version"} */ - public static final String SEC_WEBSOCKET_KEY = "Sec-WebSocket-Key"; + public static final AsciiString SEC_WEBSOCKET_VERSION = new AsciiString("sec-websocket-version"); /** - * {@code "Sec-WebSocket-Accept"} + * {@code "sec-websocket-key"} */ - public static final String SEC_WEBSOCKET_ACCEPT = "Sec-WebSocket-Accept"; + public static final AsciiString SEC_WEBSOCKET_KEY = new AsciiString("sec-websocket-key"); /** - * {@code "Server"} + * {@code "sec-websocket-accept"} */ - public static final String SERVER = "Server"; + public static final AsciiString SEC_WEBSOCKET_ACCEPT = new AsciiString("sec-websocket-accept"); /** - * {@code "Set-Cookie"} + * {@code "sec-websocket-protocol"} */ - public static final String SET_COOKIE = "Set-Cookie"; + public static final AsciiString SEC_WEBSOCKET_EXTENSIONS = new AsciiString("sec-websocket-extensions"); /** - * {@code "Set-Cookie2"} + * {@code "server"} */ - public static final String SET_COOKIE2 = "Set-Cookie2"; + public static final AsciiString SERVER = new AsciiString("server"); /** - * {@code "TE"} + * {@code "set-cookie"} */ - public static final String TE = "TE"; + public static final AsciiString SET_COOKIE = new AsciiString("set-cookie"); /** - * {@code "Trailer"} + * {@code "set-cookie2"} */ - public static final String TRAILER = "Trailer"; + public static final AsciiString SET_COOKIE2 = new AsciiString("set-cookie2"); /** - * {@code "Transfer-Encoding"} + * {@code "te"} */ - public static final String TRANSFER_ENCODING = "Transfer-Encoding"; + public static final AsciiString TE = new AsciiString("te"); /** - * {@code "Upgrade"} + * {@code "trailer"} */ - public static final String UPGRADE = "Upgrade"; + public static final AsciiString TRAILER = new AsciiString("trailer"); /** - * {@code "User-Agent"} + * {@code "transfer-encoding"} */ - public static final String USER_AGENT = "User-Agent"; + public static final AsciiString TRANSFER_ENCODING = new AsciiString("transfer-encoding"); /** - * {@code "Vary"} + * {@code "upgrade"} */ - public static final String VARY = "Vary"; + public static final AsciiString UPGRADE = new AsciiString("upgrade"); /** - * {@code "Via"} + * {@code "user-agent"} */ - public static final String VIA = "Via"; + public static final AsciiString USER_AGENT = new AsciiString("user-agent"); /** - * {@code "Warning"} + * {@code "vary"} */ - public static final String WARNING = "Warning"; + public static final AsciiString VARY = new AsciiString("vary"); /** - * {@code "WebSocket-Location"} + * {@code "via"} */ - public static final String WEBSOCKET_LOCATION = "WebSocket-Location"; + public static final AsciiString VIA = new AsciiString("via"); /** - * {@code "WebSocket-Origin"} + * {@code "warning"} */ - public static final String WEBSOCKET_ORIGIN = "WebSocket-Origin"; + public static final AsciiString WARNING = new AsciiString("warning"); /** - * {@code "WebSocket-Protocol"} + * {@code "websocket-location"} */ - public static final String WEBSOCKET_PROTOCOL = "WebSocket-Protocol"; + public static final AsciiString WEBSOCKET_LOCATION = new AsciiString("websocket-location"); /** - * {@code "WWW-Authenticate"} + * {@code "websocket-origin"} */ - public static final String WWW_AUTHENTICATE = "WWW-Authenticate"; + public static final AsciiString WEBSOCKET_ORIGIN = new AsciiString("websocket-origin"); + /** + * {@code "websocket-protocol"} + */ + public static final AsciiString WEBSOCKET_PROTOCOL = new AsciiString("websocket-protocol"); + /** + * {@code "www-authenticate"} + */ + public static final AsciiString WWW_AUTHENTICATE = new AsciiString("www-authenticate"); + /** + * {@code "keep-alive"} + * @deprecated use {@link #CONNECTION} + */ + @Deprecated + public static final AsciiString KEEP_ALIVE = new AsciiString("keep-alive"); + /** + * {@code "proxy-connection"} + * @deprecated use {@link #CONNECTION} + */ + @Deprecated + public static final AsciiString PROXY_CONNECTION = new AsciiString("proxy-connection"); private Names() { } @@ -428,8 +460,16 @@ public abstract class HttpHeaders implements Iterable> /** * {@code "application/x-www-form-urlencoded"} */ - public static final String APPLICATION_X_WWW_FORM_URLENCODED = - "application/x-www-form-urlencoded"; + public static final AsciiString APPLICATION_X_WWW_FORM_URLENCODED = + new AsciiString("application/x-www-form-urlencoded"); + /** + * {@code "application/octet-stream"} + */ + public static final AsciiString APPLICATION_OCTET_STREAM = new AsciiString("application/octet-stream"); + /** + * {@code "text/plain"} + */ + public static final AsciiString TEXT_PLAIN = new AsciiString("text/plain"); /** * {@code "base64"} */ @@ -498,6 +538,10 @@ public abstract class HttpHeaders implements Iterable> * {@code "multipart/form-data"} */ public static final String MULTIPART_FORM_DATA = "multipart/form-data"; + /** + * {@code "multipart/mixed"} + */ + public static final AsciiString MULTIPART_MIXED = new AsciiString("multipart/mixed"); /** * {@code "must-revalidate"} */ @@ -553,7 +597,32 @@ public abstract class HttpHeaders implements Iterable> /** * {@code "WebSocket"} */ - public static final String WEBSOCKET = "WebSocket"; + public static final AsciiString WEBSOCKET = new AsciiString("WebSocket"); + /** + * {@code "name"} + * See {@link Names#CONTENT_DISPOSITION} + */ + public static final AsciiString NAME = new AsciiString("name"); + /** + * {@code "filename"} + * See {@link Names#CONTENT_DISPOSITION} + */ + public static final AsciiString FILENAME = new AsciiString("filename"); + /** + * {@code "form-data"} + * See {@link Names#CONTENT_DISPOSITION} + */ + public static final AsciiString FORM_DATA = new AsciiString("form-data"); + /** + * {@code "attachment"} + * See {@link Names#CONTENT_DISPOSITION} + */ + public static final AsciiString ATTACHMENT = new AsciiString("attachment"); + /** + * {@code "file"} + * See {@link Names#CONTENT_DISPOSITION} + */ + public static final AsciiString FILE = new AsciiString("file"); private Values() { } @@ -1193,7 +1262,7 @@ public abstract class HttpHeaders implements Iterable> return AsciiString.equalsIgnoreCase(name1, name2); } - static void encode(HttpHeaders headers, ByteBuf buf) { + static void encode(HttpHeaders headers, ByteBuf buf) throws Exception { if (headers instanceof DefaultHttpHeaders) { ((DefaultHttpHeaders) headers).encode(buf); } else { diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeadersEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeadersEncoder.java index fccd251509..6e2e344ec9 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeadersEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpHeadersEncoder.java @@ -18,9 +18,11 @@ package io.netty.handler.codec.http; import io.netty.buffer.ByteBuf; import io.netty.handler.codec.AsciiString; -import io.netty.handler.codec.TextHeaderProcessor; +import io.netty.handler.codec.TextHeaders.EntryVisitor; -final class HttpHeadersEncoder implements TextHeaderProcessor { +import java.util.Map.Entry; + +final class HttpHeadersEncoder implements EntryVisitor { private final ByteBuf buf; @@ -29,7 +31,9 @@ final class HttpHeadersEncoder implements TextHeaderProcessor { } @Override - public boolean process(CharSequence name, CharSequence value) throws Exception { + public boolean visit(Entry entry) throws Exception { + final CharSequence name = entry.getKey(); + final CharSequence value = entry.getValue(); final ByteBuf buf = this.buf; final int nameLen = name.length(); final int valueLen = value.length(); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java index 49adb0356c..d717e40cfd 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java @@ -559,8 +559,12 @@ public abstract class HttpObjectDecoder extends ReplayingDecoder { List current = trailer.trailingHeaders().getAll(lastHeader); if (!current.isEmpty()) { int lastPos = current.size() - 1; - String newString = current.get(lastPos) + line.toString().trim(); - current.set(lastPos, newString); + String lineTrimmed = line.toString().trim(); + CharSequence currentLastPos = current.get(lastPos); + StringBuilder b = new StringBuilder(currentLastPos.length() + lineTrimmed.length()); + b.append(currentLastPos); + b.append(lineTrimmed); + current.set(lastPos, b.toString()); } else { // Content-Length, Transfer-Encoding, or Trailer } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectEncoder.java index d5e52d25c2..327992b0de 100755 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectEncoder.java @@ -20,6 +20,7 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.FileRegion; import io.netty.handler.codec.MessageToMessageEncoder; import io.netty.util.CharsetUtil; +import io.netty.util.internal.PlatformDependent; import io.netty.util.internal.StringUtil; import java.util.List; @@ -150,7 +151,13 @@ public abstract class HttpObjectEncoder extends MessageTo } else { ByteBuf buf = ctx.alloc().buffer(); buf.writeBytes(ZERO_CRLF); - HttpHeaders.encode(headers, buf); + + try { + HttpHeaders.encode(headers, buf); + } catch (Exception ex) { + buf.release(); + PlatformDependent.throwException(ex); + } buf.writeBytes(CRLF); out.add(buf); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/cors/CorsHandler.java b/codec-http/src/main/java/io/netty/handler/codec/http/cors/CorsHandler.java index 56fb81e820..b517ce4930 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/cors/CorsHandler.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/cors/CorsHandler.java @@ -88,7 +88,7 @@ public class CorsHandler extends ChannelDuplexHandler { } private boolean setOrigin(final HttpResponse response) { - final String origin = request.headers().get(ORIGIN); + final CharSequence origin = request.headers().get(ORIGIN); if (origin != null) { if ("null".equals(origin) && config.isNullOriginAllowed()) { setAnyOrigin(response); @@ -118,7 +118,7 @@ public class CorsHandler extends ChannelDuplexHandler { return true; } - final String origin = request.headers().get(ORIGIN); + final CharSequence origin = request.headers().get(ORIGIN); if (origin == null) { // Not a CORS request so we cannot validate it. It may be a non CORS request. return true; @@ -143,7 +143,7 @@ public class CorsHandler extends ChannelDuplexHandler { setOrigin(response, "*"); } - private static void setOrigin(final HttpResponse response, final String origin) { + private static void setOrigin(final HttpResponse response, final CharSequence origin) { response.headers().set(ACCESS_CONTROL_ALLOW_ORIGIN, origin); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostBodyUtil.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostBodyUtil.java index 7076a8029c..169b2d872c 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostBodyUtil.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostBodyUtil.java @@ -16,6 +16,7 @@ package io.netty.handler.codec.http.multipart; import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.HttpHeaders; import io.netty.util.CharsetUtil; import java.nio.charset.Charset; @@ -29,31 +30,31 @@ final class HttpPostBodyUtil { /** * HTTP content disposition header name. */ - public static final String CONTENT_DISPOSITION = "Content-Disposition"; + public static final String CONTENT_DISPOSITION = HttpHeaders.Names.CONTENT_DISPOSITION.toString(); - public static final String NAME = "name"; + public static final String NAME = HttpHeaders.Values.NAME.toString(); - public static final String FILENAME = "filename"; + public static final String FILENAME = HttpHeaders.Values.FILENAME.toString(); /** * Content-disposition value for form data. */ - public static final String FORM_DATA = "form-data"; + public static final String FORM_DATA = HttpHeaders.Values.FORM_DATA.toString(); /** * Content-disposition value for file attachment. */ - public static final String ATTACHMENT = "attachment"; + public static final String ATTACHMENT = HttpHeaders.Values.ATTACHMENT.toString(); /** * Content-disposition value for file attachment. */ - public static final String FILE = "file"; + public static final String FILE = HttpHeaders.Values.FILE.toString(); /** * HTTP content type body attribute for multiple uploads. */ - public static final String MULTIPART_MIXED = "multipart/mixed"; + public static final String MULTIPART_MIXED = HttpHeaders.Values.MULTIPART_MIXED.toString(); /** * Charset for 8BIT @@ -68,12 +69,12 @@ final class HttpPostBodyUtil { /** * Default Content-Type in binary form */ - public static final String DEFAULT_BINARY_CONTENT_TYPE = "application/octet-stream"; + public static final String DEFAULT_BINARY_CONTENT_TYPE = HttpHeaders.Values.APPLICATION_OCTET_STREAM.toString(); /** * Default Content-Type in Text form */ - public static final String DEFAULT_TEXT_CONTENT_TYPE = "text/plain"; + public static final String DEFAULT_TEXT_CONTENT_TYPE = HttpHeaders.Values.TEXT_PLAIN.toString(); /** * Allowed mechanism for multipart diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java index 2b15bd1bcc..3c438fa28c 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostMultipartRequestDecoder.java @@ -16,6 +16,7 @@ package io.netty.handler.codec.http.multipart; import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.AsciiString; import io.netty.handler.codec.http.HttpConstants; import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.HttpHeaders; @@ -695,10 +696,10 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest currentFieldAttributes.put(attribute.getName(), attribute); } } - } else if (contents[0].equalsIgnoreCase(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING)) { + } else if (AsciiString.equalsIgnoreCase(contents[0], HttpHeaders.Names.CONTENT_TRANSFER_ENCODING)) { Attribute attribute; try { - attribute = factory.createAttribute(request, HttpHeaders.Names.CONTENT_TRANSFER_ENCODING, + attribute = factory.createAttribute(request, HttpHeaders.Names.CONTENT_TRANSFER_ENCODING.toString(), cleanString(contents[1])); } catch (NullPointerException e) { throw new ErrorDataDecoderException(e); @@ -706,10 +707,10 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest throw new ErrorDataDecoderException(e); } currentFieldAttributes.put(HttpHeaders.Names.CONTENT_TRANSFER_ENCODING, attribute); - } else if (contents[0].equalsIgnoreCase(HttpHeaders.Names.CONTENT_LENGTH)) { + } else if (AsciiString.equalsIgnoreCase(contents[0], HttpHeaders.Names.CONTENT_LENGTH)) { Attribute attribute; try { - attribute = factory.createAttribute(request, HttpHeaders.Names.CONTENT_LENGTH, + attribute = factory.createAttribute(request, HttpHeaders.Names.CONTENT_LENGTH.toString(), cleanString(contents[1])); } catch (NullPointerException e) { throw new ErrorDataDecoderException(e); @@ -717,7 +718,7 @@ public class HttpPostMultipartRequestDecoder implements InterfaceHttpPostRequest throw new ErrorDataDecoderException(e); } currentFieldAttributes.put(HttpHeaders.Names.CONTENT_LENGTH, attribute); - } else if (contents[0].equalsIgnoreCase(HttpHeaders.Names.CONTENT_TYPE)) { + } else if (AsciiString.equalsIgnoreCase(contents[0], HttpHeaders.Names.CONTENT_TYPE)) { // Take care of possible "multipart/mixed" if (contents[1].equalsIgnoreCase(HttpPostBodyUtil.MULTIPART_MIXED)) { if (currentStatus == MultiPartStatus.DISPOSITION) { diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java index 044acf449d..f9269a524e 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoder.java @@ -716,7 +716,7 @@ public class HttpPostRequestEncoder implements ChunkedInput { // "multipart/form-data; boundary=--89421926422648" String lowercased = contentType.toLowerCase(); if (lowercased.startsWith(HttpHeaders.Values.MULTIPART_FORM_DATA) || - lowercased.startsWith(HttpHeaders.Values.APPLICATION_X_WWW_FORM_URLENCODED)) { + lowercased.startsWith(HttpHeaders.Values.APPLICATION_X_WWW_FORM_URLENCODED.toString())) { // ignore } else { headers.add(HttpHeaders.Names.CONTENT_TYPE, contentType); @@ -744,7 +744,7 @@ public class HttpPostRequestEncoder implements ChunkedInput { isChunked = true; if (transferEncoding != null) { headers.remove(HttpHeaders.Names.TRANSFER_ENCODING); - for (String v : transferEncoding) { + for (CharSequence v : transferEncoding) { if (AsciiString.equalsIgnoreCase(v, HttpHeaders.Values.CHUNKED)) { // ignore } else { diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java index 2bd4f4234c..80439b4e0b 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker00.java @@ -198,13 +198,13 @@ public class WebSocketClientHandshaker00 extends WebSocketClientHandshaker { HttpHeaders headers = response.headers(); - String upgrade = headers.get(Names.UPGRADE); + CharSequence upgrade = headers.get(Names.UPGRADE); if (!AsciiString.equalsIgnoreCase(Values.WEBSOCKET, upgrade)) { throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + upgrade); } - String connection = headers.get(Names.CONNECTION); + CharSequence connection = headers.get(Names.CONNECTION); if (!AsciiString.equalsIgnoreCase(Values.UPGRADE, connection)) { throw new WebSocketHandshakeException("Invalid handshake response connection: " + connection); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07.java index 49d3e42a1c..3da78cb321 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker07.java @@ -41,7 +41,7 @@ import java.net.URI; public class WebSocketClientHandshaker07 extends WebSocketClientHandshaker { private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker07.class); - private static final CharSequence WEBSOCKET = HttpHeaders.newEntity(Values.WEBSOCKET.toLowerCase()); + private static final CharSequence WEBSOCKET = Values.WEBSOCKET.toLowerCase(); public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; private String expectedChallengeResponseString; @@ -207,17 +207,17 @@ public class WebSocketClientHandshaker07 extends WebSocketClientHandshaker { throw new WebSocketHandshakeException("Invalid handshake response getStatus: " + response.status()); } - String upgrade = headers.get(Names.UPGRADE); + CharSequence upgrade = headers.get(Names.UPGRADE); if (!AsciiString.equalsIgnoreCase(Values.WEBSOCKET, upgrade)) { throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + upgrade); } - String connection = headers.get(Names.CONNECTION); + CharSequence connection = headers.get(Names.CONNECTION); if (!AsciiString.equalsIgnoreCase(Values.UPGRADE, connection)) { throw new WebSocketHandshakeException("Invalid handshake response connection: " + connection); } - String accept = headers.get(Names.SEC_WEBSOCKET_ACCEPT); + CharSequence accept = headers.get(Names.SEC_WEBSOCKET_ACCEPT); if (accept == null || !accept.equals(expectedChallengeResponseString)) { throw new WebSocketHandshakeException(String.format( "Invalid challenge. Actual: %s. Expected: %s", accept, expectedChallengeResponseString)); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java index 4140211e2b..b8d072c2b8 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker08.java @@ -41,7 +41,7 @@ import java.net.URI; public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker { private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker08.class); - private static final CharSequence WEBSOCKET = HttpHeaders.newEntity(Values.WEBSOCKET.toLowerCase()); + private static final CharSequence WEBSOCKET = Values.WEBSOCKET.toLowerCase(); public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; @@ -208,17 +208,17 @@ public class WebSocketClientHandshaker08 extends WebSocketClientHandshaker { throw new WebSocketHandshakeException("Invalid handshake response getStatus: " + response.status()); } - String upgrade = headers.get(Names.UPGRADE); + CharSequence upgrade = headers.get(Names.UPGRADE); if (!AsciiString.equalsIgnoreCase(Values.WEBSOCKET, upgrade)) { throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + upgrade); } - String connection = headers.get(Names.CONNECTION); + CharSequence connection = headers.get(Names.CONNECTION); if (!AsciiString.equalsIgnoreCase(Values.UPGRADE, connection)) { throw new WebSocketHandshakeException("Invalid handshake response connection: " + connection); } - String accept = headers.get(Names.SEC_WEBSOCKET_ACCEPT); + CharSequence accept = headers.get(Names.SEC_WEBSOCKET_ACCEPT); if (accept == null || !accept.equals(expectedChallengeResponseString)) { throw new WebSocketHandshakeException(String.format( "Invalid challenge. Actual: %s. Expected: %s", accept, expectedChallengeResponseString)); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java index 7c43955a50..4bf36acd1e 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketClientHandshaker13.java @@ -41,7 +41,7 @@ import java.net.URI; public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker { private static final InternalLogger logger = InternalLoggerFactory.getInstance(WebSocketClientHandshaker13.class); - private static final CharSequence WEBSOCKET = HttpHeaders.newEntity(Values.WEBSOCKET.toLowerCase()); + private static final CharSequence WEBSOCKET = Values.WEBSOCKET.toLowerCase(); public static final String MAGIC_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; @@ -218,17 +218,17 @@ public class WebSocketClientHandshaker13 extends WebSocketClientHandshaker { throw new WebSocketHandshakeException("Invalid handshake response getStatus: " + response.status()); } - String upgrade = headers.get(Names.UPGRADE); + CharSequence upgrade = headers.get(Names.UPGRADE); if (!AsciiString.equalsIgnoreCase(Values.WEBSOCKET, upgrade)) { throw new WebSocketHandshakeException("Invalid handshake response upgrade: " + upgrade); } - String connection = headers.get(Names.CONNECTION); + CharSequence connection = headers.get(Names.CONNECTION); if (!AsciiString.equalsIgnoreCase(Values.UPGRADE, connection)) { throw new WebSocketHandshakeException("Invalid handshake response connection: " + connection); } - String accept = headers.get(Names.SEC_WEBSOCKET_ACCEPT); + CharSequence accept = headers.get(Names.SEC_WEBSOCKET_ACCEPT); if (accept == null || !accept.equals(expectedChallengeResponseString)) { throw new WebSocketHandshakeException(String.format( "Invalid challenge. Actual: %s. Expected: %s", accept, expectedChallengeResponseString)); diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker07.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker07.java index 2d2fbbd357..9473a0841a 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker07.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker07.java @@ -35,7 +35,7 @@ import static io.netty.handler.codec.http.HttpVersion.*; */ public class WebSocketServerHandshaker07 extends WebSocketServerHandshaker { - private static final CharSequence WEBSOCKET = HttpHeaders.newEntity(Values.WEBSOCKET.toLowerCase()); + private static final CharSequence WEBSOCKET = Values.WEBSOCKET.toLowerCase(); public static final String WEBSOCKET_07_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; @@ -129,7 +129,7 @@ public class WebSocketServerHandshaker07 extends WebSocketServerHandshaker { res.headers().add(headers); } - String key = req.headers().get(Names.SEC_WEBSOCKET_KEY); + CharSequence key = req.headers().get(Names.SEC_WEBSOCKET_KEY); if (key == null) { throw new WebSocketHandshakeException("not a WebSocket request: missing key"); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java index 930024a6f9..c2ffae2fb9 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker08.java @@ -35,7 +35,7 @@ import static io.netty.handler.codec.http.HttpVersion.*; */ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker { - private static final CharSequence WEBSOCKET = HttpHeaders.newEntity(Values.WEBSOCKET.toLowerCase()); + private static final CharSequence WEBSOCKET = Values.WEBSOCKET.toLowerCase(); public static final String WEBSOCKET_08_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; @@ -128,7 +128,7 @@ public class WebSocketServerHandshaker08 extends WebSocketServerHandshaker { res.headers().add(headers); } - String key = req.headers().get(Names.SEC_WEBSOCKET_KEY); + CharSequence key = req.headers().get(Names.SEC_WEBSOCKET_KEY); if (key == null) { throw new WebSocketHandshakeException("not a WebSocket request: missing key"); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java index 9ef5cdd9f3..a07760443f 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshaker13.java @@ -34,7 +34,7 @@ import static io.netty.handler.codec.http.HttpVersion.*; */ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker { - private static final CharSequence WEBSOCKET = HttpHeaders.newEntity(Values.WEBSOCKET.toLowerCase()); + private static final CharSequence WEBSOCKET = Values.WEBSOCKET.toLowerCase(); public static final String WEBSOCKET_13_ACCEPT_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; @@ -126,7 +126,7 @@ public class WebSocketServerHandshaker13 extends WebSocketServerHandshaker { res.headers().add(headers); } - String key = req.headers().get(Names.SEC_WEBSOCKET_KEY); + CharSequence key = req.headers().get(Names.SEC_WEBSOCKET_KEY); if (key == null) { throw new WebSocketHandshakeException("not a WebSocket request: missing key"); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshakerFactory.java b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshakerFactory.java index 88aca88f79..9c482f931d 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshakerFactory.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/websocketx/WebSocketServerHandshakerFactory.java @@ -112,7 +112,7 @@ public class WebSocketServerHandshakerFactory { */ public WebSocketServerHandshaker newHandshaker(HttpRequest req) { - String version = req.headers().get(Names.SEC_WEBSOCKET_VERSION); + CharSequence version = req.headers().get(Names.SEC_WEBSOCKET_VERSION); if (version != null) { if (version.equals(WebSocketVersion.V13.toHttpHeaderValue())) { // Version 13 of the wire protocol - RFC 6455 (version 17 of the draft hybi specification). diff --git a/codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspHeaders.java index 82b5682146..f5c2508047 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspHeaders.java +++ b/codec-http/src/main/java/io/netty/handler/codec/rtsp/RtspHeaders.java @@ -15,6 +15,7 @@ */ package io.netty.handler.codec.rtsp; +import io.netty.handler.codec.AsciiString; import io.netty.handler.codec.http.HttpHeaders; @@ -30,179 +31,179 @@ public final class RtspHeaders { /** * {@code "Accept"} */ - public static final String ACCEPT = HttpHeaders.Names.ACCEPT; + public static final AsciiString ACCEPT = HttpHeaders.Names.ACCEPT; /** * {@code "Accept-Encoding"} */ - public static final String ACCEPT_ENCODING = HttpHeaders.Names.ACCEPT_ENCODING; + public static final AsciiString ACCEPT_ENCODING = HttpHeaders.Names.ACCEPT_ENCODING; /** * {@code "Accept-Lanugage"} */ - public static final String ACCEPT_LANGUAGE = HttpHeaders.Names.ACCEPT_LANGUAGE; + public static final AsciiString ACCEPT_LANGUAGE = HttpHeaders.Names.ACCEPT_LANGUAGE; /** * {@code "Allow"} */ - public static final String ALLOW = "Allow"; + public static final AsciiString ALLOW = new AsciiString("Allow"); /** * {@code "Authorization"} */ - public static final String AUTHORIZATION = HttpHeaders.Names.AUTHORIZATION; + public static final AsciiString AUTHORIZATION = HttpHeaders.Names.AUTHORIZATION; /** * {@code "Bandwidth"} */ - public static final String BANDWIDTH = "Bandwidth"; + public static final AsciiString BANDWIDTH = new AsciiString("Bandwidth"); /** * {@code "Blocksize"} */ - public static final String BLOCKSIZE = "Blocksize"; + public static final AsciiString BLOCKSIZE = new AsciiString("Blocksize"); /** * {@code "Cache-Control"} */ - public static final String CACHE_CONTROL = HttpHeaders.Names.CACHE_CONTROL; + public static final AsciiString CACHE_CONTROL = HttpHeaders.Names.CACHE_CONTROL; /** * {@code "Conference"} */ - public static final String CONFERENCE = "Conference"; + public static final AsciiString CONFERENCE = new AsciiString("Conference"); /** * {@code "Connection"} */ - public static final String CONNECTION = HttpHeaders.Names.CONNECTION; + public static final AsciiString CONNECTION = HttpHeaders.Names.CONNECTION; /** * {@code "Content-Base"} */ - public static final String CONTENT_BASE = HttpHeaders.Names.CONTENT_BASE; + public static final AsciiString CONTENT_BASE = HttpHeaders.Names.CONTENT_BASE; /** * {@code "Content-Encoding"} */ - public static final String CONTENT_ENCODING = HttpHeaders.Names.CONTENT_ENCODING; + public static final AsciiString CONTENT_ENCODING = HttpHeaders.Names.CONTENT_ENCODING; /** * {@code "Content-Language"} */ - public static final String CONTENT_LANGUAGE = HttpHeaders.Names.CONTENT_LANGUAGE; + public static final AsciiString CONTENT_LANGUAGE = HttpHeaders.Names.CONTENT_LANGUAGE; /** * {@code "Content-Length"} */ - public static final String CONTENT_LENGTH = HttpHeaders.Names.CONTENT_LENGTH; + public static final AsciiString CONTENT_LENGTH = HttpHeaders.Names.CONTENT_LENGTH; /** * {@code "Content-Location"} */ - public static final String CONTENT_LOCATION = HttpHeaders.Names.CONTENT_LOCATION; + public static final AsciiString CONTENT_LOCATION = HttpHeaders.Names.CONTENT_LOCATION; /** * {@code "Content-Type"} */ - public static final String CONTENT_TYPE = HttpHeaders.Names.CONTENT_TYPE; + public static final AsciiString CONTENT_TYPE = HttpHeaders.Names.CONTENT_TYPE; /** * {@code "CSeq"} */ - public static final String CSEQ = "CSeq"; + public static final AsciiString CSEQ = new AsciiString("CSeq"); /** * {@code "Date"} */ - public static final String DATE = HttpHeaders.Names.DATE; + public static final AsciiString DATE = HttpHeaders.Names.DATE; /** * {@code "Expires"} */ - public static final String EXPIRES = HttpHeaders.Names.EXPIRES; + public static final AsciiString EXPIRES = HttpHeaders.Names.EXPIRES; /** * {@code "From"} */ - public static final String FROM = HttpHeaders.Names.FROM; + public static final AsciiString FROM = HttpHeaders.Names.FROM; /** * {@code "Host"} */ - public static final String HOST = HttpHeaders.Names.HOST; + public static final AsciiString HOST = HttpHeaders.Names.HOST; /** * {@code "If-Match"} */ - public static final String IF_MATCH = HttpHeaders.Names.IF_MATCH; + public static final AsciiString IF_MATCH = HttpHeaders.Names.IF_MATCH; /** * {@code "If-Modified-Since"} */ - public static final String IF_MODIFIED_SINCE = HttpHeaders.Names.IF_MODIFIED_SINCE; + public static final AsciiString IF_MODIFIED_SINCE = HttpHeaders.Names.IF_MODIFIED_SINCE; /** * {@code "KeyMgmt"} */ - public static final String KEYMGMT = "KeyMgmt"; + public static final AsciiString KEYMGMT = new AsciiString("KeyMgmt"); /** * {@code "Last-Modified"} */ - public static final String LAST_MODIFIED = HttpHeaders.Names.LAST_MODIFIED; + public static final AsciiString LAST_MODIFIED = HttpHeaders.Names.LAST_MODIFIED; /** * {@code "Proxy-Authenticate"} */ - public static final String PROXY_AUTHENTICATE = HttpHeaders.Names.PROXY_AUTHENTICATE; + public static final AsciiString PROXY_AUTHENTICATE = HttpHeaders.Names.PROXY_AUTHENTICATE; /** * {@code "Proxy-Require"} */ - public static final String PROXY_REQUIRE = "Proxy-Require"; + public static final AsciiString PROXY_REQUIRE = new AsciiString("Proxy-Require"); /** * {@code "Public"} */ - public static final String PUBLIC = "Public"; + public static final AsciiString PUBLIC = new AsciiString("Public"); /** * {@code "Range"} */ - public static final String RANGE = HttpHeaders.Names.RANGE; + public static final AsciiString RANGE = HttpHeaders.Names.RANGE; /** * {@code "Referer"} */ - public static final String REFERER = HttpHeaders.Names.REFERER; + public static final AsciiString REFERER = HttpHeaders.Names.REFERER; /** * {@code "Require"} */ - public static final String REQUIRE = "Require"; + public static final AsciiString REQUIRE = new AsciiString("Require"); /** * {@code "Retry-After"} */ - public static final String RETRT_AFTER = HttpHeaders.Names.RETRY_AFTER; + public static final AsciiString RETRT_AFTER = HttpHeaders.Names.RETRY_AFTER; /** * {@code "RTP-Info"} */ - public static final String RTP_INFO = "RTP-Info"; + public static final AsciiString RTP_INFO = new AsciiString("RTP-Info"); /** * {@code "Scale"} */ - public static final String SCALE = "Scale"; + public static final AsciiString SCALE = new AsciiString("Scale"); /** * {@code "Session"} */ - public static final String SESSION = "Session"; + public static final AsciiString SESSION = new AsciiString("Session"); /** * {@code "Server"} */ - public static final String SERVER = HttpHeaders.Names.SERVER; + public static final AsciiString SERVER = HttpHeaders.Names.SERVER; /** * {@code "Speed"} */ - public static final String SPEED = "Speed"; + public static final AsciiString SPEED = new AsciiString("Speed"); /** * {@code "Timestamp"} */ - public static final String TIMESTAMP = "Timestamp"; + public static final AsciiString TIMESTAMP = new AsciiString("Timestamp"); /** * {@code "Transport"} */ - public static final String TRANSPORT = "Transport"; + public static final AsciiString TRANSPORT = new AsciiString("Transport"); /** * {@code "Unsupported"} */ - public static final String UNSUPPORTED = "Unsupported"; + public static final AsciiString UNSUPPORTED = new AsciiString("Unsupported"); /** * {@code "User-Agent"} */ - public static final String USER_AGENT = HttpHeaders.Names.USER_AGENT; + public static final AsciiString USER_AGENT = HttpHeaders.Names.USER_AGENT; /** * {@code "Vary"} */ - public static final String VARY = HttpHeaders.Names.VARY; + public static final AsciiString VARY = HttpHeaders.Names.VARY; /** * {@code "Via"} */ - public static final String VIA = HttpHeaders.Names.VIA; + public static final AsciiString VIA = HttpHeaders.Names.VIA; /** * {@code "WWW-Authenticate"} */ - public static final String WWW_AUTHENTICATE = HttpHeaders.Names.WWW_AUTHENTICATE; + public static final AsciiString WWW_AUTHENTICATE = HttpHeaders.Names.WWW_AUTHENTICATE; private Names() { } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeaders.java index a00aab5579..154627c632 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeaders.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeaders.java @@ -17,60 +17,129 @@ package io.netty.handler.codec.spdy; import io.netty.handler.codec.AsciiString; import io.netty.handler.codec.DefaultTextHeaders; -import io.netty.handler.codec.TextHeaderProcessor; +import io.netty.handler.codec.Headers; import io.netty.handler.codec.TextHeaders; import java.util.Locale; - public class DefaultSpdyHeaders extends DefaultTextHeaders implements SpdyHeaders { - @Override - protected CharSequence convertName(CharSequence name) { - name = super.convertName(name); - if (name instanceof AsciiString) { - name = ((AsciiString) name).toLowerCase(); - } else { - name = name.toString().toLowerCase(Locale.US); + private static final Headers.ValueConverter SPDY_VALUE_CONVERTER = + new DefaultTextValueTypeConverter() { + @Override + public CharSequence convertObject(Object value) { + CharSequence seq; + if (value instanceof CharSequence) { + seq = (CharSequence) value; + } else { + seq = value.toString(); + } + + SpdyCodecUtil.validateHeaderValue(seq); + return seq; } - SpdyCodecUtil.validateHeaderName(name); - return name; + }; + + private static final NameConverter SPDY_NAME_CONVERTER = new NameConverter() { + @Override + public CharSequence convertName(CharSequence name) { + if (name instanceof AsciiString) { + name = ((AsciiString) name).toLowerCase(); + } else { + name = name.toString().toLowerCase(Locale.US); + } + SpdyCodecUtil.validateHeaderName(name); + return name; + } + }; + + public DefaultSpdyHeaders() { + super(true, SPDY_VALUE_CONVERTER, SPDY_NAME_CONVERTER); } @Override - protected CharSequence convertValue(Object value) { - if (value == null) { - throw new NullPointerException("value"); - } - - CharSequence seq; - if (value instanceof CharSequence) { - seq = (CharSequence) value; - } else { - seq = value.toString(); - } - - SpdyCodecUtil.validateHeaderValue(seq); - return seq; - } - - @Override - public SpdyHeaders add(CharSequence name, Object value) { + public SpdyHeaders add(CharSequence name, CharSequence value) { super.add(name, value); return this; } @Override - public SpdyHeaders add(CharSequence name, Iterable values) { + public SpdyHeaders add(CharSequence name, Iterable values) { super.add(name, values); return this; } @Override - public SpdyHeaders add(CharSequence name, Object... values) { + public SpdyHeaders add(CharSequence name, CharSequence... values) { super.add(name, values); return this; } + @Override + public SpdyHeaders addObject(CharSequence name, Object value) { + super.addObject(name, value); + return this; + } + + @Override + public SpdyHeaders addObject(CharSequence name, Iterable values) { + super.addObject(name, values); + return this; + } + + @Override + public SpdyHeaders addObject(CharSequence name, Object... values) { + super.addObject(name, values); + return this; + } + + @Override + public SpdyHeaders addBoolean(CharSequence name, boolean value) { + super.addBoolean(name, value); + return this; + } + + @Override + public SpdyHeaders addChar(CharSequence name, char value) { + super.addChar(name, value); + return this; + } + + @Override + public SpdyHeaders addByte(CharSequence name, byte value) { + super.addByte(name, value); + return this; + } + + @Override + public SpdyHeaders addShort(CharSequence name, short value) { + super.addShort(name, value); + return this; + } + + @Override + public SpdyHeaders addInt(CharSequence name, int value) { + super.addInt(name, value); + return this; + } + + @Override + public SpdyHeaders addLong(CharSequence name, long value) { + super.addLong(name, value); + return this; + } + + @Override + public SpdyHeaders addFloat(CharSequence name, float value) { + super.addFloat(name, value); + return this; + } + + @Override + public SpdyHeaders addDouble(CharSequence name, double value) { + super.addDouble(name, value); + return this; + } + @Override public SpdyHeaders add(TextHeaders headers) { super.add(headers); @@ -78,23 +147,89 @@ public class DefaultSpdyHeaders extends DefaultTextHeaders implements SpdyHeader } @Override - public SpdyHeaders set(CharSequence name, Object value) { + public SpdyHeaders set(CharSequence name, CharSequence value) { super.set(name, value); return this; } @Override - public SpdyHeaders set(CharSequence name, Object... values) { + public SpdyHeaders set(CharSequence name, Iterable values) { super.set(name, values); return this; } @Override - public SpdyHeaders set(CharSequence name, Iterable values) { + public SpdyHeaders set(CharSequence name, CharSequence... values) { super.set(name, values); return this; } + @Override + public SpdyHeaders setObject(CharSequence name, Object value) { + super.setObject(name, value); + return this; + } + + @Override + public SpdyHeaders setObject(CharSequence name, Iterable values) { + super.setObject(name, values); + return this; + } + + @Override + public SpdyHeaders setObject(CharSequence name, Object... values) { + super.setObject(name, values); + return this; + } + + @Override + public SpdyHeaders setBoolean(CharSequence name, boolean value) { + super.setBoolean(name, value); + return this; + } + + @Override + public SpdyHeaders setChar(CharSequence name, char value) { + super.setChar(name, value); + return this; + } + + @Override + public SpdyHeaders setByte(CharSequence name, byte value) { + super.setByte(name, value); + return this; + } + + @Override + public SpdyHeaders setShort(CharSequence name, short value) { + super.setShort(name, value); + return this; + } + + @Override + public SpdyHeaders setInt(CharSequence name, int value) { + super.setInt(name, value); + return this; + } + + @Override + public SpdyHeaders setLong(CharSequence name, long value) { + super.setLong(name, value); + return this; + } + + @Override + public SpdyHeaders setFloat(CharSequence name, float value) { + super.setFloat(name, value); + return this; + } + + @Override + public SpdyHeaders setDouble(CharSequence name, double value) { + super.setDouble(name, value); + return this; + } + @Override public SpdyHeaders set(TextHeaders headers) { super.set(headers); @@ -102,14 +237,14 @@ public class DefaultSpdyHeaders extends DefaultTextHeaders implements SpdyHeader } @Override - public SpdyHeaders clear() { - super.clear(); + public SpdyHeaders setAll(TextHeaders headers) { + super.setAll(headers); return this; } @Override - public SpdyHeaders forEachEntry(TextHeaderProcessor processor) { - super.forEachEntry(processor); + public SpdyHeaders clear() { + super.clear(); return this; } } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeadersFrame.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeadersFrame.java index 65c05ebdad..e18be4cac7 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeadersFrame.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/DefaultSpdyHeadersFrame.java @@ -98,7 +98,7 @@ public class DefaultSpdyHeadersFrame extends DefaultSpdyStreamFrame } protected void appendHeaders(StringBuilder buf) { - for (Map.Entry e: headers()) { + for (Map.Entry e: headers()) { buf.append(" "); buf.append(e.getKey()); buf.append(": "); diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockRawEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockRawEncoder.java index d2af96c2c0..a5a5692541 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockRawEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaderBlockRawEncoder.java @@ -15,14 +15,15 @@ */ package io.netty.handler.codec.spdy; +import static io.netty.handler.codec.spdy.SpdyCodecUtil.SPDY_MAX_NV_LENGTH; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; +import io.netty.handler.codec.AsciiString; +import io.netty.util.CharsetUtil; import java.util.Set; -import static io.netty.handler.codec.spdy.SpdyCodecUtil.*; - public class SpdyHeaderBlockRawEncoder extends SpdyHeaderBlockEncoder { private final int version; @@ -44,7 +45,7 @@ public class SpdyHeaderBlockRawEncoder extends SpdyHeaderBlockEncoder { @Override public ByteBuf encode(ByteBufAllocator alloc, SpdyHeadersFrame frame) throws Exception { - Set names = frame.headers().names(); + Set names = frame.headers().names(); int numHeaders = names.size(); if (numHeaders == 0) { return Unpooled.EMPTY_BUFFER; @@ -55,15 +56,15 @@ public class SpdyHeaderBlockRawEncoder extends SpdyHeaderBlockEncoder { } ByteBuf headerBlock = alloc.heapBuffer(); writeLengthField(headerBlock, numHeaders); - for (String name: names) { - byte[] nameBytes = name.getBytes("UTF-8"); + for (CharSequence name: names) { + byte[] nameBytes = AsciiString.getBytes(name, CharsetUtil.UTF_8); writeLengthField(headerBlock, nameBytes.length); headerBlock.writeBytes(nameBytes); int savedIndex = headerBlock.writerIndex(); int valueLength = 0; writeLengthField(headerBlock, valueLength); - for (String value: frame.headers().getAll(name)) { - byte[] valueBytes = value.getBytes("UTF-8"); + for (CharSequence value: frame.headers().getAll(name)) { + byte[] valueBytes = AsciiString.getBytes(value, CharsetUtil.UTF_8); if (valueBytes.length > 0) { headerBlock.writeBytes(valueBytes); headerBlock.writeByte(0); diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaders.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaders.java index ec220db5b5..fa342855f0 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaders.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHeaders.java @@ -16,7 +16,6 @@ package io.netty.handler.codec.spdy; import io.netty.handler.codec.AsciiString; -import io.netty.handler.codec.TextHeaderProcessor; import io.netty.handler.codec.TextHeaders; /** @@ -58,32 +57,98 @@ public interface SpdyHeaders extends TextHeaders { } @Override - SpdyHeaders add(CharSequence name, Object value); + SpdyHeaders add(CharSequence name, CharSequence value); @Override - SpdyHeaders add(CharSequence name, Iterable values); + SpdyHeaders add(CharSequence name, Iterable values); @Override - SpdyHeaders add(CharSequence name, Object... values); + SpdyHeaders add(CharSequence name, CharSequence... values); + + @Override + SpdyHeaders addObject(CharSequence name, Object value); + + @Override + SpdyHeaders addObject(CharSequence name, Iterable values); + + @Override + SpdyHeaders addObject(CharSequence name, Object... values); + + @Override + SpdyHeaders addBoolean(CharSequence name, boolean value); + + @Override + SpdyHeaders addByte(CharSequence name, byte value); + + @Override + SpdyHeaders addChar(CharSequence name, char value); + + @Override + SpdyHeaders addShort(CharSequence name, short value); + + @Override + SpdyHeaders addInt(CharSequence name, int value); + + @Override + SpdyHeaders addLong(CharSequence name, long value); + + @Override + SpdyHeaders addFloat(CharSequence name, float value); + + @Override + SpdyHeaders addDouble(CharSequence name, double value); @Override SpdyHeaders add(TextHeaders headers); @Override - SpdyHeaders set(CharSequence name, Object value); + SpdyHeaders set(CharSequence name, CharSequence value); @Override - SpdyHeaders set(CharSequence name, Iterable values); + SpdyHeaders set(CharSequence name, Iterable values); @Override - SpdyHeaders set(CharSequence name, Object... values); + SpdyHeaders set(CharSequence name, CharSequence... values); + + @Override + SpdyHeaders setBoolean(CharSequence name, boolean value); + + @Override + SpdyHeaders setByte(CharSequence name, byte value); + + @Override + SpdyHeaders setChar(CharSequence name, char value); + + @Override + SpdyHeaders setShort(CharSequence name, short value); + + @Override + SpdyHeaders setInt(CharSequence name, int value); + + @Override + SpdyHeaders setLong(CharSequence name, long value); + + @Override + SpdyHeaders setFloat(CharSequence name, float value); + + @Override + SpdyHeaders setDouble(CharSequence name, double value); + + @Override + SpdyHeaders setObject(CharSequence name, Object value); + + @Override + SpdyHeaders setObject(CharSequence name, Iterable values); + + @Override + SpdyHeaders setObject(CharSequence name, Object... values); @Override SpdyHeaders set(TextHeaders headers); @Override - SpdyHeaders clear(); + SpdyHeaders setAll(TextHeaders headers); @Override - SpdyHeaders forEachEntry(TextHeaderProcessor processor); + SpdyHeaders clear(); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java index 9ed2687c79..92e2576258 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpDecoder.java @@ -144,7 +144,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder { return; } - String URL = spdySynStreamFrame.headers().get(PATH); + CharSequence URL = spdySynStreamFrame.headers().get(PATH); spdySynStreamFrame.headers().remove(PATH); // If a client receives a SYN_STREAM without a 'url' header @@ -197,8 +197,8 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder { SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId); spdySynReplyFrame.setLast(true); SpdyHeaders frameHeaders = spdySynReplyFrame.headers(); - frameHeaders.set(STATUS, HttpResponseStatus.REQUEST_HEADER_FIELDS_TOO_LARGE); - frameHeaders.set(VERSION, HttpVersion.HTTP_1_0); + frameHeaders.setInt(STATUS, HttpResponseStatus.REQUEST_HEADER_FIELDS_TOO_LARGE.code()); + frameHeaders.setObject(VERSION, HttpVersion.HTTP_1_0); ctx.writeAndFlush(spdySynReplyFrame); return; } @@ -222,8 +222,8 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder { SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId); spdySynReplyFrame.setLast(true); SpdyHeaders frameHeaders = spdySynReplyFrame.headers(); - frameHeaders.set(STATUS, HttpResponseStatus.BAD_REQUEST); - frameHeaders.set(VERSION, HttpVersion.HTTP_1_0); + frameHeaders.setInt(STATUS, HttpResponseStatus.BAD_REQUEST.code()); + frameHeaders.setObject(VERSION, HttpVersion.HTTP_1_0); ctx.writeAndFlush(spdySynReplyFrame); } } @@ -276,7 +276,7 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder { // Ignore trailers in a truncated HEADERS frame. if (!spdyHeadersFrame.isTruncated()) { - for (Map.Entry e: spdyHeadersFrame.headers()) { + for (Map.Entry e: spdyHeadersFrame.headers()) { fullHttpMessage.headers().add(e.getKey(), e.getValue()); } } @@ -327,9 +327,9 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder { throws Exception { // Create the first line of the request from the name/value pairs SpdyHeaders headers = requestFrame.headers(); - HttpMethod method = HttpMethod.valueOf(headers.get(METHOD)); - String url = headers.get(PATH); - HttpVersion httpVersion = HttpVersion.valueOf(headers.get(VERSION)); + HttpMethod method = HttpMethod.valueOf(headers.getAndConvert(METHOD)); + String url = headers.getAndConvert(PATH); + HttpVersion httpVersion = HttpVersion.valueOf(headers.getAndConvert(VERSION)); headers.remove(METHOD); headers.remove(PATH); headers.remove(VERSION); @@ -340,11 +340,11 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder { headers.remove(SCHEME); // Replace the SPDY host header with the HTTP host header - String host = headers.get(HOST); + CharSequence host = headers.get(HOST); headers.remove(HOST); req.headers().set(HttpHeaders.Names.HOST, host); - for (Map.Entry e: requestFrame.headers()) { + for (Map.Entry e: requestFrame.headers()) { req.headers().add(e.getKey(), e.getValue()); } @@ -363,12 +363,12 @@ public class SpdyHttpDecoder extends MessageToMessageDecoder { // Create the first line of the response from the name/value pairs SpdyHeaders headers = responseFrame.headers(); HttpResponseStatus status = HttpResponseStatus.parseLine(headers.get(STATUS)); - HttpVersion version = HttpVersion.valueOf(headers.get(VERSION)); + HttpVersion version = HttpVersion.valueOf(headers.getAndConvert(VERSION)); headers.remove(STATUS); headers.remove(VERSION); FullHttpResponse res = new DefaultFullHttpResponse(version, status, ctx.alloc().buffer(), validateHeaders); - for (Map.Entry e: responseFrame.headers()) { + for (Map.Entry e: responseFrame.headers()) { res.headers().add(e.getKey(), e.getValue()); } diff --git a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpEncoder.java index 926fca67ec..6e7bc8a0cc 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/spdy/SpdyHttpEncoder.java @@ -234,15 +234,15 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder { SpdyHeaders frameHeaders = spdySynStreamFrame.headers(); if (httpMessage instanceof FullHttpRequest) { HttpRequest httpRequest = (HttpRequest) httpMessage; - frameHeaders.set(METHOD, httpRequest.method()); + frameHeaders.setObject(METHOD, httpRequest.method()); frameHeaders.set(PATH, httpRequest.uri()); - frameHeaders.set(VERSION, httpMessage.protocolVersion()); + frameHeaders.setObject(VERSION, httpMessage.protocolVersion()); } if (httpMessage instanceof HttpResponse) { HttpResponse httpResponse = (HttpResponse) httpMessage; - frameHeaders.set(STATUS, httpResponse.status()); + frameHeaders.setInt(STATUS, httpResponse.status().code()); frameHeaders.set(PATH, URL); - frameHeaders.set(VERSION, httpMessage.protocolVersion()); + frameHeaders.setObject(VERSION, httpMessage.protocolVersion()); spdySynStreamFrame.setUnidirectional(true); } @@ -286,8 +286,8 @@ public class SpdyHttpEncoder extends MessageToMessageEncoder { SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID); SpdyHeaders frameHeaders = spdySynReplyFrame.headers(); // Unfold the first line of the response into name/value pairs - frameHeaders.set(STATUS, httpResponse.status()); - frameHeaders.set(VERSION, httpResponse.protocolVersion()); + frameHeaders.setInt(STATUS, httpResponse.status().code()); + frameHeaders.setObject(VERSION, httpResponse.protocolVersion()); // Transfer the remaining HTTP headers for (Map.Entry entry: httpHeaders) { diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpResponseEncoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpResponseEncoderTest.java index 16df5e71bc..bf9f99d4ea 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpResponseEncoderTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpResponseEncoderTest.java @@ -41,7 +41,8 @@ public class HttpResponseEncoderTest { ByteBuf buffer = channel.readOutbound(); - assertEquals("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n", buffer.toString(CharsetUtil.US_ASCII)); + assertEquals("HTTP/1.1 200 OK\r\n" + HttpHeaders.Names.TRANSFER_ENCODING + ": " + + HttpHeaders.Values.CHUNKED + "\r\n\r\n", buffer.toString(CharsetUtil.US_ASCII)); buffer.release(); assertTrue(channel.writeOutbound(FILE_REGION)); buffer = channel.readOutbound(); diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpServerCodecTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpServerCodecTest.java index f0d0faff2d..b9c5f61682 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpServerCodecTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpServerCodecTest.java @@ -108,7 +108,8 @@ public class HttpServerCodecTest { // Ensure the encoder handles the response after handling 100 Continue. ByteBuf encodedRes = ch.readOutbound(); - assertThat(encodedRes.toString(CharsetUtil.UTF_8), is("HTTP/1.1 201 Created\r\nContent-Length: 2\r\n\r\nOK")); + assertThat(encodedRes.toString(CharsetUtil.UTF_8), is("HTTP/1.1 201 Created\r\n" + + CONTENT_LENGTH + ": 2\r\n\r\nOK")); encodedRes.release(); ch.finish(); diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/cors/CorsHandlerTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/cors/CorsHandlerTest.java index 8c07132b66..6dc82e25c2 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/cors/CorsHandlerTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/cors/CorsHandlerTest.java @@ -83,7 +83,7 @@ public class CorsHandlerTest { final HttpResponse response = preflightRequest(config, "http://localhost:8888", "content-type, xheader1"); assertThat(response.headers().get(ACCESS_CONTROL_ALLOW_ORIGIN), is("http://localhost:8888")); assertThat(response.headers().getAll(ACCESS_CONTROL_ALLOW_METHODS), hasItems("GET", "DELETE")); - assertThat(response.headers().get(VARY), equalTo(ORIGIN)); + assertThat(response.headers().get(VARY), equalTo(ORIGIN.toString())); } @Test @@ -96,7 +96,7 @@ public class CorsHandlerTest { assertThat(response.headers().get(ACCESS_CONTROL_ALLOW_ORIGIN), is("http://localhost:8888")); assertThat(response.headers().getAll(ACCESS_CONTROL_ALLOW_METHODS), hasItems("OPTIONS", "GET")); assertThat(response.headers().getAll(ACCESS_CONTROL_ALLOW_HEADERS), hasItems("content-type", "xheader1")); - assertThat(response.headers().get(VARY), equalTo(ORIGIN)); + assertThat(response.headers().get(VARY), equalTo(ORIGIN.toString())); } @Test @@ -105,7 +105,7 @@ public class CorsHandlerTest { final HttpResponse response = preflightRequest(config, "http://localhost:8888", "content-type, xheader1"); assertThat(response.headers().get(CONTENT_LENGTH), is("0")); assertThat(response.headers().get(DATE), is(notNullValue())); - assertThat(response.headers().get(VARY), equalTo(ORIGIN)); + assertThat(response.headers().get(VARY), equalTo(ORIGIN.toString())); } @Test @@ -115,7 +115,7 @@ public class CorsHandlerTest { .build(); final HttpResponse response = preflightRequest(config, "http://localhost:8888", "content-type, xheader1"); assertThat(response.headers().get("CustomHeader"), equalTo("somevalue")); - assertThat(response.headers().get(VARY), equalTo(ORIGIN)); + assertThat(response.headers().get(VARY), equalTo(ORIGIN.toString())); } @Test @@ -125,7 +125,7 @@ public class CorsHandlerTest { .build(); final HttpResponse response = preflightRequest(config, "http://localhost:8888", "content-type, xheader1"); assertThat(response.headers().getAll("CustomHeader"), hasItems("value1", "value2")); - assertThat(response.headers().get(VARY), equalTo(ORIGIN)); + assertThat(response.headers().get(VARY), equalTo(ORIGIN.toString())); } @Test @@ -135,7 +135,7 @@ public class CorsHandlerTest { .build(); final HttpResponse response = preflightRequest(config, "http://localhost:8888", "content-type, xheader1"); assertThat(response.headers().getAll("CustomHeader"), hasItems("value1", "value2")); - assertThat(response.headers().get(VARY), equalTo(ORIGIN)); + assertThat(response.headers().get(VARY), equalTo(ORIGIN.toString())); } @Test @@ -149,7 +149,7 @@ public class CorsHandlerTest { }).build(); final HttpResponse response = preflightRequest(config, "http://localhost:8888", "content-type, xheader1"); assertThat(response.headers().get("GenHeader"), equalTo("generatedValue")); - assertThat(response.headers().get(VARY), equalTo(ORIGIN)); + assertThat(response.headers().get(VARY), equalTo(ORIGIN.toString())); } @Test @@ -204,7 +204,7 @@ public class CorsHandlerTest { final HttpResponse response = simpleRequest(config, "http://localhost:7777"); assertThat(response.headers().get(ACCESS_CONTROL_ALLOW_CREDENTIALS), equalTo("true")); assertThat(response.headers().get(ACCESS_CONTROL_ALLOW_ORIGIN), equalTo("http://localhost:7777")); - assertThat(response.headers().get(VARY), equalTo(ORIGIN)); + assertThat(response.headers().get(VARY), equalTo(ORIGIN.toString())); } @Test diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoderTest.java index 26273e6160..a74e1c1ce3 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoderTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/multipart/HttpPostRequestEncoderTest.java @@ -18,11 +18,13 @@ package io.netty.handler.codec.http.multipart; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.handler.codec.http.DefaultFullHttpRequest; +import static io.netty.handler.codec.http.HttpHeaders.Names.*; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.multipart.HttpPostRequestEncoder.EncoderMode; import io.netty.util.CharsetUtil; import io.netty.util.internal.StringUtil; + import org.junit.Test; import java.io.File; @@ -47,15 +49,15 @@ public class HttpPostRequestEncoderTest { String content = getRequestBody(encoder); String expected = "--" + multipartDataBoundary + "\r\n" + - "Content-Disposition: form-data; name=\"foo\"" + "\r\n" + - "Content-Type: text/plain; charset=UTF-8" + "\r\n" + + CONTENT_DISPOSITION + ": form-data; name=\"foo\"" + "\r\n" + + CONTENT_TYPE + ": text/plain; charset=UTF-8" + "\r\n" + "\r\n" + "bar" + "\r\n" + "--" + multipartDataBoundary + "\r\n" + - "Content-Disposition: form-data; name=\"quux\"; filename=\"file-01.txt\"" + "\r\n" + - "Content-Type: text/plain" + "\r\n" + - "Content-Transfer-Encoding: binary" + "\r\n" + + CONTENT_DISPOSITION + ": form-data; name=\"quux\"; filename=\"file-01.txt\"" + "\r\n" + + CONTENT_TYPE + ": text/plain" + "\r\n" + + CONTENT_TRANSFER_ENCODING + ": binary" + "\r\n" + "\r\n" + "File 01" + StringUtil.NEWLINE + "\r\n" + @@ -83,25 +85,25 @@ public class HttpPostRequestEncoderTest { String content = getRequestBody(encoder); String expected = "--" + multipartDataBoundary + "\r\n" + - "Content-Disposition: form-data; name=\"foo\"" + "\r\n" + - "Content-Type: text/plain; charset=UTF-8" + "\r\n" + + CONTENT_DISPOSITION + ": form-data; name=\"foo\"" + "\r\n" + + CONTENT_TYPE + ": text/plain; charset=UTF-8" + "\r\n" + "\r\n" + "bar" + "\r\n" + "--" + multipartDataBoundary + "\r\n" + - "Content-Disposition: form-data; name=\"quux\"" + "\r\n" + - "Content-Type: multipart/mixed; boundary=" + multipartMixedBoundary + "\r\n" + + CONTENT_DISPOSITION + ": form-data; name=\"quux\"" + "\r\n" + + CONTENT_TYPE + ": multipart/mixed; boundary=" + multipartMixedBoundary + "\r\n" + "\r\n" + "--" + multipartMixedBoundary + "\r\n" + - "Content-Disposition: attachment; filename=\"file-02.txt\"" + "\r\n" + - "Content-Type: text/plain" + "\r\n" + - "Content-Transfer-Encoding: binary" + "\r\n" + + CONTENT_DISPOSITION + ": attachment; filename=\"file-02.txt\"" + "\r\n" + + CONTENT_TYPE + ": text/plain" + "\r\n" + + CONTENT_TRANSFER_ENCODING + ": binary" + "\r\n" + "\r\n" + "File 01" + StringUtil.NEWLINE + "\r\n" + "--" + multipartMixedBoundary + "\r\n" + - "Content-Disposition: attachment; filename=\"file-02.txt\"" + "\r\n" + - "Content-Type: text/plain" + "\r\n" + - "Content-Transfer-Encoding: binary" + "\r\n" + + CONTENT_DISPOSITION + ": attachment; filename=\"file-02.txt\"" + "\r\n" + + CONTENT_TYPE + ": text/plain" + "\r\n" + + CONTENT_TRANSFER_ENCODING + ": binary" + "\r\n" + "\r\n" + "File 02" + StringUtil.NEWLINE + "\r\n" + @@ -130,20 +132,20 @@ public class HttpPostRequestEncoderTest { String content = getRequestBody(encoder); String expected = "--" + multipartDataBoundary + "\r\n" + - "Content-Disposition: form-data; name=\"foo\"" + "\r\n" + - "Content-Type: text/plain; charset=UTF-8" + "\r\n" + + CONTENT_DISPOSITION + ": form-data; name=\"foo\"" + "\r\n" + + CONTENT_TYPE + ": text/plain; charset=UTF-8" + "\r\n" + "\r\n" + "bar" + "\r\n" + "--" + multipartDataBoundary + "\r\n" + - "Content-Disposition: form-data; name=\"quux\"; filename=\"file-01.txt\"" + "\r\n" + - "Content-Type: text/plain" + "\r\n" + - "Content-Transfer-Encoding: binary" + "\r\n" + + CONTENT_DISPOSITION + ": form-data; name=\"quux\"; filename=\"file-01.txt\"" + "\r\n" + + CONTENT_TYPE + ": text/plain" + "\r\n" + + CONTENT_TRANSFER_ENCODING + ": binary" + "\r\n" + "\r\n" + "File 01" + StringUtil.NEWLINE + "\r\n" + "--" + multipartDataBoundary + "\r\n" + - "Content-Disposition: form-data; name=\"quux\"; filename=\"file-02.txt\"" + "\r\n" + - "Content-Type: text/plain" + "\r\n" + - "Content-Transfer-Encoding: binary" + "\r\n" + + CONTENT_DISPOSITION + ": form-data; name=\"quux\"; filename=\"file-02.txt\"" + "\r\n" + + CONTENT_TYPE + ": text/plain" + "\r\n" + + CONTENT_TRANSFER_ENCODING + ": binary" + "\r\n" + "\r\n" + "File 02" + StringUtil.NEWLINE + "\r\n" + @@ -169,15 +171,15 @@ public class HttpPostRequestEncoderTest { String content = getRequestBody(encoder); String expected = "--" + multipartDataBoundary + "\r\n" + - "Content-Disposition: form-data; name=\"foo\"" + "\r\n" + - "Content-Type: text/plain; charset=UTF-8" + "\r\n" + + CONTENT_DISPOSITION + ": form-data; name=\"foo\"" + "\r\n" + + CONTENT_TYPE + ": text/plain; charset=UTF-8" + "\r\n" + "\r\n" + "bar" + "\r\n" + "--" + multipartDataBoundary + "\r\n" + - "Content-Disposition: form-data; name=\"quux\"; filename=\"file-01.txt\"" + "\r\n" + - "Content-Type: text/plain" + "\r\n" + - "Content-Transfer-Encoding: binary" + "\r\n" + + CONTENT_DISPOSITION + ": form-data; name=\"quux\"; filename=\"file-01.txt\"" + "\r\n" + + CONTENT_TYPE + ": text/plain" + "\r\n" + + CONTENT_TRANSFER_ENCODING + ": binary" + "\r\n" + "\r\n" + "File 01" + StringUtil.NEWLINE + "\r\n" + diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketRequestBuilder.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketRequestBuilder.java index 71e22adaf9..7f1ac827bc 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketRequestBuilder.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketRequestBuilder.java @@ -127,7 +127,7 @@ public class WebSocketRequestBuilder { .method(HttpMethod.GET) .uri("/test") .host("server.example.com") - .upgrade(WEBSOCKET.toLowerCase()) + .upgrade(WEBSOCKET.toLowerCase().toString()) .key("dGhlIHNhbXBsZSBub25jZQ==") .origin("http://example.com") .version13() diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandlerTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandlerTest.java index 98952caf4b..efe9cfabbc 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandlerTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/websocketx/WebSocketServerProtocolHandlerTest.java @@ -95,7 +95,7 @@ public class WebSocketServerProtocolHandlerTest { .uri("/test") .key(null) .connection("Upgrade") - .upgrade(WEBSOCKET.toLowerCase()) + .upgrade(WEBSOCKET.toLowerCase().toString()) .version13() .build(); diff --git a/codec-http/src/test/java/io/netty/handler/codec/spdy/SpdySessionHandlerTest.java b/codec-http/src/test/java/io/netty/handler/codec/spdy/SpdySessionHandlerTest.java index 85b4a518e2..a52151cd94 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/spdy/SpdySessionHandlerTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/spdy/SpdySessionHandlerTest.java @@ -81,9 +81,9 @@ public class SpdySessionHandlerTest { SpdyHeadersFrame spdyHeadersFrame = (SpdyHeadersFrame) msg; assertEquals(streamId, spdyHeadersFrame.streamId()); assertEquals(last, spdyHeadersFrame.isLast()); - for (String name: headers.names()) { - List expectedValues = headers.getAll(name); - List receivedValues = spdyHeadersFrame.headers().getAll(name); + for (CharSequence name: headers.names()) { + List expectedValues = headers.getAll(name); + List receivedValues = spdyHeadersFrame.headers().getAll(name); assertTrue(receivedValues.containsAll(expectedValues)); receivedValues.removeAll(expectedValues); assertTrue(receivedValues.isEmpty()); @@ -357,7 +357,7 @@ public class SpdySessionHandlerTest { int streamId = spdySynStreamFrame.streamId(); SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamId); spdySynReplyFrame.setLast(spdySynStreamFrame.isLast()); - for (Map.Entry entry: spdySynStreamFrame.headers()) { + for (Map.Entry entry: spdySynStreamFrame.headers()) { spdySynReplyFrame.headers().add(entry.getKey(), entry.getValue()); } diff --git a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/DefaultStompHeaders.java b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/DefaultStompHeaders.java index 7df231a82f..dcf26bb290 100644 --- a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/DefaultStompHeaders.java +++ b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/DefaultStompHeaders.java @@ -17,29 +17,94 @@ package io.netty.handler.codec.stomp; import io.netty.handler.codec.DefaultTextHeaders; -import io.netty.handler.codec.TextHeaderProcessor; import io.netty.handler.codec.TextHeaders; public class DefaultStompHeaders extends DefaultTextHeaders implements StompHeaders { @Override - public StompHeaders add(CharSequence name, Object value) { + public StompHeaders add(CharSequence name, CharSequence value) { super.add(name, value); return this; } @Override - public StompHeaders add(CharSequence name, Iterable values) { + public StompHeaders add(CharSequence name, Iterable values) { super.add(name, values); return this; } @Override - public StompHeaders add(CharSequence name, Object... values) { + public StompHeaders add(CharSequence name, CharSequence... values) { super.add(name, values); return this; } + @Override + public StompHeaders addObject(CharSequence name, Object value) { + super.addObject(name, value); + return this; + } + + @Override + public StompHeaders addObject(CharSequence name, Iterable values) { + super.addObject(name, values); + return this; + } + + @Override + public StompHeaders addObject(CharSequence name, Object... values) { + super.addObject(name, values); + return this; + } + + @Override + public StompHeaders addBoolean(CharSequence name, boolean value) { + super.addBoolean(name, value); + return this; + } + + @Override + public StompHeaders addChar(CharSequence name, char value) { + super.addChar(name, value); + return this; + } + + @Override + public StompHeaders addByte(CharSequence name, byte value) { + super.addByte(name, value); + return this; + } + + @Override + public StompHeaders addShort(CharSequence name, short value) { + super.addShort(name, value); + return this; + } + + @Override + public StompHeaders addInt(CharSequence name, int value) { + super.addInt(name, value); + return this; + } + + @Override + public StompHeaders addLong(CharSequence name, long value) { + super.addLong(name, value); + return this; + } + + @Override + public StompHeaders addFloat(CharSequence name, float value) { + super.addFloat(name, value); + return this; + } + + @Override + public StompHeaders addDouble(CharSequence name, double value) { + super.addDouble(name, value); + return this; + } + @Override public StompHeaders add(TextHeaders headers) { super.add(headers); @@ -47,23 +112,89 @@ public class DefaultStompHeaders extends DefaultTextHeaders implements StompHead } @Override - public StompHeaders set(CharSequence name, Object value) { + public StompHeaders set(CharSequence name, CharSequence value) { super.set(name, value); return this; } @Override - public StompHeaders set(CharSequence name, Object... values) { + public StompHeaders set(CharSequence name, Iterable values) { super.set(name, values); return this; } @Override - public StompHeaders set(CharSequence name, Iterable values) { + public StompHeaders set(CharSequence name, CharSequence... values) { super.set(name, values); return this; } + @Override + public StompHeaders setObject(CharSequence name, Object value) { + super.setObject(name, value); + return this; + } + + @Override + public StompHeaders setObject(CharSequence name, Iterable values) { + super.setObject(name, values); + return this; + } + + @Override + public StompHeaders setObject(CharSequence name, Object... values) { + super.setObject(name, values); + return this; + } + + @Override + public StompHeaders setBoolean(CharSequence name, boolean value) { + super.setBoolean(name, value); + return this; + } + + @Override + public StompHeaders setChar(CharSequence name, char value) { + super.setChar(name, value); + return this; + } + + @Override + public StompHeaders setByte(CharSequence name, byte value) { + super.setByte(name, value); + return this; + } + + @Override + public StompHeaders setShort(CharSequence name, short value) { + super.setShort(name, value); + return this; + } + + @Override + public StompHeaders setInt(CharSequence name, int value) { + super.setInt(name, value); + return this; + } + + @Override + public StompHeaders setLong(CharSequence name, long value) { + super.setLong(name, value); + return this; + } + + @Override + public StompHeaders setFloat(CharSequence name, float value) { + super.setFloat(name, value); + return this; + } + + @Override + public StompHeaders setDouble(CharSequence name, double value) { + super.setDouble(name, value); + return this; + } + @Override public StompHeaders set(TextHeaders headers) { super.set(headers); @@ -71,14 +202,14 @@ public class DefaultStompHeaders extends DefaultTextHeaders implements StompHead } @Override - public StompHeaders clear() { - super.clear(); + public StompHeaders setAll(TextHeaders headers) { + super.setAll(headers); return this; } @Override - public StompHeaders forEachEntry(TextHeaderProcessor processor) { - super.forEachEntry(processor); + public StompHeaders clear() { + super.clear(); return this; } } diff --git a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompHeaders.java b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompHeaders.java index b929bb67dc..839103e3a5 100644 --- a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompHeaders.java +++ b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompHeaders.java @@ -16,7 +16,6 @@ package io.netty.handler.codec.stomp; import io.netty.handler.codec.AsciiString; -import io.netty.handler.codec.TextHeaderProcessor; import io.netty.handler.codec.TextHeaders; /** @@ -46,32 +45,98 @@ public interface StompHeaders extends TextHeaders { AsciiString CONTENT_TYPE = new AsciiString("content-type"); @Override - StompHeaders add(CharSequence name, Object value); + StompHeaders add(CharSequence name, CharSequence value); @Override - StompHeaders add(CharSequence name, Iterable values); + StompHeaders add(CharSequence name, Iterable values); @Override - StompHeaders add(CharSequence name, Object... values); + StompHeaders add(CharSequence name, CharSequence... values); + + @Override + StompHeaders addObject(CharSequence name, Object value); + + @Override + StompHeaders addObject(CharSequence name, Iterable values); + + @Override + StompHeaders addObject(CharSequence name, Object... values); + + @Override + StompHeaders addBoolean(CharSequence name, boolean value); + + @Override + StompHeaders addByte(CharSequence name, byte value); + + @Override + StompHeaders addChar(CharSequence name, char value); + + @Override + StompHeaders addShort(CharSequence name, short value); + + @Override + StompHeaders addInt(CharSequence name, int value); + + @Override + StompHeaders addLong(CharSequence name, long value); + + @Override + StompHeaders addFloat(CharSequence name, float value); + + @Override + StompHeaders addDouble(CharSequence name, double value); @Override StompHeaders add(TextHeaders headers); @Override - StompHeaders set(CharSequence name, Object value); + StompHeaders set(CharSequence name, CharSequence value); @Override - StompHeaders set(CharSequence name, Iterable values); + StompHeaders set(CharSequence name, Iterable values); @Override - StompHeaders set(CharSequence name, Object... values); + StompHeaders set(CharSequence name, CharSequence... values); + + @Override + StompHeaders setObject(CharSequence name, Object value); + + @Override + StompHeaders setObject(CharSequence name, Iterable values); + + @Override + StompHeaders setObject(CharSequence name, Object... values); + + @Override + StompHeaders setBoolean(CharSequence name, boolean value); + + @Override + StompHeaders setByte(CharSequence name, byte value); + + @Override + StompHeaders setChar(CharSequence name, char value); + + @Override + StompHeaders setShort(CharSequence name, short value); + + @Override + StompHeaders setInt(CharSequence name, int value); + + @Override + StompHeaders setLong(CharSequence name, long value); + + @Override + StompHeaders setFloat(CharSequence name, float value); + + @Override + StompHeaders setDouble(CharSequence name, double value); @Override StompHeaders set(TextHeaders headers); @Override - StompHeaders clear(); + StompHeaders setAll(TextHeaders headers); @Override - StompHeaders forEachEntry(TextHeaderProcessor processor); + StompHeaders clear(); } diff --git a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeAggregator.java b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeAggregator.java index 217e883037..99f728efb9 100644 --- a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeAggregator.java +++ b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeAggregator.java @@ -69,8 +69,7 @@ public class StompSubframeAggregator @Override protected long contentLength(StompHeadersSubframe start) throws Exception { - String value = start.headers().get(StompHeaders.CONTENT_LENGTH); - return Long.parseLong(value); + return start.headers().getLong(StompHeaders.CONTENT_LENGTH, 0); } @Override diff --git a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeDecoder.java b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeDecoder.java index e24de2d60c..101ea6aaca 100644 --- a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeDecoder.java +++ b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeDecoder.java @@ -219,15 +219,7 @@ public class StompSubframeDecoder extends ReplayingDecoder { } private static long getContentLength(StompHeaders headers, long defaultValue) { - String contentLength = headers.get(StompHeaders.CONTENT_LENGTH); - if (contentLength != null) { - try { - return Long.parseLong(contentLength); - } catch (NumberFormatException ignored) { - return defaultValue; - } - } - return defaultValue; + return headers.getLong(StompHeaders.CONTENT_LENGTH, defaultValue); } private static void skipNullCharacter(ByteBuf buffer) { diff --git a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeEncoder.java b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeEncoder.java index 4da3739967..668bd48f37 100644 --- a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeEncoder.java +++ b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeEncoder.java @@ -22,6 +22,7 @@ import io.netty.handler.codec.AsciiHeadersEncoder.NewlineType; import io.netty.handler.codec.AsciiHeadersEncoder.SeparatorType; import io.netty.handler.codec.MessageToMessageEncoder; import io.netty.util.CharsetUtil; +import io.netty.util.internal.PlatformDependent; import java.util.List; @@ -65,7 +66,12 @@ public class StompSubframeEncoder extends MessageToMessageEncoder buf.writeBytes(frame.command().toString().getBytes(CharsetUtil.US_ASCII)); buf.writeByte(StompConstants.LF); - frame.headers().forEachEntry(new AsciiHeadersEncoder(buf, SeparatorType.COLON, NewlineType.LF)); + try { + frame.headers().forEachEntry(new AsciiHeadersEncoder(buf, SeparatorType.COLON, NewlineType.LF)); + } catch (Exception ex) { + buf.release(); + PlatformDependent.throwException(ex); + } buf.writeByte(StompConstants.LF); return buf; } diff --git a/codec/src/main/java/io/netty/handler/codec/AsciiHeadersEncoder.java b/codec/src/main/java/io/netty/handler/codec/AsciiHeadersEncoder.java index d1ae85cdbc..805603ffb3 100644 --- a/codec/src/main/java/io/netty/handler/codec/AsciiHeadersEncoder.java +++ b/codec/src/main/java/io/netty/handler/codec/AsciiHeadersEncoder.java @@ -17,9 +17,12 @@ package io.netty.handler.codec; -import io.netty.buffer.ByteBuf; +import java.util.Map.Entry; -public final class AsciiHeadersEncoder implements TextHeaderProcessor { +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.TextHeaders.EntryVisitor; + +public final class AsciiHeadersEncoder implements EntryVisitor { /** * The separator characters to insert between a header name and a header value. @@ -74,7 +77,9 @@ public final class AsciiHeadersEncoder implements TextHeaderProcessor { } @Override - public boolean process(CharSequence name, CharSequence value) throws Exception { + public boolean visit(Entry entry) throws Exception { + final CharSequence name = entry.getKey(); + final CharSequence value = entry.getValue(); final ByteBuf buf = this.buf; final int nameLen = name.length(); final int valueLen = value.length(); diff --git a/codec/src/main/java/io/netty/handler/codec/AsciiString.java b/codec/src/main/java/io/netty/handler/codec/AsciiString.java index bc0b42fb8d..7568c5e8c8 100644 --- a/codec/src/main/java/io/netty/handler/codec/AsciiString.java +++ b/codec/src/main/java/io/netty/handler/codec/AsciiString.java @@ -16,27 +16,167 @@ package io.netty.handler.codec; import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; import io.netty.util.internal.EmptyArrays; import java.nio.ByteBuffer; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; import java.util.List; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; /** - * A string which has been encoded into a character encoding whose character always takes a single byte, similarly - * to ASCII. It internally keeps its content in a byte array unlike {@link String}, which uses a character array, - * for reduced memory footprint and faster data transfer from/to byte-based data structures such as a byte array and - * {@link ByteBuf}. It is often used in conjunction with {@link TextHeaders}. + * A string which has been encoded into a character encoding whose character always takes a single byte, similarly to + * ASCII. It internally keeps its content in a byte array unlike {@link String}, which uses a character array, for + * reduced memory footprint and faster data transfer from/to byte-based data structures such as a byte array and + * {@link ByteBuf}. It is often used in conjunction with {@link TextHeaders}. */ public final class AsciiString implements CharSequence, Comparable { public static final AsciiString EMPTY_STRING = new AsciiString(""); + public static final Comparator CASE_INSENSITIVE_ORDER = new Comparator() { + @Override + public int compare(AsciiString o1, AsciiString o2) { + return CHARSEQUENCE_CASE_INSENSITIVE_ORDER.compare(o1, o2); + } + }; + public static final Comparator CASE_SENSITIVE_ORDER = new Comparator() { + @Override + public int compare(AsciiString o1, AsciiString o2) { + return CHARSEQUENCE_CASE_SENSITIVE_ORDER.compare(o1, o2); + } + }; + + public static final Comparator CHARSEQUENCE_CASE_INSENSITIVE_ORDER = new Comparator() { + @Override + public int compare(CharSequence o1, CharSequence o2) { + if (o1 == o2) { + return 0; + } + + AsciiString a1 = o1 instanceof AsciiString ? (AsciiString) o1 : null; + AsciiString a2 = o2 instanceof AsciiString ? (AsciiString) o2 : null; + + int result; + int length1 = o1.length(); + int length2 = o2.length(); + int minLength = Math.min(length1, length2); + if (a1 != null && a2 != null) { + byte[] thisValue = a1.value; + byte[] thatValue = a2.value; + for (int i = 0; i < minLength; i++) { + byte v1 = thisValue[i]; + byte v2 = thatValue[i]; + if (v1 == v2) { + continue; + } + int c1 = toLowerCase(v1) & 0xFF; + int c2 = toLowerCase(v2) & 0xFF; + result = c1 - c2; + if (result != 0) { + return result; + } + } + } else if (a1 != null) { + byte[] thisValue = a1.value; + for (int i = 0; i < minLength; i++) { + int c1 = toLowerCase(thisValue[i]) & 0xFF; + int c2 = toLowerCase(o2.charAt(i)); + result = c1 - c2; + if (result != 0) { + return result; + } + } + } else if (a2 != null) { + byte[] thatValue = a2.value; + for (int i = 0; i < minLength; i++) { + int c1 = toLowerCase(o1.charAt(i)); + int c2 = toLowerCase(thatValue[i]) & 0xFF; + result = c1 - c2; + if (result != 0) { + return result; + } + } + } else { + for (int i = 0; i < minLength; i++) { + int c1 = toLowerCase(o1.charAt(i)); + int c2 = toLowerCase(o2.charAt(i)); + result = c1 - c2; + if (result != 0) { + return result; + } + } + } + + return length1 - length2; + } + }; + + public static final Comparator CHARSEQUENCE_CASE_SENSITIVE_ORDER = new Comparator() { + @Override + public int compare(CharSequence o1, CharSequence o2) { + if (o1 == o2) { + return 0; + } + + AsciiString a1 = o1 instanceof AsciiString ? (AsciiString) o1 : null; + AsciiString a2 = o2 instanceof AsciiString ? (AsciiString) o2 : null; + + int result; + int length1 = o1.length(); + int length2 = o2.length(); + int minLength = Math.min(length1, length2); + if (a1 != null && a2 != null) { + byte[] thisValue = a1.value; + byte[] thatValue = a2.value; + for (int i = 0; i < minLength; i++) { + byte v1 = thisValue[i]; + byte v2 = thatValue[i]; + result = v1 - v2; + if (result != 0) { + return result; + } + } + } else if (a1 != null) { + byte[] thisValue = a1.value; + for (int i = 0; i < minLength; i++) { + int c1 = thisValue[i]; + int c2 = o2.charAt(i); + result = c1 - c2; + if (result != 0) { + return result; + } + } + } else if (a2 != null) { + byte[] thatValue = a2.value; + for (int i = 0; i < minLength; i++) { + int c1 = o1.charAt(i); + int c2 = thatValue[i]; + result = c1 - c2; + if (result != 0) { + return result; + } + } + } else { + for (int i = 0; i < minLength; i++) { + int c1 = o1.charAt(i); + int c2 = o2.charAt(i); + result = c1 - c2; + if (result != 0) { + return result; + } + } + } + + return length1 - length2; + } + }; /** - * Returns the case-insensitive hash code of the specified string. Note that this method uses the same hashing + * Returns the case-insensitive hash code of the specified string. Note that this method uses the same hashing * algorithm with {@link #hashCode()} so that you can put both {@link AsciiString}s and arbitrary * {@link CharSequence}s into the same {@link TextHeaders}. */ @@ -47,7 +187,7 @@ public final class AsciiString implements CharSequence, Comparable int hash = 0; final int end = value.length(); - for (int i = 0; i < end; i ++) { + for (int i = 0; i < end; i++) { hash = hash * 31 ^ value.charAt(i) & 31; } @@ -55,8 +195,8 @@ public final class AsciiString implements CharSequence, Comparable } /** - * Returns {@code true} if both {@link CharSequence}'s are equals when ignore the case. - * This only supports 8-bit ASCII. + * Returns {@code true} if both {@link CharSequence}'s are equals when ignore the case. This only supports 8-bit + * ASCII. */ public static boolean equalsIgnoreCase(CharSequence a, CharSequence b) { if (a == b) { @@ -105,6 +245,36 @@ public final class AsciiString implements CharSequence, Comparable return a.equals(b); } + public static byte[] getBytes(CharSequence v, Charset charset) { + if (v instanceof AsciiString) { + return ((AsciiString) v).array(); + } else if (v instanceof String) { + return ((String) v).getBytes(charset); + } else if (v != null) { + final ByteBuf buf = Unpooled.copiedBuffer(v, charset); + try { + if (buf.hasArray()) { + return buf.array(); + } else { + byte[] result = new byte[buf.readableBytes()]; + buf.readBytes(result); + return result; + } + } finally { + buf.release(); + } + } + return null; + } + + /** + * Returns an {@link AsciiString} containing the given character sequence. If the given string is already a + * {@link AsciiString}, just returns the same instance. + */ + public static AsciiString of(CharSequence string) { + return string instanceof AsciiString ? (AsciiString) string : new AsciiString(string); + } + private final byte[] value; private String string; private int hash; @@ -129,9 +299,8 @@ public final class AsciiString implements CharSequence, Comparable public AsciiString(byte[] value, int start, int length, boolean copy) { checkNull(value); if (start < 0 || start > value.length - length) { - throw new IndexOutOfBoundsException("expected: " + - "0 <= start(" + start + ") <= start + length(" + length + ") <= " + - "value.length(" + value.length + ')'); + throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" + length + + ") <= " + "value.length(" + value.length + ')'); } if (copy || start != 0 || length != value.length) { @@ -148,13 +317,12 @@ public final class AsciiString implements CharSequence, Comparable public AsciiString(char[] value, int start, int length) { checkNull(value); if (start < 0 || start > value.length - length) { - throw new IndexOutOfBoundsException("expected: " + - "0 <= start(" + start + ") <= start + length(" + length + ") <= " + - "value.length(" + value.length + ')'); + throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" + length + + ") <= " + "value.length(" + value.length + ')'); } this.value = new byte[length]; - for (int i = 0, j = start; i < length; i ++, j ++) { + for (int i = 0, j = start; i < length; i++, j++) { this.value[i] = c2b(value[j]); } } @@ -169,9 +337,8 @@ public final class AsciiString implements CharSequence, Comparable } if (start < 0 || length < 0 || length > value.length() - start) { - throw new IndexOutOfBoundsException("expected: " + - "0 <= start(" + start + ") <= start + length(" + length + ") <= " + - "value.length(" + value.length() + ')'); + throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" + length + + ") <= " + "value.length(" + value.length() + ')'); } this.value = new byte[length]; @@ -190,9 +357,8 @@ public final class AsciiString implements CharSequence, Comparable } if (start < 0 || length > value.capacity() - start) { - throw new IndexOutOfBoundsException("expected: " + - "0 <= start(" + start + ") <= start + length(" + length + ") <= " + - "value.capacity(" + value.capacity() + ')'); + throw new IndexOutOfBoundsException("expected: " + "0 <= start(" + start + ") <= start + length(" + length + + ") <= " + "value.capacity(" + value.capacity() + ')'); } if (value.hasArray()) { @@ -266,12 +432,9 @@ public final class AsciiString implements CharSequence, Comparable /** * Copies a range of characters into a new string. * - * @param start - * the offset of the first character. - * @return a new string containing the characters from start to the end of - * the string. - * @throws IndexOutOfBoundsException - * if {@code start < 0} or {@code start > length()}. + * @param start the offset of the first character. + * @return a new string containing the characters from start to the end of the string. + * @throws IndexOutOfBoundsException if {@code start < 0} or {@code start > length()}. */ public AsciiString subSequence(int start) { return subSequence(start, length()); @@ -280,8 +443,8 @@ public final class AsciiString implements CharSequence, Comparable @Override public AsciiString subSequence(int start, int end) { if (start < 0 || start > end || end > length()) { - throw new IndexOutOfBoundsException( - "expected: 0 <= start(" + start + ") <= end (" + end + ") <= length(" + length() + ')'); + throw new IndexOutOfBoundsException("expected: 0 <= start(" + start + ") <= end (" + end + ") <= length(" + + length() + ')'); } final byte[] value = this.value; @@ -304,8 +467,8 @@ public final class AsciiString implements CharSequence, Comparable return hash; } - for (byte b: value) { - hash = hash * 31 ^ b & 31; + for (int i = 0; i < value.length; ++i) { + hash = hash * 31 ^ value[i] & 31; } return this.hash = hash; @@ -331,7 +494,7 @@ public final class AsciiString implements CharSequence, Comparable byte[] thisValue = value; byte[] thatValue = that.value; int end = thisValue.length; - for (int i = 0, j = 0; i < end; i ++, j ++) { + for (int i = 0, j = 0; i < end; i++, j++) { if (thisValue[i] != thatValue[j]) { return false; } @@ -368,24 +531,17 @@ public final class AsciiString implements CharSequence, Comparable } /** - * Compares the specified string to this string using the ASCII values of - * the characters. Returns 0 if the strings contain the same characters in - * the same order. Returns a negative integer if the first non-equal - * character in this string has an ASCII value which is less than the - * ASCII value of the character at the same position in the specified - * string, or if this string is a prefix of the specified string. Returns a - * positive integer if the first non-equal character in this string has a - * ASCII value which is greater than the ASCII value of the character at - * the same position in the specified string, or if the specified string is - * a prefix of this string. + * Compares the specified string to this string using the ASCII values of the characters. Returns 0 if the strings + * contain the same characters in the same order. Returns a negative integer if the first non-equal character in + * this string has an ASCII value which is less than the ASCII value of the character at the same position in the + * specified string, or if this string is a prefix of the specified string. Returns a positive integer if the first + * non-equal character in this string has a ASCII value which is greater than the ASCII value of the character at + * the same position in the specified string, or if the specified string is a prefix of this string. * - * @param string - * the string to compare. - * @return 0 if the strings are equal, a negative integer if this string is - * before the specified string, or a positive integer if this string - * is after the specified string. - * @throws NullPointerException - * if {@code string} is {@code null}. + * @param string the string to compare. + * @return 0 if the strings are equal, a negative integer if this string is before the specified string, or a + * positive integer if this string is after the specified string. + * @throws NullPointerException if {@code string} is {@code null}. */ @Override public int compareTo(CharSequence string) { @@ -398,7 +554,7 @@ public final class AsciiString implements CharSequence, Comparable int length2 = string.length(); int minLength = Math.min(length1, length2); byte[] value = this.value; - for (int i = 0, j = 0; j < minLength; i ++, j ++) { + for (int i = 0, j = 0; j < minLength; i++, j++) { result = (value[i] & 0xFF) - string.charAt(j); if (result != 0) { return result; @@ -409,72 +565,28 @@ public final class AsciiString implements CharSequence, Comparable } /** - * Compares the specified string to this string using the ASCII values of - * the characters, ignoring case differences. Returns 0 if the strings - * contain the same characters in the same order. Returns a negative integer - * if the first non-equal character in this string has an ASCII value which - * is less than the ASCII value of the character at the same position in - * the specified string, or if this string is a prefix of the specified - * string. Returns a positive integer if the first non-equal character in - * this string has an ASCII value which is greater than the ASCII value - * of the character at the same position in the specified string, or if the - * specified string is a prefix of this string. + * Compares the specified string to this string using the ASCII values of the characters, ignoring case differences. + * Returns 0 if the strings contain the same characters in the same order. Returns a negative integer if the first + * non-equal character in this string has an ASCII value which is less than the ASCII value of the character at the + * same position in the specified string, or if this string is a prefix of the specified string. Returns a positive + * integer if the first non-equal character in this string has an ASCII value which is greater than the ASCII value + * of the character at the same position in the specified string, or if the specified string is a prefix of this + * string. * - * @param string - * the string to compare. - * @return 0 if the strings are equal, a negative integer if this string is - * before the specified string, or a positive integer if this string - * is after the specified string. - * @throws NullPointerException - * if {@code string} is {@code null}. + * @param string the string to compare. + * @return 0 if the strings are equal, a negative integer if this string is before the specified string, or a + * positive integer if this string is after the specified string. + * @throws NullPointerException if {@code string} is {@code null}. */ public int compareToIgnoreCase(CharSequence string) { - if (this == string) { - return 0; - } - - int result; - int length1 = length(); - int length2 = string.length(); - int minLength = Math.min(length1, length2); - byte[] thisValue = value; - if (string instanceof AsciiString) { - AsciiString that = (AsciiString) string; - byte[] thatValue = that.value; - for (int i = 0; i < minLength; i ++) { - byte v1 = thisValue[i]; - byte v2 = thatValue[i]; - if (v1 == v2) { - continue; - } - int c1 = toLowerCase(v1) & 0xFF; - int c2 = toLowerCase(v2) & 0xFF; - result = c1 - c2; - if (result != 0) { - return result; - } - } - } else { - for (int i = 0; i < minLength; i ++) { - int c1 = toLowerCase(thisValue[i]) & 0xFF; - int c2 = toLowerCase(string.charAt(i)); - result = c1 - c2; - if (result != 0) { - return result; - } - } - } - - return length1 - length2; + return CHARSEQUENCE_CASE_INSENSITIVE_ORDER.compare(this, string); } /** * Concatenates this string and the specified string. * - * @param string - * the string to concatenate - * @return a new string which is the concatenation of this string and the - * specified string. + * @param string the string to concatenate + * @return a new string which is the concatenation of this string and the specified string. */ public AsciiString concat(CharSequence string) { int thisLen = length(); @@ -501,7 +613,7 @@ public final class AsciiString implements CharSequence, Comparable int newLen = thisLen + thatLen; byte[] newValue = Arrays.copyOf(value, newLen); - for (int i = thisLen, j = 0; i < newLen; i ++, j ++) { + for (int i = thisLen, j = 0; i < newLen; i++, j++) { newValue[i] = c2b(string.charAt(j)); } @@ -509,15 +621,11 @@ public final class AsciiString implements CharSequence, Comparable } /** - * Compares the specified string to this string to determine if the - * specified string is a suffix. + * Compares the specified string to this string to determine if the specified string is a suffix. * - * @param suffix - * the suffix to look for. - * @return {@code true} if the specified string is a suffix of this string, - * {@code false} otherwise. - * @throws NullPointerException - * if {@code suffix} is {@code null}. + * @param suffix the suffix to look for. + * @return {@code true} if the specified string is a suffix of this string, {@code false} otherwise. + * @throws NullPointerException if {@code suffix} is {@code null}. */ public boolean endsWith(CharSequence suffix) { int suffixLen = suffix.length(); @@ -525,13 +633,11 @@ public final class AsciiString implements CharSequence, Comparable } /** - * Compares the specified string to this string ignoring the case of the - * characters and returns true if they are equal. + * Compares the specified string to this string ignoring the case of the characters and returns true if they are + * equal. * - * @param string - * the string to compare. - * @return {@code true} if the specified string is equal to this string, - * {@code false} otherwise. + * @param string the string to compare. + * @return {@code true} if the specified string is equal to this string, {@code false} otherwise. */ public boolean equalsIgnoreCase(CharSequence string) { if (string == this) { @@ -549,7 +655,7 @@ public final class AsciiString implements CharSequence, Comparable return false; } - for (int i = 0; i < thisLen; i ++) { + for (int i = 0; i < thisLen; i++) { char c1 = (char) (value[i] & 0xFF); char c2 = string.charAt(i); if (c1 != c2 && toLowerCase(c1) != toLowerCase(c2)) { @@ -599,7 +705,7 @@ public final class AsciiString implements CharSequence, Comparable final byte[] value = this.value; final char[] buffer = new char[length]; - for (int i = 0, j = start; i < length; i ++, j ++) { + for (int i = 0, j = start; i < length; i++, j++) { buffer[i] = (char) (value[j] & 0xFF); } return buffer; @@ -608,14 +714,10 @@ public final class AsciiString implements CharSequence, Comparable /** * Copies the content of this string to a {@link ByteBuf} using {@link ByteBuf#writeBytes(byte[], int, int)}. * - * @param srcIdx - * the starting offset of characters to copy. - * @param dst - * the destination byte array. - * @param dstIdx - * the starting offset in the destination byte array. - * @param length - * the number of characters to copy. + * @param srcIdx the starting offset of characters to copy. + * @param dst the destination byte array. + * @param dstIdx the starting offset in the destination byte array. + * @param length the number of characters to copy. */ public void copy(int srcIdx, ByteBuf dst, int dstIdx, int length) { if (dst == null) { @@ -626,8 +728,8 @@ public final class AsciiString implements CharSequence, Comparable final int thisLen = value.length; if (srcIdx < 0 || length > thisLen - srcIdx) { - throw new IndexOutOfBoundsException("expected: " + - "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length(" + length + ") <= srcLen(" + thisLen + ')'); + throw new IndexOutOfBoundsException("expected: " + "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length(" + + length + ") <= srcLen(" + thisLen + ')'); } dst.setBytes(dstIdx, value, srcIdx, length); @@ -636,12 +738,9 @@ public final class AsciiString implements CharSequence, Comparable /** * Copies the content of this string to a {@link ByteBuf} using {@link ByteBuf#writeBytes(byte[], int, int)}. * - * @param srcIdx - * the starting offset of characters to copy. - * @param dst - * the destination byte array. - * @param length - * the number of characters to copy. + * @param srcIdx the starting offset of characters to copy. + * @param dst the destination byte array. + * @param length the number of characters to copy. */ public void copy(int srcIdx, ByteBuf dst, int length) { if (dst == null) { @@ -652,8 +751,8 @@ public final class AsciiString implements CharSequence, Comparable final int thisLen = value.length; if (srcIdx < 0 || length > thisLen - srcIdx) { - throw new IndexOutOfBoundsException("expected: " + - "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length(" + length + ") <= srcLen(" + thisLen + ')'); + throw new IndexOutOfBoundsException("expected: " + "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length(" + + length + ") <= srcLen(" + thisLen + ')'); } dst.writeBytes(value, srcIdx, length); @@ -662,14 +761,10 @@ public final class AsciiString implements CharSequence, Comparable /** * Copies the content of this string to a byte array. * - * @param srcIdx - * the starting offset of characters to copy. - * @param dst - * the destination byte array. - * @param dstIdx - * the starting offset in the destination byte array. - * @param length - * the number of characters to copy. + * @param srcIdx the starting offset of characters to copy. + * @param dst the destination byte array. + * @param dstIdx the starting offset in the destination byte array. + * @param length the number of characters to copy. */ public void copy(int srcIdx, byte[] dst, int dstIdx, int length) { if (dst == null) { @@ -680,8 +775,8 @@ public final class AsciiString implements CharSequence, Comparable final int thisLen = value.length; if (srcIdx < 0 || length > thisLen - srcIdx) { - throw new IndexOutOfBoundsException("expected: " + - "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length(" + length + ") <= srcLen(" + thisLen + ')'); + throw new IndexOutOfBoundsException("expected: " + "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length(" + + length + ") <= srcLen(" + thisLen + ')'); } System.arraycopy(value, srcIdx, dst, dstIdx, length); @@ -690,14 +785,10 @@ public final class AsciiString implements CharSequence, Comparable /** * Copied the content of this string to a character array. * - * @param srcIdx - * the starting offset of characters to copy. - * @param dst - * the destination character array. - * @param dstIdx - * the starting offset in the destination byte array. - * @param length - * the number of characters to copy. + * @param srcIdx the starting offset of characters to copy. + * @param dst the destination character array. + * @param dstIdx the starting offset in the destination byte array. + * @param length the number of characters to copy. */ public void copy(int srcIdx, char[] dst, int dstIdx, int length) { if (dst == null) { @@ -708,41 +799,34 @@ public final class AsciiString implements CharSequence, Comparable final int thisLen = value.length; if (srcIdx < 0 || length > thisLen - srcIdx) { - throw new IndexOutOfBoundsException("expected: " + - "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length(" + length + ") <= srcLen(" + thisLen + ')'); + throw new IndexOutOfBoundsException("expected: " + "0 <= srcIdx(" + srcIdx + ") <= srcIdx + length(" + + length + ") <= srcLen(" + thisLen + ')'); } final int dstEnd = dstIdx + length; - for (int i = srcIdx, j = dstIdx; j < dstEnd; i ++, j ++) { + for (int i = srcIdx, j = dstIdx; j < dstEnd; i++, j++) { dst[j] = (char) (value[i] & 0xFF); } } /** - * Searches in this string for the first index of the specified character. - * The search for the character starts at the beginning and moves towards - * the end of this string. + * Searches in this string for the first index of the specified character. The search for the character starts at + * the beginning and moves towards the end of this string. * - * @param c - * the character to find. - * @return the index in this string of the specified character, -1 if the - * character isn't found. + * @param c the character to find. + * @return the index in this string of the specified character, -1 if the character isn't found. */ public int indexOf(int c) { return indexOf(c, 0); } /** - * Searches in this string for the index of the specified character. The - * search for the character starts at the specified offset and moves towards - * the end of this string. + * Searches in this string for the index of the specified character. The search for the character starts at the + * specified offset and moves towards the end of this string. * - * @param c - * the character to find. - * @param start - * the starting offset. - * @return the index in this string of the specified character, -1 if the - * character isn't found. + * @param c the character to find. + * @param start the starting offset. + * @return the index in this string of the specified character, -1 if the character isn't found. */ public int indexOf(int c, int start) { final byte[] value = this.value; @@ -752,7 +836,7 @@ public final class AsciiString implements CharSequence, Comparable start = 0; } - for (int i = start; i < length; i ++) { + for (int i = start; i < length; i++) { if ((value[i] & 0xFF) == c) { return i; } @@ -762,34 +846,27 @@ public final class AsciiString implements CharSequence, Comparable } /** - * Searches in this string for the first index of the specified string. The - * search for the string starts at the beginning and moves towards the end - * of this string. + * Searches in this string for the first index of the specified string. The search for the string starts at the + * beginning and moves towards the end of this string. * - * @param string - * the string to find. - * @return the index of the first character of the specified string in this - * string, -1 if the specified string is not a substring. - * @throws NullPointerException - * if {@code string} is {@code null}. + * @param string the string to find. + * @return the index of the first character of the specified string in this string, -1 if the specified string is + * not a substring. + * @throws NullPointerException if {@code string} is {@code null}. */ public int indexOf(CharSequence string) { return indexOf(string, 0); } /** - * Searches in this string for the index of the specified string. The search - * for the string starts at the specified offset and moves towards the end - * of this string. + * Searches in this string for the index of the specified string. The search for the string starts at the specified + * offset and moves towards the end of this string. * - * @param subString - * the string to find. - * @param start - * the starting offset. - * @return the index of the first character of the specified string in this - * string, -1 if the specified string is not a substring. - * @throws NullPointerException - * if {@code subString} is {@code null}. + * @param subString the string to find. + * @param start the starting offset. + * @return the index of the first character of the specified string in this string, -1 if the specified string is + * not a substring. + * @throws NullPointerException if {@code subString} is {@code null}. */ public int indexOf(CharSequence subString, int start) { if (start < 0) { @@ -825,30 +902,23 @@ public final class AsciiString implements CharSequence, Comparable } /** - * Searches in this string for the last index of the specified character. - * The search for the character starts at the end and moves towards the - * beginning of this string. + * Searches in this string for the last index of the specified character. The search for the character starts at the + * end and moves towards the beginning of this string. * - * @param c - * the character to find. - * @return the index in this string of the specified character, -1 if the - * character isn't found. + * @param c the character to find. + * @return the index in this string of the specified character, -1 if the character isn't found. */ public int lastIndexOf(int c) { return lastIndexOf(c, length() - 1); } /** - * Searches in this string for the index of the specified character. The - * search for the character starts at the specified offset and moves towards - * the beginning of this string. + * Searches in this string for the index of the specified character. The search for the character starts at the + * specified offset and moves towards the beginning of this string. * - * @param c - * the character to find. - * @param start - * the starting offset. - * @return the index in this string of the specified character, -1 if the - * character isn't found. + * @param c the character to find. + * @param start the starting offset. + * @return the index in this string of the specified character, -1 if the character isn't found. */ public int lastIndexOf(int c, int start) { if (start >= 0) { @@ -857,7 +927,7 @@ public final class AsciiString implements CharSequence, Comparable if (start >= length) { start = length - 1; } - for (int i = start; i >= 0; -- i) { + for (int i = start; i >= 0; --i) { if ((value[i] & 0xFF) == c) { return i; } @@ -867,16 +937,13 @@ public final class AsciiString implements CharSequence, Comparable } /** - * Searches in this string for the last index of the specified string. The - * search for the string starts at the end and moves towards the beginning - * of this string. + * Searches in this string for the last index of the specified string. The search for the string starts at the end + * and moves towards the beginning of this string. * - * @param string - * the string to find. - * @return the index of the first character of the specified string in this - * string, -1 if the specified string is not a substring. - * @throws NullPointerException - * if {@code string} is {@code null}. + * @param string the string to find. + * @return the index of the first character of the specified string in this string, -1 if the specified string is + * not a substring. + * @throws NullPointerException if {@code string} is {@code null}. */ public int lastIndexOf(CharSequence string) { // Use count instead of count - 1 so lastIndexOf("") answers count @@ -884,18 +951,14 @@ public final class AsciiString implements CharSequence, Comparable } /** - * Searches in this string for the index of the specified string. The search - * for the string starts at the specified offset and moves towards the - * beginning of this string. + * Searches in this string for the index of the specified string. The search for the string starts at the specified + * offset and moves towards the beginning of this string. * - * @param subString - * the string to find. - * @param start - * the starting offset. - * @return the index of the first character of the specified string in this - * string , -1 if the specified string is not a substring. - * @throws NullPointerException - * if {@code subString} is {@code null}. + * @param subString the string to find. + * @param start the starting offset. + * @return the index of the first character of the specified string in this string , -1 if the specified string is + * not a substring. + * @throws NullPointerException if {@code subString} is {@code null}. */ public int lastIndexOf(CharSequence subString, int start) { final byte[] value = this.value; @@ -940,21 +1003,15 @@ public final class AsciiString implements CharSequence, Comparable } /** - * Compares the specified string to this string and compares the specified - * range of characters to determine if they are the same. + * Compares the specified string to this string and compares the specified range of characters to determine if they + * are the same. * - * @param thisStart - * the starting offset in this string. - * @param string - * the string to compare. - * @param start - * the starting offset in the specified string. - * @param length - * the number of characters to compare. - * @return {@code true} if the ranges of characters are equal, {@code false} - * otherwise - * @throws NullPointerException - * if {@code string} is {@code null}. + * @param thisStart the starting offset in this string. + * @param string the string to compare. + * @param start the starting offset in the specified string. + * @param length the number of characters to compare. + * @return {@code true} if the ranges of characters are equal, {@code false} otherwise + * @throws NullPointerException if {@code string} is {@code null}. */ public boolean regionMatches(int thisStart, CharSequence string, int start, int length) { if (string == null) { @@ -976,7 +1033,7 @@ public final class AsciiString implements CharSequence, Comparable } final int thisEnd = thisStart + length; - for (int i = thisStart, j = start; i < thisEnd; i ++, j ++) { + for (int i = thisStart, j = start; i < thisEnd; i++, j++) { if ((value[i] & 0xFF) != string.charAt(j)) { return false; } @@ -985,27 +1042,18 @@ public final class AsciiString implements CharSequence, Comparable } /** - * Compares the specified string to this string and compares the specified - * range of characters to determine if they are the same. When ignoreCase is - * true, the case of the characters is ignored during the comparison. + * Compares the specified string to this string and compares the specified range of characters to determine if they + * are the same. When ignoreCase is true, the case of the characters is ignored during the comparison. * - * @param ignoreCase - * specifies if case should be ignored. - * @param thisStart - * the starting offset in this string. - * @param string - * the string to compare. - * @param start - * the starting offset in the specified string. - * @param length - * the number of characters to compare. - * @return {@code true} if the ranges of characters are equal, {@code false} - * otherwise. - * @throws NullPointerException - * if {@code string} is {@code null}. + * @param ignoreCase specifies if case should be ignored. + * @param thisStart the starting offset in this string. + * @param string the string to compare. + * @param start the starting offset in the specified string. + * @param length the number of characters to compare. + * @return {@code true} if the ranges of characters are equal, {@code false} otherwise. + * @throws NullPointerException if {@code string} is {@code null}. */ - public boolean regionMatches(boolean ignoreCase, int thisStart, - CharSequence string, int start, int length) { + public boolean regionMatches(boolean ignoreCase, int thisStart, CharSequence string, int start, int length) { if (!ignoreCase) { return regionMatches(thisStart, string, start, length); } @@ -1035,13 +1083,10 @@ public final class AsciiString implements CharSequence, Comparable } /** - * Copies this string replacing occurrences of the specified character with - * another character. + * Copies this string replacing occurrences of the specified character with another character. * - * @param oldChar - * the character to replace. - * @param newChar - * the replacement character. + * @param oldChar the character to replace. + * @param newChar the replacement character. * @return a new string with occurrences of oldChar replaced by newChar. */ public AsciiString replace(char oldChar, char newChar) { @@ -1053,7 +1098,7 @@ public final class AsciiString implements CharSequence, Comparable final byte[] value = this.value; final int count = value.length; byte[] buffer = new byte[count]; - for (int i = 0, j = 0; i < value.length; i ++, j ++) { + for (int i = 0, j = 0; i < value.length; i++, j++) { byte b = value[i]; if ((char) (b & 0xFF) == oldChar) { b = (byte) newChar; @@ -1065,49 +1110,41 @@ public final class AsciiString implements CharSequence, Comparable } /** - * Compares the specified string to this string to determine if the - * specified string is a prefix. + * Compares the specified string to this string to determine if the specified string is a prefix. * - * @param prefix - * the string to look for. - * @return {@code true} if the specified string is a prefix of this string, - * {@code false} otherwise - * @throws NullPointerException - * if {@code prefix} is {@code null}. + * @param prefix the string to look for. + * @return {@code true} if the specified string is a prefix of this string, {@code false} otherwise + * @throws NullPointerException if {@code prefix} is {@code null}. */ public boolean startsWith(CharSequence prefix) { return startsWith(prefix, 0); } /** - * Compares the specified string to this string, starting at the specified - * offset, to determine if the specified string is a prefix. + * Compares the specified string to this string, starting at the specified offset, to determine if the specified + * string is a prefix. * - * @param prefix - * the string to look for. - * @param start - * the starting offset. - * @return {@code true} if the specified string occurs in this string at the - * specified offset, {@code false} otherwise. - * @throws NullPointerException - * if {@code prefix} is {@code null}. + * @param prefix the string to look for. + * @param start the starting offset. + * @return {@code true} if the specified string occurs in this string at the specified offset, {@code false} + * otherwise. + * @throws NullPointerException if {@code prefix} is {@code null}. */ public boolean startsWith(CharSequence prefix, int start) { return regionMatches(start, prefix, 0, prefix.length()); } /** - * Converts the characters in this string to lowercase, using the default - * Locale. + * Converts the characters in this string to lowercase, using the default Locale. * - * @return a new string containing the lowercase characters equivalent to - * the characters in this string. + * @return a new string containing the lowercase characters equivalent to the characters in this string. */ public AsciiString toLowerCase() { boolean lowercased = true; final byte[] value = this.value; - - for (byte b: value) { + int i, j; + for (i = 0; i < value.length; ++i) { + byte b = value[i]; if (b >= 'A' && b <= 'Z') { lowercased = false; break; @@ -1121,7 +1158,7 @@ public final class AsciiString implements CharSequence, Comparable final int length = value.length; final byte[] newValue = new byte[length]; - for (int i = 0, j = 0; i < length; i ++, j ++) { + for (i = 0, j = 0; i < length; ++i, ++j) { newValue[i] = toLowerCase(value[j]); } @@ -1129,16 +1166,16 @@ public final class AsciiString implements CharSequence, Comparable } /** - * Converts the characters in this string to uppercase, using the default - * Locale. + * Converts the characters in this string to uppercase, using the default Locale. * - * @return a new string containing the uppercase characters equivalent to - * the characters in this string. + * @return a new string containing the uppercase characters equivalent to the characters in this string. */ public AsciiString toUpperCase() { final byte[] value = this.value; boolean uppercased = true; - for (byte b: value) { + int i, j; + for (i = 0; i < value.length; ++i) { + byte b = value[i]; if (b >= 'a' && b <= 'z') { uppercased = false; break; @@ -1152,7 +1189,7 @@ public final class AsciiString implements CharSequence, Comparable final int length = value.length; final byte[] newValue = new byte[length]; - for (int i = 0, j = 0; i < length; i ++, j ++) { + for (i = 0, j = 0; i < length; ++i, ++j) { newValue[i] = toUpperCase(value[j]); } @@ -1160,21 +1197,19 @@ public final class AsciiString implements CharSequence, Comparable } /** - * Copies this string removing white space characters from the beginning and - * end of the string. + * Copies this string removing white space characters from the beginning and end of the string. * - * @return a new string with characters {@code <= \\u0020} removed from - * the beginning and the end. + * @return a new string with characters {@code <= \\u0020} removed from the beginning and the end. */ public AsciiString trim() { final byte[] value = this.value; int start = 0, last = value.length; int end = last; while (start <= end && value[start] <= ' ') { - start ++; + start++; } while (end >= start && value[end] <= ' ') { - end --; + end--; } if (start == 0 && end == last) { return this; @@ -1183,11 +1218,9 @@ public final class AsciiString implements CharSequence, Comparable } /** - * Compares a {@code CharSequence} to this {@code String} to determine if - * their contents are equal. + * Compares a {@code CharSequence} to this {@code String} to determine if their contents are equal. * - * @param cs - * the character sequence to compare to. + * @param cs the character sequence to compare to. * @return {@code true} if equal, otherwise {@code false} */ public boolean contentEquals(CharSequence cs) { @@ -1211,35 +1244,24 @@ public final class AsciiString implements CharSequence, Comparable /** * Determines whether this string matches a given regular expression. * - * @param expr - * the regular expression to be matched. + * @param expr the regular expression to be matched. * @return {@code true} if the expression matches, otherwise {@code false}. - * @throws PatternSyntaxException - * if the syntax of the supplied regular expression is not - * valid. - * @throws NullPointerException - * if {@code expr} is {@code null}. + * @throws PatternSyntaxException if the syntax of the supplied regular expression is not valid. + * @throws NullPointerException if {@code expr} is {@code null}. */ public boolean matches(String expr) { return Pattern.matches(expr, this); } /** - * Splits this string using the supplied regular expression {@code expr}. - * The parameter {@code max} controls the behavior how many times the - * pattern is applied to the string. + * Splits this string using the supplied regular expression {@code expr}. The parameter {@code max} controls the + * behavior how many times the pattern is applied to the string. * - * @param expr - * the regular expression used to divide the string. - * @param max - * the number of entries in the resulting array. - * @return an array of Strings created by separating the string along - * matches of the regular expression. - * @throws NullPointerException - * if {@code expr} is {@code null}. - * @throws PatternSyntaxException - * if the syntax of the supplied regular expression is not - * valid. + * @param expr the regular expression used to divide the string. + * @param max the number of entries in the resulting array. + * @return an array of Strings created by separating the string along matches of the regular expression. + * @throws NullPointerException if {@code expr} is {@code null}. + * @throws PatternSyntaxException if the syntax of the supplied regular expression is not valid. * @see Pattern#split(CharSequence, int) */ public AsciiString[] split(String expr, int max) { @@ -1248,7 +1270,7 @@ public final class AsciiString implements CharSequence, Comparable private static AsciiString[] toAsciiStringArray(String[] jdkResult) { AsciiString[] res = new AsciiString[jdkResult.length]; - for (int i = 0; i < jdkResult.length; i ++) { + for (int i = 0; i < jdkResult.length; i++) { res[i] = new AsciiString(jdkResult[i]); } return res; @@ -1263,7 +1285,7 @@ public final class AsciiString implements CharSequence, Comparable int start = 0; final byte[] value = this.value; final int length = value.length; - for (int i = start; i < length; i ++) { + for (int i = start; i < length; i++) { if (charAt(i) == delim) { if (start == i) { res.add(EMPTY_STRING); @@ -1282,7 +1304,7 @@ public final class AsciiString implements CharSequence, Comparable res.add(new AsciiString(value, start, length - start, false)); } else { // Truncate trailing empty elements. - for (int i = res.size() - 1; i >= 0; i --) { + for (int i = res.size() - 1; i >= 0; i--) { if (res.get(i).isEmpty()) { res.remove(i); } else { @@ -1296,13 +1318,10 @@ public final class AsciiString implements CharSequence, Comparable } /** - * Determines if this {@code String} contains the sequence of characters in - * the {@code CharSequence} passed. + * Determines if this {@code String} contains the sequence of characters in the {@code CharSequence} passed. * - * @param cs - * the character sequence to search for. - * @return {@code true} if the sequence of characters are contained in this - * string, otherwise {@code false}. + * @param cs the character sequence to search for. + * @return {@code true} if the sequence of characters are contained in this string, otherwise {@code false}. */ public boolean contains(CharSequence cs) { if (cs == null) { @@ -1334,7 +1353,7 @@ public final class AsciiString implements CharSequence, Comparable int i = start; boolean negative = charAt(i) == '-'; - if (negative && ++ i == end) { + if (negative && ++i == end) { throw new NumberFormatException(subSequence(start, end).toString()); } @@ -1347,7 +1366,7 @@ public final class AsciiString implements CharSequence, Comparable int result = 0; int offset = start; while (offset < end) { - int digit = Character.digit((char) (value[offset ++] & 0xFF), radix); + int digit = Character.digit((char) (value[offset++] & 0xFF), radix); if (digit == -1) { throw new NumberFormatException(subSequence(start, end).toString()); } @@ -1392,7 +1411,7 @@ public final class AsciiString implements CharSequence, Comparable int i = start; boolean negative = charAt(i) == '-'; - if (negative && ++ i == end) { + if (negative && ++i == end) { throw new NumberFormatException(subSequence(start, end).toString()); } @@ -1405,7 +1424,7 @@ public final class AsciiString implements CharSequence, Comparable long result = 0; int offset = start; while (offset < end) { - int digit = Character.digit((char) (value[offset ++] & 0xFF), radix); + int digit = Character.digit((char) (value[offset++] & 0xFF), radix); if (digit == -1) { throw new NumberFormatException(subSequence(start, end).toString()); } diff --git a/codec/src/main/java/io/netty/handler/codec/BinaryHeaders.java b/codec/src/main/java/io/netty/handler/codec/BinaryHeaders.java new file mode 100644 index 0000000000..3aca485d19 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/BinaryHeaders.java @@ -0,0 +1,138 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project 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 io.netty.handler.codec; + +/** + * A typical {@code AsciiString} multimap used by protocols that use binary headers (such as HTTP/2) for the + * representation of arbitrary key-value data. {@link AsciiString} is just a wrapper around a byte array but provides + * some additional utility when handling text data. + */ +public interface BinaryHeaders extends Headers { + /** + * A visitor that helps reduce GC pressure while iterating over a collection of {@link Headers}. + */ + interface EntryVisitor extends Headers.EntryVisitor { + } + + /** + * A visitor that helps reduce GC pressure while iterating over a collection of {@link Headers}. + */ + interface NameVisitor extends Headers.NameVisitor { + } + + @Override + BinaryHeaders add(AsciiString name, AsciiString value); + + @Override + BinaryHeaders add(AsciiString name, Iterable values); + + @Override + BinaryHeaders add(AsciiString name, AsciiString... values); + + @Override + BinaryHeaders addObject(AsciiString name, Object value); + + @Override + BinaryHeaders addObject(AsciiString name, Iterable values); + + @Override + BinaryHeaders addObject(AsciiString name, Object... values); + + @Override + BinaryHeaders addBoolean(AsciiString name, boolean value); + + @Override + BinaryHeaders addByte(AsciiString name, byte value); + + @Override + BinaryHeaders addChar(AsciiString name, char value); + + @Override + BinaryHeaders addShort(AsciiString name, short value); + + @Override + BinaryHeaders addInt(AsciiString name, int value); + + @Override + BinaryHeaders addLong(AsciiString name, long value); + + @Override + BinaryHeaders addFloat(AsciiString name, float value); + + @Override + BinaryHeaders addDouble(AsciiString name, double value); + + /** + * See {@link Headers#add(Headers)} + */ + BinaryHeaders add(BinaryHeaders headers); + + @Override + BinaryHeaders set(AsciiString name, AsciiString value); + + @Override + BinaryHeaders set(AsciiString name, Iterable values); + + @Override + BinaryHeaders set(AsciiString name, AsciiString... values); + + @Override + BinaryHeaders setObject(AsciiString name, Object value); + + @Override + BinaryHeaders setObject(AsciiString name, Iterable values); + + @Override + BinaryHeaders setObject(AsciiString name, Object... values); + + @Override + BinaryHeaders setBoolean(AsciiString name, boolean value); + + @Override + BinaryHeaders setByte(AsciiString name, byte value); + + @Override + BinaryHeaders setChar(AsciiString name, char value); + + @Override + BinaryHeaders setShort(AsciiString name, short value); + + @Override + BinaryHeaders setInt(AsciiString name, int value); + + @Override + BinaryHeaders setLong(AsciiString name, long value); + + @Override + BinaryHeaders setFloat(AsciiString name, float value); + + @Override + BinaryHeaders setDouble(AsciiString name, double value); + + /** + * See {@link Headers#set(Headers)} + */ + BinaryHeaders set(BinaryHeaders headers); + + /** + * See {@link Headers#setAll(Headers)} + */ + BinaryHeaders setAll(BinaryHeaders headers); + + @Override + BinaryHeaders clear(); +} diff --git a/codec/src/main/java/io/netty/handler/codec/ConvertibleHeaders.java b/codec/src/main/java/io/netty/handler/codec/ConvertibleHeaders.java new file mode 100644 index 0000000000..6ef16561b5 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/ConvertibleHeaders.java @@ -0,0 +1,112 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project 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 io.netty.handler.codec; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * Extension to the {@link Headers} interface to provide methods which convert the + * native {@code UnconvertedType} to the not-native {@code ConvertedType} + */ +public interface ConvertibleHeaders extends Headers { + + /** + * Interface to do conversions to and from the two generic type parameters + */ + interface TypeConverter { + /** + * Convert a native value + * @param value The value to be converted + * @return The conversion results + */ + ConvertedType toConvertedType(UnconvertedType value); + + /** + * Undo a conversion and restore the original native type + * @param value The converted value + * @return The original native type + */ + UnconvertedType toUnconvertedType(ConvertedType value); + } + + /** + * Invokes {@link Headers#get(Object)} and does a conversion on the results if not {@code null} + * @param name The name of entry to get + * @return The value corresponding to {@code name} and then converted + */ + ConvertedType getAndConvert(UnconvertedType name); + + /** + * Invokes {@link Headers#get(Object, Object)} and does a conversion on the results if not {@code null} + * @param name The name of entry to get + * @return The value corresponding to {@code name} and then converted + */ + ConvertedType getAndConvert(UnconvertedType name, ConvertedType defaultValue); + + /** + * Invokes {@link Headers#getAndRemove(Object)} and does a conversion on the results if not {@code null} + * @param name The name of entry to get + * @return The value corresponding to {@code name} and then converted + */ + ConvertedType getAndRemoveAndConvert(UnconvertedType name); + + /** + * Invokes {@link Headers#getAndRemove(Object, Object)} and does + * a conversion on the results if not {@code null} + * @param name The name of entry to get + * @return The value corresponding to {@code name} and then converted + */ + ConvertedType getAndRemoveAndConvert(UnconvertedType name, ConvertedType defaultValue); + + /** + * Invokes {@link Headers#getAll(Object)} and does a conversion on the results if not {@code null} + * @param name The name of entry to get + * @return The values corresponding to {@code name} and then converted + */ + List getAllAndConvert(UnconvertedType name); + + /** + * Invokes {@link Headers#getAllAndRemove(Object)} and does a conversion on the results if not {@code null} + * @param name The name of entry to get + * @return The values corresponding to {@code name} and then converted + */ + List getAllAndRemoveAndConvert(UnconvertedType name); + + /** + * Invokes {@link Headers#entries()} and lazily does a conversion on the results as they are accessed + * + * @return The values corresponding to {@code name} and then lazily converted + */ + List> entriesConverted(); + + /** + * Invokes {@link Headers#iterator()} and lazily does a conversion on the results as they are accessed + * + * @return Iterator which will provide converted values corresponding to {@code name} + */ + Iterator> iteratorConverted(); + + /** + * Invokes {@link Headers#names()} and does a conversion on the results + * + * @return The values corresponding to {@code name} and then converted + */ + Set namesAndConvert(Comparator comparator); +} diff --git a/codec/src/main/java/io/netty/handler/codec/DefaultBinaryHeaders.java b/codec/src/main/java/io/netty/handler/codec/DefaultBinaryHeaders.java new file mode 100644 index 0000000000..6861680caa --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/DefaultBinaryHeaders.java @@ -0,0 +1,349 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project 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 io.netty.handler.codec; + +import io.netty.util.internal.PlatformDependent; + +import java.text.ParseException; + +import static io.netty.handler.codec.AsciiString.*; + +public class DefaultBinaryHeaders extends DefaultHeaders implements BinaryHeaders { + private static final HashCodeGenerator ASCII_HASH_CODE_GENERATOR = + new HashCodeGenerator() { + @Override + public int generateHashCode(AsciiString name) { + return AsciiString.caseInsensitiveHashCode(name); + } + }; + + private static final ValueConverter OBJECT_TO_ASCII = new ValueConverter() { + @Override + public AsciiString convertObject(Object value) { + if (value instanceof AsciiString) { + return (AsciiString) value; + } + if (value instanceof CharSequence) { + return new AsciiString((CharSequence) value); + } + return new AsciiString(value.toString()); + } + + @Override + public AsciiString convertInt(int value) { + return new AsciiString(String.valueOf(value)); + } + + @Override + public AsciiString convertLong(long value) { + return new AsciiString(String.valueOf(value)); + } + + @Override + public AsciiString convertDouble(double value) { + return new AsciiString(String.valueOf(value)); + } + + @Override + public AsciiString convertChar(char value) { + return new AsciiString(String.valueOf(value)); + } + + @Override + public AsciiString convertBoolean(boolean value) { + return new AsciiString(String.valueOf(value)); + } + + @Override + public AsciiString convertFloat(float value) { + return new AsciiString(String.valueOf(value)); + } + + @Override + public int convertToInt(AsciiString value) { + return value.parseInt(); + } + + @Override + public long convertToLong(AsciiString value) { + return value.parseLong(); + } + + @Override + public long convertToTimeMillis(AsciiString value) { + try { + return HeaderDateFormat.get().parse(value.toString()); + } catch (ParseException e) { + PlatformDependent.throwException(e); + } + return 0; + } + + @Override + public double convertToDouble(AsciiString value) { + return value.parseDouble(); + } + + @Override + public char convertToChar(AsciiString value) { + return value.charAt(0); + } + + @Override + public boolean convertToBoolean(AsciiString value) { + return value.byteAt(0) != 0; + } + + @Override + public float convertToFloat(AsciiString value) { + return value.parseFloat(); + } + + @Override + public AsciiString convertShort(short value) { + return new AsciiString(String.valueOf(value)); + } + + @Override + public short convertToShort(AsciiString value) { + return value.parseShort(); + } + + @Override + public AsciiString convertByte(byte value) { + return new AsciiString(String.valueOf(value)); + } + + @Override + public byte convertToByte(AsciiString value) { + return value.byteAt(0); + } + }; + + private static final NameConverter ASCII_TO_LOWER_CONVERTER = new NameConverter() { + @Override + public AsciiString convertName(AsciiString name) { + return name.toLowerCase(); + } + }; + + private static final NameConverter ASCII_IDENTITY_CONVERTER = new NameConverter() { + @Override + public AsciiString convertName(AsciiString name) { + return name; + } + }; + + public DefaultBinaryHeaders() { + this(false); + } + + public DefaultBinaryHeaders(boolean forceKeyToLower) { + super(CASE_INSENSITIVE_ORDER, CASE_INSENSITIVE_ORDER, ASCII_HASH_CODE_GENERATOR, OBJECT_TO_ASCII, + forceKeyToLower ? ASCII_TO_LOWER_CONVERTER : ASCII_IDENTITY_CONVERTER); + } + + @Override + public BinaryHeaders add(AsciiString name, AsciiString value) { + super.add(name, value); + return this; + } + + @Override + public BinaryHeaders add(AsciiString name, Iterable values) { + super.add(name, values); + return this; + } + + @Override + public BinaryHeaders add(AsciiString name, AsciiString... values) { + super.add(name, values); + return this; + } + + @Override + public BinaryHeaders addObject(AsciiString name, Object value) { + super.addObject(name, value); + return this; + } + + @Override + public BinaryHeaders addObject(AsciiString name, Iterable values) { + super.addObject(name, values); + return this; + } + + @Override + public BinaryHeaders addObject(AsciiString name, Object... values) { + super.addObject(name, values); + return this; + } + + @Override + public BinaryHeaders addBoolean(AsciiString name, boolean value) { + super.addBoolean(name, value); + return this; + } + + @Override + public BinaryHeaders addChar(AsciiString name, char value) { + super.addChar(name, value); + return this; + } + + @Override + public BinaryHeaders addByte(AsciiString name, byte value) { + super.addByte(name, value); + return this; + } + + @Override + public BinaryHeaders addShort(AsciiString name, short value) { + super.addShort(name, value); + return this; + } + + @Override + public BinaryHeaders addInt(AsciiString name, int value) { + super.addInt(name, value); + return this; + } + + @Override + public BinaryHeaders addLong(AsciiString name, long value) { + super.addLong(name, value); + return this; + } + + @Override + public BinaryHeaders addFloat(AsciiString name, float value) { + super.addFloat(name, value); + return this; + } + + @Override + public BinaryHeaders addDouble(AsciiString name, double value) { + super.addDouble(name, value); + return this; + } + + @Override + public BinaryHeaders add(BinaryHeaders headers) { + super.add(headers); + return this; + } + + @Override + public BinaryHeaders set(AsciiString name, AsciiString value) { + super.set(name, value); + return this; + } + + @Override + public BinaryHeaders set(AsciiString name, Iterable values) { + super.set(name, values); + return this; + } + + @Override + public BinaryHeaders set(AsciiString name, AsciiString... values) { + super.set(name, values); + return this; + } + + @Override + public BinaryHeaders setObject(AsciiString name, Object value) { + super.setObject(name, value); + return this; + } + + @Override + public BinaryHeaders setObject(AsciiString name, Iterable values) { + super.setObject(name, values); + return this; + } + + @Override + public BinaryHeaders setObject(AsciiString name, Object... values) { + super.setObject(name, values); + return this; + } + + @Override + public BinaryHeaders setBoolean(AsciiString name, boolean value) { + super.setBoolean(name, value); + return this; + } + + @Override + public BinaryHeaders setChar(AsciiString name, char value) { + super.setChar(name, value); + return this; + } + + @Override + public BinaryHeaders setByte(AsciiString name, byte value) { + super.setByte(name, value); + return this; + } + + @Override + public BinaryHeaders setShort(AsciiString name, short value) { + super.setShort(name, value); + return this; + } + + @Override + public BinaryHeaders setInt(AsciiString name, int value) { + super.setInt(name, value); + return this; + } + + @Override + public BinaryHeaders setLong(AsciiString name, long value) { + super.setLong(name, value); + return this; + } + + @Override + public BinaryHeaders setFloat(AsciiString name, float value) { + super.setFloat(name, value); + return this; + } + + @Override + public BinaryHeaders setDouble(AsciiString name, double value) { + super.setDouble(name, value); + return this; + } + + @Override + public BinaryHeaders set(BinaryHeaders headers) { + super.set(headers); + return this; + } + + @Override + public BinaryHeaders setAll(BinaryHeaders headers) { + super.setAll(headers); + return this; + } + + @Override + public BinaryHeaders clear() { + super.clear(); + return this; + } +} diff --git a/codec/src/main/java/io/netty/handler/codec/DefaultConvertibleHeaders.java b/codec/src/main/java/io/netty/handler/codec/DefaultConvertibleHeaders.java new file mode 100644 index 0000000000..7a18943d93 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/DefaultConvertibleHeaders.java @@ -0,0 +1,181 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project 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 io.netty.handler.codec; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeSet; + +public class DefaultConvertibleHeaders extends DefaultHeaders + implements ConvertibleHeaders { + + private final TypeConverter typeConverter; + + public DefaultConvertibleHeaders(Comparator keyComparator, + Comparator valueComparator, + HashCodeGenerator hashCodeGenerator, + ValueConverter valueConverter, + TypeConverter typeConverter) { + super(keyComparator, valueComparator, hashCodeGenerator, valueConverter); + this.typeConverter = typeConverter; + } + + public DefaultConvertibleHeaders(Comparator keyComparator, + Comparator valueComparator, + HashCodeGenerator hashCodeGenerator, + ValueConverter valueConverter, + TypeConverter typeConverter, + NameConverter nameConverter) { + super(keyComparator, valueComparator, hashCodeGenerator, valueConverter, nameConverter); + this.typeConverter = typeConverter; + } + + @Override + public ConvertedType getAndConvert(UnconvertedType name) { + return getAndConvert(name, null); + } + + @Override + public ConvertedType getAndConvert(UnconvertedType name, ConvertedType defaultValue) { + UnconvertedType v = get(name); + if (v == null) { + return defaultValue; + } + return typeConverter.toConvertedType(v); + } + + @Override + public ConvertedType getAndRemoveAndConvert(UnconvertedType name) { + return getAndRemoveAndConvert(name, null); + } + + @Override + public ConvertedType getAndRemoveAndConvert(UnconvertedType name, ConvertedType defaultValue) { + UnconvertedType v = getAndRemove(name); + if (v == null) { + return defaultValue; + } + return typeConverter.toConvertedType(v); + } + + @Override + public List getAllAndConvert(UnconvertedType name) { + List all = getAll(name); + List allConverted = new ArrayList(all.size()); + for (int i = 0; i < all.size(); ++i) { + allConverted.add(typeConverter.toConvertedType(all.get(i))); + } + return allConverted; + } + + @Override + public List getAllAndRemoveAndConvert(UnconvertedType name) { + List all = getAllAndRemove(name); + List allConverted = new ArrayList(all.size()); + for (int i = 0; i < all.size(); ++i) { + allConverted.add(typeConverter.toConvertedType(all.get(i))); + } + return allConverted; + } + + @Override + public List> entriesConverted() { + List> entries = entries(); + List> entriesConverted = new ArrayList>( + entries.size()); + for (int i = 0; i < entries.size(); ++i) { + entriesConverted.add(new ConvertedEntry(entries.get(i))); + } + return entriesConverted; + } + + @Override + public Iterator> iteratorConverted() { + return new ConvertedIterator(); + } + + @Override + public Set namesAndConvert(Comparator comparator) { + Set names = names(); + Set namesConverted = new TreeSet(comparator); + for (UnconvertedType unconverted : names) { + namesConverted.add(typeConverter.toConvertedType(unconverted)); + } + return namesConverted; + } + + private final class ConvertedIterator implements Iterator> { + private final Iterator> iter = iterator(); + + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + @Override + public Entry next() { + Entry next = iter.next(); + + return new ConvertedEntry(next); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + private final class ConvertedEntry implements Entry { + private final Entry entry; + private ConvertedType name; + private ConvertedType value; + + ConvertedEntry(Entry entry) { + this.entry = entry; + } + + @Override + public ConvertedType getKey() { + if (name == null) { + name = typeConverter.toConvertedType(entry.getKey()); + } + return name; + } + + @Override + public ConvertedType getValue() { + if (value == null) { + value = typeConverter.toConvertedType(entry.getValue()); + } + return value; + } + + @Override + public ConvertedType setValue(ConvertedType value) { + ConvertedType old = getValue(); + entry.setValue(typeConverter.toUnconvertedType(value)); + return old; + } + + @Override + public String toString() { + return entry.toString(); + } + } +} diff --git a/codec/src/main/java/io/netty/handler/codec/DefaultHeaders.java b/codec/src/main/java/io/netty/handler/codec/DefaultHeaders.java new file mode 100644 index 0000000000..805b9e414c --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/DefaultHeaders.java @@ -0,0 +1,1461 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project 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 io.netty.handler.codec; + +import io.netty.util.collection.CollectionUtils; +import io.netty.util.collection.IntObjectHashMap; +import io.netty.util.collection.IntObjectMap; +import io.netty.util.concurrent.FastThreadLocal; +import io.netty.util.internal.PlatformDependent; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.TimeZone; +import java.util.TreeSet; + +import static io.netty.util.internal.ObjectUtil.*; + +public class DefaultHeaders implements Headers { + /** + * Allows users of this interface to specify a hash code other than the default {@link Object#hashCode()} + */ + public interface HashCodeGenerator { + /** + * Obtain the hash code for the given {@code name} + * + * @param name The name to generate the hash code for + * @return The hash code for {@code name} + */ + int generateHashCode(T name); + } + + /** + * Allows users to convert the {@code name} elements before being processed by this map + */ + public interface NameConverter { + /** + * Convert the {@code name} to some other form of the same object type + * + * @param name The object to convert + * @return The results of the conversion + */ + T convertName(T name); + } + + /** + * A name converted which does not covert but instead just returns this {@code name} unchanged + */ + public static final class IdentityNameConverter implements NameConverter { + @Override + public T convertName(T name) { + return name; + } + } + + private static final int HASH_CODE_PRIME = 31; + private static final int DEFAULT_BUCKET_SIZE = 17; + private static final int DEFAULT_MAP_SIZE = 4; + + private static final NameConverter DEFAULT_NAME_CONVERTER = new IdentityNameConverter(); + + private final EntryVisitor setAllVisitor = new EntryVisitor() { + @Override + public boolean visit(Entry entry) { + set(entry.getKey(), entry.getValue()); + return true; + } + }; + + private final EntryVisitor addAllVisitor = new EntryVisitor() { + @Override + public boolean visit(Entry entry) { + add(entry.getKey(), entry.getValue()); + return true; + } + }; + + private final IntObjectMap entries; + private final IntObjectMap tailEntries; + private final HeaderEntry head; + private final Comparator keyComparator; + private final Comparator valueComparator; + private final HashCodeGenerator hashCodeGenerator; + private final ValueConverter valueConverter; + private final NameConverter nameConverter; + private final int bucketSize; + int size; + + @SuppressWarnings("unchecked") + public DefaultHeaders(Comparator keyComparator, Comparator valueComparator, + HashCodeGenerator hashCodeGenerator, ValueConverter typeConverter) { + this(keyComparator, valueComparator, hashCodeGenerator, typeConverter, + (NameConverter) DEFAULT_NAME_CONVERTER); + } + + public DefaultHeaders(Comparator keyComparator, Comparator valueComparator, + HashCodeGenerator hashCodeGenerator, ValueConverter typeConverter, NameConverter nameConverter) { + this(keyComparator, valueComparator, hashCodeGenerator, typeConverter, nameConverter, DEFAULT_BUCKET_SIZE, + DEFAULT_MAP_SIZE); + } + + public DefaultHeaders(Comparator keyComparator, Comparator valueComparator, + HashCodeGenerator hashCodeGenerator, ValueConverter valueConverter, NameConverter nameConverter, + int bucketSize, int initialMapSize) { + if (keyComparator == null) { + throw new NullPointerException("keyComparator"); + } + if (valueComparator == null) { + throw new NullPointerException("valueComparator"); + } + if (hashCodeGenerator == null) { + throw new NullPointerException("hashCodeGenerator"); + } + if (valueConverter == null) { + throw new NullPointerException("valueConverter"); + } + if (nameConverter == null) { + throw new NullPointerException("nameConverter"); + } + if (bucketSize < 1) { + throw new IllegalArgumentException("bucketSize must be a positive integer"); + } + head = new HeaderEntry(); + head.before = head.after = head; + this.keyComparator = keyComparator; + this.valueComparator = valueComparator; + this.hashCodeGenerator = hashCodeGenerator; + this.valueConverter = valueConverter; + this.nameConverter = nameConverter; + this.bucketSize = bucketSize; + entries = new IntObjectHashMap(initialMapSize); + tailEntries = new IntObjectHashMap(initialMapSize); + } + + @Override + public T get(T name) { + checkNotNull(name, "name"); + + int h = hashCodeGenerator.generateHashCode(name); + int i = index(h); + HeaderEntry e = entries.get(i); + while (e != null) { + if (e.hash == h && keyComparator.compare(e.name, name) == 0) { + return e.value; + } + e = e.next; + } + return null; + } + + @Override + public T get(T name, T defaultValue) { + T value = get(name); + if (value == null) { + return defaultValue; + } + return value; + } + + @Override + public T getAndRemove(T name) { + checkNotNull(name, "name"); + int h = hashCodeGenerator.generateHashCode(name); + int i = index(h); + HeaderEntry e = entries.get(i); + if (e == null) { + return null; + } + + T value = null; + for (;;) { + if (e.hash == h && keyComparator.compare(e.name, name) == 0) { + if (value == null) { + value = e.value; + } + e.remove(); + HeaderEntry next = e.next; + if (next != null) { + entries.put(i, next); + e = next; + } else { + entries.remove(i); + tailEntries.remove(i); + return value; + } + } else { + break; + } + } + + for (;;) { + HeaderEntry next = e.next; + if (next == null) { + break; + } + if (next.hash == h && keyComparator.compare(e.name, name) == 0) { + if (value == null) { + value = next.value; + } + e.next = next.next; + if (e.next == null) { + tailEntries.put(i, e); + } + next.remove(); + } else { + e = next; + } + } + + return value; + } + + @Override + public T getAndRemove(T name, T defaultValue) { + T value = getAndRemove(name); + if (value == null) { + return defaultValue; + } + return value; + } + + @Override + public List getAll(T name) { + checkNotNull(name, "name"); + List values = new ArrayList(4); + int h = hashCodeGenerator.generateHashCode(name); + int i = index(h); + HeaderEntry e = entries.get(i); + while (e != null) { + if (e.hash == h && keyComparator.compare(e.name, name) == 0) { + values.add(e.value); + } + e = e.next; + } + + return values; + } + + @Override + public List getAllAndRemove(T name) { + checkNotNull(name, "name"); + int h = hashCodeGenerator.generateHashCode(name); + int i = index(h); + HeaderEntry e = entries.get(i); + if (e == null) { + return null; + } + + List values = new ArrayList(4); + for (;;) { + if (e.hash == h && keyComparator.compare(e.name, name) == 0) { + values.add(e.value); + e.remove(); + HeaderEntry next = e.next; + if (next != null) { + entries.put(i, next); + e = next; + } else { + entries.remove(i); + tailEntries.remove(i); + return values; + } + } else { + break; + } + } + + for (;;) { + HeaderEntry next = e.next; + if (next == null) { + break; + } + if (next.hash == h && keyComparator.compare(next.name, name) == 0) { + values.add(next.value); + e.next = next.next; + if (e.next == null) { + tailEntries.put(i, e); + } + next.remove(); + } else { + e = next; + } + } + + return values; + } + + @Override + public List> entries() { + final int size = size(); + List> localEntries = new ArrayList>(size); + + HeaderEntry e = head.after; + while (e != head) { + localEntries.add(e); + e = e.after; + } + + assert size == localEntries.size(); + return localEntries; + } + + @Override + public boolean contains(T name) { + return get(name) != null; + } + + @Override + public boolean contains(T name, T value) { + return contains(name, value, keyComparator, valueComparator); + } + + @Override + public boolean containsObject(T name, Object value) { + return contains(name, valueConverter.convertObject(checkNotNull(value, "value"))); + } + + @Override + public boolean containsBoolean(T name, boolean value) { + return contains(name, valueConverter.convertBoolean(checkNotNull(value, "value"))); + } + + @Override + public boolean containsByte(T name, byte value) { + return contains(name, valueConverter.convertByte(checkNotNull(value, "value"))); + } + + @Override + public boolean containsChar(T name, char value) { + return contains(name, valueConverter.convertChar(checkNotNull(value, "value"))); + } + + @Override + public boolean containsShort(T name, short value) { + return contains(name, valueConverter.convertShort(checkNotNull(value, "value"))); + } + + @Override + public boolean containsInt(T name, int value) { + return contains(name, valueConverter.convertInt(checkNotNull(value, "value"))); + } + + @Override + public boolean containsLong(T name, long value) { + return contains(name, valueConverter.convertLong(checkNotNull(value, "value"))); + } + + @Override + public boolean containsFloat(T name, float value) { + return contains(name, valueConverter.convertFloat(checkNotNull(value, "value"))); + } + + @Override + public boolean containsDouble(T name, double value) { + return contains(name, valueConverter.convertDouble(checkNotNull(value, "value"))); + } + + @Override + public boolean contains(T name, T value, Comparator comparator) { + return contains(name, value, comparator, comparator); + } + + @Override + public boolean contains(T name, T value, + Comparator keyComparator, Comparator valueComparator) { + checkNotNull(name, "name"); + checkNotNull(value, "value"); + checkNotNull(keyComparator, "keyComparator"); + checkNotNull(valueComparator, "valueComparator"); + int h = hashCodeGenerator.generateHashCode(name); + int i = index(h); + HeaderEntry e = entries.get(i); + while (e != null) { + if (e.hash == h && + keyComparator.compare(e.name, name) == 0 && + valueComparator.compare(e.value, value) == 0) { + return true; + } + e = e.next; + } + return false; + } + + @Override + public boolean containsObject(T name, Object value, Comparator comparator) { + return containsObject(name, value, comparator, comparator); + } + + @Override + public boolean containsObject(T name, Object value, Comparator keyComparator, + Comparator valueComparator) { + return contains( + name, valueConverter.convertObject(checkNotNull(value, "value")), keyComparator, valueComparator); + } + + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return head == head.after; + } + + @Override + public Set names() { + final Set names = new TreeSet(keyComparator); + + HeaderEntry e = head.after; + while (e != head) { + names.add(e.name); + e = e.after; + } + + return names; + } + + @Override + public List namesList() { + final List names = new ArrayList(size()); + + HeaderEntry e = head.after; + while (e != head) { + names.add(e.name); + e = e.after; + } + + return names; + } + + @Override + public Headers add(T name, T value) { + name = convertName(name); + checkNotNull(value, "value"); + int h = hashCodeGenerator.generateHashCode(name); + int i = index(h); + add0(h, i, name, value); + return this; + } + + @Override + public Headers add(T name, Iterable values) { + name = convertName(name); + checkNotNull(values, "values"); + + int h = hashCodeGenerator.generateHashCode(name); + int i = index(h); + for (T v : values) { + if (v == null) { + break; + } + add0(h, i, name, v); + } + return this; + } + + @Override + public Headers add(T name, T... values) { + name = convertName(name); + checkNotNull(values, "values"); + + int h = hashCodeGenerator.generateHashCode(name); + int i = index(h); + for (T v : values) { + if (v == null) { + break; + } + add0(h, i, name, v); + } + return this; + } + + @Override + public Headers addObject(T name, Object value) { + return add(name, valueConverter.convertObject(checkNotNull(value, "value"))); + } + + @Override + public Headers addObject(T name, Iterable values) { + name = convertName(name); + checkNotNull(values, "values"); + + int h = hashCodeGenerator.generateHashCode(name); + int i = index(h); + for (Object o : values) { + if (o == null) { + break; + } + T converted = valueConverter.convertObject(o); + checkNotNull(converted, "converted"); + add0(h, i, name, converted); + } + return this; + } + + @Override + public Headers addObject(T name, Object... values) { + name = convertName(name); + checkNotNull(values, "values"); + + int h = hashCodeGenerator.generateHashCode(name); + int i = index(h); + for (Object o : values) { + if (o == null) { + break; + } + T converted = valueConverter.convertObject(o); + checkNotNull(converted, "converted"); + add0(h, i, name, converted); + } + return this; + } + + @Override + public Headers addInt(T name, int value) { + return add(name, valueConverter.convertInt(value)); + } + + @Override + public Headers addLong(T name, long value) { + return add(name, valueConverter.convertLong(value)); + } + + @Override + public Headers addDouble(T name, double value) { + return add(name, valueConverter.convertDouble(value)); + } + + @Override + public Headers addChar(T name, char value) { + return add(name, valueConverter.convertChar(value)); + } + + @Override + public Headers addBoolean(T name, boolean value) { + return add(name, valueConverter.convertBoolean(value)); + } + + @Override + public Headers addFloat(T name, float value) { + return add(name, valueConverter.convertFloat(value)); + } + + @Override + public Headers addByte(T name, byte value) { + return add(name, valueConverter.convertByte(value)); + } + + @Override + public Headers addShort(T name, short value) { + return add(name, valueConverter.convertShort(value)); + } + + @Override + public Headers add(Headers headers) { + checkNotNull(headers, "headers"); + + add0(headers); + return this; + } + + @Override + public Headers set(T name, T value) { + name = convertName(name); + checkNotNull(value, "value"); + int h = hashCodeGenerator.generateHashCode(name); + int i = index(h); + remove0(h, i, name); + add0(h, i, name, value); + return this; + } + + @Override + public Headers set(T name, Iterable values) { + name = convertName(name); + checkNotNull(values, "values"); + + int h = hashCodeGenerator.generateHashCode(name); + int i = index(h); + remove0(h, i, name); + for (T v : values) { + if (v == null) { + break; + } + add0(h, i, name, v); + } + + return this; + } + + @Override + public Headers set(T name, T... values) { + name = convertName(name); + checkNotNull(values, "values"); + + int h = hashCodeGenerator.generateHashCode(name); + int i = index(h); + remove0(h, i, name); + for (T v : values) { + if (v == null) { + break; + } + add0(h, i, name, v); + } + + return this; + } + + @Override + public Headers setObject(T name, Object value) { + return set(name, valueConverter.convertObject(checkNotNull(value, "value"))); + } + + @Override + public Headers setObject(T name, Iterable values) { + name = convertName(name); + checkNotNull(values, "values"); + + int h = hashCodeGenerator.generateHashCode(name); + int i = index(h); + remove0(h, i, name); + for (Object o : values) { + if (o == null) { + break; + } + T converted = valueConverter.convertObject(o); + checkNotNull(converted, "converted"); + add0(h, i, name, converted); + } + + return this; + } + + @Override + public Headers setObject(T name, Object... values) { + name = convertName(name); + checkNotNull(values, "values"); + + int h = hashCodeGenerator.generateHashCode(name); + int i = index(h); + remove0(h, i, name); + for (Object o : values) { + if (o == null) { + break; + } + T converted = valueConverter.convertObject(o); + checkNotNull(converted, "converted"); + add0(h, i, name, converted); + } + + return this; + } + + @Override + public Headers setInt(T name, int value) { + return set(name, valueConverter.convertInt(value)); + } + + @Override + public Headers setLong(T name, long value) { + return set(name, valueConverter.convertLong(value)); + } + + @Override + public Headers setDouble(T name, double value) { + return set(name, valueConverter.convertDouble(value)); + } + + @Override + public Headers setFloat(T name, float value) { + return set(name, valueConverter.convertFloat(value)); + } + + @Override + public Headers setChar(T name, char value) { + return set(name, valueConverter.convertChar(value)); + } + + @Override + public Headers setBoolean(T name, boolean value) { + return set(name, valueConverter.convertBoolean(value)); + } + + @Override + public Headers setByte(T name, byte value) { + return set(name, valueConverter.convertByte(value)); + } + + @Override + public Headers setShort(T name, short value) { + return set(name, valueConverter.convertShort(value)); + } + + @Override + public Headers set(Headers headers) { + checkNotNull(headers, "headers"); + + clear(); + add0(headers); + return this; + } + + @Override + public Headers setAll(Headers headers) { + checkNotNull(headers, "headers"); + + if (headers instanceof DefaultHeaders) { + DefaultHeaders m = (DefaultHeaders) headers; + HeaderEntry e = m.head.after; + while (e != m.head) { + set(e.name, e.value); + e = e.after; + } + } else { + try { + headers.forEachEntry(setAllVisitor); + } catch (Exception ex) { + PlatformDependent.throwException(ex); + } + } + + return this; + } + + @Override + public boolean remove(T name) { + checkNotNull(name, "name"); + int h = hashCodeGenerator.generateHashCode(name); + int i = index(h); + return remove0(h, i, name); + } + + @Override + public Headers clear() { + entries.clear(); + tailEntries.clear(); + head.before = head.after = head; + size = 0; + return this; + } + + @Override + public Iterator> iterator() { + return new KeyValueHeaderIterator(); + } + + @Override + public Map.Entry forEachEntry(EntryVisitor visitor) throws Exception { + HeaderEntry e = head.after; + while (e != head) { + if (!visitor.visit(e)) { + return e; + } + e = e.after; + } + return null; + } + + @Override + public T forEachName(NameVisitor visitor) throws Exception { + HeaderEntry e = head.after; + while (e != head) { + if (!visitor.visit(e.name)) { + return e.name; + } + e = e.after; + } + return null; + } + + @Override + public Boolean getBoolean(T name) { + T v = get(name); + if (v == null) { + return null; + } + try { + return valueConverter.convertToBoolean(v); + } catch (Throwable ignored) { + return null; + } + } + + @Override + public boolean getBoolean(T name, boolean defaultValue) { + Boolean v = getBoolean(name); + return v == null ? defaultValue : v; + } + + @Override + public Byte getByte(T name) { + T v = get(name); + if (v == null) { + return null; + } + try { + return valueConverter.convertToByte(v); + } catch (Throwable ignored) { + return null; + } + } + + @Override + public byte getByte(T name, byte defaultValue) { + Byte v = getByte(name); + return v == null ? defaultValue : v; + } + + @Override + public Character getChar(T name) { + T v = get(name); + if (v == null) { + return null; + } + try { + return valueConverter.convertToChar(v); + } catch (Throwable ignored) { + return null; + } + } + + @Override + public char getChar(T name, char defaultValue) { + Character v = getChar(name); + return v == null ? defaultValue : v; + } + + @Override + public Short getShort(T name) { + T v = get(name); + if (v == null) { + return null; + } + try { + return valueConverter.convertToShort(v); + } catch (Throwable ignored) { + return null; + } + } + + @Override + public short getInt(T name, short defaultValue) { + Short v = getShort(name); + return v == null ? defaultValue : v; + } + + @Override + public Integer getInt(T name) { + T v = get(name); + if (v == null) { + return null; + } + try { + return valueConverter.convertToInt(v); + } catch (Throwable ignored) { + return null; + } + } + + @Override + public int getInt(T name, int defaultValue) { + Integer v = getInt(name); + return v == null ? defaultValue : v; + } + + @Override + public Long getLong(T name) { + T v = get(name); + if (v == null) { + return null; + } + try { + return valueConverter.convertToLong(v); + } catch (Throwable ignored) { + return null; + } + } + + @Override + public long getLong(T name, long defaultValue) { + Long v = getLong(name); + return v == null ? defaultValue : v; + } + + @Override + public Float getFloat(T name) { + T v = get(name); + if (v == null) { + return null; + } + try { + return valueConverter.convertToFloat(v); + } catch (Throwable ignored) { + return null; + } + } + + @Override + public float getFloat(T name, float defaultValue) { + Float v = getFloat(name); + return v == null ? defaultValue : v; + } + + @Override + public Double getDouble(T name) { + T v = get(name); + if (v == null) { + return null; + } + try { + return valueConverter.convertToDouble(v); + } catch (Throwable ignored) { + return null; + } + } + + @Override + public double getDouble(T name, double defaultValue) { + Double v = getDouble(name); + return v == null ? defaultValue : v; + } + + @Override + public Long getTimeMillis(T name) { + T v = get(name); + if (v == null) { + return null; + } + try { + return valueConverter.convertToTimeMillis(v); + } catch (Throwable ignored) { + return null; + } + } + + @Override + public long getTimeMillis(T name, long defaultValue) { + Long v = getTimeMillis(name); + return v == null ? defaultValue : v; + } + + @Override + public Boolean getBooleanAndRemove(T name) { + T v = getAndRemove(name); + if (v == null) { + return null; + } + try { + return valueConverter.convertToBoolean(v); + } catch (Throwable ignored) { + return null; + } + } + + @Override + public boolean getBooleanAndRemove(T name, boolean defaultValue) { + Boolean v = getBooleanAndRemove(name); + return v == null ? defaultValue : v; + } + + @Override + public Byte getByteAndRemove(T name) { + T v = getAndRemove(name); + if (v == null) { + return null; + } + try { + return valueConverter.convertToByte(v); + } catch (Throwable ignored) { + return null; + } + } + + @Override + public byte getByteAndRemove(T name, byte defaultValue) { + Byte v = getByteAndRemove(name); + return v == null ? defaultValue : v; + } + + @Override + public Character getCharAndRemove(T name) { + T v = getAndRemove(name); + if (v == null) { + return null; + } + try { + return valueConverter.convertToChar(v); + } catch (Throwable ignored) { + return null; + } + } + + @Override + public char getCharAndRemove(T name, char defaultValue) { + Character v = getCharAndRemove(name); + return v == null ? defaultValue : v; + } + + @Override + public Short getShortAndRemove(T name) { + T v = getAndRemove(name); + if (v == null) { + return null; + } + try { + return valueConverter.convertToShort(v); + } catch (Throwable ignored) { + return null; + } + } + + @Override + public short getShortAndRemove(T name, short defaultValue) { + Short v = getShortAndRemove(name); + return v == null ? defaultValue : v; + } + + @Override + public Integer getIntAndRemove(T name) { + T v = getAndRemove(name); + if (v == null) { + return null; + } + try { + return valueConverter.convertToInt(v); + } catch (Throwable ignored) { + return null; + } + } + + @Override + public int getIntAndRemove(T name, int defaultValue) { + Integer v = getIntAndRemove(name); + return v == null ? defaultValue : v; + } + + @Override + public Long getLongAndRemove(T name) { + T v = getAndRemove(name); + if (v == null) { + return null; + } + try { + return valueConverter.convertToLong(v); + } catch (Throwable ignored) { + return null; + } + } + + @Override + public long getLongAndRemove(T name, long defaultValue) { + Long v = getLongAndRemove(name); + return v == null ? defaultValue : v; + } + + @Override + public Float getFloatAndRemove(T name) { + T v = getAndRemove(name); + if (v == null) { + return null; + } + try { + return valueConverter.convertToFloat(v); + } catch (Throwable ignored) { + return null; + } + } + + @Override + public float getFloatAndRemove(T name, float defaultValue) { + Float v = getFloatAndRemove(name); + return v == null ? defaultValue : v; + } + + @Override + public Double getDoubleAndRemove(T name) { + T v = getAndRemove(name); + if (v == null) { + return null; + } + try { + return valueConverter.convertToDouble(v); + } catch (Throwable ignored) { + return null; + } + } + + @Override + public double getDoubleAndRemove(T name, double defaultValue) { + Double v = getDoubleAndRemove(name); + return v == null ? defaultValue : v; + } + + @Override + public Long getTimeMillisAndRemove(T name) { + T v = getAndRemove(name); + if (v == null) { + return null; + } + try { + return valueConverter.convertToTimeMillis(v); + } catch (Throwable ignored) { + return null; + } + } + + @Override + public long getTimeMillisAndRemove(T name, long defaultValue) { + Long v = getTimeMillisAndRemove(name); + return v == null ? defaultValue : v; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof DefaultHeaders)) { + return false; + } + + @SuppressWarnings("unchecked") + DefaultHeaders h2 = (DefaultHeaders) o; + // First, check that the set of names match. Don't use a TreeSet for comparison + // because we want to force the keyComparator to be used for all comparisons + List namesList = namesList(); + List otherNamesList = h2.namesList(); + if (!CollectionUtils.equals(namesList, otherNamesList, keyComparator)) { + return false; + } + + // Compare the values for each name. Don't use a TreeSet for comparison + // because we want to force the valueComparator to be used for all comparisons + Set names = new TreeSet(keyComparator); + names.addAll(namesList); + for (T name : names) { + if (!CollectionUtils.equals(getAll(name), h2.getAll(name), valueComparator)) { + return false; + } + } + + return true; + } + + @Override + public int hashCode() { + int result = 1; + for (T name : names()) { + result = HASH_CODE_PRIME * result + name.hashCode(); + List values = getAll(name); + Collections.sort(values, valueComparator); + for (int i = 0; i < values.size(); ++i) { + result = HASH_CODE_PRIME * result + hashCodeGenerator.generateHashCode(values.get(i)); + } + } + return result; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(getClass().getSimpleName()).append('['); + for (T name : names()) { + List values = getAll(name); + Collections.sort(values, valueComparator); + for (int i = 0; i < values.size(); ++i) { + builder.append(name).append(": ").append(values.get(i)).append(", "); + } + } + if (builder.length() > 2) { // remove the trailing ", " + builder.setLength(builder.length() - 2); + } + return builder.append(']').toString(); + } + + private T convertName(T name) { + return nameConverter.convertName(checkNotNull(name, "name")); + } + + private int index(int hash) { + return Math.abs(hash % bucketSize); + } + + private void add0(Headers headers) { + if (headers.isEmpty()) { + return; + } + + if (headers instanceof DefaultHeaders) { + DefaultHeaders m = (DefaultHeaders) headers; + HeaderEntry e = m.head.after; + while (e != m.head) { + add(e.name, e.value); + e = e.after; + } + } else { + try { + headers.forEachEntry(addAllVisitor); + } catch (Exception ex) { + PlatformDependent.throwException(ex); + } + } + } + + private void add0(int h, int i, T name, T value) { + // Update the per-bucket hash table linked list + HeaderEntry newEntry = new HeaderEntry(h, name, value); + HeaderEntry oldTail = tailEntries.get(i); + if (oldTail == null) { + entries.put(i, newEntry); + } else { + oldTail.next = newEntry; + } + tailEntries.put(i, newEntry); + + // Update the overall insertion order linked list + newEntry.addBefore(head); + } + + private boolean remove0(int h, int i, T name) { + HeaderEntry e = entries.get(i); + if (e == null) { + return false; + } + + boolean removed = false; + for (;;) { + if (e.hash == h && keyComparator.compare(e.name, name) == 0) { + e.remove(); + HeaderEntry next = e.next; + if (next != null) { + entries.put(i, next); + e = next; + } else { + entries.remove(i); + tailEntries.remove(i); + return true; + } + removed = true; + } else { + break; + } + } + + for (;;) { + HeaderEntry next = e.next; + if (next == null) { + break; + } + if (next.hash == h && keyComparator.compare(next.name, name) == 0) { + e.next = next.next; + if (e.next == null) { + tailEntries.put(i, e); + } + next.remove(); + removed = true; + } else { + e = next; + } + } + + return removed; + } + + private final class HeaderEntry implements Map.Entry { + final int hash; + final T name; + T value; + /** + * In bucket linked list + */ + HeaderEntry next; + /** + * Overall insertion order linked list + */ + HeaderEntry before, after; + + HeaderEntry(int hash, T name, T value) { + this.hash = hash; + this.name = name; + this.value = value; + } + + HeaderEntry() { + hash = -1; + name = null; + value = null; + } + + void remove() { + before.after = after; + after.before = before; + --size; + } + + void addBefore(HeaderEntry e) { + after = e; + before = e.before; + before.after = this; + after.before = this; + ++size; + } + + @Override + public T getKey() { + return name; + } + + @Override + public T getValue() { + return value; + } + + @Override + public T setValue(T value) { + checkNotNull(value, "value"); + T oldValue = this.value; + this.value = value; + return oldValue; + } + + @Override + public String toString() { + StringBuilder b = new StringBuilder(); + b.append(name); + b.append('='); + b.append(value); + return b.toString(); + } + } + + protected final class KeyValueHeaderIterator implements Iterator> { + + private HeaderEntry current = head; + + @Override + public boolean hasNext() { + return current.after != head; + } + + @Override + public Entry next() { + current = current.after; + + if (current == head) { + throw new NoSuchElementException(); + } + + return current; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + /** + * This DateFormat decodes 3 formats of {@link java.util.Date}, but only encodes the one, the first: + *
    + *
  • Sun, 06 Nov 1994 08:49:37 GMT: standard specification, the only one with valid generation
  • + *
  • Sun, 06 Nov 1994 08:49:37 GMT: obsolete specification
  • + *
  • Sun Nov 6 08:49:37 1994: obsolete specification
  • + *
+ */ + static final class HeaderDateFormat { + private static final ParsePosition parsePos = new ParsePosition(0); + private static final FastThreadLocal dateFormatThreadLocal = + new FastThreadLocal() { + @Override + protected HeaderDateFormat initialValue() { + return new HeaderDateFormat(); + } + }; + + static HeaderDateFormat get() { + return dateFormatThreadLocal.get(); + } + + /** + * Standard date format: + * + *
+         * Sun, 06 Nov 1994 08:49:37 GMT -> E, d MMM yyyy HH:mm:ss z
+         * 
+ */ + private final DateFormat dateFormat1 = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH); + /** + * First obsolete format: + * + *
+         * Sunday, 06-Nov-94 08:49:37 GMT -> E, d-MMM-y HH:mm:ss z
+         * 
+ */ + private final DateFormat dateFormat2 = new SimpleDateFormat("E, dd-MMM-yy HH:mm:ss z", Locale.ENGLISH); + /** + * Second obsolete format + * + *
+         * Sun Nov 6 08:49:37 1994 -> EEE, MMM d HH:mm:ss yyyy
+         * 
+ */ + private final DateFormat dateFormat3 = new SimpleDateFormat("E MMM d HH:mm:ss yyyy", Locale.ENGLISH); + + private HeaderDateFormat() { + TimeZone tz = TimeZone.getTimeZone("GMT"); + dateFormat1.setTimeZone(tz); + dateFormat2.setTimeZone(tz); + dateFormat3.setTimeZone(tz); + } + + long parse(String text) throws ParseException { + Date date = dateFormat1.parse(text, parsePos); + if (date == null) { + date = dateFormat2.parse(text, parsePos); + } + if (date == null) { + date = dateFormat3.parse(text, parsePos); + } + if (date == null) { + throw new ParseException(text, 0); + } + return date.getTime(); + } + + long parse(String text, long defaultValue) { + Date date = dateFormat1.parse(text, parsePos); + if (date == null) { + date = dateFormat2.parse(text, parsePos); + } + if (date == null) { + date = dateFormat3.parse(text, parsePos); + } + if (date == null) { + return defaultValue; + } + return date.getTime(); + } + } +} diff --git a/codec/src/main/java/io/netty/handler/codec/DefaultTextHeaders.java b/codec/src/main/java/io/netty/handler/codec/DefaultTextHeaders.java index 450fac31e3..39557b15e6 100644 --- a/codec/src/main/java/io/netty/handler/codec/DefaultTextHeaders.java +++ b/codec/src/main/java/io/netty/handler/codec/DefaultTextHeaders.java @@ -16,1114 +16,367 @@ package io.netty.handler.codec; -import io.netty.util.concurrent.FastThreadLocal; import io.netty.util.internal.PlatformDependent; -import java.text.DateFormat; import java.text.ParseException; -import java.text.ParsePosition; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.TimeZone; +import java.util.Comparator; -public class DefaultTextHeaders implements TextHeaders { +import static io.netty.handler.codec.AsciiString.*; - private static final int INITIAL_VALUELIST_CAPACITY = 2; - private static final int BUCKET_SIZE = 17; +public class DefaultTextHeaders extends DefaultConvertibleHeaders implements TextHeaders { + private static final HashCodeGenerator CHARSEQUECE_CASE_INSENSITIVE_HASH_CODE_GENERATOR = + new HashCodeGenerator() { + @Override + public int generateHashCode(CharSequence name) { + return AsciiString.caseInsensitiveHashCode(name); + } + }; - private static int index(int hash) { - return Math.abs(hash % BUCKET_SIZE); + private static final HashCodeGenerator CHARSEQUECE_CASE_SENSITIVE_HASH_CODE_GENERATOR = + new HashCodeGenerator() { + @Override + public int generateHashCode(CharSequence name) { + return name.hashCode(); + } + }; + + public static class DefaultTextValueTypeConverter implements ValueConverter { + @Override + public CharSequence convertObject(Object value) { + if (value instanceof CharSequence) { + return (CharSequence) value; + } + return value.toString(); + } + + @Override + public CharSequence convertInt(int value) { + return String.valueOf(value); + } + + @Override + public CharSequence convertLong(long value) { + return String.valueOf(value); + } + + @Override + public CharSequence convertDouble(double value) { + return String.valueOf(value); + } + + @Override + public CharSequence convertChar(char value) { + return String.valueOf(value); + } + + @Override + public CharSequence convertBoolean(boolean value) { + return String.valueOf(value); + } + + @Override + public CharSequence convertFloat(float value) { + return String.valueOf(value); + } + + @Override + public boolean convertToBoolean(CharSequence value) { + return Boolean.parseBoolean(value.toString()); + } + + @Override + public CharSequence convertByte(byte value) { + return String.valueOf(value); + } + + @Override + public byte convertToByte(CharSequence value) { + return Byte.valueOf(value.toString()); + } + + @Override + public char convertToChar(CharSequence value) { + return value.charAt(0); + } + + @Override + public CharSequence convertShort(short value) { + return String.valueOf(value); + } + + @Override + public short convertToShort(CharSequence value) { + return Short.valueOf(value.toString()); + } + + @Override + public int convertToInt(CharSequence value) { + return Integer.valueOf(value.toString()); + } + + @Override + public long convertToLong(CharSequence value) { + return Long.valueOf(value.toString()); + } + + @Override + public long convertToTimeMillis(CharSequence value) { + try { + return HeaderDateFormat.get().parse(value.toString()); + } catch (ParseException e) { + PlatformDependent.throwException(e); + } + return 0; + } + + @Override + public float convertToFloat(CharSequence value) { + return Float.valueOf(value.toString()); + } + + @Override + public double convertToDouble(CharSequence value) { + return Double.valueOf(value.toString()); + } } - private final HeaderEntry[] entries = new HeaderEntry[BUCKET_SIZE]; - private final HeaderEntry head = new HeaderEntry(this); - private final boolean ignoreCase; - int size; + private static final Headers.ValueConverter CHARSEQUENCE_FROM_OBJECT_CONVERTER = + new DefaultTextValueTypeConverter(); + private static final ConvertibleHeaders.TypeConverter CHARSEQUENCE_TO_STRING_CONVERTER = + new ConvertibleHeaders.TypeConverter() { + @Override + public String toConvertedType(CharSequence value) { + return value.toString(); + } + + @Override + public CharSequence toUnconvertedType(String value) { + return value; + } + }; + + private static final NameConverter CHARSEQUENCE_IDENTITY_CONVERTER = + new IdentityNameConverter(); public DefaultTextHeaders() { this(true); } public DefaultTextHeaders(boolean ignoreCase) { - head.before = head.after = head; - this.ignoreCase = ignoreCase; + this(ignoreCase, CHARSEQUENCE_FROM_OBJECT_CONVERTER, CHARSEQUENCE_IDENTITY_CONVERTER); } - protected int hashCode(CharSequence name) { - return AsciiString.caseInsensitiveHashCode(name); - } - - protected CharSequence convertName(CharSequence name) { - if (name == null) { - throw new NullPointerException("name"); - } - return name; - } - - protected CharSequence convertValue(Object value) { - if (value == null) { - throw new NullPointerException("value"); - } - if (value instanceof CharSequence) { - return (CharSequence) value; - } - return value.toString(); - } - - protected boolean nameEquals(CharSequence a, CharSequence b) { - return equals(a, b, ignoreCase); - } - - protected boolean valueEquals(CharSequence a, CharSequence b, boolean ignoreCase) { - return equals(a, b, ignoreCase); - } - - private static boolean equals(CharSequence a, CharSequence b, boolean ignoreCase) { - if (a == b) { - return true; - } - - if (a instanceof AsciiString) { - AsciiString aa = (AsciiString) a; - if (ignoreCase) { - return aa.equalsIgnoreCase(b); - } else { - return aa.equals(b); - } - } - - if (b instanceof AsciiString) { - AsciiString ab = (AsciiString) b; - if (ignoreCase) { - return ab.equalsIgnoreCase(a); - } else { - return ab.equals(a); - } - } - - if (ignoreCase) { - return a.toString().equalsIgnoreCase(b.toString()); - } else { - return a.equals(b); - } + public DefaultTextHeaders(boolean ignoreCase, Headers.ValueConverter valueConverter, + NameConverter nameConverter) { + super(comparator(ignoreCase), comparator(ignoreCase), + ignoreCase ? CHARSEQUECE_CASE_INSENSITIVE_HASH_CODE_GENERATOR + : CHARSEQUECE_CASE_SENSITIVE_HASH_CODE_GENERATOR, valueConverter, + CHARSEQUENCE_TO_STRING_CONVERTER, nameConverter); } @Override - public TextHeaders add(CharSequence name, Object value) { - name = convertName(name); - CharSequence convertedVal = convertValue(value); - int h = hashCode(name); - int i = index(h); - add0(h, i, name, convertedVal); + public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) { + return contains(name, value, comparator(ignoreCase)); + } + + @Override + public boolean containsObject(CharSequence name, Object value, boolean ignoreCase) { + return containsObject(name, value, comparator(ignoreCase)); + } + + @Override + public TextHeaders add(CharSequence name, CharSequence value) { + super.add(name, value); return this; } @Override - public TextHeaders add(CharSequence name, Iterable values) { - name = convertName(name); - if (values == null) { - throw new NullPointerException("values"); - } - - int h = hashCode(name); - int i = index(h); - for (Object v: values) { - if (v == null) { - break; - } - CharSequence convertedVal = convertValue(v); - add0(h, i, name, convertedVal); - } + public TextHeaders add(CharSequence name, Iterable values) { + super.add(name, values); return this; } @Override - public TextHeaders add(CharSequence name, Object... values) { - name = convertName(name); - if (values == null) { - throw new NullPointerException("values"); - } - - int h = hashCode(name); - int i = index(h); - for (Object v: values) { - if (v == null) { - break; - } - CharSequence convertedVal = convertValue(v); - add0(h, i, name, convertedVal); - } + public TextHeaders add(CharSequence name, CharSequence... values) { + super.add(name, values); return this; } - private void add0(int h, int i, CharSequence name, CharSequence value) { - // Update the hash table. - HeaderEntry e = entries[i]; - HeaderEntry newEntry; - entries[i] = newEntry = new HeaderEntry(this, h, name, value); - newEntry.next = e; + @Override + public TextHeaders addObject(CharSequence name, Object value) { + super.addObject(name, value); + return this; + } - // Update the linked list. - newEntry.addBefore(head); + @Override + public TextHeaders addObject(CharSequence name, Iterable values) { + super.addObject(name, values); + return this; + } + + @Override + public TextHeaders addObject(CharSequence name, Object... values) { + super.addObject(name, values); + return this; + } + + @Override + public TextHeaders addBoolean(CharSequence name, boolean value) { + super.addBoolean(name, value); + return this; + } + + @Override + public TextHeaders addChar(CharSequence name, char value) { + super.addChar(name, value); + return this; + } + + @Override + public TextHeaders addByte(CharSequence name, byte value) { + super.addByte(name, value); + return this; + } + + @Override + public TextHeaders addShort(CharSequence name, short value) { + super.addShort(name, value); + return this; + } + + @Override + public TextHeaders addInt(CharSequence name, int value) { + super.addInt(name, value); + return this; + } + + @Override + public TextHeaders addLong(CharSequence name, long value) { + super.addLong(name, value); + return this; + } + + @Override + public TextHeaders addFloat(CharSequence name, float value) { + super.addFloat(name, value); + return this; + } + + @Override + public TextHeaders addDouble(CharSequence name, double value) { + super.addDouble(name, value); + return this; } @Override public TextHeaders add(TextHeaders headers) { - if (headers == null) { - throw new NullPointerException("headers"); - } - - add0(headers); - return this; - } - - private void add0(TextHeaders headers) { - if (headers.isEmpty()) { - return; - } - - if (headers instanceof DefaultTextHeaders) { - DefaultTextHeaders m = (DefaultTextHeaders) headers; - HeaderEntry e = m.head.after; - while (e != m.head) { - CharSequence name = e.name; - name = convertName(name); - add(name, convertValue(e.value)); - e = e.after; - } - } else { - for (Entry e: headers.unconvertedEntries()) { - add(e.getKey(), e.getValue()); - } - } - } - - @Override - public boolean remove(CharSequence name) { - if (name == null) { - throw new NullPointerException("name"); - } - int h = hashCode(name); - int i = index(h); - return remove0(h, i, name); - } - - private boolean remove0(int h, int i, CharSequence name) { - HeaderEntry e = entries[i]; - if (e == null) { - return false; - } - - boolean removed = false; - for (;;) { - if (e.hash == h && nameEquals(e.name, name)) { - e.remove(); - HeaderEntry next = e.next; - if (next != null) { - entries[i] = next; - e = next; - } else { - entries[i] = null; - return true; - } - removed = true; - } else { - break; - } - } - - for (;;) { - HeaderEntry next = e.next; - if (next == null) { - break; - } - if (next.hash == h && nameEquals(next.name, name)) { - e.next = next.next; - next.remove(); - removed = true; - } else { - e = next; - } - } - - return removed; - } - - @Override - public TextHeaders set(CharSequence name, Object value) { - name = convertName(name); - CharSequence convertedVal = convertValue(value); - int h = hashCode(name); - int i = index(h); - remove0(h, i, name); - add0(h, i, name, convertedVal); + super.add(headers); return this; } @Override - public TextHeaders set(CharSequence name, Iterable values) { - name = convertName(name); - if (values == null) { - throw new NullPointerException("values"); - } - - int h = hashCode(name); - int i = index(h); - - remove0(h, i, name); - for (Object v: values) { - if (v == null) { - break; - } - CharSequence convertedVal = convertValue(v); - add0(h, i, name, convertedVal); - } - + public TextHeaders set(CharSequence name, CharSequence value) { + super.set(name, value); return this; } @Override - public TextHeaders set(CharSequence name, Object... values) { - name = convertName(name); - if (values == null) { - throw new NullPointerException("values"); - } + public TextHeaders set(CharSequence name, Iterable values) { + super.set(name, values); + return this; + } - int h = hashCode(name); - int i = index(h); + @Override + public TextHeaders set(CharSequence name, CharSequence... values) { + super.set(name, values); + return this; + } - remove0(h, i, name); - for (Object v: values) { - if (v == null) { - break; - } - CharSequence convertedVal = convertValue(v); - add0(h, i, name, convertedVal); - } + @Override + public TextHeaders setObject(CharSequence name, Object value) { + super.setObject(name, value); + return this; + } + @Override + public TextHeaders setObject(CharSequence name, Iterable values) { + super.setObject(name, values); + return this; + } + + @Override + public TextHeaders setObject(CharSequence name, Object... values) { + super.setObject(name, values); + return this; + } + + @Override + public TextHeaders setBoolean(CharSequence name, boolean value) { + super.setBoolean(name, value); + return this; + } + + @Override + public TextHeaders setChar(CharSequence name, char value) { + super.setChar(name, value); + return this; + } + + @Override + public TextHeaders setByte(CharSequence name, byte value) { + super.setByte(name, value); + return this; + } + + @Override + public TextHeaders setShort(CharSequence name, short value) { + super.setShort(name, value); + return this; + } + + @Override + public TextHeaders setInt(CharSequence name, int value) { + super.setInt(name, value); + return this; + } + + @Override + public TextHeaders setLong(CharSequence name, long value) { + super.setLong(name, value); + return this; + } + + @Override + public TextHeaders setFloat(CharSequence name, float value) { + super.setFloat(name, value); + return this; + } + + @Override + public TextHeaders setDouble(CharSequence name, double value) { + super.setDouble(name, value); return this; } @Override public TextHeaders set(TextHeaders headers) { - if (headers == null) { - throw new NullPointerException("headers"); - } + super.set(headers); + return this; + } - clear(); - add0(headers); + @Override + public TextHeaders setAll(TextHeaders headers) { + super.setAll(headers); return this; } @Override public TextHeaders clear() { - Arrays.fill(entries, null); - head.before = head.after = head; - size = 0; + super.clear(); return this; } - @Override - public CharSequence getUnconverted(CharSequence name) { - if (name == null) { - throw new NullPointerException("name"); - } - - int h = hashCode(name); - int i = index(h); - HeaderEntry e = entries[i]; - CharSequence value = null; - // loop until the first header was found - while (e != null) { - if (e.hash == h && nameEquals(e.name, name)) { - value = e.value; - } - - e = e.next; - } - if (value != null) { - return value; - } - return null; - } - - @Override - public String get(CharSequence name) { - CharSequence v = getUnconverted(name); - if (v == null) { - return null; - } - return v.toString(); - } - - @Override - public String get(CharSequence name, String defaultValue) { - CharSequence v = getUnconverted(name); - if (v == null) { - return defaultValue; - } - return v.toString(); - } - - @Override - public Integer getInt(CharSequence name) { - CharSequence v = getUnconverted(name); - if (v == null) { - return null; - } - - try { - if (v instanceof AsciiString) { - return ((AsciiString) v).parseInt(); - } else { - return Integer.parseInt(v.toString()); - } - } catch (NumberFormatException ignored) { - return null; - } - } - - @Override - public int getInt(CharSequence name, int defaultValue) { - CharSequence v = getUnconverted(name); - if (v == null) { - return defaultValue; - } - - try { - if (v instanceof AsciiString) { - return ((AsciiString) v).parseInt(); - } else { - return Integer.parseInt(v.toString()); - } - } catch (NumberFormatException ignored) { - return defaultValue; - } - } - - @Override - public Long getLong(CharSequence name) { - CharSequence v = getUnconverted(name); - if (v == null) { - return null; - } - - try { - if (v instanceof AsciiString) { - return ((AsciiString) v).parseLong(); - } else { - return Long.parseLong(v.toString()); - } - } catch (NumberFormatException ignored) { - return null; - } - } - - @Override - public long getLong(CharSequence name, long defaultValue) { - CharSequence v = getUnconverted(name); - if (v == null) { - return defaultValue; - } - - try { - if (v instanceof AsciiString) { - return ((AsciiString) v).parseLong(); - } else { - return Long.parseLong(v.toString()); - } - } catch (NumberFormatException ignored) { - return defaultValue; - } - } - - @Override - public Long getTimeMillis(CharSequence name) { - CharSequence v = getUnconverted(name); - if (v == null) { - return null; - } - - try { - return HttpHeaderDateFormat.get().parse(v.toString()); - } catch (ParseException ignored) { - return null; - } - } - - @Override - public long getTimeMillis(CharSequence name, long defaultValue) { - CharSequence v = getUnconverted(name); - if (v == null) { - return defaultValue; - } - - return HttpHeaderDateFormat.get().parse(v.toString(), defaultValue); - } - - @Override - public CharSequence getUnconvertedAndRemove(CharSequence name) { - if (name == null) { - throw new NullPointerException("name"); - } - int h = hashCode(name); - int i = index(h); - HeaderEntry e = entries[i]; - if (e == null) { - return null; - } - - CharSequence value = null; - for (;;) { - if (e.hash == h && nameEquals(e.name, name)) { - value = e.value; - e.remove(); - HeaderEntry next = e.next; - if (next != null) { - entries[i] = next; - e = next; - } else { - entries[i] = null; - return value; - } - } else { - break; - } - } - - for (;;) { - HeaderEntry next = e.next; - if (next == null) { - break; - } - if (next.hash == h && nameEquals(next.name, name)) { - value = next.value; - e.next = next.next; - next.remove(); - } else { - e = next; - } - } - - if (value != null) { - return value; - } - return null; - } - - @Override - public String getAndRemove(CharSequence name) { - CharSequence v = getUnconvertedAndRemove(name); - if (v == null) { - return null; - } - return v.toString(); - } - - @Override - public String getAndRemove(CharSequence name, String defaultValue) { - CharSequence v = getUnconvertedAndRemove(name); - if (v == null) { - return defaultValue; - } - return v.toString(); - } - - @Override - public Integer getIntAndRemove(CharSequence name) { - CharSequence v = getUnconvertedAndRemove(name); - if (v == null) { - return null; - } - - try { - if (v instanceof AsciiString) { - return ((AsciiString) v).parseInt(); - } else { - return Integer.parseInt(v.toString()); - } - } catch (NumberFormatException ignored) { - return null; - } - } - - @Override - public int getIntAndRemove(CharSequence name, int defaultValue) { - CharSequence v = getUnconvertedAndRemove(name); - if (v == null) { - return defaultValue; - } - - try { - if (v instanceof AsciiString) { - return ((AsciiString) v).parseInt(); - } else { - return Integer.parseInt(v.toString()); - } - } catch (NumberFormatException ignored) { - return defaultValue; - } - } - - @Override - public Long getLongAndRemove(CharSequence name) { - CharSequence v = getUnconvertedAndRemove(name); - if (v == null) { - return null; - } - - try { - if (v instanceof AsciiString) { - return ((AsciiString) v).parseLong(); - } else { - return Long.parseLong(v.toString()); - } - } catch (NumberFormatException ignored) { - return null; - } - } - - @Override - public long getLongAndRemove(CharSequence name, long defaultValue) { - CharSequence v = getUnconvertedAndRemove(name); - if (v == null) { - return defaultValue; - } - - try { - if (v instanceof AsciiString) { - return ((AsciiString) v).parseLong(); - } else { - return Long.parseLong(v.toString()); - } - } catch (NumberFormatException ignored) { - return defaultValue; - } - } - - @Override - public Long getTimeMillisAndRemove(CharSequence name) { - CharSequence v = getUnconvertedAndRemove(name); - if (v == null) { - return null; - } - - try { - return HttpHeaderDateFormat.get().parse(v.toString()); - } catch (ParseException ignored) { - return null; - } - } - - @Override - public long getTimeMillisAndRemove(CharSequence name, long defaultValue) { - CharSequence v = getUnconvertedAndRemove(name); - if (v == null) { - return defaultValue; - } - - return HttpHeaderDateFormat.get().parse(v.toString(), defaultValue); - } - - @Override - public List getAllUnconverted(CharSequence name) { - if (name == null) { - throw new NullPointerException("name"); - } - - List values = new ArrayList(INITIAL_VALUELIST_CAPACITY); - int h = hashCode(name); - int i = index(h); - HeaderEntry e = entries[i]; - while (e != null) { - if (e.hash == h && nameEquals(e.name, name)) { - values.add(e.getValue()); - } - e = e.next; - } - - Collections.reverse(values); - return values; - } - - @Override - public List getAll(CharSequence name) { - if (name == null) { - throw new NullPointerException("name"); - } - - List values = new ArrayList(INITIAL_VALUELIST_CAPACITY); - int h = hashCode(name); - int i = index(h); - HeaderEntry e = entries[i]; - while (e != null) { - if (e.hash == h && nameEquals(e.name, name)) { - values.add(e.getValue().toString()); - } - e = e.next; - } - - Collections.reverse(values); - return values; - } - - @Override - public List getAllAndRemove(CharSequence name) { - if (name == null) { - throw new NullPointerException("name"); - } - int h = hashCode(name); - int i = index(h); - HeaderEntry e = entries[i]; - if (e == null) { - return null; - } - - List values = new ArrayList(INITIAL_VALUELIST_CAPACITY); - for (;;) { - if (e.hash == h && nameEquals(e.name, name)) { - values.add(e.getValue().toString()); - e.remove(); - HeaderEntry next = e.next; - if (next != null) { - entries[i] = next; - e = next; - } else { - entries[i] = null; - Collections.reverse(values); - return values; - } - } else { - break; - } - } - - for (;;) { - HeaderEntry next = e.next; - if (next == null) { - break; - } - if (next.hash == h && nameEquals(next.name, name)) { - values.add(next.getValue().toString()); - e.next = next.next; - next.remove(); - } else { - e = next; - } - } - - Collections.reverse(values); - return values; - } - - @Override - public List getAllUnconvertedAndRemove(CharSequence name) { - if (name == null) { - throw new NullPointerException("name"); - } - int h = hashCode(name); - int i = index(h); - HeaderEntry e = entries[i]; - if (e == null) { - return null; - } - - List values = new ArrayList(INITIAL_VALUELIST_CAPACITY); - for (;;) { - if (e.hash == h && nameEquals(e.name, name)) { - values.add(e.getValue()); - e.remove(); - HeaderEntry next = e.next; - if (next != null) { - entries[i] = next; - e = next; - } else { - entries[i] = null; - Collections.reverse(values); - return values; - } - } else { - break; - } - } - - for (;;) { - HeaderEntry next = e.next; - if (next == null) { - break; - } - if (next.hash == h && nameEquals(next.name, name)) { - values.add(next.getValue()); - e.next = next.next; - next.remove(); - } else { - e = next; - } - } - - Collections.reverse(values); - return values; - } - - @Override - public List> entries() { - int cnt = 0; - int size = size(); - @SuppressWarnings("unchecked") - Map.Entry[] all = new Map.Entry[size]; - - HeaderEntry e = head.after; - while (e != head) { - all[cnt ++] = new StringHeaderEntry(e); - e = e.after; - } - - assert size == cnt; - return Arrays.asList(all); - } - - @Override - public List> unconvertedEntries() { - int cnt = 0; - int size = size(); - @SuppressWarnings("unchecked") - Map.Entry[] all = new Map.Entry[size]; - - HeaderEntry e = head.after; - while (e != head) { - all[cnt ++] = e; - e = e.after; - } - - assert size == cnt; - return Arrays.asList(all); - } - - @Override - public Iterator> iterator() { - return new StringHeaderIterator(); - } - - @Override - public Iterator> unconvertedIterator() { - return new HeaderIterator(); - } - - @Override - public boolean contains(CharSequence name) { - return getUnconverted(name) != null; - } - - @Override - public int size() { - return size; - } - - @Override - public boolean isEmpty() { - return head == head.after; - } - - @Override - public boolean contains(CharSequence name, Object value) { - return contains(name, value, false); - } - - @Override - public boolean contains(CharSequence name, Object value, boolean ignoreCase) { - if (name == null) { - throw new NullPointerException("name"); - } - - int h = hashCode(name); - int i = index(h); - CharSequence convertedVal = convertValue(value); - HeaderEntry e = entries[i]; - while (e != null) { - if (e.hash == h && nameEquals(e.name, name)) { - if (valueEquals(e.value, convertedVal, ignoreCase)) { - return true; - } - } - e = e.next; - } - return false; - } - - @Override - public Set unconvertedNames() { - Set names = new LinkedHashSet(size()); - HeaderEntry e = head.after; - while (e != head) { - names.add(e.getKey()); - e = e.after; - } - return names; - } - - @Override - public Set names() { - Set names = new LinkedHashSet(size()); - HeaderEntry e = head.after; - while (e != head) { - names.add(e.getKey().toString()); - e = e.after; - } - return names; - } - - @Override - public TextHeaders forEachEntry(TextHeaderProcessor processor) { - HeaderEntry e = head.after; - try { - while (e != head) { - if (!processor.process(e.getKey(), e.getValue())) { - break; - } - e = e.after; - } - } catch (Exception ex) { - PlatformDependent.throwException(ex); - } - return this; - } - - private static final class HeaderEntry implements Map.Entry { - private final DefaultTextHeaders parent; - final int hash; - final CharSequence name; - CharSequence value; - HeaderEntry next; - HeaderEntry before, after; - - HeaderEntry(DefaultTextHeaders parent, int hash, CharSequence name, CharSequence value) { - this.parent = parent; - this.hash = hash; - this.name = name; - this.value = value; - } - - HeaderEntry(DefaultTextHeaders parent) { - this.parent = parent; - hash = -1; - name = null; - value = null; - } - - void remove() { - before.after = after; - after.before = before; - parent.size --; - } - - void addBefore(HeaderEntry e) { - after = e; - before = e.before; - before.after = this; - after.before = this; - parent.size ++; - } - - @Override - public CharSequence getKey() { - return name; - } - - @Override - public CharSequence getValue() { - return value; - } - - @Override - public CharSequence setValue(CharSequence value) { - if (value == null) { - throw new NullPointerException("value"); - } - value = parent.convertValue(value); - CharSequence oldValue = this.value; - this.value = value; - return oldValue; - } - - @Override - public String toString() { - return name.toString() + '=' + value.toString(); - } - } - - private static final class StringHeaderEntry implements Entry { - private final Entry entry; - private String name; - private String value; - - StringHeaderEntry(Entry entry) { - this.entry = entry; - } - - @Override - public String getKey() { - if (name == null) { - name = entry.getKey().toString(); - } - return name; - } - - @Override - public String getValue() { - if (value == null) { - value = entry.getValue().toString(); - } - return value; - } - - @Override - public String setValue(String value) { - return entry.setValue(value).toString(); - } - - @Override - public String toString() { - return entry.toString(); - } - } - - private final class HeaderIterator implements Iterator> { - - private HeaderEntry current = head; - - @Override - public boolean hasNext() { - return current.after != head; - } - - @Override - public Entry next() { - current = current.after; - - if (current == head) { - throw new NoSuchElementException(); - } - - return current; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - } - - private final class StringHeaderIterator implements Iterator> { - - private HeaderEntry current = head; - - @Override - public boolean hasNext() { - return current.after != head; - } - - @Override - public Entry next() { - current = current.after; - - if (current == head) { - throw new NoSuchElementException(); - } - - return new StringHeaderEntry(current); - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - } - - /** - * This DateFormat decodes 3 formats of {@link java.util.Date}, but only encodes the one, - * the first: - *
    - *
  • Sun, 06 Nov 1994 08:49:37 GMT: standard specification, the only one with - * valid generation
  • - *
  • Sun, 06 Nov 1994 08:49:37 GMT: obsolete specification
  • - *
  • Sun Nov 6 08:49:37 1994: obsolete specification
  • - *
- */ - static final class HttpHeaderDateFormat { - - private static final ParsePosition parsePos = new ParsePosition(0); - private static final FastThreadLocal dateFormatThreadLocal = - new FastThreadLocal() { - @Override - protected HttpHeaderDateFormat initialValue() { - return new HttpHeaderDateFormat(); - } - }; - - static HttpHeaderDateFormat get() { - return dateFormatThreadLocal.get(); - } - - /** - * Standard date format: - *
Sun, 06 Nov 1994 08:49:37 GMT -> E, d MMM yyyy HH:mm:ss z
- */ - private final DateFormat dateFormat1 = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH); - /** - * First obsolete format: - *
Sunday, 06-Nov-94 08:49:37 GMT -> E, d-MMM-y HH:mm:ss z
- */ - private final DateFormat dateFormat2 = new SimpleDateFormat("E, dd-MMM-yy HH:mm:ss z", Locale.ENGLISH); - /** - * Second obsolete format - *
Sun Nov 6 08:49:37 1994 -> EEE, MMM d HH:mm:ss yyyy
- */ - private final DateFormat dateFormat3 = new SimpleDateFormat("E MMM d HH:mm:ss yyyy", Locale.ENGLISH); - - private HttpHeaderDateFormat() { - TimeZone tz = TimeZone.getTimeZone("GMT"); - dateFormat1.setTimeZone(tz); - dateFormat2.setTimeZone(tz); - dateFormat3.setTimeZone(tz); - } - - long parse(String text) throws ParseException { - Date date = dateFormat1.parse(text, parsePos); - if (date == null) { - date = dateFormat2.parse(text, parsePos); - } - if (date == null) { - date = dateFormat3.parse(text, parsePos); - } - if (date == null) { - throw new ParseException(text, 0); - } - return date.getTime(); - } - - long parse(String text, long defaultValue) { - Date date = dateFormat1.parse(text, parsePos); - if (date == null) { - date = dateFormat2.parse(text, parsePos); - } - if (date == null) { - date = dateFormat3.parse(text, parsePos); - } - if (date == null) { - return defaultValue; - } - return date.getTime(); - } + private static Comparator comparator(boolean ignoreCase) { + return ignoreCase ? CHARSEQUENCE_CASE_INSENSITIVE_ORDER : CHARSEQUENCE_CASE_SENSITIVE_ORDER; } } diff --git a/codec/src/main/java/io/netty/handler/codec/EmptyBinaryHeaders.java b/codec/src/main/java/io/netty/handler/codec/EmptyBinaryHeaders.java new file mode 100644 index 0000000000..a6ff1e7a4c --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/EmptyBinaryHeaders.java @@ -0,0 +1,214 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project 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 io.netty.handler.codec; + +public class EmptyBinaryHeaders extends EmptyHeaders implements BinaryHeaders { + protected EmptyBinaryHeaders() { + } + + @Override + public BinaryHeaders add(AsciiString name, AsciiString value) { + super.add(name, value); + return this; + } + + @Override + public BinaryHeaders add(AsciiString name, Iterable values) { + super.add(name, values); + return this; + } + + @Override + public BinaryHeaders add(AsciiString name, AsciiString... values) { + super.add(name, values); + return this; + } + + @Override + public BinaryHeaders addObject(AsciiString name, Object value) { + super.addObject(name, value); + return this; + } + + @Override + public BinaryHeaders addObject(AsciiString name, Iterable values) { + super.addObject(name, values); + return this; + } + + @Override + public BinaryHeaders addObject(AsciiString name, Object... values) { + super.addObject(name, values); + return this; + } + + @Override + public BinaryHeaders addBoolean(AsciiString name, boolean value) { + super.addBoolean(name, value); + return this; + } + + @Override + public BinaryHeaders addChar(AsciiString name, char value) { + super.addChar(name, value); + return this; + } + + @Override + public BinaryHeaders addByte(AsciiString name, byte value) { + super.addByte(name, value); + return this; + } + + @Override + public BinaryHeaders addShort(AsciiString name, short value) { + super.addShort(name, value); + return this; + } + + @Override + public BinaryHeaders addInt(AsciiString name, int value) { + super.addInt(name, value); + return this; + } + + @Override + public BinaryHeaders addLong(AsciiString name, long value) { + super.addLong(name, value); + return this; + } + + @Override + public BinaryHeaders addFloat(AsciiString name, float value) { + super.addFloat(name, value); + return this; + } + + @Override + public BinaryHeaders addDouble(AsciiString name, double value) { + super.addDouble(name, value); + return this; + } + + @Override + public BinaryHeaders add(BinaryHeaders headers) { + super.add(headers); + return this; + } + + @Override + public BinaryHeaders set(AsciiString name, AsciiString value) { + super.set(name, value); + return this; + } + + @Override + public BinaryHeaders set(AsciiString name, Iterable values) { + super.set(name, values); + return this; + } + + @Override + public BinaryHeaders set(AsciiString name, AsciiString... values) { + super.set(name, values); + return this; + } + + @Override + public BinaryHeaders setObject(AsciiString name, Object value) { + super.setObject(name, value); + return this; + } + + @Override + public BinaryHeaders setObject(AsciiString name, Iterable values) { + super.setObject(name, values); + return this; + } + + @Override + public BinaryHeaders setObject(AsciiString name, Object... values) { + super.setObject(name, values); + return this; + } + + @Override + public BinaryHeaders setBoolean(AsciiString name, boolean value) { + super.setBoolean(name, value); + return this; + } + + @Override + public BinaryHeaders setChar(AsciiString name, char value) { + super.setChar(name, value); + return this; + } + + @Override + public BinaryHeaders setByte(AsciiString name, byte value) { + super.setByte(name, value); + return this; + } + + @Override + public BinaryHeaders setShort(AsciiString name, short value) { + super.setShort(name, value); + return this; + } + + @Override + public BinaryHeaders setInt(AsciiString name, int value) { + super.setInt(name, value); + return this; + } + + @Override + public BinaryHeaders setLong(AsciiString name, long value) { + super.setLong(name, value); + return this; + } + + @Override + public BinaryHeaders setFloat(AsciiString name, float value) { + super.setFloat(name, value); + return this; + } + + @Override + public BinaryHeaders setDouble(AsciiString name, double value) { + super.setDouble(name, value); + return this; + } + + @Override + public BinaryHeaders set(BinaryHeaders headers) { + super.set(headers); + return this; + } + + @Override + public BinaryHeaders setAll(BinaryHeaders headers) { + super.setAll(headers); + return this; + } + + @Override + public BinaryHeaders clear() { + super.clear(); + return this; + } +} diff --git a/codec/src/main/java/io/netty/handler/codec/EmptyConvertibleHeaders.java b/codec/src/main/java/io/netty/handler/codec/EmptyConvertibleHeaders.java new file mode 100644 index 0000000000..9a4ce83876 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/EmptyConvertibleHeaders.java @@ -0,0 +1,71 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project 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 io.netty.handler.codec; + +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; + +public class EmptyConvertibleHeaders extends + EmptyHeaders implements ConvertibleHeaders { + + @Override + public ConvertedType getAndConvert(UnconvertedType name) { + return null; + } + + @Override + public ConvertedType getAndConvert(UnconvertedType name, ConvertedType defaultValue) { + return defaultValue; + } + + @Override + public ConvertedType getAndRemoveAndConvert(UnconvertedType name) { + return null; + } + + @Override + public ConvertedType getAndRemoveAndConvert(UnconvertedType name, ConvertedType defaultValue) { + return defaultValue; + } + + @Override + public List getAllAndConvert(UnconvertedType name) { + return Collections.emptyList(); + } + + @Override + public List getAllAndRemoveAndConvert(UnconvertedType name) { + return Collections.emptyList(); + } + + @Override + public List> entriesConverted() { + return Collections.emptyList(); + } + + @Override + public Iterator> iteratorConverted() { + return entriesConverted().iterator(); + } + + @Override + public Set namesAndConvert(Comparator comparator) { + return Collections.emptySet(); + } +} diff --git a/codec/src/main/java/io/netty/handler/codec/EmptyHeaders.java b/codec/src/main/java/io/netty/handler/codec/EmptyHeaders.java new file mode 100644 index 0000000000..351381b7d1 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/EmptyHeaders.java @@ -0,0 +1,536 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project 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 io.netty.handler.codec; + +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; + +public class EmptyHeaders implements Headers { + @Override + public T get(T name) { + return null; + } + + @Override + public T get(T name, T defaultValue) { + return null; + } + + @Override + public T getAndRemove(T name) { + return null; + } + + @Override + public T getAndRemove(T name, T defaultValue) { + return null; + } + + @Override + public List getAll(T name) { + return Collections.emptyList(); + } + + @Override + public List getAllAndRemove(T name) { + return Collections.emptyList(); + } + + @Override + public Boolean getBoolean(T name) { + return null; + } + + @Override + public boolean getBoolean(T name, boolean defaultValue) { + return defaultValue; + } + + @Override + public Byte getByte(T name) { + return null; + } + + @Override + public byte getByte(T name, byte defaultValue) { + return defaultValue; + } + + @Override + public Character getChar(T name) { + return null; + } + + @Override + public char getChar(T name, char defaultValue) { + return defaultValue; + } + + @Override + public Short getShort(T name) { + return null; + } + + @Override + public short getInt(T name, short defaultValue) { + return defaultValue; + } + + @Override + public Integer getInt(T name) { + return null; + } + + @Override + public int getInt(T name, int defaultValue) { + return defaultValue; + } + + @Override + public Long getLong(T name) { + return null; + } + + @Override + public long getLong(T name, long defaultValue) { + return defaultValue; + } + + @Override + public Float getFloat(T name) { + return null; + } + + @Override + public float getFloat(T name, float defaultValue) { + return defaultValue; + } + + @Override + public Double getDouble(T name) { + return null; + } + + @Override + public double getDouble(T name, double defaultValue) { + return defaultValue; + } + + @Override + public Long getTimeMillis(T name) { + return null; + } + + @Override + public long getTimeMillis(T name, long defaultValue) { + return defaultValue; + } + + @Override + public Boolean getBooleanAndRemove(T name) { + return null; + } + + @Override + public boolean getBooleanAndRemove(T name, boolean defaultValue) { + return defaultValue; + } + + @Override + public Byte getByteAndRemove(T name) { + return null; + } + + @Override + public byte getByteAndRemove(T name, byte defaultValue) { + return defaultValue; + } + + @Override + public Character getCharAndRemove(T name) { + return null; + } + + @Override + public char getCharAndRemove(T name, char defaultValue) { + return defaultValue; + } + + @Override + public Short getShortAndRemove(T name) { + return null; + } + + @Override + public short getShortAndRemove(T name, short defaultValue) { + return defaultValue; + } + + @Override + public Integer getIntAndRemove(T name) { + return null; + } + + @Override + public int getIntAndRemove(T name, int defaultValue) { + return defaultValue; + } + + @Override + public Long getLongAndRemove(T name) { + return null; + } + + @Override + public long getLongAndRemove(T name, long defaultValue) { + return defaultValue; + } + + @Override + public Float getFloatAndRemove(T name) { + return null; + } + + @Override + public float getFloatAndRemove(T name, float defaultValue) { + return defaultValue; + } + + @Override + public Double getDoubleAndRemove(T name) { + return null; + } + + @Override + public double getDoubleAndRemove(T name, double defaultValue) { + return defaultValue; + } + + @Override + public Long getTimeMillisAndRemove(T name) { + return null; + } + + @Override + public long getTimeMillisAndRemove(T name, long defaultValue) { + return defaultValue; + } + + @Override + public List> entries() { + return Collections.emptyList(); + } + + @Override + public boolean contains(T name) { + return false; + } + + @Override + public boolean contains(T name, T value) { + return false; + } + + @Override + public boolean containsObject(T name, Object value) { + return false; + } + + @Override + public boolean containsBoolean(T name, boolean value) { + return false; + } + + @Override + public boolean containsByte(T name, byte value) { + return false; + } + + @Override + public boolean containsChar(T name, char value) { + return false; + } + + @Override + public boolean containsShort(T name, short value) { + return false; + } + + @Override + public boolean containsInt(T name, int value) { + return false; + } + + @Override + public boolean containsLong(T name, long value) { + return false; + } + + @Override + public boolean containsFloat(T name, float value) { + return false; + } + + @Override + public boolean containsDouble(T name, double value) { + return false; + } + + @Override + public boolean contains(T name, T value, Comparator comparator) { + return false; + } + + @Override + public boolean contains(T name, T value, + Comparator keyComparator, Comparator valueComparator) { + return false; + } + + @Override + public boolean containsObject(T name, Object value, Comparator comparator) { + return false; + } + + @Override + public boolean containsObject(T name, Object value, Comparator keyComparator, + Comparator valueComparator) { + return false; + } + + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public Set names() { + return Collections.emptySet(); + } + + @Override + public List namesList() { + return Collections.emptyList(); + } + + @Override + public Headers add(T name, T value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers add(T name, Iterable values) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers add(T name, T... values) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers addObject(T name, Object value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers addObject(T name, Iterable values) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers addObject(T name, Object... values) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers addBoolean(T name, boolean value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers addByte(T name, byte value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers addChar(T name, char value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers addShort(T name, short value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers addInt(T name, int value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers addLong(T name, long value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers addFloat(T name, float value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers addDouble(T name, double value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers add(Headers headers) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers set(T name, T value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers set(T name, Iterable values) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers set(T name, T... values) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers setObject(T name, Object value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers setObject(T name, Iterable values) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers setObject(T name, Object... values) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers setBoolean(T name, boolean value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers setByte(T name, byte value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers setChar(T name, char value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers setShort(T name, short value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers setInt(T name, int value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers setLong(T name, long value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers setFloat(T name, float value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers setDouble(T name, double value) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers set(Headers headers) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public Headers setAll(Headers headers) { + throw new UnsupportedOperationException("read only"); + } + + @Override + public boolean remove(T name) { + return false; + } + + @Override + public Headers clear() { + return this; + } + + @Override + public Iterator> iterator() { + return entries().iterator(); + } + + @Override + public Entry forEachEntry(Headers.EntryVisitor visitor) throws Exception { + return null; + } + + @Override + public T forEachName(Headers.NameVisitor visitor) throws Exception { + return null; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Headers)) { + return false; + } + + Headers rhs = (Headers) o; + return isEmpty() && rhs.isEmpty(); + } + + @Override + public int hashCode() { + return 1; + } + + @Override + public String toString() { + return new StringBuilder(getClass().getSimpleName()).append('[').append(']').toString(); + } +} diff --git a/codec/src/main/java/io/netty/handler/codec/EmptyTextHeaders.java b/codec/src/main/java/io/netty/handler/codec/EmptyTextHeaders.java index 307f85415c..1f9a7dc43e 100644 --- a/codec/src/main/java/io/netty/handler/codec/EmptyTextHeaders.java +++ b/codec/src/main/java/io/netty/handler/codec/EmptyTextHeaders.java @@ -16,233 +16,209 @@ package io.netty.handler.codec; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Map.Entry; -import java.util.Set; - -public class EmptyTextHeaders implements TextHeaders { - - protected EmptyTextHeaders() { } - - @Override - public String get(CharSequence name) { - return null; +public class EmptyTextHeaders extends EmptyConvertibleHeaders implements TextHeaders { + protected EmptyTextHeaders() { } @Override - public String get(CharSequence name, String defaultValue) { - return defaultValue; - } - - @Override - public Integer getInt(CharSequence name) { - return null; - } - - @Override - public int getInt(CharSequence name, int defaultValue) { - return defaultValue; - } - - @Override - public Long getLong(CharSequence name) { - return null; - } - - @Override - public long getLong(CharSequence name, long defaultValue) { - return defaultValue; - } - - @Override - public Long getTimeMillis(CharSequence name) { - return null; - } - - @Override - public long getTimeMillis(CharSequence name, long defaultValue) { - return defaultValue; - } - - @Override - public String getAndRemove(CharSequence name) { - return null; - } - - @Override - public String getAndRemove(CharSequence name, String defaultValue) { - return defaultValue; - } - - @Override - public Integer getIntAndRemove(CharSequence name) { - return null; - } - - @Override - public int getIntAndRemove(CharSequence name, int defaultValue) { - return defaultValue; - } - - @Override - public Long getLongAndRemove(CharSequence name) { - return null; - } - - @Override - public long getLongAndRemove(CharSequence name, long defaultValue) { - return defaultValue; - } - - @Override - public Long getTimeMillisAndRemove(CharSequence name) { - return null; - } - - @Override - public long getTimeMillisAndRemove(CharSequence name, long defaultValue) { - return defaultValue; - } - - @Override - public CharSequence getUnconverted(CharSequence name) { - return null; - } - - @Override - public CharSequence getUnconvertedAndRemove(CharSequence name) { - return null; - } - - @Override - public List getAll(CharSequence name) { - return Collections.emptyList(); - } - - @Override - public List getAllUnconverted(CharSequence name) { - return Collections.emptyList(); - } - - @Override - public List getAllAndRemove(CharSequence name) { - return Collections.emptyList(); - } - - @Override - public List getAllUnconvertedAndRemove(CharSequence name) { - return Collections.emptyList(); - } - - @Override - public List> entries() { - return Collections.emptyList(); - } - - @Override - public List> unconvertedEntries() { - return Collections.emptyList(); - } - - @Override - public boolean contains(CharSequence name) { + public boolean contains(CharSequence name, CharSequence value, boolean ignoreCase) { return false; } @Override - public int size() { - return 0; + public boolean containsObject(CharSequence name, Object value, boolean ignoreCase) { + return false; } @Override - public boolean isEmpty() { - return true; + public TextHeaders add(CharSequence name, CharSequence value) { + super.add(name, value); + return this; } @Override - public Set names() { - return Collections.emptySet(); + public TextHeaders add(CharSequence name, Iterable values) { + super.add(name, values); + return this; } @Override - public Set unconvertedNames() { - return Collections.emptySet(); + public TextHeaders add(CharSequence name, CharSequence... values) { + super.add(name, values); + return this; } @Override - public TextHeaders add(CharSequence name, Object value) { - throw new UnsupportedOperationException("read only"); + public TextHeaders addObject(CharSequence name, Object value) { + super.addObject(name, value); + return this; } @Override - public TextHeaders add(CharSequence name, Iterable values) { - throw new UnsupportedOperationException("read only"); + public TextHeaders addObject(CharSequence name, Iterable values) { + super.addObject(name, values); + return this; } @Override - public TextHeaders add(CharSequence name, Object... values) { - throw new UnsupportedOperationException("read only"); + public TextHeaders addObject(CharSequence name, Object... values) { + super.addObject(name, values); + return this; + } + + @Override + public TextHeaders addBoolean(CharSequence name, boolean value) { + super.addBoolean(name, value); + return this; + } + + @Override + public TextHeaders addChar(CharSequence name, char value) { + super.addChar(name, value); + return this; + } + + @Override + public TextHeaders addByte(CharSequence name, byte value) { + super.addByte(name, value); + return this; + } + + @Override + public TextHeaders addShort(CharSequence name, short value) { + super.addShort(name, value); + return this; + } + + @Override + public TextHeaders addInt(CharSequence name, int value) { + super.addInt(name, value); + return this; + } + + @Override + public TextHeaders addLong(CharSequence name, long value) { + super.addLong(name, value); + return this; + } + + @Override + public TextHeaders addFloat(CharSequence name, float value) { + super.addFloat(name, value); + return this; + } + + @Override + public TextHeaders addDouble(CharSequence name, double value) { + super.addDouble(name, value); + return this; } @Override public TextHeaders add(TextHeaders headers) { - throw new UnsupportedOperationException("read only"); + super.add(headers); + return this; } @Override - public TextHeaders set(CharSequence name, Object value) { - throw new UnsupportedOperationException("read only"); + public TextHeaders set(CharSequence name, CharSequence value) { + super.set(name, value); + return this; } @Override - public TextHeaders set(CharSequence name, Iterable values) { - throw new UnsupportedOperationException("read only"); + public TextHeaders set(CharSequence name, Iterable values) { + super.set(name, values); + return this; } @Override - public TextHeaders set(CharSequence name, Object... values) { - throw new UnsupportedOperationException("read only"); + public TextHeaders set(CharSequence name, CharSequence... values) { + super.set(name, values); + return this; + } + + @Override + public TextHeaders setObject(CharSequence name, Object value) { + super.setObject(name, value); + return this; + } + + @Override + public TextHeaders setObject(CharSequence name, Iterable values) { + super.setObject(name, values); + return this; + } + + @Override + public TextHeaders setObject(CharSequence name, Object... values) { + super.setObject(name, values); + return this; + } + + @Override + public TextHeaders setBoolean(CharSequence name, boolean value) { + super.setBoolean(name, value); + return this; + } + + @Override + public TextHeaders setChar(CharSequence name, char value) { + super.setChar(name, value); + return this; + } + + @Override + public TextHeaders setByte(CharSequence name, byte value) { + super.setByte(name, value); + return this; + } + + @Override + public TextHeaders setShort(CharSequence name, short value) { + super.setShort(name, value); + return this; + } + + @Override + public TextHeaders setInt(CharSequence name, int value) { + super.setInt(name, value); + return this; + } + + @Override + public TextHeaders setLong(CharSequence name, long value) { + super.setLong(name, value); + return this; + } + + @Override + public TextHeaders setFloat(CharSequence name, float value) { + super.setFloat(name, value); + return this; + } + + @Override + public TextHeaders setDouble(CharSequence name, double value) { + super.setDouble(name, value); + return this; } @Override public TextHeaders set(TextHeaders headers) { - throw new UnsupportedOperationException("read only"); + super.set(headers); + return this; } @Override - public boolean remove(CharSequence name) { - return false; + public TextHeaders setAll(TextHeaders headers) { + super.setAll(headers); + return this; } @Override public TextHeaders clear() { - return this; - } - - @Override - public boolean contains(CharSequence name, Object value) { - return false; - } - - @Override - public boolean contains(CharSequence name, Object value, boolean ignoreCase) { - return false; - } - - @Override - public Iterator> iterator() { - return entries().iterator(); - } - - @Override - public Iterator> unconvertedIterator() { - return unconvertedEntries().iterator(); - } - - @Override - public TextHeaders forEachEntry(TextHeaderProcessor processor) { + super.clear(); return this; } } diff --git a/codec/src/main/java/io/netty/handler/codec/Headers.java b/codec/src/main/java/io/netty/handler/codec/Headers.java new file mode 100644 index 0000000000..2e270c8345 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/Headers.java @@ -0,0 +1,1085 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project 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 io.netty.handler.codec; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public interface Headers extends Iterable> { + /** + * A visitor that helps reduce GC pressure while iterating over a collection of {@link Headers}. + */ + interface EntryVisitor { + /** + * @return
    + *
  • {@code true} if the processor wants to continue the loop and handle the entry.
  • + *
  • {@code false} if the processor wants to stop handling headers and abort the loop.
  • + *
+ */ + boolean visit(Map.Entry entry) throws Exception; + } + + /** + * A visitor that helps reduce GC pressure while iterating over a collection of {@link Headers}. + */ + interface NameVisitor { + /** + * @return
    + *
  • {@code true} if the processor wants to continue the loop and handle the entry.
  • + *
  • {@code false} if the processor wants to stop handling headers and abort the loop.
  • + *
+ */ + boolean visit(T name) throws Exception; + } + + /** + * Converts to/from a generic object to the type of the name for this map + */ + interface ValueConverter { + T convertObject(Object value); + + T convertBoolean(boolean value); + + boolean convertToBoolean(T value); + + T convertByte(byte value); + + byte convertToByte(T value); + + T convertChar(char value); + + char convertToChar(T value); + + T convertShort(short value); + + short convertToShort(T value); + + T convertInt(int value); + + int convertToInt(T value); + + T convertLong(long value); + + long convertToLong(T value); + + long convertToTimeMillis(T value); + + T convertFloat(float value); + + float convertToFloat(T value); + + T convertDouble(double value); + + double convertToDouble(T value); + } + + /** + * Returns the value of a header with the specified name. If there are more than one values for the specified name, + * the first value is returned. + * + * @param name the name of the header to search + * @return the first header value if the header is found. {@code null} if there's no such header. + */ + T get(T name); + + /** + * Returns the value of a header with the specified name. If there are more than one values for the specified name, + * the first value is returned. + * + * @param name the name of the header to search + * @param defaultValue the default value + * @return the first header value if the header is found. {@code defaultValue} if there's no such header. + */ + T get(T name, T defaultValue); + + /** + * Returns and removes the value of a header with the specified name. If there are more than one values for the + * specified name, the first value is returned. + * + * @param name the name of the header to search + * @return the first header value or {@code null} if there is no such header + */ + T getAndRemove(T name); + + /** + * Returns and removes the value of a header with the specified name. If there are more than one values for the + * specified name, the first value is returned. + * + * @param name the name of the header to search + * @param defaultValue the default value + * @return the first header value or {@code defaultValue} if there is no such header + */ + T getAndRemove(T name, T defaultValue); + + /** + * Returns the values of headers with the specified name + * + * @param name The name of the headers to search + * @return A {@link List} of header values which will be empty if no values are found + */ + List getAll(T name); + + /** + * Returns and Removes the values of headers with the specified name + * + * @param name The name of the headers to search + * @return A {@link List} of header values which will be empty if no values are found + */ + List getAllAndRemove(T name); + + /** + * Returns the boolean value of a header with the specified name. If there are more than one values for the + * specified name, the first value is returned. + * + * @param name the name of the header to search + * @return the first header value if the header is found and its value is a boolean. {@code null} if there's no such + * header or its value is not a boolean. + */ + Boolean getBoolean(T name); + + /** + * Returns the boolean value of a header with the specified name. If there are more than one values for the + * specified name, the first value is returned. + * + * @param name the name of the header to search + * @param defaultValue the default value + * @return the first header value if the header is found and its value is a boolean. {@code defaultValue} if there's + * no such header or its value is not a boolean. + */ + boolean getBoolean(T name, boolean defaultValue); + + /** + * Returns the byte value of a header with the specified name. If there are more than one values for the specified + * name, the first value is returned. + * + * @param name the name of the header to search + * @return the first header value if the header is found and its value is a byte. {@code null} if there's no such + * header or its value is not a byte. + */ + Byte getByte(T name); + + /** + * Returns the byte value of a header with the specified name. If there are more than one values for the specified + * name, the first value is returned. + * + * @param name the name of the header to search + * @param defaultValue the default value + * @return the first header value if the header is found and its value is a byte. {@code defaultValue} if there's no + * such header or its value is not a byte. + */ + byte getByte(T name, byte defaultValue); + + /** + * Returns the char value of a header with the specified name. If there are more than one values for the specified + * name, the first value is returned. + * + * @param name the name of the header to search + * @return the first header value if the header is found and its value is a char. {@code null} if there's no such + * header or its value is not a char. + */ + Character getChar(T name); + + /** + * Returns the char value of a header with the specified name. If there are more than one values for the specified + * name, the first value is returned. + * + * @param name the name of the header to search + * @param defaultValue the default value + * @return the first header value if the header is found and its value is a char. {@code defaultValue} if there's no + * such header or its value is not a char. + */ + char getChar(T name, char defaultValue); + + /** + * Returns the short value of a header with the specified name. If there are more than one values for the specified + * name, the first value is returned. + * + * @param name the name of the header to search + * @return the first header value if the header is found and its value is a short. {@code null} if there's no such + * header or its value is not a short. + */ + Short getShort(T name); + + /** + * Returns the short value of a header with the specified name. If there are more than one values for the specified + * name, the first value is returned. + * + * @param name the name of the header to search + * @param defaultValue the default value + * @return the first header value if the header is found and its value is a short. {@code defaultValue} if there's + * no such header or its value is not a short. + */ + short getInt(T name, short defaultValue); + + /** + * Returns the integer value of a header with the specified name. If there are more than one values for the + * specified name, the first value is returned. + * + * @param name the name of the header to search + * @return the first header value if the header is found and its value is an integer. {@code null} if there's no + * such header or its value is not an integer. + */ + Integer getInt(T name); + + /** + * Returns the integer value of a header with the specified name. If there are more than one values for the + * specified name, the first value is returned. + * + * @param name the name of the header to search + * @param defaultValue the default value + * @return the first header value if the header is found and its value is an integer. {@code defaultValue} if + * there's no such header or its value is not an integer. + */ + int getInt(T name, int defaultValue); + + /** + * Returns the long value of a header with the specified name. If there are more than one values for the specified + * name, the first value is returned. + * + * @param name the name of the header to search + * @return the first header value if the header is found and its value is a long. {@code null} if there's no such + * header or its value is not a long. + */ + Long getLong(T name); + + /** + * Returns the long value of a header with the specified name. If there are more than one values for the specified + * name, the first value is returned. + * + * @param name the name of the header to search + * @param defaultValue the default value + * @return the first header value if the header is found and its value is a long. {@code defaultValue} if there's no + * such header or its value is not a long. + */ + long getLong(T name, long defaultValue); + + /** + * Returns the float value of a header with the specified name. If there are more than one values for the specified + * name, the first value is returned. + * + * @param name the name of the header to search + * @return the first header value if the header is found and its value is a float. {@code null} if there's no such + * header or its value is not a float. + */ + Float getFloat(T name); + + /** + * Returns the float value of a header with the specified name. If there are more than one values for the specified + * name, the first value is returned. + * + * @param name the name of the header to search + * @param defaultValue the default value + * @return the first header value if the header is found and its value is a float. {@code defaultValue} if there's + * no such header or its value is not a float. + */ + float getFloat(T name, float defaultValue); + + /** + * Returns the double value of a header with the specified name. If there are more than one values for the specified + * name, the first value is returned. + * + * @param name the name of the header to search + * @return the first header value if the header is found and its value is a double. {@code null} if there's no such + * header or its value is not a double. + */ + Double getDouble(T name); + + /** + * Returns the double value of a header with the specified name. If there are more than one values for the specified + * name, the first value is returned. + * + * @param name the name of the header to search + * @param defaultValue the default value + * @return the first header value if the header is found and its value is a double. {@code defaultValue} if there's + * no such header or its value is not a double. + */ + double getDouble(T name, double defaultValue); + + /** + * Returns the date value of a header with the specified name as milliseconds. If there are more than one values for + * the specified name, the first value is returned. + * + * @param name The name of the header to search + * @return the first header value in milliseconds if the header is found and its value is a date. {@code null} if + * there's no such header or its value is not a date. + */ + Long getTimeMillis(T name); + + /** + * Returns the date value of a header with the specified name as milliseconds. If there are more than one values for + * the specified name, the first value is returned. + * + * @param name The name of the header to search + * @param defaultValue default value + * @return the first header value in milliseconds if the header is found and its value is a date. + * {@code defaultValue} if there's no such header or its value is not a date. + */ + long getTimeMillis(T name, long defaultValue); + + /** + * Returns and removes the boolean value of a header with the specified name. If there are more than one values for + * the specified name, the first value is returned. + * + * @param name the name of the header to search + * @return the first header value if the header is found and its value is a boolean. {@code null} if there's no such + * header or its value is not a boolean. + */ + Boolean getBooleanAndRemove(T name); + + /** + * Returns and removes the boolean value of a header with the specified name. If there are more than one values for + * the specified name, the first value is returned. + * + * @param name the name of the header to search + * @param defaultValue the default value + * @return the first header value if the header is found and its value is a boolean. {@code defaultValue} if there + * is no such header or its value of header is not a boolean. + */ + boolean getBooleanAndRemove(T name, boolean defaultValue); + + /** + * Returns and removes the byte value of a header with the specified name. If there are more than one values for the + * specified name, the first value is returned. + * + * @param name the name of the header to search + * @return the first header value if the header is found and its value is a byte. {@code null} if there's no such + * header or its value is not a byte. + */ + Byte getByteAndRemove(T name); + + /** + * Returns and removes the byte value of a header with the specified name. If there are more than one values for the + * specified name, the first value is returned. + * + * @param name the name of the header to search + * @param defaultValue the default value + * @return the first header value if the header is found and its value is a byte. {@code defaultValue} if there is + * no such header or its value of header is not a byte. + */ + byte getByteAndRemove(T name, byte defaultValue); + + /** + * Returns and removes the char value of a header with the specified name. If there are more than one values for the + * specified name, the first value is returned. + * + * @param name the name of the header to search + * @return the first header value if the header is found and its value is a char. {@code null} if there's no such + * header or its value is not a char. + */ + Character getCharAndRemove(T name); + + /** + * Returns and removes the char value of a header with the specified name. If there are more than one values for the + * specified name, the first value is returned. + * + * @param name the name of the header to search + * @param defaultValue the default value + * @return the first header value if the header is found and its value is a char. {@code defaultValue} if there is + * no such header or its value of header is not a char. + */ + char getCharAndRemove(T name, char defaultValue); + + /** + * Returns and removes the short value of a header with the specified name. If there are more than one values for + * the specified name, the first value is returned. + * + * @param name the name of the header to search + * @return the first header value if the header is found and its value is a short. {@code null} if there's no such + * header or its value is not a short. + */ + Short getShortAndRemove(T name); + + /** + * Returns and removes the short value of a header with the specified name. If there are more than one values for + * the specified name, the first value is returned. + * + * @param name the name of the header to search + * @param defaultValue the default value + * @return the first header value if the header is found and its value is a short. {@code defaultValue} if there is + * no such header or its value of header is not a short. + */ + short getShortAndRemove(T name, short defaultValue); + + /** + * Returns and removes the integer value of a header with the specified name. If there are more than one values for + * the specified name, the first value is returned. + * + * @param name the name of the header to search + * @return the first header value if the header is found and its value is an integer. {@code null} if there's no + * such header or its value is not an integer. + */ + Integer getIntAndRemove(T name); + + /** + * Returns and removes the integer value of a header with the specified name. If there are more than one values for + * the specified name, the first value is returned. + * + * @param name the name of the header to search + * @param defaultValue the default value + * @return the first header value if the header is found and its value is an integer. {@code defaultValue} if there + * is no such header or its value of header is not an integer. + */ + int getIntAndRemove(T name, int defaultValue); + + /** + * Returns and removes the long value of a header with the specified name. If there are more than one values for the + * specified name, the first value is returned. + * + * @param name the name of the header to search + * @return the first header value if the header is found and its value is a long. {@code null} if there's no such + * header or its value is not a long. + */ + Long getLongAndRemove(T name); + + /** + * Returns and removes the long value of a header with the specified name. If there are more than one values for the + * specified name, the first value is returned. + * + * @param name the name of the header to search + * @param defaultValue the default value + * @return the first header value if the header is found and its value is a long. {@code defaultValue} if there's no + * such header or its value is not a long. + */ + long getLongAndRemove(T name, long defaultValue); + + /** + * Returns and removes the float value of a header with the specified name. If there are more than one values for + * the specified name, the first value is returned. + * + * @param name the name of the header to search + * @return the first header value if the header is found and its value is a float. {@code null} if there's no such + * header or its value is not a float. + */ + Float getFloatAndRemove(T name); + + /** + * Returns and removes the float value of a header with the specified name. If there are more than one values for + * the specified name, the first value is returned. + * + * @param name the name of the header to search + * @param defaultValue the default value + * @return the first header value if the header is found and its value is a float. {@code defaultValue} if there's + * no such header or its value is not a float. + */ + float getFloatAndRemove(T name, float defaultValue); + + /** + * Returns and removes the double value of a header with the specified name. If there are more than one values for + * the specified name, the first value is returned. + * + * @param name the name of the header to search + * @return the first header value if the header is found and its value is a double. {@code null} if there's no such + * header or its value is not a double. + */ + Double getDoubleAndRemove(T name); + + /** + * Returns and removes the double value of a header with the specified name. If there are more than one values for + * the specified name, the first value is returned. + * + * @param name the name of the header to search + * @param defaultValue the default value + * @return the first header value if the header is found and its value is a double. {@code defaultValue} if there's + * no such header or its value is not a double. + */ + double getDoubleAndRemove(T name, double defaultValue); + + /** + * Returns and removes the date value of a header with the specified name as milliseconds. If there are more than + * one values for the specified name, the first value is returned. + * + * @param name The name of the header to search + * @return the first header value in milliseconds if the header is found and its value is a date. {@code null} if + * there's no such header or its value is not a date. + */ + Long getTimeMillisAndRemove(T name); + + /** + * Returns and removes the date value of a header with the specified name as milliseconds. If there are more than + * one values for the specified name, the first value is returned. + * + * @param name The name of the header to search + * @param defaultValue default value + * @return the first header value in milliseconds if the header is found and its value is a date. + * {@code defaultValue} if there's no such header or its value is not a date. + */ + long getTimeMillisAndRemove(T name, long defaultValue); + + /** + * Returns a new {@link List} that contains all headers in this object. Note that modifying the returned + * {@link List} will not affect the state of this object. If you intend to enumerate over the header entries only, + * use {@link #iterator()} instead, which has much less overhead. + */ + List> entries(); + + /** + * Returns {@code true} if and only if this collection contains the header with the specified name. + * + * @param name The name of the header to search for + * @return {@code true} if at least one header is found + */ + boolean contains(T name); + + /** + * Returns {@code true} if a header with the name and value exists. + * + * @param name the header name + * @param value the header value + * @return {@code true} if it contains it {@code false} otherwise + */ + boolean contains(T name, T value); + + /** + * Returns {@code true} if a header with the name and value exists. + * + * @param name the header name + * @param value the header value + * @return {@code true} if it contains it {@code false} otherwise + */ + boolean containsObject(T name, Object value); + + /** + * Returns {@code true} if a header with the name and value exists. + * + * @param name the header name + * @param value the header value + * @return {@code true} if it contains it {@code false} otherwise + */ + boolean containsBoolean(T name, boolean value); + + /** + * Returns {@code true} if a header with the name and value exists. + * + * @param name the header name + * @param value the header value + * @return {@code true} if it contains it {@code false} otherwise + */ + boolean containsByte(T name, byte value); + + /** + * Returns {@code true} if a header with the name and value exists. + * + * @param name the header name + * @param value the header value + * @return {@code true} if it contains it {@code false} otherwise + */ + boolean containsChar(T name, char value); + + /** + * Returns {@code true} if a header with the name and value exists. + * + * @param name the header name + * @param value the header value + * @return {@code true} if it contains it {@code false} otherwise + */ + boolean containsShort(T name, short value); + + /** + * Returns {@code true} if a header with the name and value exists. + * + * @param name the header name + * @param value the header value + * @return {@code true} if it contains it {@code false} otherwise + */ + boolean containsInt(T name, int value); + + /** + * Returns {@code true} if a header with the name and value exists. + * + * @param name the header name + * @param value the header value + * @return {@code true} if it contains it {@code false} otherwise + */ + boolean containsLong(T name, long value); + + /** + * Returns {@code true} if a header with the name and value exists. + * + * @param name the header name + * @param value the header value + * @return {@code true} if it contains it {@code false} otherwise + */ + boolean containsFloat(T name, float value); + + /** + * Returns {@code true} if a header with the name and value exists. + * + * @param name the header name + * @param value the header value + * @return {@code true} if it contains it {@code false} otherwise + */ + boolean containsDouble(T name, double value); + + /** + * Returns {@code true} if a header with the name and value exists. + * + * @param name the header name + * @param value the header value + * @param comparator The comparator to use when comparing {@code name} and {@code value} to entries in this map + * @return {@code true} if it contains it {@code false} otherwise + */ + boolean contains(T name, T value, Comparator comparator); + + /** + * Returns {@code true} if a header with the name and value exists. + * + * @param name the header name + * @param value the header value + * @param keyComparator The comparator to use when comparing {@code name} to names in this map + * @param valueComparator The comparator to use when comparing {@code value} to values in this map + * @return {@code true} if it contains it {@code false} otherwise + */ + boolean contains(T name, T value, Comparator keyComparator, Comparator valueComparator); + + /** + * Returns {@code true} if a header with the name and value exists. + * + * @param name the header name + * @param value the header value + * @param comparator The comparator to use when comparing {@code name} and {@code value} to entries in this map + * @return {@code true} if it contains it {@code false} otherwise + */ + boolean containsObject(T name, Object value, Comparator comparator); + + /** + * Returns {@code true} if a header with the name and value exists. + * + * @param name the header name + * @param value the header value + * @param keyComparator The comparator to use when comparing {@code name} to names in this map + * @param valueComparator The comparator to use when comparing {@code value} to values in this map + * @return {@code true} if it contains it {@code false} otherwise + */ + boolean containsObject(T name, Object value, Comparator keyComparator, + Comparator valueComparator); + + /** + * Returns the number of header entries in this collection. + */ + int size(); + + /** + * Returns {@code true} if and only if this collection contains no header entries. + */ + boolean isEmpty(); + + /** + * Returns a new {@link Set} that contains the names of all headers in this object. Note that modifying the returned + * {@link Set} will not affect the state of this object. If you intend to enumerate over the header entries only, + * use {@link #iterator()} instead, which has much less overhead. + */ + Set names(); + + /** + * Returns a new {@link List} that contains the names of all headers in this object. Note that modifying the + * returned {@link List} will not affect the state of this object. If you intend to enumerate over the header + * entries only, use {@link #iterator()} instead, which has much less overhead. + */ + List namesList(); + + /** + * Adds a new header with the specified name and value. If the specified value is not a {@link String}, it is + * converted into a {@link String} by {@link Object#toString()}, except in the cases of {@link java.util.Date} and + * {@link java.util.Calendar}, which are formatted to the date format defined in RFC2616. + * + * @param name the name of the header being added + * @param value the value of the header being added + * @return {@code this} + */ + Headers add(T name, T value); + + /** + * Adds a new header with the specified name and values. This getMethod can be represented approximately as the + * following code: + * + *
+     * for (Object v : values) {
+     *     if (v == null) {
+     *         break;
+     *     }
+     *     headers.add(name, v);
+     * }
+     * 
+ * + * @param name the name of the headepublic abstract rs being set + * @param values the values of the headers being set + * @return {@code this} + */ + Headers add(T name, Iterable values); + + /** + * Adds a new header with the specified name and values. This getMethod can be represented approximately as the + * following code: + * + *
+     * for (Object v : values) {
+     *     if (v == null) {
+     *         break;
+     *     }
+     *     headers.add(name, v);
+     * }
+     * 
+ * + * @param name the name of the headepublic abstract rs being set + * @param values the values of the headers being set + * @return {@code this} + */ + Headers add(T name, T... values); + + /** + * Adds a new header with the specified name and value. If the specified value is not a {@link String}, it is + * converted into a {@link String} by {@link Object#toString()}, except in the cases of {@link java.util.Date} and + * {@link java.util.Calendar}, which are formatted to the date format defined in RFC2616. + * + * @param name the name of the header being added + * @param value the value of the header being added + * @return {@code this} + */ + Headers addObject(T name, Object value); + + /** + * Adds a new header with the specified name and values. This getMethod can be represented approximately as the + * following code: + * + *
+     * for (Object v : values) {
+     *     if (v == null) {
+     *         break;
+     *     }
+     *     headers.add(name, v);
+     * }
+     * 
+ * + * @param name the name of the headepublic abstract rs being set + * @param values the values of the headers being set + * @return {@code this} + */ + Headers addObject(T name, Iterable values); + + /** + * Adds a new header with the specified name and values. This getMethod can be represented approximately as the + * following code: + * + *
+     * for (Object v : values) {
+     *     if (v == null) {
+     *         break;
+     *     }
+     *     headers.add(name, v);
+     * }
+     * 
+ * + * @param name the name of the headepublic abstract rs being set + * @param values the values of the headers being set + * @return {@code this} + */ + Headers addObject(T name, Object... values); + + /** + * Add the {@code name} to {@code value}. + * @param name The name to modify + * @param value The value + * @return {@code this} + */ + Headers addBoolean(T name, boolean value); + + /** + * Add the {@code name} to {@code value}. + * @param name The name to modify + * @param value The value + * @return {@code this} + */ + Headers addByte(T name, byte value); + + /** + * Add the {@code name} to {@code value}. + * @param name The name to modify + * @param value The value + * @return {@code this} + */ + Headers addChar(T name, char value); + + /** + * Add the {@code name} to {@code value}. + * @param name The name to modify + * @param value The value + * @return {@code this} + */ + Headers addShort(T name, short value); + + /** + * Add the {@code name} to {@code value}. + * @param name The name to modify + * @param value The value + * @return {@code this} + */ + Headers addInt(T name, int value); + + /** + * Add the {@code name} to {@code value}. + * @param name The name to modify + * @param value The value + * @return {@code this} + */ + Headers addLong(T name, long value); + + /** + * Add the {@code name} to {@code value}. + * @param name The name to modify + * @param value The value + * @return {@code this} + */ + Headers addFloat(T name, float value); + + /** + * Add the {@code name} to {@code value}. + * @param name The name to modify + * @param value The value + * @return {@code this} + */ + Headers addDouble(T name, double value); + + /** + * Adds all header entries of the specified {@code headers}. + * + * @return {@code this} + */ + Headers add(Headers headers); + + /** + * Sets a header with the specified name and value. If there is an existing header with the same name, it is + * removed. If the specified value is not a {@link String}, it is converted into a {@link String} by + * {@link Object#toString()}, except for {@link java.util.Date} and {@link java.util.Calendar}, which are formatted + * to the date format defined in RFC2616. + * + * @param name The name of the header being set + * @param value The value of the header being set + * @return {@code this} + */ + Headers set(T name, T value); + + /** + * Sets a header with the specified name and values. If there is an existing header with the same name, it is + * removed. This getMethod can be represented approximately as the following code: + * + *
+     * headers.remove(name);
+     * for (Object v : values) {
+     *     if (v == null) {
+     *         break;
+     *     }
+     *     headers.add(name, v);
+     * }
+     * 
+ * + * @param name the name of the headers being set + * @param values the values of the headers being set + * @return {@code this} + */ + Headers set(T name, Iterable values); + + /** + * Sets a header with the specified name and values. If there is an existing header with the same name, it is + * removed. This getMethod can be represented approximately as the following code: + * + *
+     * headers.remove(name);
+     * for (Object v : values) {
+     *     if (v == null) {
+     *         break;
+     *     }
+     *     headers.add(name, v);
+     * }
+     * 
+ * + * @param name the name of the headers being set + * @param values the values of the headers being set + * @return {@code this} + */ + Headers set(T name, T... values); + + /** + * Sets a header with the specified name and value. If there is an existing header with the same name, it is + * removed. If the specified value is not a {@link String}, it is converted into a {@link String} by + * {@link Object#toString()}, except for {@link java.util.Date} and {@link java.util.Calendar}, which are formatted + * to the date format defined in RFC2616. + * + * @param name The name of the header being set + * @param value The value of the header being set + * @return {@code this} + */ + Headers setObject(T name, Object value); + + /** + * Sets a header with the specified name and values. If there is an existing header with the same name, it is + * removed. This getMethod can be represented approximately as the following code: + * + *
+     * headers.remove(name);
+     * for (Object v : values) {
+     *     if (v == null) {
+     *         break;
+     *     }
+     *     headers.add(name, v);
+     * }
+     * 
+ * + * @param name the name of the headers being set + * @param values the values of the headers being set + * @return {@code this} + */ + Headers setObject(T name, Iterable values); + + /** + * Sets a header with the specified name and values. If there is an existing header with the same name, it is + * removed. This getMethod can be represented approximately as the following code: + * + *
+     * headers.remove(name);
+     * for (Object v : values) {
+     *     if (v == null) {
+     *         break;
+     *     }
+     *     headers.add(name, v);
+     * }
+     * 
+ * + * @param name the name of the headers being set + * @param values the values of the headers being set + * @return {@code this} + */ + Headers setObject(T name, Object... values); + + /** + * Set the {@code name} to {@code value}. This will remove all previous values associated with {@code name}. + * @param name The name to modify + * @param value The value + * @return {@code this} + */ + Headers setBoolean(T name, boolean value); + + /** + * Set the {@code name} to {@code value}. This will remove all previous values associated with {@code name}. + * @param name The name to modify + * @param value The value + * @return {@code this} + */ + Headers setByte(T name, byte value); + + /** + * Set the {@code name} to {@code value}. This will remove all previous values associated with {@code name}. + * @param name The name to modify + * @param value The value + * @return {@code this} + */ + Headers setChar(T name, char value); + + /** + * Set the {@code name} to {@code value}. This will remove all previous values associated with {@code name}. + * @param name The name to modify + * @param value The value + * @return {@code this} + */ + Headers setShort(T name, short value); + + /** + * Set the {@code name} to {@code value}. This will remove all previous values associated with {@code name}. + * @param name The name to modify + * @param value The value + * @return {@code this} + */ + Headers setInt(T name, int value); + + /** + * Set the {@code name} to {@code value}. This will remove all previous values associated with {@code name}. + * @param name The name to modify + * @param value The value + * @return {@code this} + */ + Headers setLong(T name, long value); + + /** + * Set the {@code name} to {@code value}. This will remove all previous values associated with {@code name}. + * @param name The name to modify + * @param value The value + * @return {@code this} + */ + Headers setFloat(T name, float value); + + /** + * Set the {@code name} to {@code value}. This will remove all previous values associated with {@code name}. + * @param name The name to modify + * @param value The value + * @return {@code this} + */ + Headers setDouble(T name, double value); + + /** + * Cleans the current header entries and copies all header entries of the specified {@code headers}. + * + * @return {@code this} + */ + Headers set(Headers headers); + + /** + * Retains all current headers but calls {@link #set(Object, Object)} for each entry in {@code headers} + * + * @param headers The headers used to {@link #set(Object, Object)} values in this instance + * @return {@code this} + */ + Headers setAll(Headers headers); + + /** + * Removes the header with the specified name. + * + * @param name The name of the header to remove + * @return {@code true} if and only if at least one entry has been removed + */ + boolean remove(T name); + + /** + * Removes all headers. + * + * @return {@code this} + */ + Headers clear(); + + @Override + Iterator> iterator(); + + /** + * Provide a means of iterating over elements in this map with low GC + * + * @param visitor The visitor which will visit each element in this map + * @return The last entry before iteration stopped or {@code null} if iteration went past the end + */ + Map.Entry forEachEntry(EntryVisitor visitor) throws Exception; + + /** + * Provide a means of iterating over elements in this map with low GC + * + * @param visitor The visitor which will visit each element in this map + * @return The last key before iteration stopped or {@code null} if iteration went past the end + */ + T forEachName(NameVisitor visitor) throws Exception; +} diff --git a/codec/src/main/java/io/netty/handler/codec/TextHeaderProcessor.java b/codec/src/main/java/io/netty/handler/codec/TextHeaderProcessor.java deleted file mode 100644 index 43aa9df7a6..0000000000 --- a/codec/src/main/java/io/netty/handler/codec/TextHeaderProcessor.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2014 The Netty Project - * - * The Netty Project 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 io.netty.handler.codec; - -public interface TextHeaderProcessor { - boolean process(CharSequence name, CharSequence value) throws Exception; -} diff --git a/codec/src/main/java/io/netty/handler/codec/TextHeaders.java b/codec/src/main/java/io/netty/handler/codec/TextHeaders.java index 06d9f3d212..5798acd057 100644 --- a/codec/src/main/java/io/netty/handler/codec/TextHeaders.java +++ b/codec/src/main/java/io/netty/handler/codec/TextHeaders.java @@ -16,444 +16,142 @@ package io.netty.handler.codec; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - /** - * A typical string multimap used by text protocols such as HTTP for the representation of arbitrary key-value data. - * One thing to note is that it uses {@link CharSequence} as its primary key and value type rather than {@link String}. - * When you invoke the operations that produce {@link String}s such as {@link #get(CharSequence)}, - * a {@link CharSequence} is implicitly converted to a {@link String}. This is particularly useful for speed - * optimization because this multimap can hold a special {@link CharSequence} implementation that a codec can - * treat specially, such as {@link AsciiString}. + * A typical string multimap used by text protocols such as HTTP for the representation of arbitrary key-value data. One + * thing to note is that it uses {@link CharSequence} as its primary key and value type rather than {@link String}. When + * you invoke the operations that produce {@link String}s such as {@link #get(Object)}, a {@link CharSequence} is + * implicitly converted to a {@link String}. This is particularly useful for speed optimization because this multimap + * can hold a special {@link CharSequence} implementation that a codec can treat specially, such as {@link CharSequence} + * . */ -public interface TextHeaders extends Iterable> { +public interface TextHeaders extends ConvertibleHeaders { /** - * Returns the value of a header with the specified name. If there are - * more than one values for the specified name, the first value is returned. - * - * @param name the name of the header to search - * @return the first header value if the header is found. - * {@code null} if there's no such header. + * A visitor that helps reduce GC pressure while iterating over a collection of {@link Headers}. */ - String get(CharSequence name); + interface EntryVisitor extends Headers.EntryVisitor { + } /** - * Returns the value of a header with the specified name. If there are - * more than one values for the specified name, the first value is returned. - * - * @param name the name of the header to search - * @param defaultValue the default value - * @return the first header value if the header is found. - * {@code defaultValue} if there's no such header. + * A visitor that helps reduce GC pressure while iterating over a collection of {@link Headers}. */ - String get(CharSequence name, String defaultValue); + interface NameVisitor extends Headers.NameVisitor { + } /** - * Returns the integer value of a header with the specified name. If there are - * more than one values for the specified name, the first value is returned. - * - * @param name the name of the header to search - * @return the first header value if the header is found and its value is an integer. - * {@code null} if there's no such header or its value is not an integer. + * Returns {@code true} if a header with the name and value exists. + * @param name the header name + * @param value the header value + * @return {@code true} if it contains it {@code false} otherwise */ - Integer getInt(CharSequence name); + boolean contains(CharSequence name, CharSequence value, boolean ignoreCase); /** - * Returns the integer value of a header with the specified name. If there are - * more than one values for the specified name, the first value is returned. - * - * @param name the name of the header to search - * @param defaultValue the default value - * @return the first header value if the header is found and its value is an integer. - * {@code defaultValue} if there's no such header or its value is not an integer. + * Returns {@code true} if a header with the name and value exists. + * @param name the header name + * @param value the header value + * @return {@code true} if it contains it {@code false} otherwise */ - int getInt(CharSequence name, int defaultValue); + boolean containsObject(CharSequence name, Object value, boolean ignoreCase); + + @Override + TextHeaders add(CharSequence name, CharSequence value); + + @Override + TextHeaders add(CharSequence name, Iterable values); + + @Override + TextHeaders add(CharSequence name, CharSequence... values); + + @Override + TextHeaders addObject(CharSequence name, Object value); + + @Override + TextHeaders addObject(CharSequence name, Iterable values); + + @Override + TextHeaders addObject(CharSequence name, Object... values); + + @Override + TextHeaders addBoolean(CharSequence name, boolean value); + + @Override + TextHeaders addByte(CharSequence name, byte value); + + @Override + TextHeaders addChar(CharSequence name, char value); + + @Override + TextHeaders addShort(CharSequence name, short value); + + @Override + TextHeaders addInt(CharSequence name, int value); + + @Override + TextHeaders addLong(CharSequence name, long value); + + @Override + TextHeaders addFloat(CharSequence name, float value); + + @Override + TextHeaders addDouble(CharSequence name, double value); /** - * Returns the long integer value of a header with the specified name. If there are - * more than one values for the specified name, the first value is returned. - * - * @param name the name of the header to search - * @return the first header value if the header is found and its value is a long integer. - * {@code null} if there's no such header or its value is not a long integer. - */ - Long getLong(CharSequence name); - - /** - * Returns the long integer value of a header with the specified name. If there are - * more than one values for the specified name, the first value is returned. - * - * @param name the name of the header to search - * @param defaultValue the default value - * @return the first header value if the header is found and its value is a long integer. - * {@code defaultValue} if there's no such header or its value is not a long integer. - */ - long getLong(CharSequence name, long defaultValue); - - /** - * Returns the date value of a header with the specified name as milliseconds. If there are - * more than one values for the specified name, the first value is returned. - * - * @param name The name of the header to search - * @return the first header value in milliseconds if the header is found and its value is a date. - * {@code null} if there's no such header or its value is not a date. - */ - Long getTimeMillis(CharSequence name); - - /** - * Returns the date value of a header with the specified name as milliseconds. If there are - * more than one values for the specified name, the first value is returned. - * - * @param name The name of the header to search - * @param defaultValue default value - * @return the first header value in milliseconds if the header is found and its value is a date. - * {@code defaultValue} if there's no such header or its value is not a date. - */ - long getTimeMillis(CharSequence name, long defaultValue); - - /** - * Returns and removes the value of a header with the specified name. If there are - * more than one values for the specified name, the first value is returned. - * - * @param name the name of the header to search - * @return the first header value or {@code null} if there is no such header - */ - String getAndRemove(CharSequence name); - - /** - * Returns and removes the value of a header with the specified name. If there are - * more than one values for the specified name, the first value is returned. - * - * @param name the name of the header to search - * @param defaultValue the default value - * @return the first header value or {@code defaultValue} if there is no such header - */ - String getAndRemove(CharSequence name, String defaultValue); - - /** - * Returns and removes the integer value of a header with the specified name. If there are - * more than one values for the specified name, the first value is returned. - * - * @param name the name of the header to search - * @return the first header value if the header is found and its value is an integer. - * {@code null} if there's no such header or its value is not an integer. - */ - Integer getIntAndRemove(CharSequence name); - - /** - * Returns and removes the integer value of a header with the specified name. If there are more than one values for - * the specified name, the first value is returned. - * - * @param name the name of the header to search - * @param defaultValue the default value - * @return the first header value if the header is found and its value is an integer. - * {@code defaultValue} if there is no such header or its value of header is not an integer. - */ - int getIntAndRemove(CharSequence name, int defaultValue); - - /** - * Returns and removes the long value of a header with the specified name. If there are - * more than one values for the specified name, the first value is returned. - * - * @param name the name of the header to search - * @return the first header value if the header is found and its value is an integer. - * {@code null} if there's no such header or its value is not an integer. - */ - Long getLongAndRemove(CharSequence name); - - /** - * Returns and removes the long value of a header with the specified name. If there are more than one values for - * the specified name, the first value is returned. - * - * @param name the name of the header to search - * @param defaultValue the default value - * @return the first header value if the header is found and its value is an integer. - * {@code defaultValue} if there's no such header or its value is not an integer. - */ - long getLongAndRemove(CharSequence name, long defaultValue); - - /** - * Returns and removes the date value of a header with the specified name as milliseconds. If there are - * more than one values for the specified name, the first value is returned. - * - * @param name The name of the header to search - * @return the first header value in milliseconds if the header is found and its value is a date. - * {@code null} if there's no such header or its value is not a date. - */ - Long getTimeMillisAndRemove(CharSequence name); - - /** - * Returns and removes the date value of a header with the specified name as milliseconds. If there are - * more than one values for the specified name, the first value is returned. - * - * @param name The name of the header to search - * @param defaultValue default value - * @return the first header value in milliseconds if the header is found and its value is a date. - * {@code defaultValue} if there's no such header or its value is not a date. - */ - long getTimeMillisAndRemove(CharSequence name, long defaultValue); - - /** - * Returns the value of a header with the specified name. If there are - * more than one values for the specified name, the first value is returned. - * - * @param name The name of the header to search - * @return The first header value or {@code null} if there is no such header - */ - CharSequence getUnconverted(CharSequence name); - - /** - * Returns and Removes the value of a header with the specified name. If there are - * more than one values for the specified name, the first value is returned. - * - * @param name The name of the header to search - * @return The first header value or {@code null} if there is no such header - */ - CharSequence getUnconvertedAndRemove(CharSequence name); - - /** - * Returns the values of headers with the specified name - * - * @param name The name of the headers to search - * @return A {@link List} of header values which will be empty if no values are found - */ - List getAll(CharSequence name); - - /** - * Returns the values of headers with the specified name - * - * @param name The name of the headers to search - * @return A {@link List} of header values which will be empty if no values are found - */ - List getAllUnconverted(CharSequence name); - - /** - * Returns and Removes the values of headers with the specified name - * - * @param name The name of the headers to search - * @return A {@link List} of header values which will be empty if no values are found - */ - List getAllAndRemove(CharSequence name); - - /** - * Returns and Removes the values of headers with the specified name - * - * @param name The name of the headers to search - * @return A {@link List} of header values which will be empty if no values are found - */ - List getAllUnconvertedAndRemove(CharSequence name); - - /** - * Returns a new {@link List} that contains all headers in this object. Note that modifying the - * returned {@link List} will not affect the state of this object. If you intend to enumerate over the header - * entries only, use {@link #iterator()} instead, which has much less overhead. - */ - List> entries(); - - /** - * Returns a new {@link List} that contains all headers in this object. Note that modifying the - * returned {@link List} will not affect the state of this object. If you intend to enumerate over the header - * entries only, use {@link #iterator()} instead, which has much less overhead. - */ - List> unconvertedEntries(); - - /** - * Returns {@code true} if and only if this collection contains the header with the specified name. - * - * @param name The name of the header to search for - * @return {@code true} if at least one header is found - */ - boolean contains(CharSequence name); - - /** - * Returns the number of header entries in this collection. - */ - int size(); - - /** - * Returns {@code true} if and only if this collection contains no header entries. - */ - boolean isEmpty(); - - /** - * Returns a new {@link Set} that contains the names of all headers in this object. Note that modifying the - * returned {@link Set} will not affect the state of this object. If you intend to enumerate over the header - * entries only, use {@link #iterator()} instead, which has much less overhead. - */ - Set names(); - - /** - * Returns a new {@link Set} that contains the names of all headers in this object. Note that modifying the - * returned {@link Set} will not affect the state of this object. If you intend to enumerate over the header - * entries only, use {@link #iterator()} instead, which has much less overhead. - */ - Set unconvertedNames(); - - /** - * Adds a new header with the specified name and value. - * - * If the specified value is not a {@link String}, it is converted - * into a {@link String} by {@link Object#toString()}, except in the cases - * of {@link java.util.Date} and {@link java.util.Calendar}, which are formatted to the date - * format defined in RFC2616. - * - * @param name the name of the header being added - * @param value the value of the header being added - * - * @return {@code this} - */ - TextHeaders add(CharSequence name, Object value); - - /** - * Adds a new header with the specified name and values. - * - * This getMethod can be represented approximately as the following code: - *
-     * for (Object v: values) {
-     *     if (v == null) {
-     *         break;
-     *     }
-     *     headers.add(name, v);
-     * }
-     * 
- * - * @param name the name of the headepublic abstract rs being set - * @param values the values of the headers being set - * @return {@code this} - */ - TextHeaders add(CharSequence name, Iterable values); - - /** - * Adds a new header with the specified name and values. - * - * This getMethod can be represented approximately as the following code: - *
-     * for (Object v: values) {
-     *     if (v == null) {
-     *         break;
-     *     }
-     *     headers.add(name, v);
-     * }
-     * 
- * - * @param name the name of the headepublic abstract rs being set - * @param values the values of the headers being set - * @return {@code this} - */ - TextHeaders add(CharSequence name, Object... values); - - /** - * Adds all header entries of the specified {@code headers}. - * - * @return {@code this} + * See {@link Headers#add(Headers)} */ TextHeaders add(TextHeaders headers); - /** - * Sets a header with the specified name and value. - * - * If there is an existing header with the same name, it is removed. - * If the specified value is not a {@link String}, it is converted into a - * {@link String} by {@link Object#toString()}, except for {@link java.util.Date} - * and {@link java.util.Calendar}, which are formatted to the date format defined in - * RFC2616. - * - * @param name The name of the header being set - * @param value The value of the header being set - * @return {@code this} - */ - TextHeaders set(CharSequence name, Object value); + @Override + TextHeaders set(CharSequence name, CharSequence value); + + @Override + TextHeaders set(CharSequence name, Iterable values); + + @Override + TextHeaders set(CharSequence name, CharSequence... values); + + @Override + TextHeaders setObject(CharSequence name, Object value); + + @Override + TextHeaders setObject(CharSequence name, Iterable values); + + @Override + TextHeaders setObject(CharSequence name, Object... values); + + @Override + TextHeaders setBoolean(CharSequence name, boolean value); + + @Override + TextHeaders setByte(CharSequence name, byte value); + + @Override + TextHeaders setChar(CharSequence name, char value); + + @Override + TextHeaders setShort(CharSequence name, short value); + + @Override + TextHeaders setInt(CharSequence name, int value); + + @Override + TextHeaders setLong(CharSequence name, long value); + + @Override + TextHeaders setFloat(CharSequence name, float value); + + @Override + TextHeaders setDouble(CharSequence name, double value); /** - * Sets a header with the specified name and values. - * - * If there is an existing header with the same name, it is removed. - * This getMethod can be represented approximately as the following code: - *
-     * headers.remove(name);
-     * for (Object v: values) {
-     *     if (v == null) {
-     *         break;
-     *     }
-     *     headers.add(name, v);
-     * }
-     * 
- * - * @param name the name of the headers being set - * @param values the values of the headers being set - * @return {@code this} - */ - TextHeaders set(CharSequence name, Iterable values); - - /** - * Sets a header with the specified name and values. - * - * If there is an existing header with the same name, it is removed. - * This getMethod can be represented approximately as the following code: - *
-     * headers.remove(name);
-     * for (Object v: values) {
-     *     if (v == null) {
-     *         break;
-     *     }
-     *     headers.add(name, v);
-     * }
-     * 
- * - * @param name the name of the headers being set - * @param values the values of the headers being set - * @return {@code this} - */ - TextHeaders set(CharSequence name, Object... values); - - /** - * Cleans the current header entries and copies all header entries of the specified {@code headers}. - * - * @return {@code this} + * See {@link Headers#set(Headers)} */ TextHeaders set(TextHeaders headers); /** - * Removes the header with the specified name. - * - * @param name The name of the header to remove - * @return {@code true} if and only if at least one entry has been removed + * See {@link Headers#setAll(Headers)} */ - boolean remove(CharSequence name); - - /** - * Removes all headers. - * - * @return {@code this} - */ - TextHeaders clear(); - - /** - * Returns {@code true} if a header with the name and value exists. - * - * @param name the header name - * @param value the header value - * @return {@code true} if it contains it {@code false} otherwise - */ - boolean contains(CharSequence name, Object value); - - /** - * Returns {@code true} if a header with the name and value exists. - * - * @param name the header name - * @param value the header value - * @return {@code true} if it contains it {@code false} otherwise - */ - boolean contains(CharSequence name, Object value, boolean ignoreCase); + TextHeaders setAll(TextHeaders headers); @Override - Iterator> iterator(); - - Iterator> unconvertedIterator(); - - TextHeaders forEachEntry(TextHeaderProcessor processor); + TextHeaders clear(); } diff --git a/codec/src/test/java/io/netty/handler/codec/AsciiStringTest.java b/codec/src/test/java/io/netty/handler/codec/AsciiStringTest.java new file mode 100644 index 0000000000..52fbcd999a --- /dev/null +++ b/codec/src/test/java/io/netty/handler/codec/AsciiStringTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project 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 io.netty.handler.codec; + +import static org.junit.Assert.assertArrayEquals; +import io.netty.util.CharsetUtil; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; + +import org.junit.Test; + +/** + * Test for the {@link AsciiString} class + */ +public class AsciiStringTest { + + @Test + public void testGetBytesStringBuilder() { + final StringBuilder b = new StringBuilder(); + for (int i = 0; i < 1 << 16; ++i) { + b.append("eéaà"); + } + final String bString = b.toString(); + final Charset[] charsets = CharsetUtil.values(); + for (int i = 0; i < charsets.length; ++i) { + final Charset charset = charsets[i]; + byte[] expected = getBytesWithEncoder(bString, charset); + byte[] actual = AsciiString.getBytes(b, charset); + assertArrayEquals("failure for " + charset, expected, actual); + } + } + + @Test + public void testGetBytesString() { + final StringBuilder b = new StringBuilder(); + for (int i = 0; i < 1 << 16; ++i) { + b.append("eéaà"); + } + final String bString = b.toString(); + final Charset[] charsets = CharsetUtil.values(); + for (int i = 0; i < charsets.length; ++i) { + final Charset charset = charsets[i]; + byte[] expected = bString.getBytes(charset); + byte[] actual = AsciiString.getBytes(bString, charset); + assertArrayEquals("failure for " + charset, expected, actual); + } + } + + @Test + public void testGetBytesAsciiString() { + final StringBuilder b = new StringBuilder(); + for (int i = 0; i < 1 << 16; ++i) { + b.append("eéaà"); + } + final String bString = b.toString(); + // The AsciiString class actually limits the Charset to ISO_8859_1 + byte[] expected = bString.getBytes(CharsetUtil.ISO_8859_1); + final Charset[] charsets = CharsetUtil.values(); + for (int i = 0; i < charsets.length; ++i) { + final Charset charset = charsets[i]; + byte[] actual = AsciiString.getBytes(new AsciiString(bString), charset); + assertArrayEquals("failure for " + charset, expected, actual); + } + } + + private static byte[] getBytesWithEncoder(CharSequence value, Charset charset) { + final CharsetEncoder encoder = CharsetUtil.getEncoder(charset); + final ByteBuffer nativeBuffer = ByteBuffer.allocate((int) (encoder.maxBytesPerChar() * value.length())); + encoder.encode(CharBuffer.wrap(value), nativeBuffer, true); + return nativeBuffer.array(); + } +} diff --git a/codec/src/test/java/io/netty/handler/codec/DefaultBinaryHeadersTest.java b/codec/src/test/java/io/netty/handler/codec/DefaultBinaryHeadersTest.java new file mode 100644 index 0000000000..6d477e07bc --- /dev/null +++ b/codec/src/test/java/io/netty/handler/codec/DefaultBinaryHeadersTest.java @@ -0,0 +1,316 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project 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 io.netty.handler.codec; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Random; +import java.util.Set; + +import org.junit.Test; + +/** + * Tests for {@link DefaultBinaryHeaders}. + */ +public class DefaultBinaryHeadersTest { + + @Test + public void binaryHeadersWithSameValuesShouldBeEquivalent() { + byte[] key1 = randomBytes(); + byte[] value1 = randomBytes(); + byte[] key2 = randomBytes(); + byte[] value2 = randomBytes(); + + DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(false); + h1.set(as(key1), as(value1)); + h1.set(as(key2), as(value2)); + + DefaultBinaryHeaders h2 = new DefaultBinaryHeaders(false); + h2.set(as(key1), as(value1)); + h2.set(as(key2), as(value2)); + + assertTrue(h1.equals(h2)); + assertTrue(h2.equals(h1)); + assertTrue(h2.equals(h2)); + assertTrue(h1.equals(h1)); + } + + @Test + public void binaryHeadersWithSameDuplicateValuesShouldBeEquivalent() { + byte[] k1 = randomBytes(); + byte[] k2 = randomBytes(); + byte[] v1 = randomBytes(); + byte[] v2 = randomBytes(); + byte[] v3 = randomBytes(); + byte[] v4 = randomBytes(); + + DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(false); + h1.set(as(k1), as(v1)); + h1.set(as(k2), as(v2)); + h1.add(as(k2), as(v3)); + h1.add(as(k1), as(v4)); + + DefaultBinaryHeaders h2 = new DefaultBinaryHeaders(false); + h2.set(as(k1), as(v1)); + h2.set(as(k2), as(v2)); + h2.add(as(k1), as(v4)); + h2.add(as(k2), as(v3)); + + assertTrue(h1.equals(h2)); + assertTrue(h2.equals(h1)); + assertTrue(h2.equals(h2)); + assertTrue(h1.equals(h1)); + } + + @Test + public void binaryHeadersWithDifferentValuesShouldNotBeEquivalent() { + byte[] k1 = randomBytes(); + byte[] k2 = randomBytes(); + byte[] v1 = randomBytes(); + byte[] v2 = randomBytes(); + byte[] v3 = randomBytes(); + byte[] v4 = randomBytes(); + + DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(false); + h1.set(as(k1), as(v1)); + h1.set(as(k2), as(v2)); + h1.add(as(k2), as(v3)); + h1.add(as(k1), as(v4)); + + DefaultBinaryHeaders h2 = new DefaultBinaryHeaders(false); + h2.set(as(k1), as(v1)); + h2.set(as(k2), as(v2)); + h2.add(as(k1), as(v4)); + + assertFalse(h1.equals(h2)); + assertFalse(h2.equals(h1)); + assertTrue(h2.equals(h2)); + assertTrue(h1.equals(h1)); + } + + @Test + public void binarySetAllShouldMergeHeaders() { + byte[] k1 = randomBytes(); + byte[] k2 = randomBytes(); + byte[] v1 = randomBytes(); + byte[] v2 = randomBytes(); + byte[] v3 = randomBytes(); + byte[] v4 = randomBytes(); + + DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(false); + h1.set(as(k1), as(v1)); + h1.set(as(k2), as(v2)); + h1.add(as(k2), as(v3)); + h1.add(as(k1), as(v4)); + + DefaultBinaryHeaders h2 = new DefaultBinaryHeaders(false); + h2.set(as(k1), as(v1)); + h2.set(as(k2), as(v2)); + h2.add(as(k1), as(v4)); + + DefaultBinaryHeaders expected = new DefaultBinaryHeaders(false); + expected.set(as(k1), as(v1)); + expected.set(as(k2), as(v2)); + expected.add(as(k2), as(v3)); + expected.add(as(k1), as(v4)); + expected.set(as(k1), as(v1)); + expected.set(as(k2), as(v2)); + expected.set(as(k1), as(v4)); + + h1.setAll(h2); + + assertEquals(expected, h1); + } + + @Test + public void binarySetShouldReplacePreviousValues() { + byte[] k1 = randomBytes(); + byte[] v1 = randomBytes(); + byte[] v2 = randomBytes(); + byte[] v3 = randomBytes(); + + DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(false); + h1.add(as(k1), as(v1)); + h1.add(as(k1), as(v2)); + assertEquals(2, h1.size()); + + h1.set(as(k1), as(v3)); + assertEquals(1, h1.size()); + List list = h1.getAll(as(k1)); + assertEquals(1, list.size()); + assertEquals(as(v3), list.get(0)); + } + + @Test + public void headersWithSameValuesShouldBeEquivalent() { + DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); + h1.set(as("foo"), as("goo")); + h1.set(as("foo2"), as("goo2")); + + DefaultBinaryHeaders h2 = new DefaultBinaryHeaders(); + h2.set(as("foo"), as("goo")); + h2.set(as("foo2"), as("goo2")); + + assertTrue(h1.equals(h2)); + assertTrue(h2.equals(h1)); + assertTrue(h2.equals(h2)); + assertTrue(h1.equals(h1)); + } + + @Test + public void headersWithSameDuplicateValuesShouldBeEquivalent() { + DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); + h1.set(as("foo"), as("goo")); + h1.set(as("foo2"), as("goo2")); + h1.add(as("foo2"), as("goo3")); + h1.add(as("foo"), as("goo4")); + + DefaultBinaryHeaders h2 = new DefaultBinaryHeaders(); + h2.set(as("foo"), as("goo")); + h2.set(as("foo2"), as("goo2")); + h2.add(as("foo"), as("goo4")); + h2.add(as("foo2"), as("goo3")); + + assertTrue(h1.equals(h2)); + assertTrue(h2.equals(h1)); + assertTrue(h2.equals(h2)); + assertTrue(h1.equals(h1)); + } + + @Test + public void headersWithDifferentValuesShouldNotBeEquivalent() { + DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); + h1.set(as("foo"), as("goo")); + h1.set(as("foo2"), as("goo2")); + h1.add(as("foo2"), as("goo3")); + h1.add(as("foo"), as("goo4")); + + DefaultBinaryHeaders h2 = new DefaultBinaryHeaders(); + h2.set(as("foo"), as("goo")); + h2.set(as("foo2"), as("goo2")); + h2.add(as("foo"), as("goo4")); + + assertFalse(h1.equals(h2)); + assertFalse(h2.equals(h1)); + assertTrue(h2.equals(h2)); + assertTrue(h1.equals(h1)); + } + + @Test + public void setAllShouldMergeHeaders() { + DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); + h1.set(as("foo"), as("goo")); + h1.set(as("foo2"), as("goo2")); + h1.add(as("foo2"), as("goo3")); + h1.add(as("foo"), as("goo4")); + + DefaultBinaryHeaders h2 = new DefaultBinaryHeaders(); + h2.set(as("foo"), as("goo")); + h2.set(as("foo2"), as("goo2")); + h2.add(as("foo"), as("goo4")); + + DefaultBinaryHeaders expected = new DefaultBinaryHeaders(); + expected.set(as("foo"), as("goo")); + expected.set(as("foo2"), as("goo2")); + expected.add(as("foo2"), as("goo3")); + expected.add(as("foo"), as("goo4")); + expected.set(as("foo"), as("goo")); + expected.set(as("foo2"), as("goo2")); + expected.set(as("foo"), as("goo4")); + + h1.setAll(h2); + + assertEquals(expected, h1); + } + + @Test + public void setShouldReplacePreviousValues() { + DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); + h1.add(as("foo"), as("goo")); + h1.add(as("foo"), as("goo2")); + assertEquals(2, h1.size()); + + h1.set(as("foo"), as("goo3")); + assertEquals(1, h1.size()); + List list = h1.getAll(as("foo")); + assertEquals(1, list.size()); + assertEquals(as("goo3"), list.get(0)); + } + + @Test(expected = NoSuchElementException.class) + public void iterateEmptyHeadersShouldThrow() { + Iterator> iterator = new DefaultBinaryHeaders().iterator(); + assertFalse(iterator.hasNext()); + iterator.next(); + } + + @Test + public void iterateHeadersShouldReturnAllValues() { + Set headers = new HashSet(); + headers.add("a:1"); + headers.add("a:2"); + headers.add("a:3"); + headers.add("b:1"); + headers.add("b:2"); + headers.add("c:1"); + + // Build the headers from the input set. + DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); + for (String header : headers) { + String[] parts = header.split(":"); + h1.add(as(parts[0]), as(parts[1])); + } + + // Now iterate through the headers, removing them from the original set. + for (Map.Entry entry : h1) { + assertTrue(headers.remove(entry.getKey().toString() + ':' + entry.getValue().toString())); + } + + // Make sure we removed them all. + assertTrue(headers.isEmpty()); + } + + @Test + public void getAndRemoveShouldReturnFirstEntry() { + DefaultBinaryHeaders h1 = new DefaultBinaryHeaders(); + h1.add(as("foo"), as("goo")); + h1.add(as("foo"), as("goo2")); + assertEquals(as("goo"), h1.getAndRemove(as("foo"))); + assertEquals(0, h1.size()); + List values = h1.getAll(as("foo")); + assertEquals(0, values.size()); + } + + private static byte[] randomBytes() { + byte[] data = new byte[100]; + new Random().nextBytes(data); + return data; + } + + private AsciiString as(byte[] bytes) { + return new AsciiString(bytes); + } + + private AsciiString as(String value) { + return new AsciiString(value); + } +} diff --git a/common/src/main/java/io/netty/util/CharsetUtil.java b/common/src/main/java/io/netty/util/CharsetUtil.java index 74c0a3dc91..7da00dd749 100644 --- a/common/src/main/java/io/netty/util/CharsetUtil.java +++ b/common/src/main/java/io/netty/util/CharsetUtil.java @@ -61,6 +61,11 @@ public final class CharsetUtil { */ public static final Charset US_ASCII = Charset.forName("US-ASCII"); + private static final Charset[] CHARSETS = new Charset[] + { UTF_16, UTF_16BE, UTF_16LE, UTF_8, ISO_8859_1, US_ASCII }; + + public static Charset[] values() { return CHARSETS; } + /** * Returns a cached thread-local {@link CharsetEncoder} for the specified * charset. @@ -111,7 +116,5 @@ public final class CharsetUtil { return d; } - private CharsetUtil() { - // Unused - } + private CharsetUtil() { } } diff --git a/common/src/main/java/io/netty/util/collection/CollectionUtils.java b/common/src/main/java/io/netty/util/collection/CollectionUtils.java new file mode 100644 index 0000000000..b9e11044c4 --- /dev/null +++ b/common/src/main/java/io/netty/util/collection/CollectionUtils.java @@ -0,0 +1,52 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project 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 io.netty.util.collection; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * Provides utilities for the primitive collection types that are not supplied by the JDK + */ +public final class CollectionUtils { + + private CollectionUtils() { } + + /** + * Compare two lists using the {@code comparator} for all comparisons (not using the equals() operator) + * @param lhs Left hand side + * @param rhs Right hand side + * @param comparator Comparator which will be used for all comparisons (equals() on objects will not be used) + * @return True if {@code lhs} == {@code rhs} according to {@code comparator}. False otherwise. + */ + public static boolean equals(List lhs, List rhs, Comparator comparator) { + final int lhsSize = lhs.size(); + if (lhsSize != rhs.size()) { + return false; + } + + // Don't use a TreeSet to do the comparison. We want to force the comparator + // to be used instead of the object's equals() + Collections.sort(lhs, comparator); + Collections.sort(rhs, comparator); + for (int i = 0; i < lhsSize; ++i) { + if (comparator.compare(lhs.get(i), rhs.get(i)) != 0) { + return false; + } + } + return true; + } +} diff --git a/example/src/main/java/io/netty/example/http/file/HttpStaticFileServerHandler.java b/example/src/main/java/io/netty/example/http/file/HttpStaticFileServerHandler.java index 32443d6eb8..add631edb1 100644 --- a/example/src/main/java/io/netty/example/http/file/HttpStaticFileServerHandler.java +++ b/example/src/main/java/io/netty/example/http/file/HttpStaticFileServerHandler.java @@ -149,7 +149,7 @@ public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler LastHttpContent trailer = (LastHttpContent) msg; if (!trailer.trailingHeaders().isEmpty()) { buf.append("\r\n"); - for (String name: trailer.trailingHeaders().names()) { - for (String value: trailer.trailingHeaders().getAll(name)) { + for (CharSequence name: trailer.trailingHeaders().names()) { + for (CharSequence value: trailer.trailingHeaders().getAll(name)) { buf.append("TRAILING HEADER: "); buf.append(name).append(" = ").append(value).append("\r\n"); } @@ -154,14 +154,14 @@ public class HttpSnoopServerHandler extends SimpleChannelInboundHandler if (keepAlive) { // Add 'Content-Length' header only for a keep-alive connection. - response.headers().set(CONTENT_LENGTH, response.content().readableBytes()); + response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes()); // Add keep alive header as per: // - http://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01.html#Connection response.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE); } // Encode the cookie. - String cookieString = request.headers().get(COOKIE); + String cookieString = request.headers().getAndConvert(COOKIE); if (cookieString != null) { Set cookies = CookieDecoder.decode(cookieString); if (!cookies.isEmpty()) { diff --git a/example/src/main/java/io/netty/example/http/upload/HttpUploadClient.java b/example/src/main/java/io/netty/example/http/upload/HttpUploadClient.java index f3d4a3ca48..b2f148d387 100644 --- a/example/src/main/java/io/netty/example/http/upload/HttpUploadClient.java +++ b/example/src/main/java/io/netty/example/http/upload/HttpUploadClient.java @@ -183,7 +183,7 @@ public final class HttpUploadClient { ); // send request - List> entries = headers.entries(); + List> entries = headers.entriesConverted(); channel.writeAndFlush(request); // Wait for the server to close the connection. diff --git a/example/src/main/java/io/netty/example/http/upload/HttpUploadClientHandler.java b/example/src/main/java/io/netty/example/http/upload/HttpUploadClientHandler.java index 37377cf40b..c58bfb0b4a 100644 --- a/example/src/main/java/io/netty/example/http/upload/HttpUploadClientHandler.java +++ b/example/src/main/java/io/netty/example/http/upload/HttpUploadClientHandler.java @@ -40,8 +40,8 @@ public class HttpUploadClientHandler extends SimpleChannelInboundHandler entry : request.headers()) { + for (Entry entry : request.headers()) { responseContent.append("HEADER: " + entry.getKey() + '=' + entry.getValue() + "\r\n"); } responseContent.append("\r\n\r\n"); // new getMethod Set cookies; - String value = request.headers().get(COOKIE); + String value = request.headers().getAndConvert(COOKIE); if (value == null) { cookies = Collections.emptySet(); } else { @@ -299,11 +299,11 @@ public class HttpUploadServerHandler extends SimpleChannelInboundHandler cookies; - String value = request.headers().get(COOKIE); + String value = request.headers().getAndConvert(COOKIE); if (value == null) { cookies = Collections.emptySet(); } else { @@ -401,7 +401,7 @@ public class HttpUploadServerHandler extends SimpleChannelInboundHandler ctx.writeAndFlush(subscribeFrame); break; case RECEIPT: - String receiptHeader = frame.headers().get(StompHeaders.RECEIPT_ID); + String receiptHeader = frame.headers().getAndConvert(StompHeaders.RECEIPT_ID); if (state == ClientState.AUTHENTICATED && receiptHeader.equals(subscrReceiptId)) { StompFrame msgFrame = new DefaultStompFrame(StompCommand.SEND); msgFrame.headers().set(StompHeaders.DESTINATION, StompClient.TOPIC); diff --git a/handler-proxy/src/test/java/io/netty/handler/proxy/HttpProxyServer.java b/handler-proxy/src/test/java/io/netty/handler/proxy/HttpProxyServer.java index 963a779e83..7994490c7c 100644 --- a/handler-proxy/src/test/java/io/netty/handler/proxy/HttpProxyServer.java +++ b/handler-proxy/src/test/java/io/netty/handler/proxy/HttpProxyServer.java @@ -86,7 +86,7 @@ final class HttpProxyServer extends ProxyServer { boolean authzSuccess = false; if (username != null) { - String authz = req.headers().get(Names.AUTHORIZATION); + CharSequence authz = req.headers().get(Names.AUTHORIZATION); if (authz != null) { ByteBuf authzBuf64 = Unpooled.copiedBuffer(authz, CharsetUtil.US_ASCII); ByteBuf authzBuf = Base64.decode(authzBuf64);