--- a/jdk/src/share/classes/java/util/StringJoiner.java Thu Aug 07 13:04:26 2014 +0900
+++ b/jdk/src/share/classes/java/util/StringJoiner.java Thu Aug 07 15:07:33 2014 +0400
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -24,6 +24,9 @@
*/
package java.util;
+import sun.misc.JavaLangAccess;
+import sun.misc.SharedSecrets;
+
/**
* {@code StringJoiner} is used to construct a sequence of characters separated
* by a delimiter and optionally starting with a supplied prefix
@@ -67,22 +70,24 @@
private final String delimiter;
private final String suffix;
- /*
- * StringBuilder value -- at any time, the characters constructed from the
- * prefix, the added element separated by the delimiter, but without the
- * suffix, so that we can more easily add elements without having to jigger
- * the suffix each time.
- */
- private StringBuilder value;
+ /** Contains all the string components added so far. */
+ private String[] elts;
+
+ /** The number of string components added so far. */
+ private int size;
- /*
- * By default, the string consisting of prefix+suffix, returned by
- * toString(), or properties of value, when no elements have yet been added,
- * i.e. when it is empty. This may be overridden by the user to be some
- * other value including the empty String.
+ /** Total length in chars so far, excluding prefix and suffix. */
+ private int len;
+
+ /**
+ * When overriden by the user to be non-null via {@link setEmptyValue}, the
+ * string returned by toString() when no elements have yet been added.
+ * When null, prefix + suffix is used as the empty value.
*/
private String emptyValue;
+ private static final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
+
/**
* Constructs a {@code StringJoiner} with no characters in it, with no
* {@code prefix} or {@code suffix}, and a copy of the supplied
@@ -125,7 +130,6 @@
this.prefix = prefix.toString();
this.delimiter = delimiter.toString();
this.suffix = suffix.toString();
- this.emptyValue = this.prefix + this.suffix;
}
/**
@@ -148,29 +152,44 @@
return this;
}
+ private static int getChars(String s, char[] chars, int start) {
+ int len = s.length();
+ s.getChars(0, len, chars, start);
+ return len;
+ }
+
/**
* Returns the current value, consisting of the {@code prefix}, the values
* added so far separated by the {@code delimiter}, and the {@code suffix},
* unless no elements have been added in which case, the
- * {@code prefix + suffix} or the {@code emptyValue} characters are returned
+ * {@code prefix + suffix} or the {@code emptyValue} characters are returned.
*
* @return the string representation of this {@code StringJoiner}
*/
@Override
public String toString() {
- if (value == null) {
+ final String[] elts = this.elts;
+ if (elts == null && emptyValue != null) {
return emptyValue;
- } else {
- if (suffix.equals("")) {
- return value.toString();
- } else {
- int initialLength = value.length();
- String result = value.append(suffix).toString();
- // reset value to pre-append initialLength
- value.setLength(initialLength);
- return result;
+ }
+ final int size = this.size;
+ final int addLen = prefix.length() + suffix.length();
+ if (addLen == 0) {
+ compactElts();
+ return size == 0 ? "" : elts[0];
+ }
+ final String delimiter = this.delimiter;
+ final char[] chars = new char[len + addLen];
+ int k = getChars(prefix, chars, 0);
+ if (size > 0) {
+ k += getChars(elts[0], chars, k);
+ for (int i = 1; i < size; i++) {
+ k += getChars(delimiter, chars, k);
+ k += getChars(elts[i], chars, k);
}
}
+ k += getChars(suffix, chars, k);
+ return jla.newStringUnsafe(chars);
}
/**
@@ -182,7 +201,16 @@
* @return a reference to this {@code StringJoiner}
*/
public StringJoiner add(CharSequence newElement) {
- prepareBuilder().append(newElement);
+ final String elt = String.valueOf(newElement);
+ if (elts == null) {
+ elts = new String[8];
+ } else {
+ if (size == elts.length)
+ elts = Arrays.copyOf(elts, 2 * size);
+ len += delimiter.length();
+ }
+ len += elt.length();
+ elts[size++] = elt;
return this;
}
@@ -207,24 +235,25 @@
*/
public StringJoiner merge(StringJoiner other) {
Objects.requireNonNull(other);
- if (other.value != null) {
- final int length = other.value.length();
- // lock the length so that we can seize the data to be appended
- // before initiate copying to avoid interference, especially when
- // merge 'this'
- StringBuilder builder = prepareBuilder();
- builder.append(other.value, other.prefix.length(), length);
+ if (other.elts == null) {
+ return this;
}
- return this;
+ other.compactElts();
+ return add(other.elts[0]);
}
- private StringBuilder prepareBuilder() {
- if (value != null) {
- value.append(delimiter);
- } else {
- value = new StringBuilder().append(prefix);
+ private void compactElts() {
+ if (size > 1) {
+ final char[] chars = new char[len];
+ int i = 1, k = getChars(elts[0], chars, 0);
+ do {
+ k += getChars(delimiter, chars, k);
+ k += getChars(elts[i], chars, k);
+ elts[i] = null;
+ } while (++i < size);
+ size = 1;
+ elts[0] = jla.newStringUnsafe(chars);
}
- return value;
}
/**
@@ -238,10 +267,7 @@
* @return the length of the current value of {@code StringJoiner}
*/
public int length() {
- // Remember that we never actually append the suffix unless we return
- // the full (present) value or some sub-string or length of it, so that
- // we can add on more if we need to.
- return (value != null ? value.length() + suffix.length() :
- emptyValue.length());
+ return (size == 0 && emptyValue != null) ? emptyValue.length() :
+ len + prefix.length() + suffix.length();
}
}
--- a/jdk/test/java/util/StringJoiner/MergeTest.java Thu Aug 07 13:04:26 2014 +0900
+++ b/jdk/test/java/util/StringJoiner/MergeTest.java Thu Aug 07 15:07:33 2014 +0400
@@ -23,7 +23,7 @@
/**
* @test
- * @bug 8017231 8020977
+ * @bug 8017231 8020977 8054221
* @summary test StringJoiner::merge
* @run testng MergeTest
*/
@@ -36,98 +36,135 @@
@Test
public class MergeTest {
- public void testNull() {
- StringJoiner sj = new StringJoiner(",", "{", "}");
- try {
- sj.merge(null);
- fail("Should throw NullPointerException!");
- } catch (NullPointerException npe) {
- // expected
+ private final static String[] PREFIXES = {"", "{", "@#$%"};
+ private final static String[] SUFFIXES = {"", "}", "*&%$"};
+
+ private static class Fixes {
+ public String pre0, suf0;
+ public String pre1, suf1;
+ public Fixes(String prefix0, String suffix0,
+ String prefix1, String suffix1) {
+ this.pre0 = prefix0;
+ this.suf0 = suffix0;
+ this.pre1 = prefix1;
+ this.suf1 = suffix1;
}
}
- public void testSimple() {
+ private static Stream<Fixes> fixesStream() {
+ Stream.Builder<Fixes> builder = Stream.builder();
+ for (final String prefix0 : PREFIXES) {
+ for (final String suffix0 : SUFFIXES) {
+ for (final String prefix1 : PREFIXES) {
+ for (final String suffix1 : SUFFIXES) {
+ builder.accept(new Fixes(prefix0, suffix0,
+ prefix1, suffix1));
+ }
+ }
+ }
+ }
+ return builder.build();
+ }
+
+ @Test(expectedExceptions = {NullPointerException.class})
+ public void testNull() {
StringJoiner sj = new StringJoiner(",", "{", "}");
- StringJoiner other = new StringJoiner(",", "[", "]");
- Stream.of("a", "b", "c").forEachOrdered(sj::add);
- Stream.of("d", "e", "f").forEachOrdered(other::add);
+ sj.merge(null);
+ }
- sj.merge(other);
- assertEquals(sj.toString(), "{a,b,c,d,e,f}");
+ public void testSimple() {
+ fixesStream().forEach(fixes -> {
+ StringJoiner sj = new StringJoiner(",", fixes.pre0, fixes.suf0);
+ StringJoiner other = new StringJoiner(",", fixes.pre1, fixes.suf1);
+ Stream.of("a", "b", "c").forEachOrdered(sj::add);
+ Stream.of("d", "e", "f").forEachOrdered(other::add);
+
+ sj.merge(other);
+ assertEquals(sj.toString(), fixes.pre0 + "a,b,c,d,e,f" + fixes.suf0);
+ });
}
public void testEmptyOther() {
- StringJoiner sj = new StringJoiner(",", "{", "}");
- StringJoiner other = new StringJoiner(",", "[", "]");
- Stream.of("a", "b", "c").forEachOrdered(sj::add);
+ fixesStream().forEach(fixes -> {
+ StringJoiner sj = new StringJoiner(",", fixes.pre0, fixes.suf0);
+ StringJoiner other = new StringJoiner(",", fixes.pre1, fixes.suf1);
+ Stream.of("a", "b", "c").forEachOrdered(sj::add);
- sj.merge(other);
- assertEquals(sj.toString(), "{a,b,c}");
+ sj.merge(other);
+ assertEquals(sj.toString(), fixes.pre0 + "a,b,c" + fixes.suf0);
- other.setEmptyValue("EMPTY");
- sj.merge(other);
- assertEquals(sj.toString(), "{a,b,c}");
+ other.setEmptyValue("EMPTY");
+ sj.merge(other);
+ assertEquals(sj.toString(), fixes.pre0 + "a,b,c" + fixes.suf0);
+ });
}
public void testEmptyThis() {
- StringJoiner sj = new StringJoiner(",", "{", "}");
- StringJoiner other = new StringJoiner(":", "[", "]");
- Stream.of("d", "e", "f").forEachOrdered(other::add);
+ fixesStream().forEach(fixes -> {
+ StringJoiner sj = new StringJoiner(",", fixes.pre0, fixes.suf0);
+ StringJoiner other = new StringJoiner(":", fixes.pre1, fixes.suf1);
+ Stream.of("d", "e", "f").forEachOrdered(other::add);
- sj.merge(other);
- assertEquals(sj.toString(), "{d:e:f}");
+ sj.merge(other);
+ assertEquals(sj.toString(), fixes.pre0 + "d:e:f" + fixes.suf0);
- sj = new StringJoiner(",", "{", "}").setEmptyValue("EMPTY");
- assertEquals(sj.toString(), "EMPTY");
- sj.merge(other);
- assertEquals(sj.toString(), "{d:e:f}");
+ sj = new StringJoiner(",", fixes.pre0, fixes.suf0).setEmptyValue("EMPTY");
+ assertEquals(sj.toString(), "EMPTY");
+ sj.merge(other);
+ assertEquals(sj.toString(), fixes.pre0 + "d:e:f" + fixes.suf0);
+ });
}
public void testEmptyBoth() {
- StringJoiner sj = new StringJoiner(",", "{", "}");
- StringJoiner other = new StringJoiner(":", "[", "]");
+ fixesStream().forEach(fixes -> {
+ StringJoiner sj = new StringJoiner(",", fixes.pre0, fixes.suf0);
+ StringJoiner other = new StringJoiner(":", fixes.pre1, fixes.suf1);
- sj.merge(other);
- assertEquals(sj.toString(), "{}");
+ sj.merge(other);
+ assertEquals(sj.toString(), fixes.pre0 + fixes.suf0);
- other.setEmptyValue("NOTHING");
- sj.merge(other);
- assertEquals(sj.toString(), "{}");
+ other.setEmptyValue("NOTHING");
+ sj.merge(other);
+ assertEquals(sj.toString(), fixes.pre0 + fixes.suf0);
- sj = new StringJoiner(",", "{", "}").setEmptyValue("EMPTY");
- assertEquals(sj.toString(), "EMPTY");
- sj.merge(other);
- assertEquals(sj.toString(), "EMPTY");
+ sj = new StringJoiner(",", fixes.pre0, fixes.suf0).setEmptyValue("EMPTY");
+ assertEquals(sj.toString(), "EMPTY");
+ sj.merge(other);
+ assertEquals(sj.toString(), "EMPTY");
+ });
}
public void testCascadeEmpty() {
- StringJoiner sj = new StringJoiner(",", "{", "}");
- StringJoiner o1 = new StringJoiner(":", "[", "]").setEmptyValue("Empty1");
- StringJoiner o2 = new StringJoiner(",", "<", ">").setEmptyValue("Empty2");
+ fixesStream().forEach(fixes -> {
+ StringJoiner sj = new StringJoiner(",", fixes.pre0, fixes.suf0);
+ StringJoiner o1 = new StringJoiner(":", fixes.pre1, fixes.suf1).setEmptyValue("Empty1");
+ StringJoiner o2 = new StringJoiner(",", "<", ">").setEmptyValue("Empty2");
- o1.merge(o2);
- assertEquals(o1.toString(), "Empty1");
+ o1.merge(o2);
+ assertEquals(o1.toString(), "Empty1");
- sj.merge(o1);
- assertEquals(sj.toString(), "{}");
+ sj.merge(o1);
+ assertEquals(sj.toString(), fixes.pre0 + fixes.suf0);
+ });
}
public void testDelimiter() {
- StringJoiner sj = new StringJoiner(",", "{", "}");
- StringJoiner other = new StringJoiner(":", "[", "]");
- Stream.of("a", "b", "c").forEachOrdered(sj::add);
- Stream.of("d", "e", "f").forEachOrdered(other::add);
+ fixesStream().forEach(fixes -> {
+ StringJoiner sj = new StringJoiner(",", fixes.pre0, fixes.suf0);
+ StringJoiner other = new StringJoiner(":", fixes.pre1, fixes.suf1);
+ Stream.of("a", "b", "c").forEachOrdered(sj::add);
+ Stream.of("d", "e", "f").forEachOrdered(other::add);
- sj.merge(other);
- assertEquals(sj.toString(), "{a,b,c,d:e:f}");
+ sj.merge(other);
+ assertEquals(sj.toString(), fixes.pre0 + "a,b,c,d:e:f" + fixes.suf0);
+ });
}
public void testMergeSelf() {
- final StringJoiner sj = new StringJoiner(",", "[", "]").add("a").add("b");
- assertEquals(sj.merge(sj).toString(), "[a,b,a,b]");
- assertEquals(sj.merge(sj).toString(), "[a,b,a,b,a,b,a,b]");
-
- final StringJoiner sj2 = new StringJoiner(",").add("c").add("d");
- assertEquals(sj2.merge(sj2).toString(), "c,d,c,d");
+ fixesStream().forEach(fixes -> {
+ final StringJoiner sj = new StringJoiner(",", fixes.pre0, fixes.suf0).add("a").add("b");
+ assertEquals(sj.merge(sj).toString(), fixes.pre0 + "a,b,a,b" + fixes.suf0);
+ assertEquals(sj.merge(sj).toString(), fixes.pre0 + "a,b,a,b,a,b,a,b" + fixes.suf0);
+ });
}
}