Parsers.java

package io.github.jonloucks.variants.api;

import io.github.jonloucks.contracts.api.Contract;

import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;

import static io.github.jonloucks.variants.api.Checks.parserCheck;
import static io.github.jonloucks.variants.api.Checks.textCheck;
import static java.util.Optional.ofNullable;

/**
 * Responsibility: Parsers to assist source text conversion for a Variant
 */
public interface Parsers {
    /**
     * Contract for Parsers
     */
    Contract<Parsers> CONTRACT = Contract.create(Parsers.class, b -> b.name("Variant Parsers"));
    
    /**
     * @return a parser that converts a valid text into a String
     */
    default Function<CharSequence, String> stringParser() {
        return text -> textCheck(text).toString();
    }
    
    /**
     * No trimming or skipping empty values
     * @return a text conversion to a String instance
     */
    default Function<CharSequence, Optional<String>> ofRawString() {
        return text -> ofNullable(text).map(CharSequence::toString);
    }
    
    /**
     * Input is trimmed and empty values are skipped
     *
     * @return a text conversion to a String instance
     */
    default Function<CharSequence, Optional<String>> ofString() {
        return ofTrimAndSkipEmpty(stringParser());
    }
    
    /**
     * @return a parser that converts a valid text value into a Boolean instance
     */
    default Function<CharSequence, Boolean> booleanParser() {
        return string(Boolean::parseBoolean);
    }
    
    /**
     * Input is trimmed and empty values are skipped
     *
     * @return a text conversion to a Boolean instance
     */
    default Function<CharSequence, Optional<Boolean>> ofBoolean() {
        return ofTrimAndSkipEmpty(booleanParser());
    }
    
    /**
     * @return a parser that converts a valid text value into a Float instance
     */
    default Function<CharSequence, Float> floatParser() {
        return string(Float::parseFloat);
    }
    
    /**
     * Input is trimmed and empty values are skipped
     *
     * @return a text conversion to a Float instance
     */
    default Function<CharSequence, Optional<Float>> ofFloat() {
        return ofTrimAndSkipEmpty(floatParser());
    }
    
    /**
     * @return a parser that converts a valid text value into a Double instance
     */
    default Function<CharSequence, Double> doubleParser() {
        return string(Double::parseDouble);
    }
    
    /**
     * Input is trimmed and empty values are skipped
     *
     * @return a text conversion to a Double instance
     */
    default Function<CharSequence, Optional<Double>> ofDouble() {
        return ofTrimAndSkipEmpty(doubleParser());
    }
    
    /**
     * @return a parser that converts a valid text value into an Integer instance
     */
    default Function<CharSequence, Integer> integerParser() {
        return string(Integer::parseInt);
    }
    
    /**
     * Input is trimmed and empty values are skipped
     *
     * @return a text conversion to a Integer instance
     */
    default Function<CharSequence, Optional<Integer>> ofInteger() {
        return ofTrimAndSkipEmpty(integerParser());
    }
    
    /**
     * @return a parser that converts a valid text value into a Long instance
     */
    default Function<CharSequence, Long> longParser() {
        return string(Long::parseLong);
    }
    
    /**
     * Input is trimmed and empty values are skipped
     *
     * @return a text conversion to a Long instance
     */
    default Function<CharSequence, Optional<Long>> ofLong() {
        return ofTrimAndSkipEmpty(longParser());
    }

    /**
     * @return a parser that converts a valid text value into a Boolean instance
     */
    default Function<CharSequence, Duration> durationParser() {
        return text -> Duration.parse(textCheck(text).toString());
    }
    
    /**
     * Input is trimmed and empty values are skipped
     *
     * @return a text conversion to a Duration instance
     */
    default Function<CharSequence, Optional<Duration>> ofDuration() {
        return ofTrimAndSkipEmpty(durationParser());
    }
    
    /**
     * A parser that converts a valid text value into a Boolean instance
     *
     * @param enumClass the Enum class
     * @return A parser that converts a valid text value into a Boolean instance
     * @param <T> the type of Enum
     */
    <T extends Enum<T>> Function<CharSequence, T> enumParser(Class<T> enumClass);
    
    /**
     * text conversion to a Enm instance
     *
     * @param enumClass the enum class
     * Input is trimmed and empty values are skipped
     * @return a text conversion to a Duration instance
     * @param <T> the type of Enum
     */
    default <T extends Enum<T>> Function<CharSequence, Optional<T>> ofEnum(Class<T> enumClass) {
        return ofTrimAndSkipEmpty(enumParser(enumClass));
    }
    
    /**
     * trim leading and trailing white space
     *
     * @param text the text to trim
     * @return the trimmed text
     */
    CharSequence trim(CharSequence text);

    /**
     * A parser that converts text to a String
     *
     * @param parser the parser that accepts the String
     * @return the new parser
     * @param <T> the return type of the given parser
     */
    default <T> Function<CharSequence, T> string(Function<String, T> parser) {
        final Function<String, T> validParser = parserCheck(parser);
        return chars -> validParser.apply(textCheck(chars).toString());
    }
    
    /**
     * Text to parser helper.
     * Trims text
     * Skips empty values
     *
     * @param parser the delegate parser
     * @return the new 'of' function
     * @param <T> the return type of the given parser
     */
    default <T> Function<CharSequence, Optional<T>> ofTrimAndSkipEmpty(Function<CharSequence, T> parser) {
        final Function<CharSequence, T> validParser = parserCheck(parser);
        return text -> {
            if (ofNullable(text).isPresent()) {
                final CharSequence trimmed = trim(text);
                return trimmed.length() == 0 ? Optional.empty() : Optional.of(validParser.apply(trimmed));
            } else {
                return Optional.empty();
            }
        };
    }
    
    /**
     * Split the input text and parse each part into a list
     *
     * @param of the delegate text to value function
     * @param delimiter the string delimiter. See {@link String#split(String)}
     * @return the new parser
     * @param <T> the return type of the given parser
     */
    <T> Function<CharSequence, Optional<List<T>>> ofList(Function<CharSequence, Optional<T>> of, String delimiter);
}