changeset 5350:6b79792c3eb3

7175206: StringJoiner subsequence on suffix always returns complete suffix instead of requested subsequence Summary: Code fix to use correct start index on subsequence with new test. Also fixed line endings. Reviewed-by: briangoetz Contributed-by: Jim Gish <jim.gish@oracle.com>
author briangoetz
date Mon, 11 Jun 2012 17:02:43 -0400
parents 63a2e47cc2a7
children 2cfa10d72aad
files src/share/classes/java/util/StringJoiner.java test-ng/tests/org/openjdk/tests/java/util/StringJoinerTest.java
diffstat 2 files changed, 826 insertions(+), 818 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/java/util/StringJoiner.java	Wed Jun 06 13:39:41 2012 -0700
+++ b/src/share/classes/java/util/StringJoiner.java	Mon Jun 11 17:02:43 2012 -0400
@@ -1,347 +1,347 @@
-/*
- * Copyright (c) 2012 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;
-
-/**
- * StringJoiner is used to construct a sequence of characters separated
- * by an infix delimiter and optionally starting with a supplied prefix
- * and ending with a supplied suffix.
- *
- * For example,  the String <code>"[George:Sally:Fred]"</code> may
- * be constructed as follows:
-<code><pre>
-
-  StringJoiner sj = new StringJoiner( ":", "[", "]" );
-  sj.add( "George" ).add( "Sally" ).add( "Fred" );
-  String desiredString = sj.toString();
-
-</pre></code>
- *
- * Prior to adding something to the StringJoiner, <code>sj.toString()</code>
- * will, by default, return <code>prefix+suffix</code>.  However, if the
- * <code>emptyOutput</code> parameter is supplied via the setEmptyOutput method,
- * the value 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. "{}",
- * where the prefix is "{", the suffix is "}" and nothing has been added to the
- * StringJoiner.
- *
- * A StringJoiner may be employed to create formatted output from a
- * collection using lambda expressions.  For example,
- *
- *
- <code><pre>
-      List<Person> people = ...
-      String commaSeparatedNames =
-          people.map( p -> p.getName() )
-              .into( new StringJoiner(", ")
-              .toString();
- </pre></code>
- *
- * @author Jim Gish
- * @since  1.8
-*/
-public class StringJoiner implements Fillable<CharSequence>, CharSequence {
-
-    private static final String PREFIX_IS_NULL_MSG = "The prefix must not be null";
-    private static final String INFIX_IS_NULL_MSG = "The infix delimiter must not be null";
-    private static final String SUFFIX_IS_NULL_MSG = "The suffix must not be null";
-    private static final String EMPTY_OUTPUT_IS_NULL_MSG = "The empty output value must not be null";
-
-    private final String prefix;
-    private final String infix;
-    private final String suffix;
-
-    /**
-     * default null-handling of add() methods is to add the "null" characters
-     */
-    private final String nullOutput = "null";
-    private final char[] nullOutputChars = {'n','u','l','l'};
-
-    private final int prefixLen;
-
-    /*
-     * StringBuilder value -- at any time, the characters constructed from the
-     * prefix, the added element separated by the infix, but without the suffix,
-     * so that we can more easily add elements without having to jigger the
-     * suffix each time.
-     */
-    private final 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 emptyOutput;
-
-    /*
-     * somethingAdded == true indicates that we already have at least one element
-     * added to the value.  This helps us determine whether to append the infix
-     * onto the value before appending an element being added.
-     */
-    private boolean somethingAdded = false;
-
-    /**
-     * Constructs a string joiner with no characters in it with no prefix or suffix
-     * and using the supplied infix delimiter.  Also, if no characters are added
-     * to the StringJoiner and methods accessing the value of it are invoked, it
-     * will not return a prefix or suffix (or properties thereof) in the result, unless setEmptyOutput
-     * has first been called to define the emptyOutput CharSequence.
-     *
-     * @param infix the sequence of characters to be used between each element
-     * added to the StringJoiner value
-     * @throws NullPointerException if infix is null
-     */
-    public StringJoiner(CharSequence infix) {
-        this( infix, "", "" );
-    }
-
-    /**
-     * Constructs a string joiner with no characters in it and using the supplied prefix,
-     * infix and suffix. Also, if no characters are added
-     * to the StringJoiner and methods accessing the string value of it are invoked, it
-     * will return the prefix+suffix (or properties thereof) in the result, unless setEmptyOutput has
-     * first been called to define the emptyOutput CharSequence.
-     *
-     * @param infix the sequence of characters to be used between each element added to the 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 prefix, infix, or suffix is null
-     */
-    public StringJoiner(CharSequence infix, CharSequence prefix, CharSequence suffix) {
-        Objects.requireNonNull( prefix, PREFIX_IS_NULL_MSG );
-        Objects.requireNonNull( infix, INFIX_IS_NULL_MSG );
-        Objects.requireNonNull( suffix, SUFFIX_IS_NULL_MSG );
-        // make defensive copies of arguments
-        this.prefix = prefix.toString();
-        this.infix = infix.toString();
-        this.suffix = suffix.toString();
-        this.emptyOutput = this.prefix + this.suffix;
-        this.prefixLen = this.prefix.length();
-        this.value = new StringBuilder();
-    }
-
-    /**
-     * Sets the sequence of characters to be used when invoking toString(), subSequence(),
-     * length(), charAt() and no elements have been added to the StringJoiner yet, i.e. when
-     * it is empty.  Note that once an add method has been called, the StringJoiner is no longer considered
-     * empty, even if the element(s) added correspond to the empty String.
-     *
-     * @param emptyOutput the characters to return as the value of an empty StringJoiner
-     * @return this StringJoiner itself so the calls may be chained
-     * @throws NullPointerException when the emptyOutput parameter is null
-     */
-    public StringJoiner setEmptyOutput( CharSequence emptyOutput ) {
-        Objects.requireNonNull(emptyOutput, EMPTY_OUTPUT_IS_NULL_MSG);
-        this.emptyOutput = emptyOutput.toString();
-        return this;
-    }
-
-    /**
-     * returns the current value, consisting of the prefix, the values added so
-     * far separated by the infix delimiter, and the suffix, unless no elements have been added
-     * in which case, the prefix+suffix or the emptyOutput  characters are returned
-     */
-    @Override
-    public String toString() {
-        return (somethingAdded ? value.toString() + suffix : emptyOutput.toString());
-    }
-
-    /**
-     * adds each CharSequence from the supplied Iterable to the
-     * StringJoiner value separated by the infix delimiter (even if the CharSequence corresponds
-     * to the empty string). If the Iterable is null
-     * then the nullOuput character sequence is added.  If the Iterable is non-null, then each element of the
-     * Iterable is added.
-     *
-     * @param newElements
-     */
-    public void addAll(Iterable<? extends CharSequence> newElements) {
-        if (newElements == null) {
-            // if the Iterable is null, then use nullOutput
-            prepareBuilder().append(nullOutput);
-        } else {
-            // add on each CharSequence of the Iterable
-            for (CharSequence cs : newElements) {
-                add(cs);
-            }
-        }
-    }
-
-    /**
-     * add the supplied CharSequence as the next element of the StringJoiner value.
-     * If the CharSequence is null, then the nullOuput character sequence is added.
-     *
-     * @param newElement
-     * @return this StringJoiner
-     */
-    public StringJoiner add(CharSequence newElement) {
-        String element = (newElement == null ? nullOutput : newElement.toString());
-        prepareBuilder().append(element);
-        return this;
-    }
-
-    /**
-     * add the supplied char[] as the next element of the StringJoiner value.
-     * If the char[] is null, then the nullOuput character sequence is added.
-     *
-     * @param newElement
-     * @return this StringJoiner
-     */
-    public StringJoiner add(char[] newElement) {
-        char[] element = (newElement == null ? nullOutputChars : newElement);
-        prepareBuilder().append(element);
-        return this;
-    }
-
-    private StringBuilder prepareBuilder() {
-        if (!somethingAdded) {
-            // This is the first addition so append the prefix and then the new element
-            if (prefixLen > 0) {
-                //  append the prefix if not the empty string
-                value.append(prefix);
-            }
-            // assume we're going to put on the new element
-            somethingAdded = true;
-        } else {
-            // Since there is already something present, append the infix and then the new element
-            value.append(infix);
-        }
-        return value;
-    }
-
-  /**
-   * The length of the StringJoiner value, equivalent to toString().length(),
-   * i.e. the length of String representation of the StringJoiner. Note that if no add methods have been
-   * called, then the length of the String representation (either prefix+suffix or emptyOutput) will be
-   * returned.
-   *
-   * @return the length of the current value of StringJoiner
-   */
-    @Override
-    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 (somethingAdded ? value.length() + suffix.length() : emptyOutput.length() );
-    }
-
-    /**
-     * Equivalent to toString().charAt( index ), i.e. the index-th character of
-     * the String representation of the StringJoiner value.  Note that if the StringJoiner is
-     * currently empty this is a character from the prefix+suffix or emptyOutput character sequence.
-     *
-     * @param index an integer from 0 to StringJoiner.length()-1
-     * @return the specified char of the current value of StringJoiner
-     * @throws IndexOutOfBoundsException if index < 0 or >= length()
-     */
-    @Override
-    public char charAt(int index) {
-
-        if ( index < 0 || index >= length() ) {
-            throw new IndexOutOfBoundsException();
-        }
-
-        if (!somethingAdded) {
-            return emptyOutput.charAt( index );
-        }
-
-        // check if index is in the portion accumulated so far (chars on the left without the suffix)
-        if (index < value.length()) {
-            return value.charAt( index );
-        }
-        // else we're looking for a character from the suffix
-        return suffix.charAt(index - value.length());
-    }
-
-    /**
-     * Equivalent to toString().subSequence( start, end ), i.e. a subSequence of
-     * the current String representation of StringJoiner value.  Note that if the StringJoiner is
-     * currently empty this is a subsequence of the prefix+suffix ore emptyOutput character sequence.
-     *
-     * @param start an integer indicating the (inclusive) starting index of the subsequence
-     * @param end an integer indicating the (exclusive) ending index of the subsequence
-     * @return the requested sub-sequence of the current value of StringJoiner
-     *
-     */
-    @Override
-    public CharSequence subSequence(int start, int end) {
-        if (start < 0 || start >= length()
-                || end > length()
-                || start > end) {
-            throw new IndexOutOfBoundsException();
-        }
-        // If we haven't added anything yet, then access the requested part of the emptyOutput
-        if (!somethingAdded) {
-            return emptyOutput.subSequence(start, end);
-        }
-        // Is the requested sub-sequence included to the left of the suffix?
-        // ( probably the most likely case )
-        if (end <= value.length()) {
-            return value.subSequence(start, end);
-        }
-        // Does the requested sub-sequence span the boundary between the accumulated
-        // value and the suffix?
-        if (start < value.length() && value.length() < end) {
-            return new StringBuilder(
-                    value.subSequence( start, value.length()) ).append(
-                    suffix.subSequence( 0, end - value.length() ));
-        }
-        // The sub-sequence exists entirely within the suffix
-        return suffix.subSequence(0, end - value.length());
-    }
-
-    /**
-     * Returns the String representation of the StringJoiner value as Iterable<Character>
-     * @return the current value of StringJoiner
-     */
-    @Override
-    public Iterable<Character> asChars() {
-        return (somethingAdded ? (value.toString() + suffix).asChars() : emptyOutput.asChars() );
-    }
-
-     /**
-     * Returns the String representation of the StringJoiner value as Iterable<Integer>,
-     * i.e. Unicode code points
-     *
-     * @return the current value of StringJoiner
-     */
-    @Override
-    public Iterable<Integer> asCodePoints() {
-        return (somethingAdded ? (value.toString() + suffix).asCodePoints() : emptyOutput.asCodePoints() );
-    }
-
-    /**
-     * Returns the value used to represent the addition of a null element to the value.  The
-     * default value is "null"
-     *
-     * @return the nullOutput
-     */
-    public String getNullOutput() {
-        return nullOutput;
-    }
-
-}
+/*
+ * Copyright (c) 2012 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;
+
+/**
+ * StringJoiner is used to construct a sequence of characters separated
+ * by an infix delimiter and optionally starting with a supplied prefix
+ * and ending with a supplied suffix.
+ *
+ * For example,  the String <code>"[George:Sally:Fred]"</code> may
+ * be constructed as follows:
+<code><pre>
+
+  StringJoiner sj = new StringJoiner( ":", "[", "]" );
+  sj.add( "George" ).add( "Sally" ).add( "Fred" );
+  String desiredString = sj.toString();
+
+</pre></code>
+ *
+ * Prior to adding something to the StringJoiner, <code>sj.toString()</code>
+ * will, by default, return <code>prefix+suffix</code>.  However, if the
+ * <code>emptyOutput</code> parameter is supplied via the setEmptyOutput method,
+ * the value 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. "{}",
+ * where the prefix is "{", the suffix is "}" and nothing has been added to the
+ * StringJoiner.
+ *
+ * A StringJoiner may be employed to create formatted output from a
+ * collection using lambda expressions.  For example,
+ *
+ *
+ <code><pre>
+      List<Person> people = ...
+      String commaSeparatedNames =
+          people.map( p -> p.getName() )
+              .into( new StringJoiner(", ")
+              .toString();
+ </pre></code>
+ *
+ * @author Jim Gish
+ * @since  1.8
+*/
+public class StringJoiner implements Fillable<CharSequence>, CharSequence {
+
+    private static final String PREFIX_IS_NULL_MSG = "The prefix must not be null";
+    private static final String INFIX_IS_NULL_MSG = "The infix delimiter must not be null";
+    private static final String SUFFIX_IS_NULL_MSG = "The suffix must not be null";
+    private static final String EMPTY_OUTPUT_IS_NULL_MSG = "The empty output value must not be null";
+
+    private final String prefix;
+    private final String infix;
+    private final String suffix;
+
+    /**
+     * default null-handling of add() methods is to add the "null" characters
+     */
+    private final String nullOutput = "null";
+    private final char[] nullOutputChars = {'n','u','l','l'};
+
+    private final int prefixLen;
+
+    /*
+     * StringBuilder value -- at any time, the characters constructed from the
+     * prefix, the added element separated by the infix, but without the suffix,
+     * so that we can more easily add elements without having to jigger the
+     * suffix each time.
+     */
+    private final 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 emptyOutput;
+
+    /*
+     * somethingAdded == true indicates that we already have at least one element
+     * added to the value.  This helps us determine whether to append the infix
+     * onto the value before appending an element being added.
+     */
+    private boolean somethingAdded = false;
+
+    /**
+     * Constructs a string joiner with no characters in it with no prefix or suffix
+     * and using the supplied infix delimiter.  Also, if no characters are added
+     * to the StringJoiner and methods accessing the value of it are invoked, it
+     * will not return a prefix or suffix (or properties thereof) in the result, unless setEmptyOutput
+     * has first been called to define the emptyOutput CharSequence.
+     *
+     * @param infix the sequence of characters to be used between each element
+     * added to the StringJoiner value
+     * @throws NullPointerException if infix is null
+     */
+    public StringJoiner(CharSequence infix) {
+        this( infix, "", "" );
+    }
+
+    /**
+     * Constructs a string joiner with no characters in it and using the supplied prefix,
+     * infix and suffix. Also, if no characters are added
+     * to the StringJoiner and methods accessing the string value of it are invoked, it
+     * will return the prefix+suffix (or properties thereof) in the result, unless setEmptyOutput has
+     * first been called to define the emptyOutput CharSequence.
+     *
+     * @param infix the sequence of characters to be used between each element added to the 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 prefix, infix, or suffix is null
+     */
+    public StringJoiner(CharSequence infix, CharSequence prefix, CharSequence suffix) {
+        Objects.requireNonNull( prefix, PREFIX_IS_NULL_MSG );
+        Objects.requireNonNull( infix, INFIX_IS_NULL_MSG );
+        Objects.requireNonNull( suffix, SUFFIX_IS_NULL_MSG );
+        // make defensive copies of arguments
+        this.prefix = prefix.toString();
+        this.infix = infix.toString();
+        this.suffix = suffix.toString();
+        this.emptyOutput = this.prefix + this.suffix;
+        this.prefixLen = this.prefix.length();
+        this.value = new StringBuilder();
+    }
+
+    /**
+     * Sets the sequence of characters to be used when invoking toString(), subSequence(),
+     * length(), charAt() and no elements have been added to the StringJoiner yet, i.e. when
+     * it is empty.  Note that once an add method has been called, the StringJoiner is no longer considered
+     * empty, even if the element(s) added correspond to the empty String.
+     *
+     * @param emptyOutput the characters to return as the value of an empty StringJoiner
+     * @return this StringJoiner itself so the calls may be chained
+     * @throws NullPointerException when the emptyOutput parameter is null
+     */
+    public StringJoiner setEmptyOutput( CharSequence emptyOutput ) {
+        Objects.requireNonNull(emptyOutput, EMPTY_OUTPUT_IS_NULL_MSG);
+        this.emptyOutput = emptyOutput.toString();
+        return this;
+    }
+
+    /**
+     * returns the current value, consisting of the prefix, the values added so
+     * far separated by the infix delimiter, and the suffix, unless no elements have been added
+     * in which case, the prefix+suffix or the emptyOutput  characters are returned
+     */
+    @Override
+    public String toString() {
+        return (somethingAdded ? value.toString() + suffix : emptyOutput.toString());
+    }
+
+    /**
+     * adds each CharSequence from the supplied Iterable to the
+     * StringJoiner value separated by the infix delimiter (even if the CharSequence corresponds
+     * to the empty string). If the Iterable is null
+     * then the nullOuput character sequence is added.  If the Iterable is non-null, then each element of the
+     * Iterable is added.
+     *
+     * @param newElements
+     */
+    public void addAll(Iterable<? extends CharSequence> newElements) {
+        if (newElements == null) {
+            // if the Iterable is null, then use nullOutput
+            prepareBuilder().append(nullOutput);
+        } else {
+            // add on each CharSequence of the Iterable
+            for (CharSequence cs : newElements) {
+                add(cs);
+            }
+        }
+    }
+
+    /**
+     * add the supplied CharSequence as the next element of the StringJoiner value.
+     * If the CharSequence is null, then the nullOuput character sequence is added.
+     *
+     * @param newElement
+     * @return this StringJoiner
+     */
+    public StringJoiner add(CharSequence newElement) {
+        String element = (newElement == null ? nullOutput : newElement.toString());
+        prepareBuilder().append(element);
+        return this;
+    }
+
+    /**
+     * add the supplied char[] as the next element of the StringJoiner value.
+     * If the char[] is null, then the nullOuput character sequence is added.
+     *
+     * @param newElement
+     * @return this StringJoiner
+     */
+    public StringJoiner add(char[] newElement) {
+        char[] element = (newElement == null ? nullOutputChars : newElement);
+        prepareBuilder().append(element);
+        return this;
+    }
+
+    private StringBuilder prepareBuilder() {
+        if (!somethingAdded) {
+            // This is the first addition so append the prefix and then the new element
+            if (prefixLen > 0) {
+                //  append the prefix if not the empty string
+                value.append(prefix);
+            }
+            // assume we're going to put on the new element
+            somethingAdded = true;
+        } else {
+            // Since there is already something present, append the infix and then the new element
+            value.append(infix);
+        }
+        return value;
+    }
+
+  /**
+   * The length of the StringJoiner value, equivalent to toString().length(),
+   * i.e. the length of String representation of the StringJoiner. Note that if no add methods have been
+   * called, then the length of the String representation (either prefix+suffix or emptyOutput) will be
+   * returned.
+   *
+   * @return the length of the current value of StringJoiner
+   */
+    @Override
+    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 (somethingAdded ? value.length() + suffix.length() : emptyOutput.length() );
+    }
+
+    /**
+     * Equivalent to toString().charAt( index ), i.e. the index-th character of
+     * the String representation of the StringJoiner value.  Note that if the StringJoiner is
+     * currently empty this is a character from the prefix+suffix or emptyOutput character sequence.
+     *
+     * @param index an integer from 0 to StringJoiner.length()-1
+     * @return the specified char of the current value of StringJoiner
+     * @throws IndexOutOfBoundsException if index < 0 or >= length()
+     */
+    @Override
+    public char charAt(int index) {
+
+        if ( index < 0 || index >= length() ) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        if (!somethingAdded) {
+            return emptyOutput.charAt( index );
+        }
+
+        // check if index is in the portion accumulated so far (chars on the left without the suffix)
+        if (index < value.length()) {
+            return value.charAt( index );
+        }
+        // else we're looking for a character from the suffix
+        return suffix.charAt(index - value.length());
+    }
+
+    /**
+     * Equivalent to toString().subSequence( start, end ), i.e. a subSequence of
+     * the current String representation of StringJoiner value.  Note that if the StringJoiner is
+     * currently empty this is a subsequence of the prefix+suffix ore emptyOutput character sequence.
+     *
+     * @param start an integer indicating the (inclusive) starting index of the subsequence
+     * @param end an integer indicating the (exclusive) ending index of the subsequence
+     * @return the requested sub-sequence of the current value of StringJoiner
+     *
+     */
+    @Override
+    public CharSequence subSequence(int start, int end) {
+        if (start < 0 || start >= length()
+                || end > length()
+                || start > end) {
+            throw new IndexOutOfBoundsException();
+        }
+        // If we haven't added anything yet, then access the requested part of the emptyOutput
+        if (!somethingAdded) {
+            return emptyOutput.subSequence(start, end);
+        }
+        // Is the requested sub-sequence included to the left of the suffix?
+        // ( probably the most likely case )
+        if (end <= value.length()) {
+            return value.subSequence(start, end);
+        }
+        // Does the requested sub-sequence span the boundary between the accumulated
+        // value and the suffix?
+        if (start < value.length()) {
+            return new StringBuilder(
+                    value.subSequence( start, value.length()) ).append(
+                    suffix.subSequence( 0, end - value.length() ));
+        }
+        // The sub-sequence exists entirely within the suffix
+        return suffix.subSequence(start - value.length(), end - value.length());
+    }
+
+    /**
+     * Returns the String representation of the StringJoiner value as Iterable<Character>
+     * @return the current value of StringJoiner
+     */
+    @Override
+    public Iterable<Character> asChars() {
+        return (somethingAdded ? (value.toString() + suffix).asChars() : emptyOutput.asChars() );
+    }
+
+     /**
+     * Returns the String representation of the StringJoiner value as Iterable<Integer>,
+     * i.e. Unicode code points
+     *
+     * @return the current value of StringJoiner
+     */
+    @Override
+    public Iterable<Integer> asCodePoints() {
+        return (somethingAdded ? (value.toString() + suffix).asCodePoints() : emptyOutput.asCodePoints() );
+    }
+
+    /**
+     * Returns the value used to represent the addition of a null element to the value.  The
+     * default value is "null"
+     *
+     * @return the nullOutput
+     */
+    public String getNullOutput() {
+        return nullOutput;
+    }
+
+}
--- a/test-ng/tests/org/openjdk/tests/java/util/StringJoinerTest.java	Wed Jun 06 13:39:41 2012 -0700
+++ b/test-ng/tests/org/openjdk/tests/java/util/StringJoinerTest.java	Mon Jun 11 17:02:43 2012 -0400
@@ -1,471 +1,479 @@
-/*
- * Copyright (c) 2012 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 org.openjdk.tests.java.util;
-
-import java.util.StringJoiner;
-import java.util.ArrayList;
-import java.util.Iterator;
-import org.testng.annotations.Test;
-import static org.testng.Assert.assertEquals;
-
-
-/**
- *
- * @author Jim Gish
- *
- */
-@Test(groups = "lib")
-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";
-    public static final String FOUR = "Four";
-    public static final String FIVE = "Five";
-
-    public void addAddAll() {
-        StringJoiner sj = new StringJoiner("-", "{", "}");
-        sj.add(ONE);
-
-        ArrayList<String> nextOne = new ArrayList<>();
-        nextOne.add(TWO);
-        nextOne.add(THREE);
-        sj.addAll(nextOne);
-
-        String expected = "{"+ONE+"-"+TWO+"-"+THREE+"}";
-        assertEquals(sj.toString(), expected);
-    }
-
-    void addAlladd() {
-        StringJoiner sj = new StringJoiner("-", "{", "}");
-
-        ArrayList<String> firstOne = new ArrayList<>();
-        firstOne.add(ONE);
-        firstOne.add(TWO);
-        sj.addAll(firstOne);
-
-        sj.add(THREE);
-
-        String expected = "{"+ONE+"-"+TWO+"-"+THREE+"}";
-        assertEquals(sj.toString(), expected);
-    }
-
-    // The following tests do two successive adds of different types
-    public void addAlladdAll() {
-        StringJoiner sj = new StringJoiner("-", "{", "}");
-        ArrayList<String> firstOne = new ArrayList<>();
-        firstOne.add(ONE);
-        firstOne.add(TWO);
-        firstOne.add(THREE);
-        sj.addAll(firstOne);
-
-        ArrayList<String> nextOne = new ArrayList<>();
-        nextOne.add(FOUR);
-        nextOne.add(FIVE);
-        sj.addAll(nextOne);
-
-        String expected = "{"+ONE+"-"+TWO+"-"+THREE+"-"+FOUR+"-"+FIVE+"}";
-        assertEquals(sj.toString(), expected);
-    }
-
-    public void addAllIterableCharSequence() {
-        ArrayList<String> list = new ArrayList<>();
-        list.add(ONE);
-        list.add(TWO);
-        list.add(THREE);
-
-        StringJoiner sj = new StringJoiner(",", "{", "}");
-        sj.addAll(list);
-
-        assertEquals(sj.toString(), "{" + ONE + "," + TWO + "," + THREE + "}");
-
-    }
-
-    public void addCharArray() {
-        char[] chars = {'c', 'h', 'a', 'r', 's'};
-        char[] morechars = {'m', 'o', 'r', 'e', 'c', 'h', 'a', 'r', 's'};
-        StringJoiner sj = new StringJoiner(", ", "{..", "..}");
-        sj.add(chars);
-        assertEquals(sj.toString(), "{..chars..}");
-        sj.add(morechars);
-        assertEquals(sj.toString(), "{..chars, morechars..}");
-    }
-
-    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("-", "{", "}");
-        sj.add(cs_one).add(cs_two);
-
-        assertEquals(sj.toString(), "{" + ONE + "-" + TWO + "}");
-    }
-
-    public void addCharSequenceWithEmptyOutput() {
-
-        StringJoiner sj = new StringJoiner(",").setEmptyOutput(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("-", "{", "}");
-        sj.add(cs_one);
-        sj.add(cs_two);
-        assertEquals(sj.toString(), "{" + ONE + "-" + TWO + "}");
-
-        sj = new StringJoiner("-", "{", "}");
-        assertEquals(sj.toString(), "{}");
-
-
-
-        sj = new StringJoiner("=", "{", "}").setEmptyOutput("");
-        assertEquals(sj.toString(), "");
-
-        sj = new StringJoiner("-", "{", "}").setEmptyOutput(EMPTY);
-        assertEquals(sj.toString(), EMPTY);
-
-        sj.add(cs_one);
-        sj.add(cs_two);
-        assertEquals(sj.toString(), "{" + ONE + "-" + TWO + "}");
-    }
-
-    public void addString() {
-        StringJoiner sj = new StringJoiner("-");
-        sj.add(ONE);
-        assertEquals(sj.toString(), ONE);
-
-        sj = new StringJoiner("-", "{", "}");
-        sj.add(ONE);
-        assertEquals(sj.toString(), "{" + ONE + "}");
-
-        sj.add(TWO);
-        assertEquals(sj.toString(), "{" + ONE + "-" + TWO + "}");
-    }
-
-    public void charAtCustomEmptyOutput() {
-        StringJoiner sj = new StringJoiner("-", "<", ">").setEmptyOutput(EMPTY);
-        assertEquals(sj.charAt(0), EMPTY.charAt(0));
-        assertEquals(sj.charAt(EMPTY.length()-1), EMPTY.charAt(EMPTY.length()-1));
-    }
-
-    @Test(expectedExceptions = {IndexOutOfBoundsException.class})
-    public void charAtCustomEmptyOutputOfEmptyString() {
-        StringJoiner sj = new StringJoiner("-", "<", ">").setEmptyOutput("");
-        char c = sj.charAt(0);
-    }
-
-    @Test(expectedExceptions = {IndexOutOfBoundsException.class})
-    public void charAtDefaultEmpty() {
-        StringJoiner sj = new StringJoiner("-");
-        char c = sj.charAt(0);
-    }
-
-    public void charAtDefaultEmptyWithPrefixAndSuffix() {
-        StringJoiner sj = new StringJoiner("-", "<", ">");
-        assertEquals(sj.charAt(0), '<');
-        assertEquals(sj.charAt(1), '>');
-    }
-
-    public void charFromCharAtInt() {
-        final String left = "{!@#";
-        final String middle = "-";
-        final String right = "^&*}";
-        final String s1 = "abc";
-        final String s2 = "def";
-
-        StringJoiner sj = new StringJoiner(middle, left, right);
-        sj.add(s1).add(s2);
-
-        String expected = left + s1 + middle + s2 + right;
-        for (int i = 0; i < expected.length(); i++) {
-            assertEquals(sj.charAt(i), expected.charAt(i));
-        }
-    }
-
-    @Test(expectedExceptions = {IndexOutOfBoundsException.class})
-    public void charFromCharAtNegativeInt() {
-        StringJoiner sj = new StringJoiner("-");
-        sj.charAt(-1);
-    }
-
-    @Test(expectedExceptions = {IndexOutOfBoundsException.class})
-    public void charFromCharAtTooBigInt() {
-        StringJoiner sj = new StringJoiner("-", "{", "}");
-        sj.add("foo");
-        sj.charAt(5);
-    }
-
-    @Test(expectedExceptions = {IndexOutOfBoundsException.class})
-    public void charSequenceFromSubSequenceBadIntInt() {
-        StringJoiner sj = new StringJoiner("-");
-        sj.subSequence(-1, 1);
-    }
-
-    @Test(expectedExceptions = {IndexOutOfBoundsException.class})
-    public void charSequenceFromSubSequenceEndGreaterThanLength() {
-        StringJoiner sj = new StringJoiner("-");
-        sj.add("foo").add("fah");
-
-        sj.subSequence(0, 8);
-    }
-
-    @Test(expectedExceptions = {IndexOutOfBoundsException.class})
-    public void charSequenceFromSubSequenceEndLessThanStart() {
-        StringJoiner sj = new StringJoiner("-");
-        sj.subSequence(2, 1);
-    }
-
-    public void charSequenceFromSubSequenceIntInt() {
-        final String prefix = "{a}";
-        final int prelen = prefix.length();
-        final String delim = ", ";
-        final String postfix = "[b]";
-        StringJoiner sj = new StringJoiner(delim, prefix, postfix);
-        sj.add(ONE).add(TWO);
-
-        String expected = prefix + ONE + delim + TWO + postfix;
-        // just to make sure we got this right, although tested elsewhere
-        assertEquals(sj.toString(), expected);
-
-        // totally within the prefix
-        assertEquals(sj.subSequence(0, prelen).toString(),
-                expected.subSequence(0, prelen));
-        // spanning prefix and accumulated value
-        assertEquals(sj.subSequence(prelen - 1, prelen + ONE_LEN).toString(),
-                expected.subSequence(prelen - 1, prelen + ONE_LEN));
-        // totally within the accumulated value
-        assertEquals(sj.subSequence(prelen, prelen + ONE_LEN).toString(),
-                expected.subSequence(prelen, prelen + ONE_LEN));
-        // spanning accumulated value and postfix
-        assertEquals(
-                sj.subSequence(expected.length() - postfix.length() - TWO_LEN,
-                    expected.length()).toString(),
-                expected.subSequence(expected.length() - postfix.length() - TWO_LEN,
-                    expected.length()));
-        // the whole dang thing
-        assertEquals(sj.subSequence(0, expected.length()).toString(),
-                expected);
-    }
-
-    public void defaultNullHandling() {
-        StringJoiner sj = new StringJoiner(", ");
-        sj.add("X");
-        sj.add((String) null);
-        sj.add("Y");
-        assertEquals(sj.toString(), "X, null, Y");
-    }
-
-    public void intFromLength() {
-        StringJoiner sj = new StringJoiner(", ");
-        assertEquals(sj.length(), 0, "Prior to add, StringJoiner(\", \"\").length should be 0");
-        sj = new StringJoiner(",", "{", "}");
-        assertEquals(sj.length(), 2,"Prior to add, StringJoiner(\"{\",\",\",\"}\").length should be 2");
-
-        sj = new StringJoiner(",");
-        sj.add(ONE);
-        assertEquals(sj.length(), ONE_LEN);
-
-        sj.add(TWO);
-        assertEquals(sj.length(), ONE_LEN + 1 + TWO_LEN);
-
-        sj = new StringJoiner(",", "{--", "--}");
-        sj.add(ONE).add(TWO);
-
-        assertEquals(sj.length(), 3 + ONE_LEN + 1 + TWO_LEN + 3);
-    }
-
-    public void iterableCharacterFromAsChars() {
-        ArrayList<String> list = new ArrayList<>();
-        list.add(ONE);
-        list.add(TWO);
-        list.add(THREE);
-
-        StringJoiner sj = list.into(new StringJoiner(",", "{", "}"));
-        Iterable<Character> ic = sj.asChars();
-        String result = new String();
-        Iterator<Character> it = ic.iterator();
-        while (it.hasNext()) {
-            result += it.next();
-        }
-        String expected = "{" + ONE + "," + TWO + "," + THREE + "}";
-        assertEquals(result, expected);
-    }
-
-    public void iterableIntegerFromAsCodePoints() {
-        StringJoiner sj = new StringJoiner("-", "{", "}");
-        sj.add(ONE).add(TWO);
-
-        Iterable<Integer> codePoints = sj.asCodePoints();
-        String expected = "{"+ONE+"-"+TWO+"}";
-        assertEquals(codePoints.count(), expected.length(),
-                "Number of codePoints in result is not correct");
-        int i = 0;
-        for (Integer cp : codePoints) {
-            assertEquals(cp.intValue(), expected.codePointAt(i), "Code point at i=" + i);
-            i++;
-        }
-
-    }
-
-    public void lengthWithCustomEmptyOutput() {
-        StringJoiner sj = new StringJoiner("-", "<", ">").setEmptyOutput(EMPTY);
-        assertEquals(sj.length(), EMPTY.length());
-        sj.add("");
-        assertEquals(sj.length(), "<>".length());
-        sj.add("");
-        assertEquals(sj.length(), "<->".length());
-    }
-
-    public void noAddAndEmptyOutput() {
-        StringJoiner sj = new StringJoiner("-", "", "").setEmptyOutput(EMPTY);
-        assertEquals(sj.toString(), EMPTY);
-
-        sj = new StringJoiner("-", "<..", "");
-        assertEquals(sj.toString(), "<..");
-
-        sj = new StringJoiner("-", "<..", "");
-        assertEquals(sj.toString(), "<..");
-
-        sj = new StringJoiner("-", "", "==>");
-        assertEquals(sj.toString(), "==>");
-
-        sj = new StringJoiner("-", "{", "}");
-        assertEquals(sj.toString(), "{}");
-    }
-
-    @Test(expectedExceptions = {NullPointerException.class})
-    public void setEmptyOutputNull() {
-        new StringJoiner("-", "{", "}").setEmptyOutput(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 stringFromtoStringWithEmptyOutput() {
-        StringJoiner sj = new StringJoiner(" ", "", "");
-        assertEquals(sj.toString(), "");
-        sj = new StringJoiner(", ");
-        assertEquals(sj.toString(), "");
-        sj = new StringJoiner(",", "{", "}");
-        assertEquals(sj.toString(), "{}");
-
-        sj = new StringJoiner(",", "{", "}").setEmptyOutput("");
-        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 + "--}");
-
-    }
-
-    @Test(expectedExceptions = {IndexOutOfBoundsException.class})
-    public void subSequenceTooBigEndWithCustomEmptyOutput() {
-        StringJoiner sj = new StringJoiner("-", "<", ">").setEmptyOutput(EMPTY);
-        sj.subSequence(0, EMPTY.length() + 1);
-    }
-
-    @Test(expectedExceptions = {IndexOutOfBoundsException.class})
-    public void subSequenceTooBigEndWithEmptyOutput() {
-        StringJoiner sj = new StringJoiner("-", "<", ">");
-        sj.subSequence(0, 3);
-    }
-
-    public void subSequenceWithCustomEmptyOutput() {
-        StringJoiner sj = new StringJoiner("-", "<", ">").setEmptyOutput(EMPTY);
-        assertEquals(sj.length(), EMPTY.length());
-        assertEquals(sj.toString(), EMPTY);
-        assertEquals(sj.subSequence(0, EMPTY.length()), EMPTY);
-        sj.add("");
-        assertEquals(sj.toString(), "<>");
-        assertEquals(sj.length(), 2);
-        assertEquals(sj.subSequence(0, 2).toString(), "<>");
-    }
-
-    public void subSequenceWithDefaultEmptyWithPrefixAndSuffix() {
-        StringJoiner sj = new StringJoiner("-", "<", ">");
-        assertEquals(sj.subSequence(0, 1), "<");
-        assertEquals(sj.subSequence(1, 2), ">");
-        sj.add("");
-        assertEquals(sj.subSequence(0, 1), "<");
-        assertEquals(sj.subSequence(1, 2), ">");
-        assertEquals(sj.subSequence(0, 2).toString(), "<>");
-    }
-
-    @Test(expectedExceptions = {IndexOutOfBoundsException.class})
-    public void subSequenceWithEmptyOutput() {
-        StringJoiner sj = new StringJoiner("-");
-        sj.add("");
-        sj.subSequence(0, 1);
-    }
-
-    public void toStringWithCustomEmptyOutput() {
-        StringJoiner sj = new StringJoiner("-", "<", ">").setEmptyOutput(EMPTY);
-        assertEquals(sj.toString(), EMPTY);
-        sj.add("");
-        assertEquals(sj.toString(), "<>");
-        sj.add("");
-        assertEquals(sj.toString(), "<->");
-    }
-}
-
+/*
+ * Copyright (c) 2012 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 org.openjdk.tests.java.util;
+
+import java.util.StringJoiner;
+import java.util.ArrayList;
+import java.util.Iterator;
+import org.testng.annotations.Test;
+import static org.testng.Assert.assertEquals;
+
+
+/**
+ *
+ * @author Jim Gish
+ *
+ */
+@Test(groups = "lib")
+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";
+    public static final String FOUR = "Four";
+    public static final String FIVE = "Five";
+
+    public void addAddAll() {
+        StringJoiner sj = new StringJoiner("-", "{", "}");
+        sj.add(ONE);
+
+        ArrayList<String> nextOne = new ArrayList<>();
+        nextOne.add(TWO);
+        nextOne.add(THREE);
+        sj.addAll(nextOne);
+
+        String expected = "{"+ONE+"-"+TWO+"-"+THREE+"}";
+        assertEquals(sj.toString(), expected);
+    }
+
+    void addAlladd() {
+        StringJoiner sj = new StringJoiner("-", "{", "}");
+
+        ArrayList<String> firstOne = new ArrayList<>();
+        firstOne.add(ONE);
+        firstOne.add(TWO);
+        sj.addAll(firstOne);
+
+        sj.add(THREE);
+
+        String expected = "{"+ONE+"-"+TWO+"-"+THREE+"}";
+        assertEquals(sj.toString(), expected);
+    }
+
+    // The following tests do two successive adds of different types
+    public void addAlladdAll() {
+        StringJoiner sj = new StringJoiner("-", "{", "}");
+        ArrayList<String> firstOne = new ArrayList<>();
+        firstOne.add(ONE);
+        firstOne.add(TWO);
+        firstOne.add(THREE);
+        sj.addAll(firstOne);
+
+        ArrayList<String> nextOne = new ArrayList<>();
+        nextOne.add(FOUR);
+        nextOne.add(FIVE);
+        sj.addAll(nextOne);
+
+        String expected = "{"+ONE+"-"+TWO+"-"+THREE+"-"+FOUR+"-"+FIVE+"}";
+        assertEquals(sj.toString(), expected);
+    }
+
+    public void addAllIterableCharSequence() {
+        ArrayList<String> list = new ArrayList<>();
+        list.add(ONE);
+        list.add(TWO);
+        list.add(THREE);
+
+        StringJoiner sj = new StringJoiner(",", "{", "}");
+        sj.addAll(list);
+
+        assertEquals(sj.toString(), "{" + ONE + "," + TWO + "," + THREE + "}");
+
+    }
+
+    public void addCharArray() {
+        char[] chars = {'c', 'h', 'a', 'r', 's'};
+        char[] morechars = {'m', 'o', 'r', 'e', 'c', 'h', 'a', 'r', 's'};
+        StringJoiner sj = new StringJoiner(", ", "{..", "..}");
+        sj.add(chars);
+        assertEquals(sj.toString(), "{..chars..}");
+        sj.add(morechars);
+        assertEquals(sj.toString(), "{..chars, morechars..}");
+    }
+
+    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("-", "{", "}");
+        sj.add(cs_one).add(cs_two);
+
+        assertEquals(sj.toString(), "{" + ONE + "-" + TWO + "}");
+    }
+
+    public void addCharSequenceWithEmptyOutput() {
+
+        StringJoiner sj = new StringJoiner(",").setEmptyOutput(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("-", "{", "}");
+        sj.add(cs_one);
+        sj.add(cs_two);
+        assertEquals(sj.toString(), "{" + ONE + "-" + TWO + "}");
+
+        sj = new StringJoiner("-", "{", "}");
+        assertEquals(sj.toString(), "{}");
+
+
+
+        sj = new StringJoiner("=", "{", "}").setEmptyOutput("");
+        assertEquals(sj.toString(), "");
+
+        sj = new StringJoiner("-", "{", "}").setEmptyOutput(EMPTY);
+        assertEquals(sj.toString(), EMPTY);
+
+        sj.add(cs_one);
+        sj.add(cs_two);
+        assertEquals(sj.toString(), "{" + ONE + "-" + TWO + "}");
+    }
+
+    public void addString() {
+        StringJoiner sj = new StringJoiner("-");
+        sj.add(ONE);
+        assertEquals(sj.toString(), ONE);
+
+        sj = new StringJoiner("-", "{", "}");
+        sj.add(ONE);
+        assertEquals(sj.toString(), "{" + ONE + "}");
+
+        sj.add(TWO);
+        assertEquals(sj.toString(), "{" + ONE + "-" + TWO + "}");
+    }
+
+    public void charAtCustomEmptyOutput() {
+        StringJoiner sj = new StringJoiner("-", "<", ">").setEmptyOutput(EMPTY);
+        assertEquals(sj.charAt(0), EMPTY.charAt(0));
+        assertEquals(sj.charAt(EMPTY.length()-1), EMPTY.charAt(EMPTY.length()-1));
+    }
+
+    @Test(expectedExceptions = {IndexOutOfBoundsException.class})
+    public void charAtCustomEmptyOutputOfEmptyString() {
+        StringJoiner sj = new StringJoiner("-", "<", ">").setEmptyOutput("");
+        char c = sj.charAt(0);
+    }
+
+    @Test(expectedExceptions = {IndexOutOfBoundsException.class})
+    public void charAtDefaultEmpty() {
+        StringJoiner sj = new StringJoiner("-");
+        char c = sj.charAt(0);
+    }
+
+    public void charAtDefaultEmptyWithPrefixAndSuffix() {
+        StringJoiner sj = new StringJoiner("-", "<", ">");
+        assertEquals(sj.charAt(0), '<');
+        assertEquals(sj.charAt(1), '>');
+    }
+
+    public void charFromCharAtInt() {
+        final String left = "{!@#";
+        final String middle = "-";
+        final String right = "^&*}";
+        final String s1 = "abc";
+        final String s2 = "def";
+
+        StringJoiner sj = new StringJoiner(middle, left, right);
+        sj.add(s1).add(s2);
+
+        String expected = left + s1 + middle + s2 + right;
+        for (int i = 0; i < expected.length(); i++) {
+            assertEquals(sj.charAt(i), expected.charAt(i));
+        }
+    }
+
+    @Test(expectedExceptions = {IndexOutOfBoundsException.class})
+    public void charFromCharAtNegativeInt() {
+        StringJoiner sj = new StringJoiner("-");
+        sj.charAt(-1);
+    }
+
+    @Test(expectedExceptions = {IndexOutOfBoundsException.class})
+    public void charFromCharAtTooBigInt() {
+        StringJoiner sj = new StringJoiner("-", "{", "}");
+        sj.add("foo");
+        sj.charAt(5);
+    }
+
+    @Test(expectedExceptions = {IndexOutOfBoundsException.class})
+    public void charSequenceFromSubSequenceBadIntInt() {
+        StringJoiner sj = new StringJoiner("-");
+        sj.subSequence(-1, 1);
+    }
+
+    @Test(expectedExceptions = {IndexOutOfBoundsException.class})
+    public void charSequenceFromSubSequenceEndGreaterThanLength() {
+        StringJoiner sj = new StringJoiner("-");
+        sj.add("foo").add("fah");
+
+        sj.subSequence(0, 8);
+    }
+
+    @Test(expectedExceptions = {IndexOutOfBoundsException.class})
+    public void charSequenceFromSubSequenceEndLessThanStart() {
+        StringJoiner sj = new StringJoiner("-");
+        sj.subSequence(2, 1);
+    }
+
+    public void charSequenceFromSubSequenceIntInt() {
+        final String prefix = "{a}";
+        final int prelen = prefix.length();
+        final String delim = ", ";
+        final String postfix = "[b]";
+        StringJoiner sj = new StringJoiner(delim, prefix, postfix);
+        sj.add(ONE).add(TWO);
+
+        String expected = prefix + ONE + delim + TWO + postfix;
+        // just to make sure we got this right, although tested elsewhere
+        assertEquals(sj.toString(), expected);
+
+        // totally within the prefix
+        assertEquals(sj.subSequence(0, prelen).toString(),
+                expected.subSequence(0, prelen));
+        // spanning prefix and accumulated value
+        assertEquals(sj.subSequence(prelen - 1, prelen + ONE_LEN).toString(),
+                expected.subSequence(prelen - 1, prelen + ONE_LEN));
+        // totally within the accumulated value
+        assertEquals(sj.subSequence(prelen, prelen + ONE_LEN).toString(),
+                expected.subSequence(prelen, prelen + ONE_LEN));
+        // spanning accumulated value and postfix
+        assertEquals(
+                sj.subSequence(expected.length() - postfix.length() - TWO_LEN,
+                    expected.length()).toString(),
+                expected.subSequence(expected.length() - postfix.length() - TWO_LEN,
+                    expected.length()));
+        // the whole dang thing
+        assertEquals(sj.subSequence(0, expected.length()).toString(),
+                expected);
+    }
+
+    public void defaultNullHandling() {
+        StringJoiner sj = new StringJoiner(", ");
+        sj.add("X");
+        sj.add((String) null);
+        sj.add("Y");
+        assertEquals(sj.toString(), "X, null, Y");
+    }
+
+    public void intFromLength() {
+        StringJoiner sj = new StringJoiner(", ");
+        assertEquals(sj.length(), 0, "Prior to add, StringJoiner(\", \"\").length should be 0");
+        sj = new StringJoiner(",", "{", "}");
+        assertEquals(sj.length(), 2,"Prior to add, StringJoiner(\"{\",\",\",\"}\").length should be 2");
+
+        sj = new StringJoiner(",");
+        sj.add(ONE);
+        assertEquals(sj.length(), ONE_LEN);
+
+        sj.add(TWO);
+        assertEquals(sj.length(), ONE_LEN + 1 + TWO_LEN);
+
+        sj = new StringJoiner(",", "{--", "--}");
+        sj.add(ONE).add(TWO);
+
+        assertEquals(sj.length(), 3 + ONE_LEN + 1 + TWO_LEN + 3);
+    }
+
+    public void iterableCharacterFromAsChars() {
+        ArrayList<String> list = new ArrayList<>();
+        list.add(ONE);
+        list.add(TWO);
+        list.add(THREE);
+
+        StringJoiner sj = list.into(new StringJoiner(",", "{", "}"));
+        Iterable<Character> ic = sj.asChars();
+        String result = new String();
+        Iterator<Character> it = ic.iterator();
+        while (it.hasNext()) {
+            result += it.next();
+        }
+        String expected = "{" + ONE + "," + TWO + "," + THREE + "}";
+        assertEquals(result, expected);
+    }
+
+    public void iterableIntegerFromAsCodePoints() {
+        StringJoiner sj = new StringJoiner("-", "{", "}");
+        sj.add(ONE).add(TWO);
+
+        Iterable<Integer> codePoints = sj.asCodePoints();
+        String expected = "{"+ONE+"-"+TWO+"}";
+        assertEquals(codePoints.count(), expected.length(),
+                "Number of codePoints in result is not correct");
+        int i = 0;
+        for (Integer cp : codePoints) {
+            assertEquals(cp.intValue(), expected.codePointAt(i), "Code point at i=" + i);
+            i++;
+        }
+
+    }
+
+    public void lengthWithCustomEmptyOutput() {
+        StringJoiner sj = new StringJoiner("-", "<", ">").setEmptyOutput(EMPTY);
+        assertEquals(sj.length(), EMPTY.length());
+        sj.add("");
+        assertEquals(sj.length(), "<>".length());
+        sj.add("");
+        assertEquals(sj.length(), "<->".length());
+    }
+
+    public void noAddAndEmptyOutput() {
+        StringJoiner sj = new StringJoiner("-", "", "").setEmptyOutput(EMPTY);
+        assertEquals(sj.toString(), EMPTY);
+
+        sj = new StringJoiner("-", "<..", "");
+        assertEquals(sj.toString(), "<..");
+
+        sj = new StringJoiner("-", "<..", "");
+        assertEquals(sj.toString(), "<..");
+
+        sj = new StringJoiner("-", "", "==>");
+        assertEquals(sj.toString(), "==>");
+
+        sj = new StringJoiner("-", "{", "}");
+        assertEquals(sj.toString(), "{}");
+    }
+
+    @Test(expectedExceptions = {NullPointerException.class})
+    public void setEmptyOutputNull() {
+        new StringJoiner("-", "{", "}").setEmptyOutput(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 stringFromtoStringWithEmptyOutput() {
+        StringJoiner sj = new StringJoiner(" ", "", "");
+        assertEquals(sj.toString(), "");
+        sj = new StringJoiner(", ");
+        assertEquals(sj.toString(), "");
+        sj = new StringJoiner(",", "{", "}");
+        assertEquals(sj.toString(), "{}");
+
+        sj = new StringJoiner(",", "{", "}").setEmptyOutput("");
+        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 + "--}");
+
+    }
+
+    @Test(expectedExceptions = {IndexOutOfBoundsException.class})
+    public void subSequenceTooBigEndWithCustomEmptyOutput() {
+        StringJoiner sj = new StringJoiner("-", "<", ">").setEmptyOutput(EMPTY);
+        sj.subSequence(0, EMPTY.length() + 1);
+    }
+
+    @Test(expectedExceptions = {IndexOutOfBoundsException.class})
+    public void subSequenceTooBigEndWithEmptyOutput() {
+        StringJoiner sj = new StringJoiner("-", "<", ">");
+        sj.subSequence(0, 3);
+    }
+
+    public void subSequenceWithCustomEmptyOutput() {
+        StringJoiner sj = new StringJoiner("-", "<", ">").setEmptyOutput(EMPTY);
+        assertEquals(sj.length(), EMPTY.length());
+        assertEquals(sj.toString(), EMPTY);
+        assertEquals(sj.subSequence(0, EMPTY.length()), EMPTY);
+        sj.add("");
+        assertEquals(sj.toString(), "<>");
+        assertEquals(sj.length(), 2);
+        assertEquals(sj.subSequence(0, 2).toString(), "<>");
+    }
+
+    public void subSequenceWithDefaultEmptyWithPrefixAndSuffix() {
+        StringJoiner sj = new StringJoiner("-", "<", ">");
+        assertEquals(sj.subSequence(0, 1), "<");
+        assertEquals(sj.subSequence(1, 2), ">");
+        sj.add("");
+        assertEquals(sj.subSequence(0, 1), "<");
+        assertEquals(sj.subSequence(1, 2), ">");
+        assertEquals(sj.subSequence(0, 2).toString(), "<>");
+    }
+
+    @Test(expectedExceptions = {IndexOutOfBoundsException.class})
+    public void subSequenceWithEmptyOutput() {
+        StringJoiner sj = new StringJoiner("-");
+        sj.add("");
+        sj.subSequence(0, 1);
+    }
+
+    public void toStringWithCustomEmptyOutput() {
+        StringJoiner sj = new StringJoiner("-", "<", ">").setEmptyOutput(EMPTY);
+        assertEquals(sj.toString(), EMPTY);
+        sj.add("");
+        assertEquals(sj.toString(), "<>");
+        sj.add("");
+        assertEquals(sj.toString(), "<->");
+    }
+
+    public void subSequenceEntirelyWithinSuffix() {
+        StringJoiner sj = new StringJoiner("-", "Begin<", ">End");
+        sj.add( "XYZ" );
+        assertEquals( sj.subSequence( 9, 13 ), ">End" );
+        assertEquals( sj.subSequence( 10, 13), "End" );
+        assertEquals( sj.subSequence( 12, 13 ), "d" );
+    }
+}
+