Value.java

package Hoot.Runtime.Values;

import Hoot.Runtime.Faces.Named;
import static Hoot.Runtime.Functions.Utils.*;
import static Hoot.Runtime.Faces.Logging.*;
import static Hoot.Runtime.Names.Keyword.Self;
import static Hoot.Runtime.Names.Primitive.*;

/**
 * A named typed value.
 * @param <V> a value type
 *
 * @author nik <nikboyd@sonic.net>
 * @see "Copyright 2010,2021 Nikolas S Boyd."
 * @see "Permission is granted to copy this work provided this copyright statement is retained in all copies."
 */
public class Value<V> implements Named {

    private Value() { super(); }
    private <T> Value(Class<T> valueType) { this(); this.valueType = valueType; }
    private <T> Value(Class<T> valueType, String valueName) { this(valueType); name(valueName); }
    private Value(String name, V value) { this(value.getClass()); this.value = value; name(name); }

    public static <T> Value<T> self(T value) { return named(Self, value); }
    public static <T> Value<T> with(T value) { return named(Empty, value); }
    public static <T> Value<T> from(int index) { return named("value"+index); }
    public static <T> Value<T> named(String name) { return new Value<>(Object.class, name); }
    public static <T> Value<T> named(String name, Class<T> valueType) { return new Value<>(valueType, name); }
    public static <T> Value<T> named(String name, T value) {
        if (hasNone(value)) return named(name);
        Value<T> v = new Value<>(value.getClass(), name);
        return v.bind(value); }

    private Object value = null;
    private void value(Object value) { checkType(value); this.value = value; this.valueType = value.getClass(); }
    @Override public <R> R value() { Class<R> resultType = resultType(valueType()); return resultType.cast(value); }
    public <T extends V> Value<V> bind(T value) { if (hasOne(value)) value(value); return this; }

    public static <R> R as(Class<R> resultType, Value v) { return hasOne(v) ? resultType.cast(v.value()) : null; }
    public static <V> Value<V> asValue(Class<Value<V>> valueType, Value v) { return valueType.cast(v); }

    private Class<?> valueType = Void.class;
    @Override public Class<?> valueType() { return this.valueType; }
    private boolean voidType() { return Void.class == valueType(); }

    private void checkType(Class<?> aType) { if (hasOne(value) && !aType.isInstance(value)) reportType(value, aType); }
    private void checkType(Object value) {
        if (hasOne(value) && !voidType() && !valueType().isInstance(value)) reportType(value, value.getClass()); }

    static final String TypeWarning = "change? %s => %s '%s'";
    private void reportType(Object value, Class<?> aType) {
        warn(format(TypeWarning, toString(), aType.getSimpleName(), value.toString())); }

    private String valueName = Empty;
    @Override public String name() { return this.valueName; }
    private void inferName() { this.valueName = inferName(valueType); }
    private Value orInferName() { if (isEmpty(name())) inferName(); return this; }
    private Value name(String valueName) { this.valueName = valueName; return this.orInferName(); }
    public Value makeSelfish() { this.valueName = Self; return this; }

    static final String ValueReport = "%s %s: %s";
    @Override public String toString() {
        return format(ValueReport, valueType().getSimpleName(), name(), formatValue(value(), valueType())); }

    static final String Comma = ", ";
    static final String Arrayed = "[]";
    private static <ValueType> String formatValue(ValueType value, Class<?> valueType) {
        if (value == null) return "null";
        if (valueType.getSimpleName().endsWith(Arrayed)) {
            Object[] values = (Object[]) value;
            return joinWith(Comma, map(wrap(values), v -> formatValue(v, v.getClass())));
        }

        if (valueType.getSimpleName().contains(String.class.getSimpleName())) {
            return quoteLiterally(value.toString());
        }

        return value.toString();
    }

    static final String Array = "Array";
    static final String Vowels = "aeiou";
    static final String[] Articles = { "a", "an" };
    static String inferName(Class<?> valueType) {
        String typeName = valueType.getSimpleName();
        String letter = Character.toString(typeName.charAt(0));
        int index = Vowels.contains(letter.toLowerCase()) ? 1 : 0;
        String result = Articles[index] + typeName;
        if (typeName.endsWith(Arrayed)) result = result.replace(Arrayed, Array);
        return result; }

} // Value<ValueType>