All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opencastproject.assetmanager.api.Value Maven / Gradle / Ivy

The newest version!
/*
 * 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 java.lang.String.format;

import org.opencastproject.assetmanager.api.fn.Product;

import java.util.Date;
import java.util.Objects;
import java.util.function.Function;

import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.concurrent.Immutable;

/**
 * A property value.
 * 

* 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)}. *

* The value type is a sum type made up from *
    *
  • {@link StringValue} *
  • {@link DateValue} *
  • {@link LongValue} *
  • {@link BooleanValue} *
*

* Use one of the various mk(..) 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 UNTYPED = UntypedValue.class; private Value() { } /** Get the wrapped value. */ public 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 A 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 ev does not match the type of the wrapped value */ public final A get(ValueType 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 Function>() { @Override public ValueType apply(String a) { return STRING; } }, new Function>() { @Override public ValueType apply(Date a) { return DATE; } }, new Function>() { @Override public ValueType apply(Long a) { return LONG; } }, new Function>() { @Override public ValueType apply(Boolean a) { return BOOLEAN; } }, new Function>() { @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 error. */ public final A decompose( Function stringValue, Function dateValue, Function longValue, Function booleanValue, Function 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 throw new Error("Unexhaustive match: " + this); } } // /** * Use as a placeholder that yields an error in * value decomposition. * * @see #decompose(Fn, Fn, Fn, Fn, Fn) */ public static Function doNotMatch() { return new Function() { @Override public B apply(Object a) { throw new Error("Unexhaustive match: " + a); } }; } /* -------------------------------------------------------------------------------------------------------------- */ // // Type evidence and factory classes // /** * ValueType gives evidence that type A is suitable for the creation * of a {@link Value}. *

* This is a more advanced version of the usual Class<A> idiom. * A ValueType is also a constructor for {@link TypedValue}s of * the same type A. * * @param the type to give evidence of */ public abstract static class ValueType { /** It should not be possible to inherit from outside class {@link Value}. */ private ValueType() { } public abstract TypedValue mk(A a); public abstract B match( Product stringType, Product dateType, Product longType, Product booleanType, Product versionType); } public static final class StringType extends ValueType { @Override public TypedValue mk(String a) { return Value.mk(a); } @Override public B match( Product stringType, Product dateType, Product longType, Product booleanType, Product versionType) { return stringType.get1(); } } public static final class DateType extends ValueType { @Override public TypedValue mk(Date a) { return Value.mk(a); } @Override public B match( Product stringType, Product dateType, Product longType, Product booleanType, Product versionType) { return dateType.get1(); } } public static final class LongType extends ValueType { @Override public TypedValue mk(Long a) { return Value.mk(a); } @Override public B match( Product stringType, Product dateType, Product longType, Product booleanType, Product versionType) { return longType.get1(); } } public static final class BooleanType extends ValueType { @Override public TypedValue mk(Boolean a) { return Value.mk(a); } @Override public B match( Product stringType, Product dateType, Product longType, Product booleanType, Product versionType) { return booleanType.get1(); } } public static final class VersionType extends ValueType { @Override public TypedValue mk(Version a) { return Value.mk(a); } @Override public B match( Product stringType, Product dateType, Product longType, Product booleanType, Product versionType) { return versionType.get1(); } } public static final class UntypedType extends ValueType { @Override public TypedValue mk(Object a) { throw new RuntimeException("Cannot create an untyped value"); } @Override public B match( Product stringType, Product dateType, Product longType, Product booleanType, Product 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 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 Objects.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 Objects.equals(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 { public StringValue(@Nonnull String value) { super(value); } } /** * A value of type {@link java.util.Date}. */ public static final class DateValue extends TypedValue { public DateValue(@Nonnull Date value) { super(value); } } /** * A value of type {@link java.lang.Long}. */ @Immutable public static final class LongValue extends TypedValue { public LongValue(@Nonnull Long value) { super(value); } } /** * A value of type {@link java.lang.Boolean}. */ @Immutable public static final class BooleanValue extends TypedValue { public BooleanValue(@Nonnull Boolean value) { super(value); } } /** * A value of type {@link Version}. */ @Immutable public static final class VersionValue extends TypedValue { 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 TypedValue mk(ValueType mk, A a) { return mk.mk(a); } }