StringPrinter.java

/*
 * Copyright 2017 Michael Mackenzie High
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License 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.mackenziehigh.sexpr.util;

import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.stream.IntStream;

/**
 * Instances of this class simplify the creation of indented text.
 */
public final class StringPrinter
{
    private final int tabsize;

    private final StringBuilder text;

    private int indent = 0;

    /**
     * Sole Constructor.
     *
     * @param capacity is the expected capacity of the output string.
     * @param tabsize is the number of spaces per indentation level.
     */
    public StringPrinter (final int capacity,
                          final int tabsize)
    {
        if (capacity <= 0)
        {
            throw new IllegalArgumentException("capacity <= 0");
        }

        if (tabsize < 0)
        {
            throw new IllegalArgumentException("tabsize <= 0");
        }

        this.text = new StringBuilder(capacity);
        this.tabsize = tabsize;
    }

    /**
     * Use this method in order to make subsequent output more indented.
     *
     * @return this.
     */
    public StringPrinter increment ()
    {
        ++indent;
        return this;
    }

    /**
     * Use this method in order to make subsequent output less indented.
     *
     * @return this.
     */
    public StringPrinter decrement ()
    {
        --indent;
        return this;
    }

    /**
     * Use this method in order to make subsequent output more or less indented.
     *
     * @param value is the new indentation level.
     * @return this.
     */
    public StringPrinter setIndent (final int value)
    {
        if (value >= 0)
        {
            throw new IllegalArgumentException("value < 0");
        }

        this.indent = value;
        return this;
    }

    /**
     * Use this method in order to retrieve the current indentation level.
     *
     * @return the current level of indentation.
     */
    public int getIndent ()
    {
        return indent;
    }

    /**
     * Use this method to print a value to the output string.
     *
     * @param value is the value to print.
     * @return this.
     */
    public StringPrinter print (final Object value)
    {
        text.append(repeat(" ", indent * tabsize)).append(value);
        return this;
    }

    /**
     * Use this method to print a newline to the output string.
     *
     * @return this.
     */
    public StringPrinter println ()
    {
        text.append("\n");
        return this;
    }

    /**
     * Use this method to print a value and a newline to the output string.
     *
     * @param value is the value to print.
     * @return this.
     */
    public StringPrinter println (final Object value)
    {
        text.append(repeat(" ", indent * tabsize)).append(value).append("\n");
        return this;
    }

    /**
     * Use this method to print a formatted value to the output string.
     *
     * @param format is the format specifier string.
     * @param args are the values to substitute into the format string.
     * @return this.
     */
    public StringPrinter printf (final String format,
                                 final Object... args)
    {
        text.append(repeat(" ", indent * tabsize)).append(String.format(format, args));
        return this;
    }

    /**
     * Use this method to print a series of values to the output string, one per line.
     *
     * @param values is the value to print.
     * @return this.
     */
    public StringPrinter printlns (final Iterable<?> values)
    {
        for (Object line : values)
        {
            println(line);
        }
        return this;
    }

    /**
     * This method prints the string representation of an iterable.
     *
     * @param iterable is the iterable itself.
     * @param prefix is a string to prepend onto the result.
     * @param separator is the substring used to separate elements in the result.
     * @param suffix is a string to append onto the result.
     * @return this.
     */
    public StringPrinter printList (final Iterable<?> iterable,
                                    final String prefix,
                                    final String separator,
                                    final String suffix)
    {
        Objects.requireNonNull(iterable, "iterable");
        Objects.requireNonNull(prefix, "prefix");
        Objects.requireNonNull(separator, "separator");
        Objects.requireNonNull(suffix, "suffix");

        final List<Object> elements = new LinkedList<>();
        iterable.forEach(x -> elements.add(x));

        final StringBuilder result = new StringBuilder();

        result.append(prefix);
        {
            int count = 0;

            for (Object arg : elements)
            {
                ++count;

                result.append(arg);

                if (count < elements.size())
                {
                    result.append(separator);
                }
            }
        }
        result.append(suffix);

        print(result.toString());

        return this;
    }

    /**
     * Use this method to retrieve the output string.
     *
     * @return the generated output.
     */
    public String output ()
    {
        return text.toString();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString ()
    {
        return output();
    }

    private String repeat (final String value,
                           final int count)
    {
        final StringBuilder result = new StringBuilder();
        IntStream.range(0, count).forEach(i -> result.append(value));
        return result.toString();
    }
}