package org.wildfly.swarm.spi.api; import java.net.URL; import java.util.NoSuchElementException; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Supplier; public class Defaultable<T> implements Supplier<T> { private final Class<T> type; private final DefaultValue<T> defaultValue; private Optional<T> explicit = Optional.empty(); public static Defaultable<String> string(String defaultValue) { return string(() -> defaultValue); } public static Defaultable<String> string(Supplier<String> defaultValueSupplier) { return new Defaultable<>(String.class, defaultValueSupplier); } public static Defaultable<Integer> integer(int defaultValue) { return integer(() -> defaultValue); } public static Defaultable<Float> floating(float defaultValue) { return floating(() -> defaultValue); } public static Defaultable<Integer> integer(Supplier<Integer> defaultValueSupplier) { return new Defaultable<>(Integer.class, defaultValueSupplier); } public static Defaultable<Float> floating(Supplier<Float> defaultValueSupplier) { return new Defaultable<>(Float.class, defaultValueSupplier); } public static Defaultable<Long> longInteger(long defaultValue) { return longInteger(() -> defaultValue); } public static Defaultable<Long> longInteger(Supplier<Long> defaultValueSupplier) { return new Defaultable<>(Long.class, defaultValueSupplier); } public static Defaultable<Boolean> bool(boolean defaultValue) { return bool(() -> defaultValue); } public static Defaultable<Boolean> bool(Supplier<Boolean> defaultValueSupplier) { return new Defaultable<>(Boolean.class, defaultValueSupplier); } public static Defaultable<URL> url(URL defaultValue) { return url(() -> defaultValue); } public static Defaultable<URL> url(Supplier<URL> defaultValueSupplier) { return new Defaultable<>(URL.class, defaultValueSupplier); } /** * Create a <code>Boolean</code> configuration that has a default value of <code>true</code> if all arguments have been explicitly set to non-default values. * * @param items The items to test. * @return The new item. */ public static Defaultable<Boolean> ifAllExplicitlySet(Defaultable<?>... items) { return bool(() -> { for (Defaultable<?> item : items) { if (!item.isExplicit()) { return false; } } return true; }); } /** * Create a <code>Boolean</code> configuration that has a default value of <code>true</code> if any arguments have been explicitly set to non-default values. * * @param items The items to test. * @return The new item. */ public static Defaultable<Boolean> ifAnyExplicitlySet(Defaultable<?>... items) { return bool(() -> { for (Defaultable<?> item : items) { if (item.isExplicit()) { return true; } } return false; }); } private Defaultable(Class<T> type, Supplier<T> defaultValueSupplier) { this.type = type; this.defaultValue = new DefaultValue<T>(defaultValueSupplier); } public Class<T> type() { return this.type; } /** * Explicitly set a value. * * @param explicitValue The value to set. */ public synchronized void set(T explicitValue) { this.explicit = Optional.ofNullable(explicitValue); } /** * Retrieve the default value, if any. * * @return The default value. * @throws NoSuchElementException if no default value is available. */ public T defaultValue() throws NoSuchElementException { return defaultValue(true); } private synchronized T defaultValue(boolean throwIfNull) { return this.defaultValue.get(throwIfNull); } /** * Retrieve the explicitly set value, if any. * * @return The explicitly set value. * @throws NoSuchElementException if no explicit value is available. */ public T explicitValue() throws NoSuchElementException { return explicitValue(true); } private synchronized T explicitValue(boolean throwIfNull) { return this.explicit.orElseGet(() -> { if (throwIfNull) { throw new NoSuchElementException("No explicit value present"); } return null; }); } /** * Retrieve the value, regardless of it's origin. * * @return The value, if non-null. * @throws NoSuchElementException If no value (default nor explicit) is present. */ public T get() throws NoSuchElementException { return get(true); } private synchronized T get(boolean throwIfNull) { return explicitOrElseGet(() -> this.defaultValue.get(throwIfNull)); } /** * Retrieve a standard <code>Optional</code> containing the explicit value, if any. * * <p>If no explicit value is set, an <b>empty</b> <code>Optional</code> will be returned.</p> * * @return The <code>Optional</code> wrapper around the explicitly set value, if any. */ public Optional<T> explicit() { return this.explicit; } /** * If a default or explicit value is present, invoke the supplied consumer with it. * * @param consumer The consumer. */ public void ifPresent(Consumer<? super T> consumer) { T value = get(false); if (value != null) { consumer.accept(value); } } /** * If (and only if) an explicit value is present, invoke the supplied consumer with it * * @param consumer The consumer. */ public void ifExplicit(Consumer<? super T> consumer) { if (isExplicit()) { consumer.accept(get()); } } /** * If (and only if) only a default value is present (without an explicit value), invoke the supplied consumer with it. * * @param consumer The consumer. */ public void ifDefault(Consumer<? super T> consumer) { if (isDefault()) { consumer.accept(get()); } } /** * Retrieve the value (default or explicit) if present, otherwise the provided value parameter. * * @param defaultValue The value to return only if no value is present. * @return Either the value (default or explicit) or the <code>defaultValue</code> parameter. */ public T orElse(T defaultValue) { T value = get(); if (value != null) { return value; } return defaultValue; } /** * Retrieve the value (default or explicit) if present, otherwise return the value supplied by the <code>Supplier</code> parameter. * * @param other The value supplier to use to supply a value only if no value is present. * @return Either the value (default or explicit) or the value supplied by the <code>other</code> <code>Supplier</code> parameter. */ public T orElseGet(Supplier<? extends T> other) { T value = get(); return value != null ? value : other.get(); } /** * Retrieve the explicit value if present, otherwise return the value supplied by the <code>Supplier</code> parameter. * * @param other The value supplier to use to supply a value only if no explicit value is present. * @return Either the explicitly set value (if set) or the value supplied by the <code>other</code> <code>Supplier</code> parameter. */ public T explicitOrElseGet(Supplier<? extends T> other) { return this.explicit.orElseGet(other); } /** * Retrieve the explicit value if present, followed in precedence by whatever value, if not-null * supplied by the <code>preferred</code> parameter, followed by the default if all previous * options were null. * * @param preferred The intermediate value supplier. * @return The explicitly set, preferred or default value in that order, or possibly null. */ public T get(Supplier<? extends T> preferred) { return explicitOrElseGet(() -> { T value = preferred.get(); if (value != null) { return value; } return defaultValue.get(false); }); } /** * Determine if any value (default or explicit) is present. * * @return <code>true</code> is a value is present, otherwise <code>false</code>. */ public boolean isPresent() { return get(false) != null; } /** * Determine if either an explicit value is set of the supplied <code>other</code> <code>Supplier</code> parameter returns non-null. * * @param other The alternative value supplier. * @return <code>true</code> if either an explicit value is set or the supplier provides a non-null value. */ public boolean isExplicitPresent(Supplier<T> other) { return isExplicit() || other.get() != null; } /** * Determine if the value that would be provided (even if null) is the default value or not. * * @return <code>true</code> only if no explicit value is set. */ public boolean isDefault() { return !this.explicit.isPresent(); } /** * Determine if the value that would be provided is explicitly set or not. * * @return <code>true</code> only if an explicit value is set. */ public boolean isExplicit() { return this.explicit.isPresent(); } private static class DefaultValue<T> { private final Supplier<T> supplier; DefaultValue(Supplier<T> supplier) { this.supplier = supplier; } synchronized T get(boolean throwIfNull) { T value = supplier.get(); if (throwIfNull && value == null) { throw new NoSuchElementException("No default value present"); } return value; } } }