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

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; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy