HttpHeaders valuesIterator and contains improvements
Motivation: In order to determine if a header contains a value we currently rely upon getAll(..) and regular expressions. This operation is commonly used during the encode and decode stage to determine the transfer encoding (e.g. HttpUtil#isTransferEncodingChunked). This operation requires an intermediate collection and possibly regular expressions for the CombinedHttpHeaders use case which can be expensive. Modifications: - Add a valuesIterator to HttpHeaders and specializations of this method for DefaultHttpHeaders, ReadOnlyHttpHeaders, and CombinedHttpHeaders. Result: Less intermediate collections and allocation overhead when determining if HttpHeaders contains a name/value pair.
This commit is contained in:
parent
e6126215e0
commit
0a47c590fe
@ -28,6 +28,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
import static io.netty.util.AsciiString.CASE_INSENSITIVE_HASHER;
|
import static io.netty.util.AsciiString.CASE_INSENSITIVE_HASHER;
|
||||||
import static io.netty.util.internal.StringUtil.COMMA;
|
import static io.netty.util.internal.StringUtil.COMMA;
|
||||||
|
import static io.netty.util.internal.StringUtil.unescapeCsvFields;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will add multiple values for the same header as single header with a comma separated list of values.
|
* Will add multiple values for the same header as single header with a comma separated list of values.
|
||||||
@ -83,6 +84,19 @@ public class CombinedHttpHeaders extends DefaultHttpHeaders {
|
|||||||
super(nameHashingStrategy, valueConverter, nameValidator);
|
super(nameHashingStrategy, valueConverter, nameValidator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<CharSequence> valueIterator(CharSequence name) {
|
||||||
|
Iterator<CharSequence> itr = super.valueIterator(name);
|
||||||
|
if (!itr.hasNext()) {
|
||||||
|
return itr;
|
||||||
|
}
|
||||||
|
Iterator<CharSequence> unescapedItr = unescapeCsvFields(itr.next()).iterator();
|
||||||
|
if (itr.hasNext()) {
|
||||||
|
throw new IllegalStateException("CombinedHttpHeaders should only have one value");
|
||||||
|
}
|
||||||
|
return unescapedItr;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<CharSequence> getAll(CharSequence name) {
|
public List<CharSequence> getAll(CharSequence name) {
|
||||||
List<CharSequence> values = super.getAll(name);
|
List<CharSequence> values = super.getAll(name);
|
||||||
@ -92,7 +106,7 @@ public class CombinedHttpHeaders extends DefaultHttpHeaders {
|
|||||||
if (values.size() != 1) {
|
if (values.size() != 1) {
|
||||||
throw new IllegalStateException("CombinedHttpHeaders should only have one value");
|
throw new IllegalStateException("CombinedHttpHeaders should only have one value");
|
||||||
}
|
}
|
||||||
return StringUtil.unescapeCsvFields(values.get(0));
|
return unescapeCsvFields(values.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -276,6 +276,32 @@ public class DefaultHttpHeaders extends HttpHeaders {
|
|||||||
return headers.iterator();
|
return headers.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<String> valueStringIterator(CharSequence name) {
|
||||||
|
final Iterator<CharSequence> itr = valueCharSequenceIterator(name);
|
||||||
|
return new Iterator<String>() {
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return itr.hasNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String next() {
|
||||||
|
return itr.next().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
itr.remove();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<CharSequence> valueCharSequenceIterator(CharSequence name) {
|
||||||
|
return headers.valueIterator(name);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean contains(String name) {
|
public boolean contains(String name) {
|
||||||
return contains((CharSequence) name);
|
return contains((CharSequence) name);
|
||||||
|
@ -31,6 +31,9 @@ import java.util.Map;
|
|||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static io.netty.util.AsciiString.contentEquals;
|
||||||
|
import static io.netty.util.AsciiString.contentEqualsIgnoreCase;
|
||||||
|
import static io.netty.util.AsciiString.trim;
|
||||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1146,7 +1149,7 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
|||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static boolean equalsIgnoreCase(CharSequence name1, CharSequence name2) {
|
public static boolean equalsIgnoreCase(CharSequence name1, CharSequence name2) {
|
||||||
return AsciiString.contentEqualsIgnoreCase(name1, name2);
|
return contentEqualsIgnoreCase(name1, name2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@ -1309,6 +1312,24 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
|||||||
*/
|
*/
|
||||||
public abstract Iterator<Entry<CharSequence, CharSequence>> iteratorCharSequence();
|
public abstract Iterator<Entry<CharSequence, CharSequence>> iteratorCharSequence();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equivalent to {@link #getAll(String)} but it is possible that no intermediate list is generated.
|
||||||
|
* @param name the name of the header to retrieve
|
||||||
|
* @return an {@link Iterator} of header values corresponding to {@code name}.
|
||||||
|
*/
|
||||||
|
public Iterator<String> valueStringIterator(CharSequence name) {
|
||||||
|
return getAll(name).iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equivalent to {@link #getAll(String)} but it is possible that no intermediate list is generated.
|
||||||
|
* @param name the name of the header to retrieve
|
||||||
|
* @return an {@link Iterator} of header values corresponding to {@code name}.
|
||||||
|
*/
|
||||||
|
public Iterator<? extends CharSequence> valueCharSequenceIterator(CharSequence name) {
|
||||||
|
return valueStringIterator(name);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks to see if there is a header with the specified name
|
* Checks to see if there is a header with the specified name
|
||||||
*
|
*
|
||||||
@ -1546,18 +1567,16 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
|||||||
* @see #contains(CharSequence, CharSequence, boolean)
|
* @see #contains(CharSequence, CharSequence, boolean)
|
||||||
*/
|
*/
|
||||||
public boolean contains(String name, String value, boolean ignoreCase) {
|
public boolean contains(String name, String value, boolean ignoreCase) {
|
||||||
List<String> values = getAll(name);
|
Iterator<String> valueIterator = valueStringIterator(name);
|
||||||
if (values.isEmpty()) {
|
if (ignoreCase) {
|
||||||
return false;
|
while (valueIterator.hasNext()) {
|
||||||
}
|
if (valueIterator.next().equalsIgnoreCase(value)) {
|
||||||
|
|
||||||
for (String v: values) {
|
|
||||||
if (ignoreCase) {
|
|
||||||
if (v.equalsIgnoreCase(value)) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
if (v.equals(value)) {
|
} else {
|
||||||
|
while (valueIterator.hasNext()) {
|
||||||
|
if (valueIterator.next().equals(value)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1576,32 +1595,56 @@ public abstract class HttpHeaders implements Iterable<Map.Entry<String, String>>
|
|||||||
* otherwise a case sensitive compare is run to compare values.
|
* otherwise a case sensitive compare is run to compare values.
|
||||||
*/
|
*/
|
||||||
public boolean containsValue(CharSequence name, CharSequence value, boolean ignoreCase) {
|
public boolean containsValue(CharSequence name, CharSequence value, boolean ignoreCase) {
|
||||||
List<String> values = getAll(name);
|
Iterator<? extends CharSequence> itr = valueCharSequenceIterator(name);
|
||||||
if (values.isEmpty()) {
|
while (itr.hasNext()) {
|
||||||
return false;
|
if (containsCommaSeparatedTrimmed(itr.next(), value, ignoreCase)) {
|
||||||
}
|
|
||||||
|
|
||||||
for (String v: values) {
|
|
||||||
if (contains(v, value, ignoreCase)) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean contains(String value, CharSequence expected, boolean ignoreCase) {
|
private static boolean containsCommaSeparatedTrimmed(CharSequence rawNext, CharSequence expected,
|
||||||
String[] parts = value.split(",");
|
boolean ignoreCase) {
|
||||||
|
int begin = 0;
|
||||||
|
int end;
|
||||||
if (ignoreCase) {
|
if (ignoreCase) {
|
||||||
for (String s: parts) {
|
if ((end = AsciiString.indexOf(rawNext, ',', begin)) == -1) {
|
||||||
if (AsciiString.contentEqualsIgnoreCase(expected, s.trim())) {
|
if (contentEqualsIgnoreCase(trim(rawNext), expected)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
do {
|
||||||
|
if (contentEqualsIgnoreCase(trim(rawNext.subSequence(begin, end)), expected)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
begin = end + 1;
|
||||||
|
} while ((end = AsciiString.indexOf(rawNext, ',', begin)) != -1);
|
||||||
|
|
||||||
|
if (begin < rawNext.length()) {
|
||||||
|
if (contentEqualsIgnoreCase(trim(rawNext.subSequence(begin, rawNext.length())), expected)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (String s: parts) {
|
if ((end = AsciiString.indexOf(rawNext, ',', begin)) == -1) {
|
||||||
if (AsciiString.contentEquals(expected, s.trim())) {
|
if (contentEquals(trim(rawNext), expected)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
do {
|
||||||
|
if (contentEquals(trim(rawNext.subSequence(begin, end)), expected)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
begin = end + 1;
|
||||||
|
} while ((end = AsciiString.indexOf(rawNext, ',', begin)) != -1);
|
||||||
|
|
||||||
|
if (begin < rawNext.length()) {
|
||||||
|
if (contentEquals(trim(rawNext.subSequence(begin, rawNext.length())), expected)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -30,6 +30,7 @@ import java.util.Set;
|
|||||||
|
|
||||||
import static io.netty.handler.codec.CharSequenceValueConverter.INSTANCE;
|
import static io.netty.handler.codec.CharSequenceValueConverter.INSTANCE;
|
||||||
import static io.netty.handler.codec.http.DefaultHttpHeaders.HttpNameValidator;
|
import static io.netty.handler.codec.http.DefaultHttpHeaders.HttpNameValidator;
|
||||||
|
import static io.netty.util.AsciiString.contentEquals;
|
||||||
import static io.netty.util.AsciiString.contentEqualsIgnoreCase;
|
import static io.netty.util.AsciiString.contentEqualsIgnoreCase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -76,7 +77,7 @@ public final class ReadOnlyHttpHeaders extends HttpHeaders {
|
|||||||
final int nameHash = AsciiString.hashCode(name);
|
final int nameHash = AsciiString.hashCode(name);
|
||||||
for (int i = 0; i < nameValuePairs.length; i += 2) {
|
for (int i = 0; i < nameValuePairs.length; i += 2) {
|
||||||
CharSequence roName = nameValuePairs[i];
|
CharSequence roName = nameValuePairs[i];
|
||||||
if (roName.hashCode() == nameHash && contentEqualsIgnoreCase(roName, name)) {
|
if (AsciiString.hashCode(roName) == nameHash && contentEqualsIgnoreCase(roName, name)) {
|
||||||
return nameValuePairs[i + 1];
|
return nameValuePairs[i + 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -134,7 +135,7 @@ public final class ReadOnlyHttpHeaders extends HttpHeaders {
|
|||||||
List<String> values = new ArrayList<String>(4);
|
List<String> values = new ArrayList<String>(4);
|
||||||
for (int i = 0; i < nameValuePairs.length; i += 2) {
|
for (int i = 0; i < nameValuePairs.length; i += 2) {
|
||||||
CharSequence roName = nameValuePairs[i];
|
CharSequence roName = nameValuePairs[i];
|
||||||
if (roName.hashCode() == nameHash && contentEqualsIgnoreCase(roName, name)) {
|
if (AsciiString.hashCode(roName) == nameHash && contentEqualsIgnoreCase(roName, name)) {
|
||||||
values.add(nameValuePairs[i + 1].toString());
|
values.add(nameValuePairs[i + 1].toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,6 +160,41 @@ public final class ReadOnlyHttpHeaders extends HttpHeaders {
|
|||||||
return get0(name) != null;
|
return get0(name) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(String name, String value, boolean ignoreCase) {
|
||||||
|
return containsValue(name, value, ignoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsValue(CharSequence name, CharSequence value, boolean ignoreCase) {
|
||||||
|
if (ignoreCase) {
|
||||||
|
for (int i = 0; i < nameValuePairs.length; i += 2) {
|
||||||
|
if (contentEqualsIgnoreCase(nameValuePairs[i], name) &&
|
||||||
|
contentEqualsIgnoreCase(nameValuePairs[i + 1], value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < nameValuePairs.length; i += 2) {
|
||||||
|
if (contentEqualsIgnoreCase(nameValuePairs[i], name) &&
|
||||||
|
contentEquals(nameValuePairs[i + 1], value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<String> valueStringIterator(CharSequence name) {
|
||||||
|
return new ReadOnlyStringValueIterator(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<CharSequence> valueCharSequenceIterator(CharSequence name) {
|
||||||
|
return new ReadOnlyValueIterator(name);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<Map.Entry<String, String>> iterator() {
|
public Iterator<Map.Entry<String, String>> iterator() {
|
||||||
return new ReadOnlyStringIterator();
|
return new ReadOnlyStringIterator();
|
||||||
@ -336,4 +372,88 @@ public final class ReadOnlyHttpHeaders extends HttpHeaders {
|
|||||||
return key + '=' + value;
|
return key + '=' + value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class ReadOnlyStringValueIterator implements Iterator<String> {
|
||||||
|
private final CharSequence name;
|
||||||
|
private final int nameHash;
|
||||||
|
private int nextNameIndex;
|
||||||
|
|
||||||
|
ReadOnlyStringValueIterator(CharSequence name) {
|
||||||
|
this.name = name;
|
||||||
|
nameHash = AsciiString.hashCode(name);
|
||||||
|
nextNameIndex = findNextValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return nextNameIndex != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String next() {
|
||||||
|
if (!hasNext()) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
String value = nameValuePairs[nextNameIndex + 1].toString();
|
||||||
|
nextNameIndex = findNextValue();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
throw new UnsupportedOperationException("read only");
|
||||||
|
}
|
||||||
|
|
||||||
|
private int findNextValue() {
|
||||||
|
for (int i = nextNameIndex; i < nameValuePairs.length; i += 2) {
|
||||||
|
final CharSequence roName = nameValuePairs[i];
|
||||||
|
if (nameHash == AsciiString.hashCode(roName) && contentEqualsIgnoreCase(name, roName)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class ReadOnlyValueIterator implements Iterator<CharSequence> {
|
||||||
|
private final CharSequence name;
|
||||||
|
private final int nameHash;
|
||||||
|
private int nextNameIndex;
|
||||||
|
|
||||||
|
ReadOnlyValueIterator(CharSequence name) {
|
||||||
|
this.name = name;
|
||||||
|
nameHash = AsciiString.hashCode(name);
|
||||||
|
nextNameIndex = findNextValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return nextNameIndex != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence next() {
|
||||||
|
if (!hasNext()) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
CharSequence value = nameValuePairs[nextNameIndex + 1];
|
||||||
|
nextNameIndex = findNextValue();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
throw new UnsupportedOperationException("read only");
|
||||||
|
}
|
||||||
|
|
||||||
|
private int findNextValue() {
|
||||||
|
for (int i = nextNameIndex; i < nameValuePairs.length; i += 2) {
|
||||||
|
final CharSequence roName = nameValuePairs[i];
|
||||||
|
if (nameHash == AsciiString.hashCode(roName) && contentEqualsIgnoreCase(name, roName)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import org.junit.Test;
|
|||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
import static io.netty.util.AsciiString.contentEquals;
|
import static io.netty.util.AsciiString.contentEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@ -300,4 +301,30 @@ public class CombinedHttpHeadersTest {
|
|||||||
HttpHeaders copiedHeaders = newCombinedHttpHeaders().add(headers);
|
HttpHeaders copiedHeaders = newCombinedHttpHeaders().add(headers);
|
||||||
assertEquals(Arrays.asList("a", "", "b", "", "c, d"), copiedHeaders.getAll(HEADER_NAME));
|
assertEquals(Arrays.asList("a", "", "b", "", "c, d"), copiedHeaders.getAll(HEADER_NAME));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void valueIterator() {
|
||||||
|
final CombinedHttpHeaders headers = newCombinedHttpHeaders();
|
||||||
|
headers.set(HEADER_NAME, Arrays.asList("\ta", " ", " b ", "\t \t"));
|
||||||
|
headers.add(HEADER_NAME, " c, d \t");
|
||||||
|
|
||||||
|
assertFalse(headers.valueStringIterator("foo").hasNext());
|
||||||
|
assertValueIterator(headers.valueStringIterator(HEADER_NAME));
|
||||||
|
assertFalse(headers.valueCharSequenceIterator("foo").hasNext());
|
||||||
|
assertValueIterator(headers.valueCharSequenceIterator(HEADER_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertValueIterator(Iterator<? extends CharSequence> strItr) {
|
||||||
|
assertTrue(strItr.hasNext());
|
||||||
|
assertEquals("a", strItr.next());
|
||||||
|
assertTrue(strItr.hasNext());
|
||||||
|
assertEquals("", strItr.next());
|
||||||
|
assertTrue(strItr.hasNext());
|
||||||
|
assertEquals("b", strItr.next());
|
||||||
|
assertTrue(strItr.hasNext());
|
||||||
|
assertEquals("", strItr.next());
|
||||||
|
assertTrue(strItr.hasNext());
|
||||||
|
assertEquals("c, d", strItr.next());
|
||||||
|
assertFalse(strItr.hasNext());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1005,6 +1005,34 @@ public final class AsciiString implements CharSequence, Comparable<CharSequence>
|
|||||||
return new AsciiString(newValue, false);
|
return new AsciiString(newValue, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies this string removing white space characters from the beginning and end of the string, and tries not to
|
||||||
|
* copy if possible.
|
||||||
|
*
|
||||||
|
* @param c The {@link CharSequence} to trim.
|
||||||
|
* @return a new string with characters {@code <= \\u0020} removed from the beginning and the end.
|
||||||
|
*/
|
||||||
|
public static CharSequence trim(CharSequence c) {
|
||||||
|
if (c.getClass() == AsciiString.class) {
|
||||||
|
return ((AsciiString) c).trim();
|
||||||
|
}
|
||||||
|
if (c instanceof String) {
|
||||||
|
return ((String) c).trim();
|
||||||
|
}
|
||||||
|
int start = 0, last = c.length() - 1;
|
||||||
|
int end = last;
|
||||||
|
while (start <= end && c.charAt(start) <= ' ') {
|
||||||
|
start++;
|
||||||
|
}
|
||||||
|
while (end >= start && c.charAt(end) <= ' ') {
|
||||||
|
end--;
|
||||||
|
}
|
||||||
|
if (start == 0 && end == last) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
return c.subSequence(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Duplicates this string removing white space characters from the beginning and end of the
|
* Duplicates this string removing white space characters from the beginning and end of the
|
||||||
* string, without copying.
|
* string, without copying.
|
||||||
|
Loading…
Reference in New Issue
Block a user