IonTextWriterBuilder.java

/*
 * Copyright 2007-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package com.amazon.ion.system;

import static com.amazon.ion.system.IonWriterBuilder.InitialIvmHandling.SUPPRESS;

import com.amazon.ion.IonCatalog;
import com.amazon.ion.IonWriter;
import com.amazon.ion.SymbolTable;
import com.amazon.ion.Timestamp;
import com.amazon.ion.impl._Private_IonTextWriterBuilder;
import com.amazon.ion.impl._Private_Utils;
import java.io.OutputStream;
import java.nio.charset.Charset;

/**
 * The builder for creating {@link IonWriter}s emitting the Ion text syntax.
 * <p>
 * <b>WARNING:</b> This class should not be extended by code outside of
 * this library.
 * <p>
 * Builders may be configured once and reused to construct multiple
 * objects.
 * <p>
 * <b>Instances of this class are not not safe for use by multiple threads
 * unless they are {@linkplain #immutable() immutable}.</b>
 * <p>
 * The most general and correct approach is to use the {@link #standard()}
 * builder:
 *<pre>
 *    IonWriter w = IonTextWriterBuilder.standard().build(outputStream);
 *</pre>
 * The standard configuration gives a direct representation of what's written,
 * including version markers and local symbol tables. That's good for
 * diagnostics but it may be more than you want in many situations.
 * In such cases the {@link #minimal()} or {@link #pretty()} builders (or a
 * combination) may give more satisfying output:
 *<pre>
 *    IonWriter w = IonTextWriterBuilder.minimal()
 *                                      .withPrettyPrinting()
 *                                      .build(outputStream);
 *</pre>
 *
 * <p>
 * Configuration properties follow the standard JavaBeans idiom in order to be
 * friendly to dependency injection systems.  They also provide alternative
 * {@code with...()} mutation methods that enable a more fluid style.
 *
 * <h2>Auto-flushing</h2>
 *
 * {@link IonWriter}s created by this builder <em>auto-flush</em> to the
 * underlying data sink after writing each top-level value in the context of
 * the writer.
 * <p>
 * Currently, there is no configuration point available to disable the
 * auto-flushing mechanism. Please vote on
 * <a href="https://github.com/amazon-ion/ion-java/issues/32">issue amazon-ion/ion-java/issues/32</a>
 * if you require it.
 *
 */
public abstract class IonTextWriterBuilder
    extends IonWriterBuilderBase<IonTextWriterBuilder>
{
    /**
     * A strategy for minimizing the output of local symbol tables.
     * By default, no minimization takes place and the writer outputs all data
     * as-is.
     *

     */
    public enum LstMinimizing
    {
        /**
         * Discards local symbols, retains imports<!-- and open content-->.
         */
        LOCALS,

        /* TODO Discards local symbols and imports, retains open content.
         * This isn't implemented yet, because our symtab implmentations don't
         * support open content (so it would work the same as EVERYTHING).
         */
//      IMPORTS,

        /**
         * Discards everything, collapsing the LST to an IVM.
         * If {@link com.amazon.ion.system.IonWriterBuilder.IvmMinimizing}
         * is also in effect, then even that IVM may be suppressed.
         *
         * @see IonTextWriterBuilder#setIvmMinimizing(IonWriterBuilder.IvmMinimizing)
         */
        EVERYTHING
    }

    /**
     * The {@code "US-ASCII"} charset.
     */
    public static final Charset ASCII = _Private_Utils.ASCII_CHARSET;

    /**
     * The {@code "UTF-8"} charset.
     */
    public static final Charset UTF8 = _Private_Utils.UTF8_CHARSET;

    /**
     * Represents common new-line separators that are valid in Ion.
     *
     * Unicode defines several characters that can represent a new line, but only carriage return (CR) and linefeed (LF) are valid whitespace in Ion.
     * Using other new-line characters (such as {@code NEL}) will result in printing invalid Ion.
     */
    public enum NewLineType {
        /**
         * A carriage return and linefeed ({@code U+000D} followed by {@code U+000A}).
         */
        CRLF("\r\n"),
        /**
         * A single linefeed ({@code U+000A}).
         */
        LF("\n"),
        /**
         * The new-line separator specified in the "line.separator" system property.
         * Using this will result in writing invalid Ion if the platform uses anything other than CR and/or LF as the line separator
         * <i>and</i> the IonTextWriter is configured to use pretty printing or to write each top-level value on a separate line.
         */
        PLATFORM_DEPENDENT(System.getProperty("line.separator"));

        private final CharSequence charSequence;

        NewLineType(CharSequence cs) {
            this.charSequence = cs;
        }

        public CharSequence getCharSequence() {
            return charSequence;
        }
    }

    /**
     * The standard builder of text {@link IonWriter}s, with all configuration
     * properties having their default values. The resulting output is a
     * direct representation of what's written to the writer, including
     * version markers and local symbol tables.
     *
     * @return a new, mutable builder instance.
     *
     * @see #minimal()
     * @see #pretty()
     * @see #json()
     */
    public static IonTextWriterBuilder standard()
    {
        return _Private_IonTextWriterBuilder.standard();
    }

    /**
     * Creates a builder configured to minimize system data, eliminating local
     * symbol tables and minimizing version markers.
     *
     * @return a new, mutable builder instance.
     *
     * @see #withMinimalSystemData()
     *

     */
    public static IonTextWriterBuilder minimal()
    {
        return standard().withMinimalSystemData();
    }

    /**
     * Creates a builder preconfigured for basic pretty-printing.
     * <p>
     * The specifics of this configuration may change between releases of this
     * library, so automated processes should not depend on the exact output
     * formatting. In particular, there's currently no promise regarding
     * handling of system data.
     *
     * @return a new, mutable builder instance.
     *
     * @see #withPrettyPrinting()
     */
    public static IonTextWriterBuilder pretty()
    {
        return standard().withPrettyPrinting();
    }

    /**
     * Creates a builder preconfigured for JSON compatibility.
     *
     * @return a new, mutable builder instance.
     *
     * @see #withJsonDowngrade()
     */
    public static IonTextWriterBuilder json()
    {
        return standard().withJsonDowngrade();
    }

    //=========================================================================

    // Config points:
    //   * Default IVM
    //   * Re-use same imports after a finish
    //   * Indentation CharSeq

    private Charset myCharset;
    private InitialIvmHandling myInitialIvmHandling;
    private IvmMinimizing myIvmMinimizing;
    private LstMinimizing myLstMinimizing;
    private int myLongStringThreshold;
    private NewLineType myNewLineType;
    private boolean myTopLevelValuesOnNewLines;
    private int myMaximumTimestampPrecisionDigits = Timestamp.DEFAULT_MAXIMUM_DIGITS_TEXT;


    /** NOT FOR APPLICATION USE! */
    protected IonTextWriterBuilder()
    {
    }

    /** NOT FOR APPLICATION USE! */
    protected IonTextWriterBuilder(IonTextWriterBuilder that)
    {
        super(that);

        this.myCharset              = that.myCharset;
        this.myInitialIvmHandling   = that.myInitialIvmHandling;
        this.myIvmMinimizing        = that.myIvmMinimizing;
        this.myLstMinimizing        = that.myLstMinimizing;
        this.myLongStringThreshold  = that.myLongStringThreshold;
        this.myNewLineType          = that.myNewLineType;
        this.myTopLevelValuesOnNewLines = that.myTopLevelValuesOnNewLines;
        this.myMaximumTimestampPrecisionDigits = that.myMaximumTimestampPrecisionDigits;
    }


    //=========================================================================
    // Overrides to fix the return type in JavaDocs


    @Override
    public abstract IonTextWriterBuilder copy();

    @Override
    public abstract IonTextWriterBuilder immutable();

    @Override
    public abstract IonTextWriterBuilder mutable();


    @Override
    public final IonTextWriterBuilder withCatalog(IonCatalog catalog)
    {
        return super.withCatalog(catalog);
    }

    @Override
    public final IonTextWriterBuilder withImports(SymbolTable... imports)
    {
        return super.withImports(imports);
    }


    //-------------------------------------------------------------------------

    /**
     * Gets the charset denoting the output encoding.
     * Only ASCII and UTF-8 are supported.
     *
     * @return may be null, denoting the default of UTF-8.
     *
     * @see #setCharset(Charset)
     * @see #withCharset(Charset)
     */
    public final Charset getCharset()
    {
        return myCharset;
    }

    /**
     * Sets the charset denoting the output encoding.
     * Only ASCII and UTF-8 are supported; applications can use the helper
     * constants {@link #ASCII} and {@link #UTF8}.
     *
     * @param charset may be null, denoting the default of UTF-8.
     *
     * @see #getCharset()
     * @see #withCharset(Charset)
     *
     * @throws UnsupportedOperationException if this is immutable.
     */
    public void setCharset(Charset charset)
    {
        mutationCheck();
        if (charset == null
            || charset.equals(ASCII)
            || charset.equals(UTF8))
        {
            myCharset = charset;
        }
        else
        {
            throw new IllegalArgumentException("Unsupported Charset "
                                               + charset);
        }
    }

    /**
     * Declares the charset denoting the output encoding,
     * returning a new mutable builder if this is immutable.
     * Only ASCII and UTF-8 are supported; applications can use the helper
     * constants {@link #ASCII} and {@link #UTF8}.
     *
     * @param charset may be null, denoting the default of UTF-8.
     *
     * @return this instance, if mutable;
     * otherwise a mutable copy of this instance.
     *
     * @see #getCharset()
     * @see #setCharset(Charset)
     */
    public final IonTextWriterBuilder withCharset(Charset charset)
    {
        IonTextWriterBuilder b = mutable();
        b.setCharset(charset);
        return b;
    }

    /**
     * Declares the output encoding to be {@code US-ASCII}.
     *
     * @return this instance, if mutable;
     * otherwise a mutable copy of this instance.
     */
    public final IonTextWriterBuilder withCharsetAscii()
    {
        return withCharset(ASCII);
    }

    //-------------------------------------------------------------------------

    /**
     * Declares that this builder should minimize system-level output
     * (Ion version markers and local symbol tables).
     * <p>
     * This is equivalent to:
     * <ul>
     *   <li>{@link #setInitialIvmHandling(IonWriterBuilder.InitialIvmHandling)
     *   setInitialIvmHandling}{@code (}{@link IonWriterBuilder.InitialIvmHandling#SUPPRESS SUPPRESS}{@code )}
     *   <li>{@link #setIvmMinimizing(IonWriterBuilder.IvmMinimizing)
     *   setIvmMinimizing}{@code (}{@link IonWriterBuilder.IvmMinimizing#DISTANT DISTANT}{@code )}
     *   <li>{@link #setLstMinimizing(LstMinimizing)
     *   setLstMinimizing}{@code (}{@link LstMinimizing#EVERYTHING EVERYTHING}{@code )}
     * </ul>
     *
     * @return this instance, if mutable;
     * otherwise a mutable copy of this instance.
     *

     */
    public final IonTextWriterBuilder withMinimalSystemData()
    {
        IonTextWriterBuilder b = mutable();
        b.setInitialIvmHandling(SUPPRESS);
        b.setIvmMinimizing(IvmMinimizing.DISTANT);
        b.setLstMinimizing(LstMinimizing.EVERYTHING);
        return b;
    }


    /**
     * Declares that this builder should use basic pretty-printing.
     * Does not alter the handling of system data.
     * Calling this method alters several other configuration properties,
     * so code should call it first, then make any necessary overrides.
     * <p>
     * The specifics of this configuration may change between releases of this
     * library, so automated processes should not depend on the exact output
     * formatting.
     *
     * @return this instance, if mutable;
     * otherwise a mutable copy of this instance.
     */
    public abstract IonTextWriterBuilder withPrettyPrinting();


    /**
     * Declares that this builder should downgrade the writers' output to
     * JSON compatibility. This format cannot round-trip back to Ion with full
     * fidelity.
     * <p>
     * The specific conversions are as follows:
     * <ul>
     *   <li>System data is suppressed per {@link #withMinimalSystemData()}.
     *   <li>All annotations are suppressed.
     *   <li>Nulls of any type are printed as JSON {@code null}.
     *   <li>Blobs are printed as strings, containing Base64.
     *   <li>Clobs are printed as strings, containing only Unicode code points
     *       U+00 through U+FF.
     *   <li>Sexps are printed as lists.
     *   <li>Symbols are printed as strings.
     *   <li>Timestamps are printed as strings, using Ion timestamp format.
     * </ul>
     *
     * @return this instance, if mutable;
     * otherwise a mutable copy of this instance.
     */
    public abstract IonTextWriterBuilder withJsonDowngrade();


    //-------------------------------------------------------------------------

    /**
     * {@inheritDoc}
     *
     * @return the initial IVM strategy.
     * The default value ({@code null}) indicates that an initial IVM is
     * emitted if and only if it is received by the writer.
     *
     * @see #setInitialIvmHandling(IonWriterBuilder.InitialIvmHandling)
     * @see #withInitialIvmHandling(IonWriterBuilder.InitialIvmHandling)
     */
    @Override
    public final InitialIvmHandling getInitialIvmHandling()
    {
        return myInitialIvmHandling;
    }

    /**
     * Sets the strategy for emitting Ion version markers at the start
     * of the stream. By default, IVMs are emitted only when explicitly
     * written or when necessary (for example, before data that's not Ion 1.0).
     *
     * @param handling the initial IVM strategy.
     * Null indicates that explicitly-written IVMs will be emitted.
     *
     * @see #getInitialIvmHandling()
     * @see #withInitialIvmHandling(IonWriterBuilder.InitialIvmHandling)
     *
     * @throws UnsupportedOperationException if this is immutable.
     */
    public void setInitialIvmHandling(InitialIvmHandling handling)
    {
        mutationCheck();
        myInitialIvmHandling = handling;
    }

    /**
     * Declares the strategy for emitting Ion version markers at the start
     * of the stream, returning a new mutable builder if this is immutable.
     * By default, IVMs are emitted only when explicitly
     * written or when necessary (for example, before data that's not Ion 1.0).
     *
     * @param handling the initial IVM strategy.
     * Null indicates that explicitly-written IVMs will be emitted.
     *
     * @return this instance, if mutable;
     * otherwise a mutable copy of this instance.
     *
     * @see #setInitialIvmHandling(IonWriterBuilder.InitialIvmHandling)
     * @see #withInitialIvmHandling(IonWriterBuilder.InitialIvmHandling)
     */
    public final IonTextWriterBuilder
    withInitialIvmHandling(InitialIvmHandling handling)
    {
        IonTextWriterBuilder b = mutable();
        b.setInitialIvmHandling(handling);
        return b;
    }

    //-------------------------------------------------------------------------

    /**
     * {@inheritDoc}
     *
     * @return the IVM minimizing strategy.
     * The default value ({@code null}) indicates that no minimization occurs
     * and IVMs are emitted as received by the writer.
     *
     * @see #setIvmMinimizing(IonWriterBuilder.IvmMinimizing)
     * @see #withIvmMinimizing(IonWriterBuilder.IvmMinimizing)
     *

     */
    @Override
    public final IvmMinimizing getIvmMinimizing()
    {
        return myIvmMinimizing;
    }

    /**
     * Sets the strategy for reducing or eliminating non-initial Ion version
     * markers. When null, IVMs are emitted as they are written.
     *
     * @param minimizing the IVM minimization strategy.
     * Null indicates that all explicitly-written IVMs will be emitted.
     *
     * @see #getIvmMinimizing()
     * @see #withIvmMinimizing(IonWriterBuilder.IvmMinimizing)
     *
     * @throws UnsupportedOperationException if this is immutable.
     *

     */
    public void setIvmMinimizing(IvmMinimizing minimizing)
    {
        mutationCheck();
        myIvmMinimizing = minimizing;
    }

    /**
     * Declares the strategy for reducing or eliminating non-initial Ion version
     * markers, returning a new mutable builder if this is immutable.
     * When null, IVMs are emitted as they are written.
     *
     * @param minimizing the IVM minimization strategy.
     * Null indicates that all explicitly-written IVMs will be emitted.
     *
     * @return this instance, if mutable;
     * otherwise a mutable copy of this instance.
     *
     * @see #setIvmMinimizing(IonWriterBuilder.IvmMinimizing)
     * @see #getIvmMinimizing()
     *

     */
    public final IonTextWriterBuilder
    withIvmMinimizing(IvmMinimizing minimizing)
    {
        IonTextWriterBuilder b = mutable();
        b.setIvmMinimizing(minimizing);
        return b;
    }

    //-------------------------------------------------------------------------

    /**
     * Gets the strategy for reducing or eliminating local symbol tables.
     * By default, LST data is emitted as received or when necessary
     * (for example, binary data will always collect and emit local symbols).
     *
     * @see #setLstMinimizing(LstMinimizing)
     * @see #withLstMinimizing(LstMinimizing)
     *

     */
    public final LstMinimizing getLstMinimizing()
    {
        return myLstMinimizing;
    }

    /**
     * Sets the strategy for reducing or eliminating local symbol tables.
     * By default, LST data is emitted as received or when necessary
     * (for example, binary data will always collect and emit local symbols).
     *
     * @param minimizing the LST minimization strategy.
     * Null indicates that LSTs will be emitted as received.
     *
     * @see #getLstMinimizing()
     * @see #withLstMinimizing(LstMinimizing)
     *
     * @throws UnsupportedOperationException if this is immutable.
     *

     */
    public void setLstMinimizing(LstMinimizing minimizing)
    {
        mutationCheck();
        myLstMinimizing = minimizing;
    }

    /**
     * Sets the strategy for reducing or eliminating local symbol tables.
     * By default, LST data is emitted as received or when necessary
     * (for example, binary data will always collect and emit local symbols).
     *
     * @param minimizing the LST minimization strategy.
     * Null indicates that LSTs will be emitted as received.
     *
     * @return this instance, if mutable;
     * otherwise a mutable copy of this instance.
     *
     * @see #getLstMinimizing()
     * @see #setLstMinimizing(LstMinimizing)
     *

     */
    public final IonTextWriterBuilder
    withLstMinimizing(LstMinimizing minimizing)
    {
        IonTextWriterBuilder b = mutable();
        b.setLstMinimizing(minimizing);
        return b;
    }

    //-------------------------------------------------------------------------

    /**
     * Gets the length beyond which string and clob content will be rendered
     * as triple-quoted "long strings".
     * At present, such content will only line-break on extant newlines.
     *
     * @return the threshold for printing triple-quoted strings and clobs.
     * Zero means no limit.
     *
     * @see #setLongStringThreshold(int)
     * @see #withLongStringThreshold(int)
     */
    public final int getLongStringThreshold()
    {
        return myLongStringThreshold;
    }

    /**
     * Sets the length beyond which string and clob content will be rendered
     * as triple-quoted "long strings".
     * At present, such content will only line-break on extant newlines.
     *
     * @param threshold the new threshold; zero means none.
     *
     * @see #getLongStringThreshold()
     * @see #withLongStringThreshold(int)
     *
     * @throws UnsupportedOperationException if this is immutable.
     */
    public void setLongStringThreshold(int threshold)
    {
        mutationCheck();
        myLongStringThreshold = threshold;
    }

    /**
     * Declares the length beyond which string and clob content will be rendered
     * as triple-quoted "long strings".
     * At present, such content will only line-break on extant newlines.
     *
     * @param threshold the new threshold; zero means none.
     *
     * @see #getLongStringThreshold()
     * @see #setLongStringThreshold(int)
     *
     * @return this instance, if mutable;
     * otherwise a mutable copy of this instance.
     */
    public final IonTextWriterBuilder withLongStringThreshold(int threshold)
    {
        IonTextWriterBuilder b = mutable();
        b.setLongStringThreshold(threshold);
        return b;
    }


    //=========================================================================

    /**
     * Gets the character sequence that will be written as a line separator.
     * The default is {@link NewLineType#PLATFORM_DEPENDENT}
     *
     * @return the character sequence to be written between top-level values; null means the default should be used.
     *
     * @see #setNewLineType(NewLineType)
     * @see #withNewLineType(NewLineType)
     */
    public final NewLineType getNewLineType()
    {
        return myNewLineType;
    }

    /**
     * Sets the character sequence that will be written as a line separator.
     * The default is {@link NewLineType#PLATFORM_DEPENDENT}
     *
     * @param newLineType the character sequence to be written between top-level values; null means the default should be used.
     *
     * @see #getNewLineType()
     * @see #withNewLineType(NewLineType)
     *
     * @throws UnsupportedOperationException if this is immutable.
     */
    public void setNewLineType(NewLineType newLineType)
    {
        mutationCheck();
        this.myNewLineType = newLineType;
    }

    /**
     * Declares the character sequence that will be written as a line separator.
     * The default is {@link NewLineType#PLATFORM_DEPENDENT}
     *
     * @param newLineType the character sequence to be written between top-level values; null means the default should be used.
     *
     * @see #getNewLineType()
     * @see #setNewLineType(NewLineType)
     *
     * @return this instance, if mutable;
     * otherwise a mutable copy of this instance.
     */
    public final IonTextWriterBuilder withNewLineType(NewLineType newLineType)
    {
        IonTextWriterBuilder b = mutable();
        b.setNewLineType(newLineType);
        return b;
    }

    //=========================================================================

    /**
     * Gets whether each top level value for standard printing should start on a new line. The default value is {@code false}.
     * When false, the IonTextWriter will insert a single space character (U+0020) between top-level values.
     * When pretty-printing, this setting is ignored; the pretty printer will always start top-level values on a new line.
     *
     * @return value indicating whether standard printing will insert a newline between top-level values
     *
     * @see #setWriteTopLevelValuesOnNewLines(boolean)
     * @see #withWriteTopLevelValuesOnNewLines(boolean)
     */
    public final boolean getWriteTopLevelValuesOnNewLines()
    {
        return myTopLevelValuesOnNewLines;
    }

    /**
     * Sets whether each top level value for standard printing should start on a new line. The default value is {@code false}.
     * When false, the IonTextWriter will insert a single space character (U+0020) between top-level values.
     * When pretty-printing, this setting is ignored; the pretty printer will always start top-level values on a new line.
     *
     * @param writeTopLevelValuesOnNewLines value indicating whether standard printing will insert a newline between top-level values
     *
     * @see #getWriteTopLevelValuesOnNewLines()
     * @see #withWriteTopLevelValuesOnNewLines(boolean)
     */
    public void setWriteTopLevelValuesOnNewLines(boolean writeTopLevelValuesOnNewLines)
    {
        mutationCheck();
        myTopLevelValuesOnNewLines = writeTopLevelValuesOnNewLines;
    }

    /**
     * Declares whether each top level value for standard printing should start on a new line. The default value is {@code false}.
     * When false, the IonTextWriter will insert a single space character (U+0020) between top-level values.
     * When pretty-printing, this setting is ignored; the pretty printer will always start top-level values on a new line.
     *
     * @param writeTopLevelValuesOnNewLines value indicating whether standard printing will insert a newline between top-level values
     *
     * @see #getWriteTopLevelValuesOnNewLines()
     * @see #setWriteTopLevelValuesOnNewLines(boolean)
     */
    public final IonTextWriterBuilder withWriteTopLevelValuesOnNewLines(boolean writeTopLevelValuesOnNewLines)
    {
        IonTextWriterBuilder b = mutable();
        b.setWriteTopLevelValuesOnNewLines(writeTopLevelValuesOnNewLines);
        return b;
    }

    //=========================================================================

    /**
     * Gets the maximum number of digits of fractional second precision allowed to be written for timestamp values.
     *
     * @return the currently configured maximum.
     *
     * @see #setMaximumTimestampPrecisionDigits(int)
     * @see #withMaximumTimestampPrecisionDigits(int)
     */
    public final int getMaximumTimestampPrecisionDigits() {
        return myMaximumTimestampPrecisionDigits;
    }

    /**
     * Sets the maximum number of digits of fractional second precision allowed to be written for timestamp values.
     * Default: {@link Timestamp#DEFAULT_MAXIMUM_DIGITS_TEXT}.
     *
     * @see #getMaximumTimestampPrecisionDigits()
     * @see #withMaximumTimestampPrecisionDigits(int)
     */
    public void setMaximumTimestampPrecisionDigits(int maximumTimestampPrecisionDigits) {
        mutationCheck();
        myMaximumTimestampPrecisionDigits = maximumTimestampPrecisionDigits;
    }

    /**
     * Sets the maximum number of digits of fractional second precision allowed to be written for timestamp values.
     * Default: {@link Timestamp#DEFAULT_MAXIMUM_DIGITS_TEXT}.
     *
     * @return this instance, if mutable; otherwise a mutable copy of this instance.
     *
     * @see #getMaximumTimestampPrecisionDigits()
     * @see #setMaximumTimestampPrecisionDigits(int)
     */
    public final IonTextWriterBuilder withMaximumTimestampPrecisionDigits(int maximumTimestampPrecisionDigits) {
        IonTextWriterBuilder b = mutable();
        b.setMaximumTimestampPrecisionDigits(maximumTimestampPrecisionDigits);
        return b;
    }

    //=========================================================================

    /**
     * Creates a new writer that will write text to the given output
     * stream.
     * <p>
     * If you have an {@link OutputStream}, you'll get better performance using
     * {@link #build(OutputStream)} as opposed to wrapping your stream in an
     * {@link Appendable} and calling this method.
     *
     * @param out the stream that will receive Ion text data.
     * Must not be null.
     *
     * @return a new {@link IonWriter} instance; not {@code null}.
     */
    public abstract IonWriter build(Appendable out);
}