CsvValueDecorators.java

package com.fasterxml.jackson.dataformat.csv;

import java.io.IOException;
import java.util.Objects;

/**
 * Container class for default {@link CsvValueDecorator} implementations
 *
 * @since 2.18
 */
public class CsvValueDecorators
{
    /**
     * {@link StringPrefixSuffixDecorator} that uses square brackets ({@code []})
     * around decorated value, but does not require their use (removes if used,
     * ignores it not).
     */
    public final static CsvValueDecorator OPTIONAL_BRACKETS_DECORATOR
         = new StringPrefixSuffixDecorator("[", "]", true);

    /**
     * {@link StringPrefixSuffixDecorator} that uses square brackets ({@code []})
     * around decorated value, and requires their use (if value has no matching
     * decoration, an exception is thrown when attempting to read the value).
     */
    public final static CsvValueDecorator STRICT_BRACKETS_DECORATOR
        = new StringPrefixSuffixDecorator("[", "]", false);

    /**
     * Factory method for constructing a {@link StringPrefixSuffixDecorator} with
     * given prefix and suffix, both optional.
     */
    public static CsvValueDecorator optionalPrefixSuffixDecorator(String prefix, String suffix) {
        return new StringPrefixSuffixDecorator(prefix, suffix, true);
    }

    /**
     * Factory method for constructing a {@link StringPrefixSuffixDecorator} with
     * given prefix and suffix, both required.
     */
    public static CsvValueDecorator requiredPrefixSuffixDecorator(String prefix, String suffix) {
        return new StringPrefixSuffixDecorator(prefix, suffix, false);
    }

    /**
     * Decorated that adds static prefix and suffix around value to decorate value;
     * removes the same when un-decorating. Handling of the case where decoration
     * is missing on deserialization (reading) depends on where decorator is
     * created with "optional" or "strict" setting
     * (see {@link StringPrefixSuffixDecorator#StringPrefixSuffixDecorator}).
     */
    public static class StringPrefixSuffixDecorator
        implements CsvValueDecorator
    {
        /**
         * Decoration added before value being decorated: for example, if decorating
         * with brackets, this would be opening bracket {@code [ }.
         */
        protected final String _prefix;

        /**
         * Decoration added after value being decorated: for example, if decorating
         * with brackets, this would be closing bracket {@code ] }.
         */
        protected final String _suffix;

        /**
         * Whether existence of prefix and suffix decoration is optional
         * ({@code true}) or required ({@code false}): if required
         * and value does does not have decorations, deserialization (reading)
         * will fail with an exception; if optional value is exposed as is.
         */
        protected final boolean _optional;

        public StringPrefixSuffixDecorator(String prefix, String suffix, boolean optional) {
            _prefix = Objects.requireNonNull(prefix);
            _suffix = Objects.requireNonNull(suffix);
            _optional = optional;
        }

        @Override
        public String decorateValue(CsvGenerator gen, String plainValue) throws IOException {
            return new StringBuilder(plainValue.length() + _prefix.length() + _suffix.length())
                    .append(_prefix)
                    .append(plainValue)
                    .append(_suffix)
                    .toString()
                    ;
        }

        @Override
        public String undecorateValue(CsvParser parser, String decoratedValue) throws IOException {
            if (!decoratedValue.startsWith(_prefix)) {
                if (!_optional) {
                    parser._reportParsingError(String.format(
                            "Decorated value of column '%s' does not start with expected prefix (\"%s\"); value: \"%s\"",
                            parser.currentName(), _prefix, decoratedValue));
                }
                return decoratedValue;
            }
            if (!decoratedValue.endsWith(_suffix)) {
                if (!_optional) {
                    parser._reportParsingError(String.format(
                            "Decorated value of column '%s' does not end with expected suffix (\"%s\"); value: \"%s\"",
                            parser.currentName(), _suffix, decoratedValue));
                }
                return decoratedValue;
            }
            int start = _prefix.length();
            int end = decoratedValue.length() - _suffix.length();
            // One minor complication: suffix and prefix could overlap
            if (start >= end) {
                return "";
            }
            return decoratedValue.substring(start, end);
        }
    }
}