/** * Licensed to The Apereo Foundation under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * * The Apereo Foundation licenses this file to you under the Educational * Community License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License * at: * * http://opensource.org/licenses/ecl2.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * */ package org.opencastproject.assetmanager.api; import static com.entwinemedia.fn.Equality.eq; import static com.entwinemedia.fn.Equality.hash; import static java.lang.String.format; import com.entwinemedia.fn.Fn; import com.entwinemedia.fn.P1; import com.entwinemedia.fn.Prelude; import java.util.Date; import javax.annotation.Nonnull; import javax.annotation.ParametersAreNonnullByDefault; import javax.annotation.concurrent.Immutable; /** * A property value. * <p> * The wrapped type is not exposed as a generic type parameter since {@link Value}s appear in contexts like lists where this type information cannot be preserved. * To access the wrapped type one can choose between two options. * If the type is known, use {@link #get(ValueType)}. * If the type is not known, safely decompose the value with {@link #decompose(Fn, Fn, Fn, Fn, Fn)}. * </p> * The value type is a sum type made up from * <ul> * <li>{@link StringValue} * <li>{@link DateValue} * <li>{@link LongValue} * <li>{@link BooleanValue} * </ul> * <p> * Use one of the various <code>mk(..)</code> constructors to create a new {@link Value}. * * @see Property */ @ParametersAreNonnullByDefault public abstract class Value { public static final StringType STRING = new StringType(); public static final DateType DATE = new DateType(); public static final LongType LONG = new LongType(); public static final BooleanType BOOLEAN = new BooleanType(); public static final VersionType VERSION = new VersionType(); // TODO: rename to UNKNOWN public static final UntypedType UNTYPED = new UntypedType(); // public static final Class<UntypedValue> UNTYPED = UntypedValue.class; private Value() { } /** Get the wrapped value. */ protected abstract Object get(); /** * Get the wrapped value in a type safe way. Use this method if you are * sure about the contained value type. Otherwise consider the use * of {@link #decompose(Fn, Fn, Fn, Fn, Fn)}. * * @param ev * Evidence type. The type parameter <code>A</code> of the evidence type * must match the type of the wrapped value. Any other value will result * in an exception being thrown. * @throws java.lang.RuntimeException * if the passed evidence <code>ev</code> does not match the type of the wrapped value */ public final <A> A get(ValueType<A> ev) { if (getType().getClass().equals(ev.getClass())) { return (A) get(); } else { throw new RuntimeException(this + " is not a " + ev.getClass().getSimpleName()); } } public final ValueType<?> getType() { return decompose(new Fn<String, ValueType<?>>() { @Override public ValueType<?> apply(String a) { return STRING; } }, new Fn<Date, ValueType<?>>() { @Override public ValueType<?> apply(Date a) { return DATE; } }, new Fn<Long, ValueType<?>>() { @Override public ValueType<?> apply(Long a) { return LONG; } }, new Fn<Boolean, ValueType<?>>() { @Override public ValueType<?> apply(Boolean a) { return BOOLEAN; } }, new Fn<Version, ValueType<?>>() { @Override public ValueType<?> apply(Version a) { return VERSION; } }); } /** * Decompose (or pattern match) the value instance. Provide a function to handle each possible type. * Use {@link #doNotMatch()} as a placeholder that yields an {@link Prelude#unexhaustiveMatch()} error. */ public final <A> A decompose( Fn<? super String, ? extends A> stringValue, Fn<? super Date, ? extends A> dateValue, Fn<? super Long, ? extends A> longValue, Fn<? super Boolean, ? extends A> booleanValue, Fn<? super Version, ? extends A> versionValue) { if (this instanceof StringValue) { return stringValue.apply(((StringValue) this).get()); } else if (this instanceof DateValue) { return dateValue.apply(((DateValue) this).get()); } else if (this instanceof LongValue) { return longValue.apply(((LongValue) this).get()); } else if (this instanceof BooleanValue) { return booleanValue.apply(((BooleanValue) this).get()); } else if (this instanceof VersionValue) { return versionValue.apply(((VersionValue) this).get()); } else { // catch bug return Prelude.unexhaustiveMatch(this); } } // /** * Use as a placeholder that yields an {@link Prelude#unexhaustiveMatch()} error in * value decomposition. * * @see #decompose(Fn, Fn, Fn, Fn, Fn) */ public static <B> Fn<Object, B> doNotMatch() { return new Fn<Object, B>() { @Override public B apply(Object a) { return Prelude.unexhaustiveMatch(a); } }; } /* ------------------------------------------------------------------------------------------------------------------ */ // // Type evidence and factory classes // /** * ValueType gives evidence that type <code>A</code> is suitable for the creation * of a {@link Value}. * <p> * This is a more advanced version of the usual <code>Class<A></code> idiom. * A <code>ValueType</code> is also a constructor for {@link TypedValue}s of * the same type <code>A</code>. * * @param <A> the type to give evidence of */ public abstract static class ValueType<A> { /** It should not be possible to inherit from outside class {@link Value}. */ private ValueType() { } public abstract TypedValue<A> mk(A a); public abstract <B> B match( P1<? extends B> stringType, P1<? extends B> dateType, P1<? extends B> longType, P1<? extends B> booleanType, P1<? extends B> versionType); } public static final class StringType extends ValueType<String> { @Override public TypedValue<String> mk(String a) { return Value.mk(a); } @Override public <B> B match( P1<? extends B> stringType, P1<? extends B> dateType, P1<? extends B> longType, P1<? extends B> booleanType, P1<? extends B> versionType) { return stringType.get1(); } } public static final class DateType extends ValueType<Date> { @Override public TypedValue<Date> mk(Date a) { return Value.mk(a); } @Override public <B> B match( P1<? extends B> stringType, P1<? extends B> dateType, P1<? extends B> longType, P1<? extends B> booleanType, P1<? extends B> versionType) { return dateType.get1(); } } public static final class LongType extends ValueType<Long> { @Override public TypedValue<Long> mk(Long a) { return Value.mk(a); } @Override public <B> B match( P1<? extends B> stringType, P1<? extends B> dateType, P1<? extends B> longType, P1<? extends B> booleanType, P1<? extends B> versionType) { return longType.get1(); } } public static final class BooleanType extends ValueType<Boolean> { @Override public TypedValue<Boolean> mk(Boolean a) { return Value.mk(a); } @Override public <B> B match( P1<? extends B> stringType, P1<? extends B> dateType, P1<? extends B> longType, P1<? extends B> booleanType, P1<? extends B> versionType) { return booleanType.get1(); } } public static final class VersionType extends ValueType<Version> { @Override public TypedValue<Version> mk(Version a) { return Value.mk(a); } @Override public <B> B match( P1<? extends B> stringType, P1<? extends B> dateType, P1<? extends B> longType, P1<? extends B> booleanType, P1<? extends B> versionType) { return versionType.get1(); } } public static final class UntypedType extends ValueType<Object> { @Override public TypedValue<Object> mk(Object a) { throw new RuntimeException("Cannot create an untyped value"); } @Override public <B> B match( P1<? extends B> stringType, P1<? extends B> dateType, P1<? extends B> longType, P1<? extends B> booleanType, P1<? extends B> versionType) { throw new RuntimeException("Cannot match an untyped value type"); } } /* ------------------------------------------------------------------------------------------------------------------ */ // // Value classes // /** Helper type to reduce boilerplate code. */ // CHECKSTYLE:OFF -> class shall be public but not the constructor public static class TypedValue<A> extends Value { private final A value; /** It should not be possible to inherit from outside class {@link Value}. */ private TypedValue(@Nonnull A value) { this.value = value; } @Override public A get() { return value; } @Override public int hashCode() { return hash(value); } // generic implementation of equals // since all wrapped types cannot equal each other this is safe @Override public boolean equals(Object that) { return (this == that) || (that instanceof TypedValue && eqFields((TypedValue) that)); } private boolean eqFields(TypedValue that) { return eq(value, that.value); } @Override public String toString() { return format("%s(%s)", getClass().getSimpleName(), value); } } // CHECKSTYLE:ON /** * A value of type {@link String}. */ @Immutable public static final class StringValue extends TypedValue<String> { public StringValue(@Nonnull String value) { super(value); } } /** * A value of type {@link java.util.Date}. */ public static final class DateValue extends TypedValue<Date> { public DateValue(@Nonnull Date value) { super(value); } } /** * A value of type {@link java.lang.Long}. */ @Immutable public static final class LongValue extends TypedValue<Long> { public LongValue(@Nonnull Long value) { super(value); } } /** * A value of type {@link java.lang.Boolean}. */ @Immutable public static final class BooleanValue extends TypedValue<Boolean> { public BooleanValue(@Nonnull Boolean value) { super(value); } } /** * A value of type {@link Version}. */ @Immutable public static final class VersionValue extends TypedValue<Version> { public VersionValue(@Nonnull Version value) { super(value); } } /* ------------------------------------------------------------------------------------------------------------------ */ // // constructor methods // /** Create a new value of type {@link String}. */ public static StringValue mk(String value) { return new StringValue(value); } /** Create a new value of type {@link java.util.Date}. */ public static DateValue mk(Date value) { return new DateValue(value); } /** Create a new value of type {@link java.lang.Long}. */ public static LongValue mk(Long value) { return new LongValue(value); } /** Create a new value of type {@link java.lang.Boolean}. */ public static BooleanValue mk(Boolean value) { return new BooleanValue(value); } /** Create a new value of type {@link Version}. */ public static VersionValue mk(Version value) { return new VersionValue(value); } /** Generic constructor. Creates a value for any existing ValueType. */ public static <A> TypedValue<A> mk(ValueType<A> mk, A a) { return mk.mk(a); } }