ExpressionType.java
package org.opentrafficsim.xml.bindings.types;
import java.util.Objects;
import java.util.function.Function;
import org.djutils.eval.Eval;
import org.djutils.exceptions.Throw;
/**
* ExpressionType is the parent class for all types in XML that need to be parsed with the JAXB generated classes, and which may
* be given in XML as an expression between { }. Adapters (extensions of {@code XmlAdapter}) have to deliver a subclass of this
* class, where only the generics type and constructors are usually defined. This is required as JAXB bindings do not allow
* generics types. This class takes care of returning a given value or the result of an evaluated expression for further XML
* parsing in the {@code get(InputParameters)} method. To this aim, there are two constructors; one to simply provide a value,
* and one to provide an expression.
* <p>
* Copyright (c) 2023-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
* BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
* </p>
* @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
* @param <T> wrapped/returned value type
*/
public abstract class ExpressionType<T>
{
/** Function to forward expression output as is. */
private static final Function<Object, ?> AS_IS = (o) -> o;
/** The value, when given. */
private final T value;
/** The expression, when given. */
private final String expression;
/** Function to convert output from expression to the right type. */
private final Function<Object, T> toType;
/**
* Constructor with value.
* @param value T; value.
*/
@SuppressWarnings("unchecked")
public ExpressionType(final T value)
{
// value may be null
this.value = value;
this.expression = null;
this.toType = (Function<Object, T>) AS_IS;
}
/**
* Constructor with value and type function.
* @param value T; value.
* @param toType Function<Object, T>; function to convert output from expression to the right type.
*/
public ExpressionType(final T value, final Function<Object, T> toType)
{
// value may be null
this.value = value;
this.expression = null;
this.toType = toType;
}
/**
* Constructor with expression.
* @param expression String; expression, without { }.
*/
@SuppressWarnings("unchecked")
public ExpressionType(final String expression)
{
Throw.whenNull(expression, "Expression may not be null. Consider using constructor with value.");
Throw.when(expression.contains("{") || expression.contains("}"), IllegalArgumentException.class,
"Expression should not have { }.");
this.value = null;
this.expression = expression;
this.toType = (Function<Object, T>) AS_IS;
}
/**
* Constructor with expression and type function.
* @param expression String; expression, without { }.
* @param toType Function<Object, T>; function to convert output from expression to the right type.
*/
public ExpressionType(final String expression, final Function<Object, T> toType)
{
Throw.whenNull(expression, "Expression may not be null. Consider using constructor with value.");
Throw.when(expression.contains("{") || expression.contains("}"), IllegalArgumentException.class,
"Expression should not have { }.");
this.value = null;
this.expression = expression;
this.toType = toType;
}
/**
* Constructor specifically for the subclass that has {@code T = String}, as this creates ambiguous constructors.
* @param input String; input, either the value or an expression, may be {@code null} as value.
* @param isExpression boolean; whether the input is an expression.
*/
@SuppressWarnings("unchecked")
ExpressionType(final String input, final boolean isExpression)
{
if (isExpression)
{
Throw.whenNull(input, "Expression may not be null.");
Throw.when(input.contains("{") || input.contains("}"), IllegalArgumentException.class,
"Expression should not have { }.");
this.value = null;
this.expression = input;
}
else
{
this.value = (T) input;
this.expression = null;
}
this.toType = (o) -> (T) o.toString();
}
/**
* Returns the value, either directly, or from an internal expression and using the input parameters.
* @param eval Eval; expression evaluator.
* @return value, either directly, or from an internal expression and using the input parameters
*/
public T get(final Eval eval)
{
return this.expression == null ? this.value : this.toType.apply(eval.evaluate(this.expression));
}
/**
* Returns whether this instance wraps an expression (or a value otherwise).
* @return boolean; whether this instance wraps an expression (or a value otherwise)
*/
public boolean isExpression()
{
return this.expression != null;
}
/**
* Returns the expression.
* @return String; expression.
*/
public String getExpression()
{
Throw.when(this.expression == null, IllegalStateException.class,
"Expression requested for expression type that wraps a value. Use !isExpression() to check.");
return this.expression;
}
/**
* Returns the expression enclosed in brackets { }. This is useful to marshal an expression value in an adapter.
* @return String; expression enclosed in brackets { }.
*/
public String getBracedExpression()
{
return "{" + getExpression() + "}";
}
/**
* Returns the wrapped value.
* @return wrapped value.
*/
public T getValue()
{
Throw.when(this.expression != null, IllegalStateException.class,
"Direct value requested for expression type that wraps an expression. Use isExpression() to check.");
return this.value;
}
/** {@inheritDoc} */
@Override
public int hashCode()
{
return Objects.hash(this.expression, this.value);
}
/** {@inheritDoc} */
@Override
public boolean equals(final Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
ExpressionType<?> other = (ExpressionType<?>) obj;
return Objects.equals(this.expression, other.expression) && Objects.equals(this.value, other.value);
}
}