5015163: (str) String merge/join that is the inverse of String.split()
7172553: A utility class that forms the basis of a String.join() operation
Summary: Integrate StringJoiner changes from lambda
Reviewed-by: alanb, mduigou
--- a/jdk/make/java/java/FILES_java.gmk Tue Apr 23 11:54:22 2013 -0700
+++ b/jdk/make/java/java/FILES_java.gmk Tue Apr 02 18:41:04 2013 -0400
@@ -252,6 +252,7 @@
java/util/Scanner.java \
java/util/InputMismatchException.java \
java/util/Stack.java \
+ java/util/StringJoiner.java \
java/util/StringTokenizer.java \
java/util/TimeZone.java \
java/util/SimpleTimeZone.java \
--- a/jdk/src/share/classes/java/lang/String.java Tue Apr 23 11:54:22 2013 -0700
+++ b/jdk/src/share/classes/java/lang/String.java Tue Apr 02 18:41:04 2013 -0400
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1994, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1994, 2013, 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
@@ -34,6 +34,7 @@
import java.util.Formatter;
import java.util.Locale;
import java.util.Objects;
+import java.util.StringJoiner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
@@ -131,7 +132,7 @@
* string instance within the stream.
*/
private static final ObjectStreamField[] serialPersistentFields =
- new ObjectStreamField[0];
+ new ObjectStreamField[0];
/**
* Initializes a newly created {@code String} object so that it represents
@@ -970,7 +971,7 @@
return true;
}
if (anObject instanceof String) {
- String anotherString = (String) anObject;
+ String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
@@ -978,7 +979,7 @@
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
- return false;
+ return false;
i++;
}
return true;
@@ -1003,7 +1004,7 @@
* @since 1.4
*/
public boolean contentEquals(StringBuffer sb) {
- return contentEquals((CharSequence) sb);
+ return contentEquals((CharSequence)sb);
}
private boolean nonSyncContentEquals(AbstractStringBuilder sb) {
@@ -1248,7 +1249,8 @@
* argument.
* <li>There is some nonnegative integer <i>k</i> less than {@code len}
* such that:
- * <code>this.charAt(toffset+<i>k</i>) != other.charAt(ooffset+<i>k</i>)</code>
+ * {@code this.charAt(toffset + }<i>k</i>{@code ) != other.charAt(ooffset + }
+ * <i>k</i>{@code )}
* </ul>
*
* @param toffset the starting offset of the subregion in this string.
@@ -1872,7 +1874,7 @@
int min = sourceOffset + targetCount - 1;
int i = min + fromIndex;
- startSearchForLastChar:
+ startSearchForLastChar:
while (true) {
while (i >= min && source[i] != strLastChar) {
i--;
@@ -1973,7 +1975,7 @@
* str.substring(begin, end)</pre></blockquote>
*
* This method is defined so that the {@code String} class can implement
- * the {@link CharSequence} interface. </p>
+ * the {@link CharSequence} interface.
*
* @param beginIndex the begin index, inclusive.
* @param endIndex the end index, exclusive.
@@ -2352,9 +2354,11 @@
// Construct result
int resultSize = list.size();
- if (limit == 0)
- while (resultSize > 0 && list.get(resultSize - 1).length() == 0)
+ if (limit == 0) {
+ while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
resultSize--;
+ }
+ }
String[] result = new String[resultSize];
return list.subList(0, resultSize).toArray(result);
}
@@ -2404,6 +2408,90 @@
}
/**
+ * Returns a new String composed of copies of the
+ * {@code CharSequence elements} joined together with a copy of
+ * the specified {@code delimiter}.
+ *
+ * <blockquote>For example,
+ * <pre>{@code
+ * String message = String.join("-", "Java", "is", "cool");
+ * // message returned is: "Java-is-cool"
+ * }</pre></blockquote>
+ *
+ * Note that if an element is null, then {@code "null"} is added.
+ *
+ * @param delimiter the delimiter that separates each element
+ * @param elements the elements to join together.
+ *
+ * @return a new {@code String} that is composed of the {@code elements}
+ * separated by the {@code delimiter}
+ *
+ * @throws NullPointerException If {@code delimiter} or {@code elements}
+ * is {@code null}
+ *
+ * @see java.util.StringJoiner
+ * @since 1.8
+ */
+ public static String join(CharSequence delimiter, CharSequence... elements) {
+ Objects.requireNonNull(delimiter);
+ Objects.requireNonNull(elements);
+ // Number of elements not likely worth Arrays.stream overhead.
+ StringJoiner joiner = new StringJoiner(delimiter);
+ for (CharSequence cs: elements) {
+ joiner.add(cs);
+ }
+ return joiner.toString();
+ }
+
+ /**
+ * Returns a new {@code String} composed of copies of the
+ * {@code CharSequence elements} joined together with a copy of the
+ * specified {@code delimiter}.
+ *
+ * <blockquote>For example,
+ * <pre>{@code
+ * List<String> strings = new LinkedList<>();
+ * strings.add("Java");strings.add("is");
+ * strings.add("cool");
+ * String message = String.join(" ", strings);
+ * //message returned is: "Java is cool"
+ *
+ * Set<String> strings = new HashSet<>();
+ * Strings.add("Java"); strings.add("is");
+ * strings.add("very"); strings.add("cool");
+ * String message = String.join("-", strings);
+ * //message returned is: "Java-is-very-cool"
+ * }</pre></blockquote>
+ *
+ * Note that if an individual element is {@code null}, then {@code "null"} is added.
+ *
+ * @param delimiter a sequence of characters that is used to separate each
+ * of the {@code elements} in the resulting {@code String}
+ * @param elements an {@code Iterable} that will have its {@code elements}
+ * joined together.
+ *
+ * @return a new {@code String} that is composed from the {@code elements}
+ * argument
+ *
+ * @throws NullPointerException If {@code delimiter} or {@code elements}
+ * is {@code null}
+ *
+ * @see #join(CharSequence,CharSequence...)
+ * @see java.util.StringJoiner
+ * @since 1.8
+ */
+ public static String join(CharSequence delimiter,
+ Iterable<? extends CharSequence> elements) {
+ Objects.requireNonNull(delimiter);
+ Objects.requireNonNull(elements);
+ StringJoiner joiner = new StringJoiner(delimiter);
+ for (CharSequence cs: elements) {
+ joiner.add(cs);
+ }
+ return joiner.toString();
+ }
+
+ /**
* Converts all of the characters in this {@code String} to lower
* case using the rules of the given {@code Locale}. Case mapping is based
* on the Unicode Standard version specified by the {@link java.lang.Character Character}
@@ -2650,9 +2738,9 @@
return this;
}
+ /* result may grow, so i+resultOffset is the write location in result */
+ int resultOffset = 0;
char[] result = new char[len]; /* may grow */
- int resultOffset = 0; /* result may grow, so i+resultOffset
- * is the write location in result */
/* Just copy the first few upperCase characters. */
System.arraycopy(value, 0, result, 0, firstLower);
@@ -2757,7 +2845,7 @@
* object is created, representing the substring of this string that
* begins with the character at index <i>k</i> and ends with the
* character at index <i>m</i>-that is, the result of
- * <code>this.substring(<i>k</i>, <i>m</i>+1)</code>.
+ * {@code this.substring(k, m + 1)}.
* <p>
* This method may be used to trim whitespace (as defined above) from
* the beginning and end of a string.
@@ -3143,8 +3231,8 @@
* programmer should be aware that producing distinct integer results
* for unequal objects may improve the performance of hash tables.
* </ul>
- * </p>
- * The hash value will never be zero.
+ *
+ * The hash value will never be zero.
*
* @return a hash code value for this object.
* @see java.lang.Object#equals(java.lang.Object)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/java/util/StringJoiner.java Tue Apr 02 18:41:04 2013 -0400
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2013 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package java.util;
+
+/**
+ * {@code StringJoiner} is used to construct a sequence of characters separated
+ * by a delimiter and optionally starting with a supplied prefix
+ * and ending with a supplied suffix.
+ * <p>
+ * For example, the String {@code "[George:Sally:Fred]"} may
+ * be constructed as follows:
+ * <pre> {@code
+ * StringJoiner sj = new StringJoiner(":", "[", "]");
+ * sj.add("George").add("Sally").add("Fred");
+ * String desiredString = sj.toString();
+ * }</pre>
+ * <p>
+ * Prior to adding something to the {@code StringJoiner}, its
+ * {@code sj.toString()} method will, by default, return {@code prefix + suffix}.
+ * However, if the {@code setEmptyValue} method is called, the {@code emptyValue}
+ * supplied will be returned instead. This can be used, for example, when
+ * creating a string using set notation to indicate an empty set, i.e.
+ * <code>"{}"</code>, where the {@code prefix} is <code>"{"</code>, the
+ * {@code suffix} is <code>"}"</code> and nothing has been added to the
+ * {@code StringJoiner}.
+ * <p>
+ * A {@code StringJoiner} may be employed to create formatted output from a
+ * collection using lambda expressions as shown in the following example.
+ *
+ * <pre> {@code
+ * List<Person> people = ...
+ * String commaSeparatedNames =
+ * people.map(p -> p.getName()).into(new StringJoiner(", ")).toString();
+ * }</pre>
+ *
+ * @author Jim Gish
+ * @since 1.8
+*/
+public final class StringJoiner {
+ private final String prefix;
+ 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;
+
+ /*
+ * 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.
+ */
+ private String emptyValue;
+
+ /**
+ * Constructs a {@code StringJoiner} with no characters in it, with no
+ * {@code prefix} or {@code suffix}, and a copy of the supplied
+ * {@code delimiter}.
+ * If no characters are added to the {@code StringJoiner} and methods
+ * accessing the value of it are invoked, it will not return a
+ * {@code prefix} or {@code suffix} (or properties thereof) in the result,
+ * unless {@code setEmptyValue} has first been called.
+ *
+ * @param delimiter the sequence of characters to be used between each
+ * element added to the {@code StringJoiner} value
+ * @throws NullPointerException if {@code delimiter} is {@code null}
+ */
+ public StringJoiner(CharSequence delimiter) {
+ this(delimiter, "", "");
+ }
+
+ /**
+ * Constructs a {@code StringJoiner} with no characters in it using copies
+ * of the supplied {@code prefix}, {@code delimiter} and {@code suffix}.
+ * If no characters are added to the {@code StringJoiner} and methods
+ * accessing the string value of it are invoked, it will return the
+ * {@code prefix + suffix} (or properties thereof) in the result, unless
+ * {@code setEmptyValue} has first been called.
+ *
+ * @param delimiter the sequence of characters to be used between each
+ * element added to the {@code StringJoiner}
+ * @param prefix the sequence of characters to be used at the beginning
+ * @param suffix the sequence of characters to be used at the end
+ * @throws NullPointerException if {@code prefix}, {@code delimiter}, or
+ * {@code suffix} is {@code null}
+ */
+ public StringJoiner(CharSequence delimiter, CharSequence prefix,
+ CharSequence suffix) {
+ Objects.requireNonNull(prefix, "The prefix must not be null");
+ Objects.requireNonNull(delimiter, "The delimiter must not be null");
+ Objects.requireNonNull(suffix, "The suffix must not be null");
+ // make defensive copies of arguments
+ this.prefix = prefix.toString();
+ this.delimiter = delimiter.toString();
+ this.suffix = suffix.toString();
+ this.emptyValue = this.prefix + this.suffix;
+ }
+
+ /**
+ * Sets the sequence of characters to be used when determining the string
+ * representation of this {@code StringJoiner} and no elements have been
+ * added yet, i.e. when it is empty. A copy of the {@code emptyValue}
+ * parameter is made for this purpose. Note that once an add method has been
+ * called, the {@code StringJoiner} is no longer considered empty, even if
+ * the element(s) added correspond to the empty {@code String}.
+ *
+ * @param emptyValue the characters to return as the value of an empty
+ * {@code StringJoiner}
+ * @return this {@code StringJoiner} itself so the calls may be chained
+ * @throws NullPointerException when the {@code emptyValue} parameter is
+ * {@code null}
+ */
+ public StringJoiner setEmptyValue(CharSequence emptyValue) {
+ this.emptyValue = Objects.requireNonNull(emptyValue,
+ "The empty value must not be null").toString();
+ return this;
+ }
+
+ /**
+ * 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
+ *
+ * @return the string representation of this {@code StringJoiner}
+ */
+ @Override
+ public String toString() {
+ if (value == 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;
+ }
+ }
+ }
+
+ /**
+ * Add the a copy of the supplied {@code CharSequence} value as the next
+ * element of the {@code StringJoiner} value. If {@code newElement} is
+ * {@code null}, then {@code "null"} is added.
+ *
+ * @param newElement The element to add
+ * @return a reference to this {@code StringJoiner}
+ */
+ public StringJoiner add(CharSequence newElement) {
+ prepareBuilder().append(newElement);
+ return this;
+ }
+
+ private StringBuilder prepareBuilder() {
+ if (value != null) {
+ value.append(delimiter);
+ } else {
+ value = new StringBuilder().append(prefix);
+ }
+ return value;
+ }
+
+ /**
+ * The length of the {@code StringJoiner} value, i.e. the length of
+ * {@code String} representation of the {@code StringJoiner}. Note that if
+ * no add methods have been called, then the length of the {@code String}
+ * representation (either {@code prefix + suffix} or {@code emptyValue})
+ * will be returned. The value should be equivalent to
+ * {@code toString().length()}.
+ *
+ * @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());
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/String/StringJoinTest.java Tue Apr 02 18:41:04 2013 -0400
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2013, 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+/**
+ * @test @bug 5015163
+ * @summary test String merge/join that is the inverse of String.split()
+ * @run testng StringJoinTest
+ * @author Jim Gish
+ */
+import java.util.ArrayList;
+import java.util.List;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.*;
+
+@Test(groups = {"unit","string","lang","libs"})
+public class StringJoinTest {
+ private final static String DASH = "-";
+ private final static String BEGIN = "Hi there";
+ private final static String JIM = "Jim";
+ private final static String JOHN = "John";
+ private final static String AND_JOE = "and Joe";
+ private final static String BILL = "Bill";
+ private final static String BOB = "Bob";
+ private final static String AND_BO = "and Bo";
+ private final static String ZEKE = "Zeke";
+ private final static String ZACK = "Zack";
+ private final static String AND_ZOE = "and Zoe";
+
+ /**
+ * Tests the join() methods on String
+ */
+ public void testJoinStringVarargs() {
+ // check a non-null join of String array (var-args) elements
+ String expectedResult = BEGIN + DASH + JIM + DASH + JOHN + DASH + AND_JOE;
+ String result = String.join(DASH, BEGIN, JIM, JOHN, AND_JOE);
+
+ assertEquals(result, expectedResult, "BEGIN.join(DASH, JIM, JOHN, AND_JOE)");
+ // test with just one element
+ assertEquals(String.join(DASH, BEGIN), BEGIN);
+ }
+
+ public void testJoinStringArray() {
+ // check a non-null join of Object[] with String elements
+ String[] theBs = {BILL, BOB, AND_BO};
+ String result = String.join(DASH, theBs);
+ String expectedResult = BILL + DASH + BOB + DASH + AND_BO;
+ assertEquals(result, expectedResult, "String.join(DASH, theBs)");
+ }
+
+ public void testJoinEmptyStringArray() {
+ // check a non-null join of Object[] with String elements
+ String[] empties = {};
+ String result = String.join(DASH, empties);
+ assertEquals(result, "", "String.join(DASH, empties)");
+ }
+
+ @Test(expectedExceptions = {NullPointerException.class})
+ public void testJoinNullStringArray() {
+ // check a non-null join of Object[] with String elements
+ String[] empties = null;
+ String result = String.join(DASH, empties);
+ }
+
+ @Test(expectedExceptions = {NullPointerException.class})
+ public void testJoinNullIterableStringList() {
+ // check join of an Iterables
+ List<CharSequence> theZsList = null;
+ String.join(DASH, theZsList);
+ }
+
+ public void testJoinIterableStringList() {
+ // check join of an Iterables
+ List<CharSequence> theZsList = new ArrayList<>();
+ theZsList.add(ZEKE);
+ theZsList.add(ZACK);
+ theZsList.add(AND_ZOE);
+ assertEquals(String.join(DASH, theZsList), ZEKE + DASH + ZACK + DASH
+ + AND_ZOE, "String.join(DASH, theZsList))");
+ }
+
+ public void testJoinNullStringList() {
+ List<CharSequence> nullList = null;
+ try {
+ assertEquals( String.join( DASH, nullList ), "null" );
+ fail("Null container should cause NPE");
+ } catch (NullPointerException npe) {}
+ assertEquals(String.join(DASH, null, null), "null" + DASH + "null");
+ }
+
+ @Test(expectedExceptions = {NullPointerException.class})
+ public void testJoinNullDelimiter() {
+ String.join(null, JIM, JOHN);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/util/StringJoiner/StringJoinerTest.java Tue Apr 02 18:41:04 2013 -0400
@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) 2013 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+/**
+ * @test
+ * @bug 5015163 7172553
+ * @summary tests StringJoinerTest
+ * @run testng StringJoinerTest
+ * @author Jim Gish
+ */
+import java.util.StringJoiner;
+import org.testng.annotations.Test;
+import static org.testng.Assert.assertEquals;
+
+@Test(groups = {"unit","string","util","libs"})
+public class StringJoinerTest {
+
+ private static final String EMPTY = "EMPTY";
+ private static final String ONE = "One";
+ private static final int ONE_LEN = ONE.length();
+ private static final String TWO = "Two";
+ private static final int TWO_LEN = TWO.length();
+ private static final String THREE = "Three";
+ private static final String FOUR = "Four";
+ private static final String FIVE = "Five";
+ private static final String DASH = "-";
+
+ /* Uncomment when we have streams
+ public void addAddAll() {
+ StringJoiner sj = new StringJoiner(DASH, "{", "}");
+ sj.add(ONE);
+
+ ArrayList<String> nextOne = new ArrayList<>();
+ nextOne.add(TWO);
+ nextOne.add(THREE);
+ nextOne.stream().forEach(sj::add);
+
+ String expected = "{"+ONE+DASH+TWO+DASH+THREE+"}";
+ assertEquals(sj.toString(), expected);
+ }
+
+ void addAlladd() {
+ StringJoiner sj = new StringJoiner(DASH, "{", "}");
+
+ ArrayList<String> firstOne = new ArrayList<>();
+ firstOne.add(ONE);
+ firstOne.add(TWO);
+ firstOne.stream().forEach(sj::add);
+
+ sj.add(THREE);
+
+ String expected = "{"+ONE+DASH+TWO+DASH+THREE+"}";
+ assertEquals(sj.toString(), expected);
+ }
+
+ // The following tests do two successive adds of different types
+ public void addAlladdAll() {
+ StringJoiner sj = new StringJoiner(DASH, "{", "}");
+ ArrayList<String> firstOne = new ArrayList<>();
+ firstOne.add(ONE);
+ firstOne.add(TWO);
+ firstOne.add(THREE);
+ firstOne.stream().forEach(sj::add);
+
+ ArrayList<String> nextOne = new ArrayList<>();
+ nextOne.add(FOUR);
+ nextOne.add(FIVE);
+ nextOne.stream().forEach(sj::add);
+
+ String expected = "{"+ONE+DASH+TWO+DASH+THREE+DASH+FOUR+DASH+FIVE+"}";
+ assertEquals(sj.toString(), expected);
+ }
+
+ public void testInto() {
+ ArrayList<String> list = new ArrayList<>();
+ list.add(ONE);
+ list.add(TWO);
+ list.add(THREE);
+
+ StringJoiner target = new StringJoiner(",", "{", "}");
+ assertEquals(target.toString(), "{" + ONE + "," + TWO + "," + THREE +
+ "}");
+ }
+ */
+
+ public void addCharSequence() {
+ StringJoiner sj = new StringJoiner(",");
+ CharSequence cs_one = ONE;
+ CharSequence cs_two = TWO;
+
+ sj.add(cs_one);
+ sj.add(cs_two);
+
+ assertEquals(sj.toString(), ONE + "," + TWO);
+
+ sj = new StringJoiner(DASH, "{", "}");
+ sj.add(cs_one);
+ sj.add(cs_two);
+
+ assertEquals(sj.toString(), "{" + ONE + DASH + TWO + "}");
+
+ StringBuilder builder = new StringBuilder(ONE);
+ StringBuffer buffer = new StringBuffer(THREE);
+ sj = new StringJoiner(", ", "{ ", " }");
+ sj.add(builder).add(buffer);
+ builder.append(TWO);
+ buffer.append(FOUR);
+ assertEquals(sj.toString(), "{ " + ONE + ", " + THREE + " }",
+ "CharSequence is copied when add");
+ sj.add(builder);
+ assertEquals(sj.toString(), "{ " + ONE + ", " + THREE + ", " + ONE +
+ TWO + " }");
+ }
+
+ public void addCharSequenceWithEmptyValue() {
+ StringJoiner sj = new StringJoiner(",").setEmptyValue(EMPTY);
+ CharSequence cs_one = ONE;
+ CharSequence cs_two = TWO;
+
+ sj.add(cs_one);
+ sj.add(cs_two);
+
+ assertEquals(sj.toString(), ONE + "," + TWO);
+
+ sj = new StringJoiner(DASH, "{", "}");
+ sj.add(cs_one);
+ sj.add(cs_two);
+ assertEquals(sj.toString(), "{" + ONE + DASH + TWO + "}");
+
+ sj = new StringJoiner(DASH, "{", "}");
+ assertEquals(sj.toString(), "{}");
+
+ sj = new StringJoiner("=", "{", "}").setEmptyValue("");
+ assertEquals(sj.toString(), "");
+
+ sj = new StringJoiner(DASH, "{", "}").setEmptyValue(EMPTY);
+ assertEquals(sj.toString(), EMPTY);
+
+ sj.add(cs_one);
+ sj.add(cs_two);
+ assertEquals(sj.toString(), "{" + ONE + DASH + TWO + "}");
+ }
+
+ public void addString() {
+ StringJoiner sj = new StringJoiner(DASH);
+ sj.add(ONE);
+ assertEquals(sj.toString(), ONE);
+
+ sj = new StringJoiner(DASH, "{", "}");
+ sj.add(ONE);
+ assertEquals(sj.toString(), "{" + ONE + "}");
+
+ sj.add(TWO);
+ assertEquals(sj.toString(), "{" + ONE + DASH + TWO + "}");
+ }
+
+ public void lengthWithCustomEmptyValue() {
+ StringJoiner sj = new StringJoiner(DASH, "<", ">").setEmptyValue(EMPTY);
+ assertEquals(sj.length(), EMPTY.length());
+ sj.add("");
+ assertEquals(sj.length(), "<>".length());
+ sj.add("");
+ assertEquals(sj.length(), "<->".length());
+ sj.add(ONE);
+ assertEquals(sj.length(), 4 + ONE_LEN);
+ assertEquals(sj.toString().length(), sj.length());
+ sj.add(TWO);
+ assertEquals(sj.length(), 5 + ONE_LEN + TWO_LEN);
+ assertEquals(sj.toString().length(), sj.length());
+ sj = new StringJoiner("||", "<", "-->");
+ assertEquals(sj.length(), 4);
+ assertEquals(sj.toString().length(), sj.length());
+ sj.add("abcdef");
+ assertEquals(sj.length(), 10);
+ assertEquals(sj.toString().length(), sj.length());
+ sj.add("xyz");
+ assertEquals(sj.length(), 15);
+ assertEquals(sj.toString().length(), sj.length());
+ }
+
+ public void noAddAndEmptyValue() {
+ StringJoiner sj = new StringJoiner(DASH, "", "").setEmptyValue(EMPTY);
+ assertEquals(sj.toString(), EMPTY);
+
+ sj = new StringJoiner(DASH, "<..", "");
+ assertEquals(sj.toString(), "<..");
+
+ sj = new StringJoiner(DASH, "<..", "");
+ assertEquals(sj.toString(), "<..");
+
+ sj = new StringJoiner(DASH, "", "==>");
+ assertEquals(sj.toString(), "==>");
+
+ sj = new StringJoiner(DASH, "{", "}");
+ assertEquals(sj.toString(), "{}");
+ }
+
+ @Test(expectedExceptions = {NullPointerException.class})
+ public void setEmptyValueNull() {
+ new StringJoiner(DASH, "{", "}").setEmptyValue(null);
+ }
+
+ @Test(expectedExceptions = {NullPointerException.class})
+ public void setDelimiterNull() {
+ new StringJoiner(null);
+ }
+
+ @Test(expectedExceptions = {NullPointerException.class})
+ public void setPrefixNull() {
+ new StringJoiner(DASH, null, "}");
+ }
+
+ @Test(expectedExceptions = {NullPointerException.class})
+ public void setSuffixNull() {
+ new StringJoiner(DASH, "{", null);
+ }
+
+ public void stringFromtoString() {
+ StringJoiner sj = new StringJoiner(", ");
+ assertEquals(sj.toString(), "");
+ sj = new StringJoiner(",", "{", "}");
+ assertEquals(sj.toString(), "{}");
+
+ sj = new StringJoiner(",");
+ sj.add(ONE);
+ assertEquals(sj.toString(), ONE);
+
+ sj.add(TWO);
+ assertEquals(sj.toString(), ONE + "," + TWO);
+
+ sj = new StringJoiner(",", "{--", "--}");
+ sj.add(ONE);
+ sj.add(TWO);
+ assertEquals(sj.toString(), "{--" + ONE + "," + TWO + "--}");
+
+ }
+
+ public void stringFromtoStringWithEmptyValue() {
+ StringJoiner sj = new StringJoiner(" ", "", "");
+ assertEquals(sj.toString(), "");
+ sj = new StringJoiner(", ");
+ assertEquals(sj.toString(), "");
+ sj = new StringJoiner(",", "{", "}");
+ assertEquals(sj.toString(), "{}");
+
+ sj = new StringJoiner(",", "{", "}").setEmptyValue("");
+ assertEquals(sj.toString(), "");
+
+ sj = new StringJoiner(",");
+ sj.add(ONE);
+ assertEquals(sj.toString(), ONE);
+
+ sj.add(TWO);
+ assertEquals(sj.toString(), ONE + "," + TWO);
+
+ sj = new StringJoiner(",", "{--", "--}");
+ sj.add(ONE);
+ assertEquals(sj.toString(), "{--" + ONE + "--}" );
+
+ sj.add(TWO);
+ assertEquals(sj.toString(), "{--" + ONE + "," + TWO + "--}");
+
+ }
+
+ public void toStringWithCustomEmptyValue() {
+ StringJoiner sj = new StringJoiner(DASH, "<", ">").setEmptyValue(EMPTY);
+ assertEquals(sj.toString(), EMPTY);
+ sj.add("");
+ assertEquals(sj.toString(), "<>");
+ sj.add("");
+ assertEquals(sj.toString(), "<->");
+ }
+
+ private void testCombos(String infix, String prefix, String suffix) {
+ StringJoiner sj = new StringJoiner(infix, prefix, suffix);
+ assertEquals(sj.toString(), prefix + suffix);
+ assertEquals(sj.toString().length(), sj.length());
+ // EmptyValue
+ sj = new StringJoiner(infix, prefix, suffix).setEmptyValue("<NONE>");
+ assertEquals(sj.toString(), "<NONE>");
+ assertEquals(sj.toString().length(), sj.length());
+
+ // empty in front
+ sj.add("");
+ assertEquals(sj.toString(), prefix + suffix);
+ // empty in middle
+ sj.add("");
+ assertEquals(sj.toString(), prefix + infix + suffix);
+ sj.add("1");
+ assertEquals(sj.toString(), prefix + infix + infix + "1" + suffix);
+ // empty at end
+ sj.add("");
+ assertEquals(sj.toString(), prefix + infix + infix + "1" + infix + suffix);
+
+ sj = new StringJoiner(infix, prefix, suffix).setEmptyValue("<NONE>");
+ sj.add("1");
+ assertEquals(sj.toString(), prefix + "1" + suffix);
+ sj.add("2");
+ assertEquals(sj.toString(), prefix + "1" + infix + "2" + suffix);
+ sj.add("");
+ assertEquals(sj.toString(), prefix + "1" + infix + "2" +infix + suffix);
+ sj.add("3");
+ assertEquals(sj.toString(), prefix + "1" + infix + "2" +infix + infix + "3" + suffix);
+ }
+
+ public void testDelimiterCombinations() {
+ testCombos("", "", "");
+ testCombos("", "<", "");
+ testCombos("", "", ">");
+ testCombos("", "<", ">");
+ testCombos(",", "", "");
+ testCombos(",", "<", "");
+ testCombos(",", "", ">");
+ testCombos(",", "<", ">");
+ }
+}
+