CommonProperties.java
package org.klojang.check;
import org.klojang.check.types.Relation;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.function.*;
import static org.klojang.check.x.Misc.typeNotSupported;
import static org.klojang.check.x.msg.MsgUtil.*;
/**
* Defines various functions that can optionally be passed as the first argument to
* the {@code has(...)} and {@code notHas(...) } methods of {@link IntCheck} and
* {@link ObjectCheck}. These functions are associated with a description of the
* property they expose, so generating an error message requires very little
* hand-crafting. For example:
*
* <blockquote>
*
* <pre>{@code
* Check.that(car, "car").has(strval(), equalTo(), "BMW");
* // Error message: "car.toString() must be equal to BMW (was Toyota)"
* }</pre>
*
* </blockquote>
*
* <p>Note that the word "property" is suggestive, but also misleading. The
* functions defined here really are just that: functions that transform the argument
* into some other value, which can then be subjected to further tests.
*
* <blockquote>
*
* <pre>{@code
* Check.that(temperature, "temperature").has(abs(), lt(), 20);
* // Error message: "abs(temperature) must be < 20 (was -39)"
* }</pre>
*
* </blockquote>
*
* <p>As with the checks in the {@link CommonChecks} class, <i>none of the functions
* defined here execute a preliminary null check</i>. Many of them simply return a
* method reference. They rely upon being embedded in chain of checks, the first of
* which should be the {@link CommonChecks#notNull() notNull} check (if necessary).
*
* @author Ayco Holleman
*/
@SuppressWarnings("rawtypes")
public class CommonProperties {
private CommonProperties() {}
private static final Map<Object, BiFunction<Object, String, String>> NAMES;
private static Map<Object, BiFunction<Object, String, String>> tmp = new HashMap<>();
/**
* Returns the boxed version of the argument. Equivalent to
* {@link Integer#valueOf(int) Integer::valueOf}. This "property" is especially
* useful to get access to the many {@link Relation} checks in the
* {@link CommonChecks} class when validating an {@code int} argument:
*
* <blockquote>
*
* <pre>{@code
* // WON'T COMPILE! IntCheck does not have method is(Relation, Object)
* Check.that(42).is(keyIn(), map);
*
* // OK. ObjectCheck does have method is(Relation, Object)
* Check.that((Integer) 42).is(keyIn(), map);
*
* // More idiomatic:
* Check.that(42).has(box(), keyIn(), map);
*
* }</pre>
*
* </blockquote>
*
* @return The boxed version of the argument
*/
public static IntFunction<Integer> box() {
return Integer::valueOf;
}
static {
tmp.put(box(), (arg, argName) ->
"Integer.valueOf(" + ifNull(argName, DEF_ARG_NAME) + ")");
}
/**
* Returns the unboxed version of the argument. Equivalent to
* {@link Integer#intValue() Integer::intValue}.
*
* @return The unboxed version of the argument
*/
public static ToIntFunction<Integer> unbox() {
return Integer::intValue;
}
static {
tmp.put(unbox(), (arg, argName) ->
"Integer.intValue(" + ifNull(argName, DEF_ARG_NAME) + ")");
}
/**
* Returns the result of calling {@code toString()} on the argument. Equivalent to
* {@link Object#toString() Object::toString}.
*
* @param <T> The type of the object on which to call {@code toString{}}.
* @return The result of calling {@code toString()} on the argument
*/
public static <T> Function<T, String> strval() {
return Object::toString;
}
static {
tmp.put(strval(), (arg, argName) -> base(argName, arg) + ".toString()");
}
/**
* Returns the length of a {@code CharSequence}. Equivalent to
* {@code CharSequence::length}.
*
* @param <T> the type of the {@code CharSequence}
* @return The length of a {@code CharSequence}
*/
public static <T extends CharSequence> ToIntFunction<T> strlen() {
return CharSequence::length;
}
static {
tmp.put(strlen(), (arg, argName) -> base(argName, arg) + ".length()");
}
/**
* Returns the upper case version of the argument. Equivalent to
* {@link String#toUpperCase() String::toUpperCase}.
*
* @return The upper case version of the argument
*/
public static Function<String, String> toUpperCase() {
return String::toUpperCase;
}
static {
tmp.put(toUpperCase(), (arg, argName) -> base(argName, arg) + ".toUpperCase()");
}
/**
* Returns the lower case version of the argument. Equivalent to
* {@link String#toLowerCase() String::toLowerCase}.
*
* @return The lower case version of the argument
*/
public static Function<String, String> toLowerCase() {
return String::toLowerCase;
}
static {
tmp.put(toLowerCase(), (arg, argName) -> base(argName, arg) + ".toLowerCase()");
}
/**
* Returns the {@code Class} of the argument. Equivalent to
* {@link Object#getClass() Object::getClass}.
*
* @param <T> The type of the object
* @return The {@code Class} of the argument
*/
public static <T> Function<T, Class<?>> type() {
return Object::getClass;
}
static {
tmp.put(type(), (arg, argName) -> base(argName, arg) + ".getClass()");
}
/**
* Returns the constants of an enum class. Equivalent to
* {@link Class#getEnumConstants() Class::getEnumConstants}.
*
* @param <T> The enum class
* @return The constants of an enum class
*/
public static <T extends Enum<T>> Function<Class<T>, T[]> constants() {
return x -> (T[]) x.getEnumConstants();
}
static {
tmp.put(constants(),
(arg, argName) -> base(argName, arg) + ".getEnumConstants()");
}
/**
* Returns the name of an enum constant. Equivalent to
* {@link Enum#name() Enum::name}.
*
* @param <T> The type of the enum class
* @return The name of the enum constant
*/
public static <T extends Enum<?>> Function<T, String> name() {
return Enum::name;
}
static {
tmp.put(name(), (arg, argName) -> base(argName, arg) + ".name()");
}
/**
* Returns the ordinal of an enum constant. Equivalent to
* {@link Enum#ordinal() Enum::ordinal}.
*
* @param <T> The type of the enum class
* @return The ordinal of the enum constant
*/
public static <T extends Enum<?>> ToIntFunction<T> ordinal() {
return Enum::ordinal;
}
static {
tmp.put(ordinal(), (arg, argName) -> base(argName, arg) + ".ordinal()");
}
/**
* A function that returns the length of an array argument.
*
* @param <T> The type of the array.
* @return A {@code Function} that returns the length of an array
*/
public static <T> ToIntFunction<T> length() {
return Array::getLength;
}
static {
tmp.put(length(), (arg, argName) -> base(argName, arg) + ".length");
}
/**
* Returns the size of a {@code Collection} argument. Equivalent to
* {@code Collection::size}.
*
* @param <C> The type of the {@code Collection}
* @return The size of a {@code Collection} argument
*/
public static <C extends Collection<?>> ToIntFunction<C> size() {
return Collection::size;
}
static {
tmp.put(size(), (arg, argName) -> base(argName, arg) + ".size()");
}
/**
* Returns the size of a {@code List} argument. Equivalent to {@code List::size}.
*
* @param <L> The type of the {@code List}
* @return Returns the size of a {@code List} argument
*/
public static <L extends List<?>> ToIntFunction<L> listSize() {
return List::size;
}
static {
tmp.put(listSize(), tmp.get(size()));
}
/**
* Returns the size of a {@code Set} argument. Equivalent to {@code Set::size}.
*
* @param <S> The type of the {@code Set}.
* @return The size of a {@code Set} argument
*/
public static <S extends Set<?>> ToIntFunction<S> setSize() {
return Set::size;
}
static {
tmp.put(setSize(), tmp.get(size()));
}
/**
* Returns the size of a {@code Map} argument. Equivalent to {@code Map::size}.
*
* @param <M> The type of the {@code Map}
* @return The size of a {@code Map} argument
*/
public static <M extends Map<?, ?>> ToIntFunction<M> mapSize() {
return Map::size;
}
static {
tmp.put(mapSize(), tmp.get(size()));
}
/**
* Returns the keys of a {@code Map} argument. Equivalent to
* {@link Map#keySet() Map::keySet}.
*
* @param <K> The type of the keys in the map
* @param <V> The type of the values in the map
* @param <M> The type of the map
* @return The keys of a {@code Map} argument
*/
public static <K, V, M extends Map<K, V>> Function<M, Set<K>> keySet() {
return Map::keySet;
}
static {
tmp.put(keySet(),
(arg, argName) -> base(argName, arg) + ".keySet()");
}
/**
* Returns the keys of a {@code Map} argument. Equivalent to
* {@link Map#values() Map::values}.
*
* @param <K> The type of the keys in the map
* @param <V> The type of the values in the map
* @param <M> The type of the map
* @return The values of a {@code Map} argument
*/
public static <K, V, M extends Map<K, V>> Function<M, Collection<V>> values() {
return Map::values;
}
static {
tmp.put(values(), (arg, argName) -> base(argName, arg) + ".values()");
}
/**
* Returns the key of a {@code Map} entry. Equivalent to
* {@code Map.Entry::getKey}.
*
* @param <K> The type of the key of the entry
* @param <V> The type of the value of the entry
* @return The key of a {@code Map} entry
*/
public static <K, V> Function<Map.Entry<K, V>, K> key() {
return Map.Entry::getKey;
}
static {
tmp.put(key(), (arg, argName) -> base(argName, arg) + ".getKey()");
}
/**
* Returns the value of a {@code Map} entry. Equivalent to
* {@code Map.Entry::getValue}.
*
* @param <K> The type of the key of the entry
* @param <V> The type of the value of the entry
* @return A {@code Function} that returns the value of a {@code Map} entry
*/
public static <K, V> Function<Map.Entry<K, V>, V> value() {
return Map.Entry::getValue;
}
static {
tmp.put(value(), (arg, argName) -> base(argName, arg) + ".getValue()");
}
/**
* Returns the absolute value of an {@code int} argument. Equivalent to
* {@link Math#abs(int) Math::abs}.
*
* @return A {@code Function} that returns the absolute value of an integer
*/
public static IntUnaryOperator abs() {
return Math::abs;
}
static {
tmp.put(abs(),
(arg, argName) -> "abs(" + ifNull(argName, DEF_ARG_NAME) + ")");
}
//@formatter:off
private static final Map<Class<?>, UnaryOperator<? extends Number>> absFunctions = Map.of(
Integer.class, n -> n.intValue() >= 0 ? n : Integer.valueOf(-n.intValue()),
Double.class, n -> n.doubleValue() >= 0 ? n : Double.valueOf(-n.doubleValue()),
Long.class, n -> n.longValue() >= 0 ? n : Long.valueOf(-n.longValue()),
Float.class, n -> n.floatValue() >= 0 ? n : Float.valueOf(-n.floatValue()),
Short.class, n -> n.shortValue() >= 0 ? n : Short.valueOf((short) -n.shortValue()),
Byte.class, n -> n.byteValue() >= 0 ? n : Byte.valueOf((byte) -n.byteValue()),
BigInteger.class, n -> ((BigInteger) n).abs(),
BigDecimal.class, n -> ((BigDecimal) n).abs());
//@formatter:on
/**
* Returns the absolute value of a {@code Number}. The following {@code Number}
* types are supported: {@code Integer}, {@code Double}, {@code Long},
* {@code Float}, {@code Short}, {@code Byte}, {@code BigInteger},
* {@code BigDecimal}.
*
* @param <T> The type of the {@code Number}
* @return The absolute value of a {@code Number}
*/
@SuppressWarnings("unchecked")
public static <T extends Number> Function<T, T> ABS() {
return n -> {
UnaryOperator op = absFunctions.get(n.getClass());
if (op != null) {
return (T) op.apply(n);
}
throw typeNotSupported(n.getClass());
};
}
static {
tmp.put(ABS(), (arg, argName) -> "abs(" + base(argName, arg) + ")");
}
/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
/* End of getter definitions */
/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
static String formatProperty(Object arg,
String argName,
Object getter,
Class getterClass) {
BiFunction<Object, String, String> fmt = NAMES.get(getter);
if (fmt == null) {
String s0 = getterClass == ToIntFunction.class ? "applyAsInt" : "apply";
return simpleClassName(getterClass) + "." + s0 + "(" + toStr(arg) + ")";
}
return fmt.apply(arg, argName);
}
static String formatProperty(int arg,
String argName,
Object getter,
Class getterClass) {
BiFunction<Object, String, String> fmt = NAMES.get(getter);
if (fmt == null) {
String s0 = getterClass == IntUnaryOperator.class ? "applyAsInt" : "apply";
return simpleClassName(getterClass) + "." + s0 + "(" + arg + ")";
}
return fmt.apply(arg, argName);
}
static {
NAMES = Map.copyOf(tmp);
tmp = null;
}
private static String base(String argName, Object arg) {
return ifNull(argName, simpleClassName(arg));
}
private static String ifNull(String value, String defVal) {
return value == null ? defVal : value;
}
}