sirius.kernel.commons.Value Maven / Gradle / Ivy
/*
* Made with all the love in the world
* by scireum in Remshalden, Germany
*
* Copyright by scireum GmbH
* http://www.scireum.de - [email protected]
*/
package sirius.kernel.commons;
import sirius.kernel.health.Exceptions;
import sirius.kernel.nls.NLS;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.math.BigDecimal;
import java.math.MathContext;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.TemporalAccessor;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Pattern;
/**
* Provides a generic wrapper for a value which is read from an untyped context
* like HTTP parameters.
*
* It supports elegant {@code null} handling and type
* conversions.
*/
public class Value {
/**
* Represents an empty value which contains null as data.
*/
public static final Value EMPTY = new Value();
private static final Pattern NUMBER = Pattern.compile("-?\\d+(\\.\\d+)?");
private Object data;
/**
* Use {@code Amount.of} to create a new instance.
*/
private Value() {
super();
}
/**
* Creates a new wrapper for the given data.
*
* @param data the object wrap
* @return the newly created value which wraps the given data object
*/
@Nonnull
public static Value of(@Nullable Object data) {
if (data == null) {
return EMPTY;
} else if (data instanceof Value) {
return (Value) data;
}
Value val = new Value();
val.data = data;
return val;
}
/**
* Returns the n-th (index-th) element of the given collection.
*
* @param index the zero based index of the element to fetch
* @param collection the collection to pick the element from
* @return the element at index wrapped as Value
* or an empty value if the collection is null or if the index is outside of the collections bounds
*/
@Nonnull
public static Value indexOf(int index, @Nullable Collection> collection) {
if (collection == null || index < 0 || index >= collection.size()) {
return Value.of(null);
}
if (collection instanceof List) {
return Value.of(((List>) collection).get(index));
}
return Value.of(collection.stream().skip(index).findFirst().orElse(null));
}
/**
* Returns the n-th (index-th) element of the given array.
*
* @param index the zero based index of the element to fetch
* @param array the array to pick the element from
* @return the element at index wrapped as Value
* or an empty value if the array is null or if the index is outside of the arrays bounds
*/
@Nonnull
public static Value indexOf(int index, @Nullable Object[] array) {
if (array == null || index < 0 || index >= array.length) {
return Value.of(null);
}
return Value.of(array[index]);
}
/**
* Determines if the wrapped value is null
*
* @return true if the wrapped value is null, false otherwise
*/
public boolean isNull() {
return data == null;
}
/**
* Determines if the wrapped value is an empty string.
*
* @return true if the wrapped value is an empty string, false otherwise
*/
public boolean isEmptyString() {
return Strings.isEmpty(data);
}
/**
* Checks if the given value is filled and contains the given needle in its
* string representation
*
* @param needle the substring to search
* @return true if the given substring needle was found in the wrapped objects string
* representation, false otherwise
*/
public boolean contains(String needle) {
return asString("").contains(needle);
}
/**
* Determines if the wrapped value is not null.
*
* @return true if the wrapped value is neither null nor ""
*/
public boolean isFilled() {
return !isEmptyString();
}
/**
* Calls the given consumer with this if the value is filled.
*
* @param consumer the consumer to call with this object if it is filled
* @return the value itself for fluent method calls
*/
public Value ifFilled(Consumer consumer) {
if (isFilled()) {
consumer.accept(this);
}
return this;
}
/**
* Returns a new {@link Value} which will be empty its value equals one of the given ignored values.
*
* @param ignoredValues the list of values which will be replaced by an empty value
* @return a Value which is empty if the currently wrapped value equals to one of the given values.
* Otherwise the current value is returned.
*/
@Nonnull
public Value ignore(String... ignoredValues) {
if (isEmptyString()) {
return this;
}
for (String val : ignoredValues) {
if (data.equals(val)) {
return Value.EMPTY;
}
}
return this;
}
/**
* Returns a new Value which will wrap the given value, if the current value is empty.
* Otherwise, the current value will be returned.
*
* @param replacement the value which is used as replacement if this value is empty
* @return a new Value wrapping the given value or the current value if this is not empty.
*/
@Nonnull
public Value replaceEmptyWith(@Nullable Object replacement) {
if (isFilled()) {
return this;
}
return Value.of(replacement);
}
/**
* Returns a new Value which will wrap the value produced by the given supplier, if the current
* value is empty. Otherwise, the current value will be returned.
*
* @param supplier the supplier used to compute a replacement value if this value is empty
* @return a new Value wrapping the produced value of the given supplier or the current value if this is not empty.
*/
@Nonnull
public Value replaceIfEmpty(@Nonnull Supplier> supplier) {
if (isFilled()) {
return this;
}
return Value.of(supplier.get());
}
/**
* Returns an optional value computed by the given mapper. If this value is empty the mapper will not
* be called, but an empty optional will be returned.
*
* @param mapper the function used to convert the value into the desired object
* @param the type of the desired result
* @return an Optional object wrapping the result of the computation or an empty Optional, if the value wasn't
* filled
*/
@Nonnull
public Optional map(@Nonnull Function mapper) {
if (isFilled()) {
return Optional.ofNullable(mapper.apply(this));
} else {
return Optional.empty();
}
}
/**
* Boilerplate for {@code Optional.ofNullable(get(type, null))}.
*
* Returns the internal value wrapped as Optional.
*
* @param type the desired type of the data
* @param the expected type of the contents of this value
* @return the internal value (casted to the given type) wrapped as Optional or an empty Optional if the value
* was empty or the cast failed.
*/
@Nonnull
public Optional asOptional(@Nonnull Class type) {
return Optional.ofNullable(get(type, null));
}
/**
* Returns the internal value wrapped as Optional while expecting it to be an integer number.
*
* If the value is empty or not an integer, an empty Optional will be returned.
*
* @return the internal value wrapped as Optional or an empty Optional if the value is not filled or non-integer
*/
public Optional asOptionalInt() {
return isFilled() ? Optional.of(getInteger()) : Optional.empty();
}
/**
* Returns the internal value wrapped as Optional.
*
* If the value is empty, an empty Optional will be returned.
*
* @return the internal value wrapped as Optional or an empty Optional if the value is not filled
*/
public Optional asOptionalString() {
return isFilled() ? Optional.of(asString()) : Optional.empty();
}
/**
* Returns a value which wraps {@code this + separator + value}
*
* If the current value is empty, the given value is returned (without the separator). If the given
* value is an empty string, the current value is returned (without the separator).
*
* @param separator the separator to be put in between the two. If the given value is null, "" is assumed
* @param value the value to be appended to the current value.
* @return a Value representing the current value appended with the given value and separated
* with the given separator
*/
@Nonnull
public Value append(@Nullable String separator, @Nullable Object value) {
if (Strings.isEmpty(value)) {
return this;
}
if (isEmptyString()) {
return Value.of(value);
}
if (separator == null) {
separator = "";
}
return Value.of(toString() + separator + value);
}
/**
* Returns a value which wraps {@code value + separator + this}
*
* If the current value is empty, the given value is returned (without the separator). If the given
* value is an empty string, the current value is returned (without the separator).
*
* @param separator the separator to be put in between the two. If the given value is null, "" is assumed
* @param value the value to be appended to the current value.
* @return a Value representing the given value appended with the current value and separated
* with the given separator
*/
@Nonnull
public Value prepend(@Nullable String separator, @Nullable Object value) {
if (Strings.isEmpty(value)) {
return this;
}
if (isEmptyString()) {
return Value.of(value);
}
if (separator == null) {
separator = "";
}
return Value.of(value + separator + toString());
}
/**
* Cuts and returns the first n given characters of the string representation of this value.
*
* Note: This modifies the internal state of this value, since the number of characters is cut from
* the string representation of the current object and the remaining string is stored as new internal value.
*
* If the wrapped value is empty, "" is returned. If the string representation of the wrapped object
* is shorter than maxNumberOfCharacters, the remaining string is returned and the internal value is set to
* null.
*
* This can be used to cut a string into sub strings of a given length:
*
* {@code
* Value v = Value.of("This is a long string...");
* while(v.isFilled()) {
* System.out.println("Up to 5 chars of v: "+v.eat(5));
* }
* }
*
*
* @param maxNumberOfCharacters the max length of the string to cut from the wrapped value
* @return the first maxNumberOfCharacters of the wrapped values string representation, or less if it is shorter.
* Returns "" if the wrapped value is empty.
*/
@Nonnull
public String eat(int maxNumberOfCharacters) {
if (isEmptyString()) {
return "";
}
String value = asString();
if (value.length() < maxNumberOfCharacters) {
data = null;
return value;
}
data = value.substring(maxNumberOfCharacters);
return value.substring(0, maxNumberOfCharacters);
}
/**
* Checks if the current value is numeric (integer or double).
*
* @return true if the wrapped value is either a {@link Number} or an {@link Amount} or
* if it is a string which can be converted to a long or double
*/
public boolean isNumeric() {
return data != null && (data instanceof Number || data instanceof Amount || NUMBER.matcher(asString(""))
.matches());
}
/**
* Returns the wrapped object
*
* @return the wrapped object of this Value
*/
@Nullable
public Object get() {
return data;
}
/**
* Returns the internal data or the given defaultValue
*
* @param defaultValue the value to use if the inner value is null
* @return the wrapped value or the given defaultValue if the wrapped value is null
*/
@Nonnull
public Object get(@Nonnull Object defaultValue) {
return data == null ? defaultValue : data;
}
/**
* If the underlying data is a {@link Collection} this will return the first element wrapped as value. Otherwise
* this is returned.
*
* @return the first element of the underlying collection or the element itself wrapped as value
*/
@Nonnull
public Value first() {
if (data instanceof Collection) {
Iterator> iter = ((Collection>) data).iterator();
if (iter.hasNext()) {
return Value.of(iter.next());
}
return EMPTY;
}
return this;
}
/**
* Converts or casts the wrapped object to the given targetClazz
*
* @param targetClazz the desired class to which the wrapped value should be converted or casted.
* @param defaultValue the default value if the wrapped object is empty or cannot be cast to the given target.
* @param the type to coerce to
* @return a converted instance of type targetClass or the defaultValue if no conversion was possible
* @throws IllegalArgumentException if the given targetClazz is unknown
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public T coerce(Class targetClazz, T defaultValue) {
if (boolean.class.equals(targetClazz) && defaultValue == null) {
if (Strings.isEmpty(data)) {
return (T) Boolean.FALSE;
}
return (T) NLS.parseUserString(Boolean.class, String.valueOf(data));
}
if (data == null) {
return defaultValue;
}
if (targetClazz.isAssignableFrom(data.getClass())) {
return (T) data;
}
return continueCoerceWithBasicTypes(targetClazz, defaultValue);
}
@SuppressWarnings("unchecked")
private T continueCoerceWithBasicTypes(Class targetClazz, T defaultValue) {
if (Integer.class.equals(targetClazz) || int.class.equals(targetClazz)) {
if (data instanceof Double) {
return (T) (Integer) ((Long) Math.round((Double) data)).intValue();
}
return (T) getInteger();
}
if (Long.class.equals(targetClazz) || long.class.equals(targetClazz)) {
if (data instanceof Double) {
return (T) (Long) Math.round((Double) data);
}
return (T) getLong();
}
if (String.class.equals(targetClazz)) {
return (T) NLS.toMachineString(data);
}
if (BigDecimal.class.equals(targetClazz)) {
return (T) getBigDecimal();
}
if (Amount.class.equals(targetClazz)) {
return (T) getAmount();
}
return continueCoerceWithDateTypes(targetClazz, defaultValue);
}
@SuppressWarnings("unchecked")
private T continueCoerceWithDateTypes(Class targetClazz, T defaultValue) {
if (LocalDate.class.equals(targetClazz)) {
if (is(TemporalAccessor.class, Calendar.class, Date.class, java.sql.Date.class, Timestamp.class)) {
return (T) asLocalDate((LocalDate) defaultValue);
}
}
if (LocalDateTime.class.equals(targetClazz)) {
if (is(TemporalAccessor.class, Calendar.class, Date.class, java.sql.Date.class, Timestamp.class)) {
return (T) asLocalDateTime((LocalDateTime) defaultValue);
}
}
if (ZonedDateTime.class.equals(targetClazz)) {
if (is(TemporalAccessor.class, Calendar.class, Date.class, java.sql.Date.class, Timestamp.class)) {
return (T) asZonedDateTime((ZonedDateTime) defaultValue);
}
}
if (LocalTime.class.equals(targetClazz) && data instanceof TemporalAccessor) {
if (is(TemporalAccessor.class,
Calendar.class,
Date.class,
java.sql.Date.class,
Timestamp.class,
Time.class)) {
return (T) asLocalTime((LocalTime) defaultValue);
}
}
return continueCoerceWithEnumTypes(targetClazz, defaultValue);
}
@SuppressWarnings({"unchecked", "rawtypes"})
private T continueCoerceWithEnumTypes(Class targetClazz, T defaultValue) {
if (targetClazz.isEnum()) {
try {
if (Strings.isEmpty(asString(""))) {
return defaultValue;
}
return (T) Enum.valueOf((Class) targetClazz, asString(""));
} catch (Exception e) {
Exceptions.ignore(e);
return (T) Enum.valueOf((Class) targetClazz, asString("").toUpperCase());
}
}
return continueCoerceWithConversion(targetClazz, defaultValue);
}
private T continueCoerceWithConversion(Class targetClazz, T defaultValue) {
if (data instanceof String) {
try {
return NLS.parseMachineString(targetClazz, data.toString().trim());
} catch (Exception e) {
Exceptions.ignore(e);
return defaultValue;
}
}
throw new IllegalArgumentException(Strings.apply("Cannot convert '%s' to target class: %s ",
data,
targetClazz));
}
/**
* Returns the wrapped value if it is an instance of the given clazz or the defaultValue otherwise.
*
* @param clazz the desired class of the return type
* @param defaultValue the value which is returned if the wrapped value is not assignable to the given class.
* @param the expected type of the wrapped value
* @return the wrapped value if the given clazz is assignable from wrapped values class
* or the defaultValue otherwise
*/
@SuppressWarnings("unchecked")
public V get(Class clazz, V defaultValue) {
Object result = get(defaultValue);
if (!clazz.isAssignableFrom(result.getClass())) {
return defaultValue;
}
return (V) result;
}
/**
* Returns the data converted to a string, or null if the wrapped value is null
*
* The conversion method used is {@link NLS#toMachineString(Object)}
*
* @return a string representation of the wrapped object or null if the wrapped value is null
*/
@Nullable
public String getString() {
return isNull() ? null : NLS.toMachineString(data);
}
/**
* Returns the wrapped data converted to a string or defaultValue if the wrapped value is null
*
* The conversion method used is {@link NLS#toMachineString(Object)}
*
* @param defaultValue the value to use if the wrapped object was null
* @return a string representation of the wrapped object
* or defaultValue if the wrapped value is null
*/
@Nonnull
public String asString(@Nonnull String defaultValue) {
return isNull() ? defaultValue : NLS.toMachineString(data);
}
/**
* Returns the wrapped data converted to a string or "" if the wrapped value is null
*
* The conversion method used is {@link NLS#toMachineString(Object)}
*
* @return a string representation of the wrapped object or "" if the wrapped value is null
*/
@Nonnull
public String asString() {
return NLS.toMachineString(data);
}
@Override
public String toString() {
return asString();
}
/**
* Returns the wrapped data converted to a string like {@link #asString()}
* while "smart rounding" ({@link NLS#smartRound(double)} Double and BigDecimal values.
*
* This method behaves just like asString, except for Double and BigDecimal values
* where the output is "smart rounded". Therefore, 12.34 will be formatted as {@code 12.34} but 1.000 will
* be formatted as {@code 1}
*
* @return a string representation of the wrapped object as generated by asString
* except for Double or BigDecimal values, which are "smart rounded".
* @see NLS#smartRound(double)
*/
@Nonnull
public String asSmartRoundedString() {
if (data == null) {
return "";
}
if (data instanceof Double) {
return NLS.smartRound((Double) data);
}
if (data instanceof BigDecimal) {
return NLS.smartRound(((BigDecimal) data).doubleValue());
}
return asString();
}
/**
* Converts the wrapped value to a boolean or returns the given defaultValue
* if no conversion is possible.
*
* To convert a value, {@link Boolean#parseBoolean(String)} is used, where {@code toString} is called on all
* non-string objects.
*
* @param defaultValue the value to be used if the wrapped value cannot be converted to a boolean.
* @return true if the wrapped value is true
* or if the string representation of it is {@code "true"}. Returns false otherwise,
* especially if the wrapped value is null
*/
public boolean asBoolean(boolean defaultValue) {
if (isNull() || Strings.isEmpty(data)) {
return defaultValue;
}
if (data instanceof Boolean) {
return (Boolean) data;
}
return NLS.parseUserString(Boolean.class, String.valueOf(data).trim());
}
/**
* Boilerplate method for {@code asBoolean(false)}
*
* @return true if the wrapped value is true
* or if the string representation of it is {@code "true"}. Returns false otherwise,
* especially if the wrapped value is null
*/
public boolean asBoolean() {
return asBoolean(false);
}
/**
* Returns the int value for the wrapped value or defaultValue if the wrapped value isn't an integer and
* cannot be converted to one.
*
* If the wrapped value is an Integer or BigDecimal, it is either directly returned or converted
* by calling {@link java.math.BigDecimal#longValue()}.
*
* Otherwise {@link Integer#parseInt(String)} is called on the string representation of the wrapped value. If
* parsing fails, or if the wrapped value was null, the defaultValue will be returned.
*
* @param defaultValue the value to be used, if no conversion to int is possible.
* @return the wrapped value casted or converted to int or defaultValue
* if no conversion is possible.
*/
public int asInt(int defaultValue) {
try {
if (isNull()) {
return defaultValue;
}
if (data instanceof Integer) {
return (Integer) data;
}
if (data instanceof BigDecimal) {
return (int) ((BigDecimal) data).longValue();
}
return Integer.parseInt(String.valueOf(data).trim());
} catch (NumberFormatException e) {
Exceptions.ignore(e);
return defaultValue;
}
}
/**
* Tries to convert the wrapped value to a roman numeral representation
* This only works if the wrapped value can be converted to int and is >0 and <4000.
*
* @param defaultValue the value to be converted to roman numeral if the wrapped value can not be converted
* @return a roman numeral representation of either the wrapped value or the defaultValue. values >=4000 and
* <=0 are represented as an empty String
*/
public String asRomanNumeral(int defaultValue) {
return RomanNumeral.toRoman(asInt(defaultValue));
}
/**
* Returns the int value for the wrapped value or null if the wrapped value isn't an integer and
* cannot be converted to one.
*
* If the wrapped value is an Integer or BigDecimal, it is either directly returned or converted
* by calling {@link java.math.BigDecimal#longValue()}.
*
* Otherwise {@link Integer#parseInt(String)} is called on the string representation of the wrapped value. If
* parsing fails, or if the wrapped value was null, null will be returned.
*
* @return the wrapped value casted or converted to Integer or null
* if no conversion is possible.
*/
@Nullable
public Integer getInteger() {
try {
if (isNull()) {
return null;
}
if (data instanceof Integer) {
return (Integer) data;
}
if (data instanceof BigDecimal) {
return (int) ((BigDecimal) data).longValue();
}
return Integer.parseInt(String.valueOf(data).trim());
} catch (NumberFormatException e) {
Exceptions.ignore(e);
return null;
}
}
/**
* Returns the long value for the wrapped value or defaultValue if the wrapped value isn't a long and
* cannot be converted to one.
*
* If the wrapped value is a Long, Integer or BigDecimal,
* it is either directly returned or converted by calling {@link java.math.BigDecimal#longValue()}.
*
* Otherwise {@link Long#parseLong(String)} is called on the string representation of the wrapped value. If
* parsing fails, or if the wrapped value was null, the defaultValue will be returned.
*
* @param defaultValue the value to be used, if no conversion to long is possible.
* @return the wrapped value casted or converted to long or defaultValue
* if no conversion is possible.
*/
public long asLong(long defaultValue) {
try {
if (isNull()) {
return defaultValue;
}
if (data instanceof Long) {
return (Long) data;
}
if (data instanceof Integer) {
return (Integer) data;
}
if (data instanceof BigDecimal) {
return ((BigDecimal) data).longValue();
}
return Long.parseLong(String.valueOf(data).trim());
} catch (NumberFormatException e) {
Exceptions.ignore(e);
return defaultValue;
}
}
/**
* Returns the long value for the wrapped value or null if the wrapped value isn't a long and
* cannot be converted to one.
*
* If the wrapped value is a Long, Integer or BigDecimal, it is either directly
* returned or by calling {@link java.math.BigDecimal#longValue()}.
*
* Otherwise {@link Long#parseLong(String)} is called on the string representation of the wrapped value. If
* parsing fails, or if the wrapped value was null, null will be returned.
*
* @return the wrapped value casted or converted to Long or null
* if no conversion is possible.
*/
@Nullable
public Long getLong() {
try {
if (isNull()) {
return null;
}
if (data instanceof Long) {
return (Long) data;
}
return Long.parseLong(String.valueOf(data).trim());
} catch (NumberFormatException e) {
Exceptions.ignore(e);
return null;
}
}
/**
* Returns the double value for the wrapped value or defaultValue if the wrapped value isn't a double and
* cannot be converted to one.
*
* If the wrapped value is a Double, Long, Integer or BigDecimal,
* it is either directly returned or converted by calling {@link java.math.BigDecimal#doubleValue()}.
*
* Otherwise {@link Double#parseDouble(String)} is called on the string representation of the wrapped value. If
* parsing fails, or if the wrapped value was null, the defaultValue will be returned.
*
* @param defaultValue the value to be used, if no conversion to double is possible.
* @return the wrapped value casted or converted to double or defaultValue
* if no conversion is possible.
*/
public double asDouble(double defaultValue) {
try {
if (isNull()) {
return defaultValue;
}
if (data instanceof Double) {
return (Double) data;
}
if (data instanceof Long) {
return (Long) data;
}
if (data instanceof Integer) {
return (Integer) data;
}
if (data instanceof BigDecimal) {
return ((BigDecimal) data).doubleValue();
}
return Double.parseDouble(String.valueOf(data).trim());
} catch (NumberFormatException e) {
Exceptions.ignore(e);
return defaultValue;
}
}
/**
* Returns the wrapped value as {@link java.time.LocalDate} or defaultValue if the wrapped value
* cannot be converted.
*
* If the wrapped value is an Instant, LocalDateTime, ZonedDateTime,
* Date, Calendar, java.sql.Date, Timestamp or long,
* it is converted to a {@link java.time.LocalDate}.
*
* @param defaultValue the value to be used, if no conversion is possible
* @return the wrapped value casted or converted to LocalDate or defaultValue
* if no conversion is possible.
*/
public LocalDate asLocalDate(LocalDate defaultValue) {
if (data == null) {
return defaultValue;
}
if (is(Instant.class)) {
return LocalDate.from((Instant) data);
}
if (is(LocalDate.class)) {
return (LocalDate) data;
}
if (is(LocalDateTime.class)) {
return ((LocalDateTime) data).toLocalDate();
}
if (is(ZonedDateTime.class)) {
return ((ZonedDateTime) data).withZoneSameInstant(ZoneId.systemDefault()).toLocalDate();
}
if (is(Date.class)) {
return Instant.ofEpochMilli(((Date) data).getTime()).atZone(ZoneId.systemDefault()).toLocalDate();
}
if (is(Calendar.class)) {
return Instant.ofEpochMilli(((Calendar) data).getTimeInMillis())
.atZone(ZoneId.systemDefault())
.toLocalDate();
}
if (is(java.sql.Date.class)) {
return Instant.ofEpochMilli(((java.sql.Date) data).getTime()).atZone(ZoneId.systemDefault()).toLocalDate();
}
if (is(Timestamp.class)) {
return Instant.ofEpochMilli(((java.sql.Timestamp) data).getTime())
.atZone(ZoneId.systemDefault())
.toLocalDate();
}
if (is(long.class) || is(Long.class)) {
return Instant.ofEpochMilli((long) data).atZone(ZoneId.systemDefault()).toLocalDate();
}
return defaultValue;
}
/**
* Returns the wrapped value as {@link java.time.LocalDateTime} or defaultValue if the wrapped value
* cannot be converted.
*
* If the wrapped value is an Instant, LocalDateTime, ZonedDateTime,
* Date, Calendar, java.sql.Date, Timestamp or long,
* it is converted to a {@link java.time.LocalDateTime}.
*
* @param defaultValue the value to be used, if no conversion is possible
* @return the wrapped value casted or converted to LocalDateTime or defaultValue
* if no conversion is possible.
*/
public LocalDateTime asLocalDateTime(LocalDateTime defaultValue) {
if (data == null) {
return defaultValue;
}
if (is(Instant.class)) {
return LocalDateTime.from((Instant) data);
}
if (is(LocalDateTime.class)) {
return (LocalDateTime) data;
}
if (is(LocalTime.class)) {
return ((LocalTime) data).atDate(LocalDate.now());
}
if (is(ZonedDateTime.class)) {
return ((ZonedDateTime) data).withZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime();
}
if (is(Date.class)) {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(((Date) data).getTime()), ZoneId.systemDefault());
}
if (is(Calendar.class)) {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(((Calendar) data).getTimeInMillis()),
ZoneId.systemDefault());
}
if (is(java.sql.Date.class)) {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(((java.sql.Date) data).getTime()),
ZoneId.systemDefault());
}
if (is(Timestamp.class)) {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(((java.sql.Timestamp) data).getTime()),
ZoneId.systemDefault());
}
if (is(long.class) || is(Long.class)) {
return LocalDateTime.ofInstant(Instant.ofEpochMilli((long) data), ZoneId.systemDefault());
}
return defaultValue;
}
/**
* Returns the wrapped value as {@link java.time.LocalTime} or defaultValue if the wrapped value
* cannot be converted.
*
* If the wrapped value is an Instant, LocalDateTime, ZonedDateTime,
* Date, Calendar, java.sql.Date, Timestamp or long,
* it is converted to a {@link java.time.LocalTime}.
*
* @param defaultValue the value to be used, if no conversion is possible
* @return the wrapped value casted or converted to LocalTime or defaultValue
* if no conversion is possible.
*/
public LocalTime asLocalTime(LocalTime defaultValue) {
if (data == null) {
return defaultValue;
}
if (is(Instant.class)) {
return LocalTime.from((Instant) data);
}
if (is(LocalDateTime.class)) {
return ((LocalDateTime) data).toLocalTime();
}
if (is(LocalTime.class)) {
return (LocalTime) data;
}
if (is(ZonedDateTime.class)) {
return ((ZonedDateTime) data).withZoneSameInstant(ZoneId.systemDefault()).toLocalTime();
}
if (is(Date.class)) {
return LocalDateTime.from(Instant.ofEpochMilli(((Date) data).getTime())).toLocalTime();
}
if (is(Calendar.class)) {
return LocalDateTime.from(Instant.ofEpochMilli(((Calendar) data).getTimeInMillis())).toLocalTime();
}
if (is(java.sql.Date.class)) {
return LocalDateTime.from(Instant.ofEpochMilli(((java.sql.Date) data).getTime())).toLocalTime();
}
if (is(Timestamp.class)) {
return LocalDateTime.from(Instant.ofEpochMilli(((java.sql.Timestamp) data).getTime())).toLocalTime();
}
if (is(long.class) || is(Long.class)) {
return LocalDateTime.from(Instant.ofEpochMilli((long) data)).toLocalTime();
}
return defaultValue;
}
/**
* Returns the wrapped value as {@link java.time.ZonedDateTime} or defaultValue if the wrapped value
* cannot be converted.
*
* If the wrapped value is an Instant, LocalDateTime, ZonedDateTime,
* Date, Calendar, java.sql.Date, Timestamp or long,
* it is converted to a {@link java.time.ZonedDateTime}.
*
* @param defaultValue the value to be used, if no conversion is possible
* @return the wrapped value casted or converted to ZonedDateTime or defaultValue
* if no conversion is possible.
*/
public ZonedDateTime asZonedDateTime(ZonedDateTime defaultValue) {
if (data == null) {
return defaultValue;
}
if (is(Instant.class)) {
return ZonedDateTime.from((Instant) data);
}
if (is(LocalDate.class)) {
return ((LocalDate) data).atStartOfDay(ZoneId.systemDefault());
}
if (is(LocalDateTime.class)) {
return ((LocalDateTime) data).atZone(ZoneId.systemDefault());
}
if (is(LocalTime.class)) {
return ((LocalTime) data).atDate(LocalDate.now()).atZone(ZoneId.systemDefault());
}
if (is(ZonedDateTime.class)) {
return (ZonedDateTime) data;
}
if (is(Date.class)) {
return ZonedDateTime.from(Instant.ofEpochMilli(((Date) data).getTime()));
}
if (is(Calendar.class)) {
return ZonedDateTime.from(Instant.ofEpochMilli(((Calendar) data).getTimeInMillis()));
}
if (is(java.sql.Date.class)) {
return ZonedDateTime.from(Instant.ofEpochMilli(((java.sql.Date) data).getTime()));
}
if (is(Timestamp.class)) {
return ZonedDateTime.from(Instant.ofEpochMilli(((java.sql.Timestamp) data).getTime()));
}
return defaultValue;
}
/**
* Returns the wrapped value as {@link java.time.Instant} or defaultValue if the wrapped value
* cannot be converted.
*
* If the wrapped value is an Instant, LocalDateTime, ZonedDateTime,
* Date, Calendar, java.sql.Date, Timestamp or long,
* it is converted to an {@link java.time.Instant}.
*
* @param defaultValue the value to be used, if no conversion is possible
* @return the wrapped value casted or converted to Instant or defaultValue
* if no conversion is possible.
*/
public Instant asInstant(Instant defaultValue) {
if (data == null) {
return defaultValue;
}
if (is(Instant.class)) {
return (Instant) data;
}
if (is(LocalDate.class)) {
return ((LocalDate) data).atStartOfDay(ZoneId.systemDefault()).toInstant();
}
if (is(LocalDateTime.class)) {
return ((LocalDateTime) data).atZone(ZoneId.systemDefault()).toInstant();
}
if (is(LocalTime.class)) {
return ((LocalTime) data).atDate(LocalDate.now()).atZone(ZoneId.systemDefault()).toInstant();
}
if (is(ZonedDateTime.class)) {
return ((ZonedDateTime) data).toInstant();
}
if (is(Date.class)) {
return Instant.ofEpochMilli(((Date) data).getTime());
}
if (is(Calendar.class)) {
return Instant.ofEpochMilli(((Calendar) data).getTimeInMillis());
}
if (is(java.sql.Date.class)) {
return Instant.ofEpochMilli(((java.sql.Date) data).getTime());
}
if (is(Timestamp.class)) {
return Instant.ofEpochMilli(((java.sql.Timestamp) data).getTime());
}
return defaultValue;
}
/**
* Converts the wrapped number into an {@link java.time.Instant} by assuming the number represents a
* "unix timestamp" in seconds.
*
* @param defaultValue used if an invalid or non numeric value was found
* @return the Instant as determined by the wrapped timestamp value or defaultValue if no
* conversion was possible
*/
public Instant asInstantOfEpochSeconds(Instant defaultValue) {
long epochSeconds = asLong(-1);
if (epochSeconds < 0) {
return defaultValue;
}
return Instant.ofEpochSecond(epochSeconds);
}
/**
* Converts the wrapped number into an {@link java.time.Instant} by assuming the number represents a
* "unix timestamp" in milliseconds.
*
* @param defaultValue used if an invalid or non numeric value was found
* @return the Instant as determined by the wrapped timestamp value or defaultValue if no
* conversion was possible
*/
public Instant asInstantOfEpochMillis(Instant defaultValue) {
long epochSeconds = asLong(-1);
if (epochSeconds < 0) {
return defaultValue;
}
return Instant.ofEpochMilli(epochSeconds);
}
/**
* Converts the wrapped number into {@link java.time.LocalDateTime} by assuming the number represents a
* "unix timestamp" in milliseconds.
*
* @param defaultValue used if an invalid or non numeric value was found
* @return the LocalDateTime as determined by the wrapped timestamp value or defaultValue if no
* conversion was possible
*/
public LocalDateTime asLocalDateTimeOfEpochMillis(LocalDateTime defaultValue) {
Instant temporal = asInstantOfEpochMillis(null);
if (temporal == null) {
return defaultValue;
}
return LocalDateTime.ofInstant(temporal, ZoneId.systemDefault());
}
/**
* Converts the wrapped number into {@link java.time.LocalDateTime} by assuming the number represents a
* "unix timestamp" in seconds.
*
* @param defaultValue used if an invalid or non numeric value was found
* @return the LocalDateTime as determined by the wrapped timestamp value or defaultValue if no
* conversion was possible
*/
public LocalDateTime asLocalDateTimeOfEpochSeconds(LocalDateTime defaultValue) {
Instant temporal = asInstantOfEpochSeconds(null);
if (temporal == null) {
return defaultValue;
}
return LocalDateTime.ofInstant(temporal, ZoneId.systemDefault());
}
/**
* Returns the BigDecimal value for the wrapped value or defaultValue if the wrapped value
* isn't a BigDecimal and cannot be converted to one.
*
* If the wrapped value is a BigDecimal, Double, Long or Integer,
* it is either directly returned or converted by calling java.math.BigDecimal#valueOf.
*
* Otherwise {@link BigDecimal#BigDecimal(String, java.math.MathContext)} is called on the string representation
* of the wrapped value (with "," replaced to ".") and MathContext.UNLIMITED. If parsing fails, or if
* the wrapped value was null, the defaultValue will be returned.
*
* @param defaultValue the value to be used, if no conversion to BigDecimal is possible.
* @return the wrapped value casted or converted to BigDecimal or defaultValue
* if no conversion is possible.
*/
@Nonnull
public BigDecimal getBigDecimal(@Nonnull BigDecimal defaultValue) {
BigDecimal result = getBigDecimal();
if (result == null) {
return defaultValue;
}
return result;
}
/**
* Returns the BigDecimal value for the wrapped value or null if the wrapped value
* isn't a BigDecimal and cannot be converted to one.
*
* If the wrapped value is a BigDecimal, Double, Long or Integer,
* it is either directly returned or converted by calling java.math.BigDecimal#valueOf.
*
* Otherwise {@link BigDecimal#BigDecimal(String, java.math.MathContext)} is called on the string representation
* of the wrapped value (with "," replaced to ".") and MathContext.UNLIMITED. If parsing fails, or if
* the wrapped value was null, the null will be returned.
*
* @return the wrapped value casted or converted to BigDecimal or null
* if no conversion is possible.
*/
@Nullable
public BigDecimal getBigDecimal() {
try {
if (isNull()) {
return null;
}
if (data instanceof BigDecimal) {
return (BigDecimal) data;
}
if (data instanceof Double) {
return BigDecimal.valueOf((Double) data);
}
if (data instanceof Long) {
return BigDecimal.valueOf((Long) data);
}
if (data instanceof Integer) {
return BigDecimal.valueOf((Integer) data);
}
return new BigDecimal(asString().replace(',', '.').trim(), MathContext.UNLIMITED);
} catch (NumberFormatException e) {
Exceptions.ignore(e);
return null;
}
}
/**
* Returns the Amount for the wrapped value.
*
* If the wrapped value can be converted to a BigDecimal ({@link #getBigDecimal(java.math.BigDecimal)},
* an Amount for the result is returned. Otherwise an empty Amount is returned.
*
* @return the wrapped value converted to Amount. The result might be an empty amount, if the wrapped
* value is null or if no conversion was possible.
* @see #getBigDecimal(java.math.BigDecimal)
*/
public Amount getAmount() {
return Amount.of(getBigDecimal());
}
/**
* Converts the wrapped value to an enum constant of the given clazz.
*
* @param clazz the type of the enum to use
* @param the type of the enum
* @return an enum constant of the given clazz with the same name as the wrapped value
* or null if no matching constant was found
*/
@SuppressWarnings("unchecked")
public > E asEnum(Class clazz) {
if (data == null) {
return null;
}
if (clazz.isAssignableFrom(data.getClass())) {
return (E) data;
}
try {
return Enum.valueOf(clazz, String.valueOf(data).trim());
} catch (Exception e) {
Exceptions.ignore(e);
return null;
}
}
/**
* Converts the wrapped value to an enum constant of the given clazz.
*
* @param clazz the enum to convert to
* @param to generic type of the enum
* @return the enum constant wrapped as optional or an empty optional if no conversion was possible.
*/
public > Optional getEnum(Class clazz) {
return Optional.ofNullable((E) asEnum(clazz));
}
/**
* Checks if the string representation of the wrapped value starts with the given string.
*
* @param value the substring with which the string representation must start
* @return true if the string representation starts with value, false otherwise.
* If the current value is empty, it is treated as ""
*/
public boolean startsWith(@Nonnull String value) {
return asString().startsWith(value);
}
/**
* Checks if the string representation of the wrapped value ends with the given string.
*
* @param value the substring with which the string representation must end
* @return true if the string representation ends with value, false otherwise.
* If the current value is empty, it is treated as ""
*/
public boolean endsWith(@Nonnull String value) {
return asString().endsWith(value);
}
/**
* Returns a trimmed version of the string representation of the wrapped value.
*
* The conversion method used is {@link #asString()}, therefore an empty value will yield {@code ""}.
*
* @return a string representing the wrapped value without leading or trailing spaces.
*/
@Nonnull
public String trim() {
return asString().trim();
}
/**
* Returns the first N (length) characters of the string representation of the wrapped value.
*
* If the wrapped value is null, {@code ""} will be returned. If the string representation is
* shorter than length, the whole string is returned.
*
* If length is negative, the string representation without the first N (length)
* characters is returned. If the string representation is too short, {@code ""} is returned.
*
* @param length the number of characters to return or to omit (if length is negative)
* @return the first N characters (or less if the string representation of the wrapped value is shorter)
* or the string representation without the first N characters (or "" if the representation is too short)
* if length is negative. Returns {@code ""} if the wrapped value is null
*/
@Nonnull
public String left(int length) {
if (isNull()) {
return "";
}
String value = asString();
if (length < 0) {
length = length * -1;
if (value.length() < length) {
return "";
}
return value.substring(length);
} else {
if (value.length() < length) {
return value;
}
return value.substring(0, length);
}
}
/**
* Returns the last N (length) characters of the string representation of the wrapped value.
*
* If the wrapped value is null, {@code ""} will be returned. If the string representation is
* shorter than length, the whole string is returned.
*
* If length is negative, the string representation without the last N (length)
* characters is returned. If the string representation is too short, {@code ""} is returned.
*
* @param length the number of characters to return or to omit (if length is negative)
* @return the last N characters (or less if the string representation of the wrapped value is shorter)
* or the string representation without the last N characters (or "" if the representation is too short)
* if length is negative. Returns {@code ""} if the wrapped value is null
*/
@Nonnull
public String right(int length) {
if (isNull()) {
return "";
}
String value = asString();
if (length < 0) {
length = length * -1;
if (value.length() < length) {
return value;
}
return value.substring(0, value.length() - length);
} else {
if (value.length() < length) {
return value;
}
return value.substring(value.length() - length);
}
}
/**
* Returns the substring of the internal value starting right after the last occurrence of the given separator.
*
* If the separator is not found in the string, or if the internal value is empty, "" is returned.
*
* An example would be:
*
* {@code Value.of("test.tmp.pdf").afterLast("."); // returns "pdf"}
*
*
* @param separator the separator string to search for
* @return the substring right after the last occurrence of the given separator. This will not include the
* separator itself.
*/
@Nonnull
public String afterLast(@Nonnull String separator) {
if (!isEmptyString()) {
int idx = asString().lastIndexOf(separator);
if (idx > -1) {
return left(idx * -1 - 1);
}
}
return "";
}
/**
* Returns the substring of the internal value containing everything up to the last occurrence of the given
* separator.
*
* If the separator is not found in the string, or if the internal value is empty, "" is returned.
*
* An example would be:
*
* {@code Value.of("test.tmp.pdf").beforeLast("."); // returns "test.tmp"}
*
*
* @param separator the separator string to search for
* @return the substring up to the last occurrence of the given separator. This will not include the separator
* itself.
*/
@Nonnull
public String beforeLast(@Nonnull String separator) {
if (!isEmptyString()) {
int idx = asString().lastIndexOf(separator);
if (idx > -1) {
return left(idx);
}
}
return "";
}
/**
* Returns the substring of the internal value starting right after the first occurrence of the given separator.
*
* If the separator is not found in the string, or if the internal value is empty, "" is returned.
*
* An example would be:
*
* {@code Value.of("test.tmp.pdf").afterFirst("."); // returns "tmp.pdf"}
*
*
* @param separator the separator string to search for
* @return the substring right after the first occurrence of the given separator. This will not include the
* separator itself.
*/
@Nonnull
public String afterFirst(@Nonnull String separator) {
if (!isEmptyString()) {
int idx = asString().indexOf(separator);
if (idx > -1) {
return left(idx * -1 - 1);
}
}
return "";
}
/**
* Returns the substring of the internal value containing everything up to the first occurrence of the given
* separator.
*
* If the separator is not found in the string, or if the internal value is empty, "" is returned.
*
* An example would be:
*
* {@code Value.of("test.tmp.pdf").beforeFirst("."); // returns "test"}
*
*
* @param separator the separator string to search for
* @return the substring up to the first occurrence of the given separator. This will not include the separator
* itself.
*/
@Nonnull
public String beforeFirst(@Nonnull String separator) {
if (!isEmptyString()) {
int idx = asString().indexOf(separator);
if (idx > -1) {
return left(idx);
}
}
return "";
}
/**
* Returns a substring of the string representation of the wrapped value.
*
* Returns the substring starting at startIndex and ending at endIndex. If the given
* end index is greater than the string length, the complete substring from startIndex to the end of
* the string is returned. If the startIndex is greater than the string length, {@code ""} is
* returned.
*
* @param startIndex the index of the first character to be included in the sub string
* @param endIndex the index of the last character to be included in the sub string
* @return a substring like {@link String#substring(int, int)} or {@code ""} if the wrapped value
*/
@Nonnull
public String substring(int startIndex, int endIndex) {
if (isNull()) {
return "";
}
String value = asString();
if (startIndex > value.length()) {
return "";
}
return value.substring(startIndex, Math.min(value.length(), endIndex));
}
/**
* Returns the length of the string representation of the wrapped value.
*
* @return the length of the string representation of the wrapped value or 0, if the wrapped value is null
*/
public int length() {
if (isNull()) {
return 0;
}
return asString().length();
}
/**
* Returns an uppercase version of the string representation of the wrapped value.
*
* @return an uppercase version of the string representation of the wrapped value or {@code ""} if the
* wrapped value is null
*/
@Nonnull
public String toUpperCase() {
if (isNull()) {
return "";
}
return asString().toUpperCase();
}
/**
* Returns an lowercase version of the string representation of the wrapped value.
*
* @return an lowercase version of the string representation of the wrapped value or {@code ""} if the
* wrapped value is null
*/
@Nonnull
public String toLowerCase() {
if (isNull()) {
return "";
}
return asString().toLowerCase();
}
/**
* Checks if the value implements one of the given classes.
*
* @param classes the classes to check against
* @return true if the wrapped value is assignable to one of the given classes
*/
public boolean is(Class>... classes) {
if (data == null) {
return false;
}
for (Class> clazz : classes) {
if (clazz.isAssignableFrom(data.getClass())) {
return true;
}
}
return false;
}
/**
* Replaces the given pattern with the given replacement in the string representation
* of the wrapped object
*
* @param pattern the pattern to replace
* @param replacement the replacement to be used for pattern
* @return a Value where all occurrences of pattern in the string representation of the
* wrapped value are replaced by replacement. If the wrapped value is null, this
* is returned.
*/
@Nonnull
public Value replace(String pattern, String replacement) {
if (data != null) {
return Value.of(data.toString().replace(pattern, replacement));
}
return this;
}
/**
* Replaces the given regular expression pattern with the given replacement in the string representation
* of the wrapped object
*
* @param pattern the regular expression to replace
* @param replacement the replacement to be used for pattern
* @return a Value where all occurences of pattern in the string representation of the
* wrapped value are replaced by replacement. If the wrapped value is null, this
* is returned.
*/
@Nonnull
public Value regExReplace(String pattern, String replacement) {
if (data != null) {
data = data.toString().replaceAll(pattern, replacement);
}
return this;
}
@Override
public boolean equals(Object other) {
// Compare for identity
if (this == other) {
return true;
}
// Unwrap values
if (other instanceof Value) {
other = ((Value) other).data;
}
// Compare for object identity
if (data == other) {
return true;
}
// Compare with null
if (data == null) {
return other == null;
}
// Call equals against wrapped data
return data.equals(other);
}
/**
* Determines if the wrapped value is equal to one of the given objects.
*
* Instead of using {@code if (!value.in(...)) {}} consider {@link #notIn(Object...)}.
*
* @param objects the set of objects to check against
* @return true if the wrapped data is contained in the given objects array, false otherwise
*/
public boolean in(Object... objects) {
for (Object obj : objects) {
if (obj == null) {
if (data == null) {
return true;
}
} else if (obj == data || obj.equals(data)) {
return true;
}
}
return false;
}
/**
* Determines if the wrapped value isn't equal to any of the given objects.
*
* This is the inverse of {@link #in(Object...)}
*
* @param objects the set of objects to check against
* @return true if the wrapped data isn't contained in the given objects array, false otherwise
*/
public boolean notIn(Object... objects) {
return !in(objects);
}
/**
* Determines if the string representation of the wrapped value is equal to the string representation of the given
* object.
*
In this case equality does not take differences of upper and lower case characters into account. Therefore
* this is boilerplate for {@code asString().equalsIgnoreCase(otherString.toString())}
* (With proper null checks.)
*
* @param otherString the input to compare against
* @return true if the string representation of the wrapped object is equal to the string representation
* of the given parameter, where differences of lower- and uppercase are not taken into account.
* @see String#equalsIgnoreCase(String)
*/
public boolean equalsIgnoreCase(Object otherString) {
if (Strings.isEmpty(otherString)) {
return isEmptyString();
}
return asString().equalsIgnoreCase(otherString.toString());
}
@Override
public int hashCode() {
return data == null ? 0 : data.hashCode();
}
/**
* Returns a Value containing a translated value using the string representation
* of the wrapped value as key.
*
* @return a Value containing a translated value by calling {@link NLS#get(String)}
* if the string representation of the wrapped value starts with {@code $}.
* The dollar sign is skipped when passing the key to NLS. Otherwise this is returned.
* @see NLS#get(String)
*/
@Nonnull
@CheckReturnValue
public Value translate() {
return translate(null);
}
/**
* Returns a Value containing a translated value using the string representation
* of the wrapped value as key.
*
* @param lang a two-letter language code for which the translation is requested
* @return a Value containing a translated value by calling {@link NLS#get(String, String)}
* if the string representation of the wrapped value starts with {@code $}.
* The dollar sign is skipped when passing the key to NLS. Otherwise this is returned.
* @see NLS#get(String, String)
*/
@Nonnull
@CheckReturnValue
public Value translate(String lang) {
if (isFilled() && is(String.class)) {
return Value.of(NLS.smartGet(asString(), lang));
} else {
return this;
}
}
}