DefaultPrettyPrinter.java

package tools.jackson.core.util;

import tools.jackson.core.*;
import tools.jackson.core.io.SerializedString;

/**
 * Default {@link PrettyPrinter} implementation that uses 2-space
 * indentation with platform-default linefeeds.
 * Usually this class is not instantiated directly, but instead
 * instantiated by {@code JsonFactory} or databind level mapper.
 *<p>
 * If you override this class, take note of {@link Instantiatable},
 * as subclasses will still create an instance of DefaultPrettyPrinter.
 *<p>
 * This class is designed for the JSON data format. It works on other formats
 * with same logical model (such as binary {@code CBOR} and {@code Smile} formats),
 * but may not work as-is for other data formats, most notably {@code XML}.
 * It may be necessary to use format-specific {@link PrettyPrinter}
 * implementation specific to that format.
 */
@SuppressWarnings("serial")
public class DefaultPrettyPrinter
    implements PrettyPrinter, Instantiatable<DefaultPrettyPrinter>,
        java.io.Serializable
{
    private static final long serialVersionUID = 1;

    /**
     * Interface that defines objects that can produce indentation used
     * to separate object entries and array values. Indentation in this
     * context just means insertion of white space, independent of whether
     * linefeeds are output.
     */
    public interface Indenter
    {
        void writeIndentation(JsonGenerator g, int level) throws JacksonException;

        /**
         * @return True if indenter is considered inline (does not add linefeeds),
         *   false otherwise
         */
        boolean isInline();
    }

    // // // Config, indentation

    /**
     * By default, let's use only spaces to separate array values.
     */
    protected Indenter _arrayIndenter = FixedSpaceIndenter.instance();

    /**
     * By default, let's use linefeed-adding indenter for separate
     * object entries. We'll further configure indenter to use
     * system-specific linefeeds, and 2 spaces per level (as opposed to,
     * say, single tabs)
     */
    protected Indenter _objectIndenter = DefaultIndenter.SYSTEM_LINEFEED_INSTANCE;

    /**
     * String printed between root-level values, if any.
     */
    protected final SerializableString _rootValueSeparator;

    // // // Config, other white space configuration

    // // // State:

    /**
     * Number of open levels of nesting. Used to determine amount of
     * indentation to use.
     */
    protected transient int _nesting;

    protected final Separators _separators;

    /**
     * Separator between Object property names and values,
     * including possible before/after spaces.
     */
    protected final String _objectNameValueSeparator;

    /**
     * Separator between Object property entries, 
     * including possible before/after spaces.
     */
    protected final String _objectEntrySeparator;

    /**
     * String to use in empty Object to separate start and end markers.
     * Default is single space, resulting in output of {@code { }}.
     */
    protected final String _objectEmptySeparator;

    /**
     * Separator between Array elements, 
     * including possible before/after spaces.
     */
    protected final String _arrayElementSeparator;

    /**
     * String to use in empty Array to separate start and end markers.
     * Default is single space, resulting in output of {@code [ ]}.
     */
    protected final String _arrayEmptySeparator;    

    /*
    /**********************************************************************
    /* Life-cycle (construct, configure)
    /**********************************************************************
     */

    /**
     * Default constructor for "vanilla" instance with default settings.
     */
    public DefaultPrettyPrinter() {
        this(DEFAULT_SEPARATORS);
    }

    /**
     * Default constructor for "vanilla" instance with default settings,
     * except for {@link Separators} overrides.
     *
     * @param separators Custom separator definition over defaults.
     */
    public DefaultPrettyPrinter(Separators separators)
    {
        _separators = separators;

        _rootValueSeparator = separators.getRootSeparator() == null ? null : new SerializedString(separators.getRootSeparator());
        _objectNameValueSeparator = separators.getObjectNameValueSpacing().apply(
                separators.getObjectNameValueSeparator());
        _objectEntrySeparator = separators.getObjectEntrySpacing().apply(separators.getObjectEntrySeparator());
        _objectEmptySeparator = separators.getObjectEmptySeparator();
        _arrayElementSeparator = separators.getArrayElementSpacing().apply(separators.getArrayElementSeparator());
        _arrayEmptySeparator = separators.getArrayEmptySeparator();
    }
    
    /**
     * Copy constructor.
     *
     * @param base DefaultPrettyPrinter being copied
     */
    public DefaultPrettyPrinter(DefaultPrettyPrinter base) {
        _arrayIndenter = base._arrayIndenter;
        _objectIndenter = base._objectIndenter;
        _nesting = base._nesting;

        _separators = base._separators;
        _rootValueSeparator = base._rootValueSeparator;
        _objectNameValueSeparator = base._objectNameValueSeparator;
        _objectEntrySeparator = base._objectEntrySeparator;
        _objectEmptySeparator = base._objectEmptySeparator;
        _arrayElementSeparator = base._arrayElementSeparator;
        _arrayEmptySeparator = base._arrayEmptySeparator;
    }

    /**
     * Copy constructor with override
     *
     * @param base DefaultPrettyPrinter being copied
     * @param separators Separators to use instead of ones {@code base} had
     */
    public DefaultPrettyPrinter(DefaultPrettyPrinter base, Separators separators) {
        _arrayIndenter = base._arrayIndenter;
        _objectIndenter = base._objectIndenter;
        _nesting = base._nesting;

        _separators = separators;
        _rootValueSeparator = separators.getRootSeparator() == null ? null : new SerializedString(separators.getRootSeparator());
        _objectNameValueSeparator = separators.getObjectNameValueSpacing().apply(
                separators.getObjectNameValueSeparator());
        _objectEntrySeparator = separators.getObjectEntrySpacing().apply(separators.getObjectEntrySeparator());
        _objectEmptySeparator = separators.getObjectEmptySeparator();
        _arrayElementSeparator = separators.getArrayElementSpacing().apply(separators.getArrayElementSeparator());
        _arrayEmptySeparator = separators.getArrayEmptySeparator();
    }
    
    public void indentArraysWith(Indenter i) {
        _arrayIndenter = (i == null) ? NopIndenter.instance() : i;
    }

    public void indentObjectsWith(Indenter i) {
        _objectIndenter = (i == null) ? NopIndenter.instance() : i;
    }

    public DefaultPrettyPrinter withArrayIndenter(Indenter i) {
        if (i == null) {
            i = NopIndenter.instance();
        }
        if (_arrayIndenter == i) {
            return this;
        }
        DefaultPrettyPrinter pp = new DefaultPrettyPrinter(this);
        pp._arrayIndenter = i;
        return pp;
    }

    public DefaultPrettyPrinter withObjectIndenter(Indenter i) {
        if (i == null) {
            i = NopIndenter.instance();
        }
        if (_objectIndenter == i) {
            return this;
        }
        DefaultPrettyPrinter pp = new DefaultPrettyPrinter(this);
        pp._objectIndenter = i;
        return pp;
    }

    protected DefaultPrettyPrinter _withSpaces(boolean state)
    {
        Separators copy = _separators.withObjectNameValueSpacing(state ? Separators.Spacing.BOTH : Separators.Spacing.NONE);
        DefaultPrettyPrinter pp = withSeparators(copy);
        return pp;
    }

    /**
     * Method for configuring separators for this pretty-printer to use
     *
     * @param separators Separator definitions to use
     *
     * @return This pretty-printer instance (for call chaining)
     */
    public DefaultPrettyPrinter withSeparators(Separators separators) {
        return new DefaultPrettyPrinter(this, separators);
    }

    /*
    /**********************************************************************
    /* Instantiatable impl
    /**********************************************************************
     */

    @Override
    public DefaultPrettyPrinter createInstance() {
        if (getClass() != DefaultPrettyPrinter.class) {
            throw new IllegalStateException("Failed `createInstance()`: "+getClass().getName()
                    +" does not override method; it has to");
        }
        return new DefaultPrettyPrinter(this);
    }

    /*
    /**********************************************************************
    /* PrettyPrinter impl
    /**********************************************************************
     */

    @Override
    public void writeRootValueSeparator(JsonGenerator g) throws JacksonException
    {
        if (_rootValueSeparator != null) {
            g.writeRaw(_rootValueSeparator);
        }
    }

    @Override
    public void writeStartObject(JsonGenerator g) throws JacksonException
    {
        g.writeRaw('{');
        if (!_objectIndenter.isInline()) {
            ++_nesting;
        }
    }

    @Override
    public void beforeObjectEntries(JsonGenerator g) throws JacksonException
    {
        _objectIndenter.writeIndentation(g, _nesting);
    }

    /**
     * Method called after the object property name has been output, but
     * before the value is output.
     *<p>
     * Default handling (without pretty-printing) will output a single
     * colon to separate the two. Pretty-printer is
     * to output a colon as well, but can surround that with other
     * (white-space) decoration.
     */
    @Override
    public void writeObjectNameValueSeparator(JsonGenerator g) throws JacksonException
    {
        g.writeRaw(_objectNameValueSeparator);
    }

    /**
     * Method called after an object entry (field:value) has been completely
     * output, and before another value is to be output.
     *<p>
     * Default handling (without pretty-printing) will output a single
     * comma to separate the two. Pretty-printer is
     * to output a comma as well, but can surround that with other
     * (white-space) decoration.
     */
    @Override
    public void writeObjectEntrySeparator(JsonGenerator g) throws JacksonException
    {
        g.writeRaw(_objectEntrySeparator);
        _objectIndenter.writeIndentation(g, _nesting);
    }

    @Override
    public void writeEndObject(JsonGenerator g, int nrOfEntries) throws JacksonException
    {
        if (!_objectIndenter.isInline()) {
            --_nesting;
        }
        if (nrOfEntries > 0) {
            _objectIndenter.writeIndentation(g, _nesting);
        } else {
            g.writeRaw(_objectEmptySeparator);
        }
        g.writeRaw('}');
    }

    @Override
    public void writeStartArray(JsonGenerator g) throws JacksonException
    {
        if (!_arrayIndenter.isInline()) {
            ++_nesting;
        }
        g.writeRaw('[');
    }

    @Override
    public void beforeArrayValues(JsonGenerator g) throws JacksonException {
        _arrayIndenter.writeIndentation(g, _nesting);
    }

    /**
     * Method called after an array value has been completely
     * output, and before another value is to be output.
     *<p>
     * Default handling (without pretty-printing) will output a single
     * comma to separate the two. Pretty-printer is
     * to output a comma as well, but can surround that with other
     * (white-space) decoration.
     */
    @Override
    public void writeArrayValueSeparator(JsonGenerator g) throws JacksonException
    {
        g.writeRaw(_arrayElementSeparator);
        _arrayIndenter.writeIndentation(g, _nesting);
    }

    @Override
    public void writeEndArray(JsonGenerator g, int nrOfValues) throws JacksonException
    {
        if (!_arrayIndenter.isInline()) {
            --_nesting;
        }
        if (nrOfValues > 0) {
            _arrayIndenter.writeIndentation(g, _nesting);
        } else {
            g.writeRaw(_arrayEmptySeparator);
        }
        g.writeRaw(']');
    }

    /*
    /**********************************************************************
    /* Helper classes
    /**********************************************************************
     */

    /**
     * Dummy implementation that adds no indentation whatsoever
     */
    public static class NopIndenter
        implements Indenter, java.io.Serializable
    {
        private static final NopIndenter _nopInstance = new NopIndenter();

        public static NopIndenter instance() { return _nopInstance; }

        @Override
        public void writeIndentation(JsonGenerator g, int level) { }

        @Override
        public boolean isInline() { return true; }
    }

    /**
     * This is a very simple indenter that only adds a
     * single space for indentation. It is used as the default
     * indenter for array values.
     */
    public static class FixedSpaceIndenter extends NopIndenter
    {
        private static final FixedSpaceIndenter _fixedInstance = new FixedSpaceIndenter();

        public static FixedSpaceIndenter instance() { return _fixedInstance; }

        @Override
        public void writeIndentation(JsonGenerator g, int level) throws JacksonException
        {
            g.writeRaw(' ');
        }

        @Override
        public boolean isInline() { return true; }
    }
}