Parameters.java

package org.greenbytes.http.sfv;

import java.math.BigDecimal;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;

/**
 * Represents the Parameters of an Item or an Inner List.
 * 
 * @see <a href=
 *      "https://www.rfc-editor.org/rfc/rfc9651.html#param">Section
 *      3.1.2 of RFC 9651</a>
 */
public class Parameters implements Map<String, Item<?>> {

    private final Map<String, Item<?>> delegate;

    /** Empty parameters instance. */
    protected static final Parameters EMPTY = new Parameters(Collections.emptyMap());

    private Parameters(Map<String, Object> value) {
        this.delegate = Collections.unmodifiableMap(checkAndTransformMap(value));
    }

    /**
     * Creates an unmodifiable {@link Parameters} instance representing the
     * specified {@code Map<String, Item>} value.
     * <p>
     * Note that the {@link Map} implementation that is used here needs to
     * iterate predictably based on insertion order, such as
     * {@link java.util.LinkedHashMap}.
     * 
     * @param value
     *            a {@code Map<String, Item>} value
     * @return a {@link Parameters} representing {@code value}.
     */
    public static Parameters valueOf(Map<String, Object> value) {
        return new Parameters(value);
    }

    /**
     * Serialize this parameter to a {@linkplain StringBuilder}
     * @param sb to serialize to
     * @return updated {@linkplain StringBuilder}
     */
    public StringBuilder serializeTo(StringBuilder sb) {
        for (Map.Entry<String, Item<?>> e : delegate.entrySet()) {
            sb.append(';').append(e.getKey());
            if (!(e.getValue().get().equals(Boolean.TRUE))) {
                sb.append('=');
                e.getValue().serializeTo(sb);
            }
        }
        return sb;
    }

    /**
     * Serialize this parameter.
     * @return serialization
     */
    public String serialize() {
        return serializeTo(new StringBuilder()).toString();
    }

    public StringBuilder serializeToForDebug(StringBuilder sb, int indentLevel, Function<Class, String> formatter) {
        if (!delegate.isEmpty()) {
            String indent = indentLevel != 0 ? String.format("%" + indentLevel + "s", "") : "";
            String classn = formatter.apply(this.getClass());
            sb.append(indent).append(serialize()).append(classn).append("\n");
            for (Map.Entry<String, Item<?>> e : delegate.entrySet()) {
                sb.append("  " + indent).append(e.getKey()).append(" -> ");
                e.getValue().serializeToForDebug(sb, 0, formatter);
            }
            return sb;
        } else {
            return sb;
        }
    }

    private static Map<String, Item<?>> checkAndTransformMap(Map<String, Object> map) {
        Map<String, Item<?>> result = new LinkedHashMap<>(
                Objects.requireNonNull(map, "Map must not be null").size());
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            String key = Utils.checkKey(entry.getKey());
            Item<?> value = asItem(key, entry.getValue());
            if (!value.getParams().isEmpty()) {
                throw new IllegalArgumentException("Parameter value for '" + key + "' must be bare item (no parameters)");
            }
            result.put(entry.getKey(), value);
        }
        return result;
    }

    private static Item<?> asItem(String key, Object o) {
        if (o instanceof Item) {
            if (o instanceof Parameterizable) {
                Parameterizable p = ((Parameterizable)o);
                if (!p.getParams().isEmpty()) {
                    throw new IllegalArgumentException("Can't map value for parameter '" + key + "': " + o.getClass() + " carries parameters");
                }
            }
            return (Item<?>) o;
        } else if (o instanceof Integer) {
            return IntegerItem.valueOf(((Integer) o).longValue());
        } else if (o instanceof Long) {
            return IntegerItem.valueOf((Long) o);
        } else if (o instanceof String) {
            return StringItem.valueOf((String) o);
        } else if (o instanceof Boolean) {
            return BooleanItem.valueOf((Boolean) o);
        } else if (o instanceof byte[]) {
            return ByteSequenceItem.valueOf((byte[]) o);
        } else if (o instanceof BigDecimal) {
            return DecimalItem.valueOf((BigDecimal)o);
        } else {
            throw new IllegalArgumentException("Can't map value for parameter '" + key + "': " + o.getClass());
        }
    }

    // delegate methods, autogenerated

    public void clear() {
        throw new UnsupportedOperationException();
    }

    @Override
    public Item<?> compute(String key,
                           BiFunction<? super String, ? super Item<?>, ? extends Item<?>> remappingFunction) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Item<?> computeIfAbsent(String key,
                                   Function<? super String, ? extends Item<?>> mappingFunction) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Item<?> computeIfPresent(String key,
                                    BiFunction<? super String, ? super Item<?>, ? extends Item<?>> remappingFunction) {
        throw new UnsupportedOperationException();
    }

    public boolean containsKey(Object key) {
        return delegate.containsKey(key);
    }

    public boolean containsValue(Object value) {
        return delegate.containsValue(value);
    }

    public Set<Entry<String, Item<?>>> entrySet() {
        return delegate.entrySet();
    }

    public boolean equals(Object o) {
        return Objects.equals(delegate, o);
    }

    @Override
    public void forEach(BiConsumer<? super String, ? super Item<?>> action) {
        delegate.forEach(action);
    }

    public Item<?> get(Object key) {
        return delegate.get(key);
    }

    @Override
    public Item<?> getOrDefault(Object key, Item<?> defaultValue) {
        return delegate.getOrDefault(key, defaultValue);
    }

    public int hashCode() {
        return delegate.hashCode();
    }

    public boolean isEmpty() {
        return delegate.isEmpty();
    }

    public Set<String> keySet() {
        return delegate.keySet();
    }

    @Override
    public Item<?> merge(String key, Item<?> value,
            BiFunction<? super Item<?>, ? super Item<?>, ? extends Item<?>> remappingFunction) {
        throw new UnsupportedOperationException();
    }

    public Item<?> put(String key, Item<?> value) {
        throw new UnsupportedOperationException();
    }

    public void putAll(Map<? extends String, ? extends Item<?>> m) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Item<?> putIfAbsent(String key, Item<?> value) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean remove(Object key, Object value) {
        throw new UnsupportedOperationException();
    }

    public Item<?> remove(Object key) {
        throw new UnsupportedOperationException();
    }

    @Override
    public boolean replace(String key, Item<?> oldValue, Item<?> newValue) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Item<?> replace(String key, Item<?> value) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void replaceAll(BiFunction<? super String, ? super Item<?>, ? extends Item<?>> function) {
        throw new UnsupportedOperationException();
    }

    public int size() {
        return delegate.size();
    }

    public Collection<Item<?>> values() {
        return delegate.values();
    }
}