Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.spongepowered.api.util.Coerce Maven / Gradle / Ivy
/*
* This file is part of SpongeAPI, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.spongepowered.api.util;
import com.google.common.collect.Lists;
import com.google.common.primitives.Booleans;
import com.google.common.primitives.Bytes;
import com.google.common.primitives.Chars;
import com.google.common.primitives.Doubles;
import com.google.common.primitives.Floats;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.google.common.primitives.Shorts;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.math.vector.Vector2i;
import org.spongepowered.math.vector.Vector3i;
import org.spongepowered.math.vector.Vector4i;
import org.spongepowered.math.vector.VectorNi;
import org.spongepowered.math.vector.Vectord;
import org.spongepowered.math.vector.Vectorf;
import org.spongepowered.math.vector.Vectorl;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Utility class for coercing unknown values to specific target types.
*/
public final class Coerce {
private static final Pattern listPattern = Pattern.compile("^([\\(\\[\\{]?)(.+?)([\\)\\]\\}]?)$");
private static final String[] listPairings = { "([{", ")]}" };
private static final Pattern vector2Pattern = Pattern.compile("^\\( *(-?[\\d\\.]{1,10}), *(-?[\\d\\.]{1,10}) *\\)$");
/**
* No subclasses for you.
*/
private Coerce() {}
/**
* Coerce the supplied object to a string.
*
* @param obj Object to coerce
* @return Object as a string, empty string if the object is null
*/
public static String toString(@Nullable Object obj) {
if (obj == null) {
return "";
}
if (obj.getClass().isArray()) {
return Coerce.toList(obj).toString();
}
return obj.toString();
}
/**
* Gets the given object as a {@link String}.
*
* @param obj The object to translate
* @return The string value, if available
*/
public static Optional asString(@Nullable Object obj) {
if (obj instanceof String) {
return Optional.of((String) obj);
} else if (obj == null) {
return Optional.empty();
} else {
return Optional.of(obj.toString());
}
}
/**
* Coerce the supplied object to a list. Accepts lists and all types of 1D
* arrays. Also (naively) supports lists in Strings in a format like
* {1,2,3,I,am,a,list}
*
* @param obj Object to coerce
* @return Some kind of List filled with unimaginable horrors
*/
public static List> toList(@Nullable Object obj) {
if (obj == null) {
return Collections.emptyList();
}
if (obj instanceof List) {
return (List>) obj;
}
final Class> clazz = obj.getClass();
if (clazz.isArray()) {
if (clazz.getComponentType().isPrimitive()) {
return Coerce.primitiveArrayToList(obj);
}
return Arrays.asList((Object[]) obj);
}
return Coerce.parseStringToList(obj.toString());
}
/**
* Gets the given object as a {@link List}.
*
* @param obj The object to translate
* @return The list, if available
*/
public static Optional> asList(@Nullable Object obj) {
if (obj == null) {
return Optional.empty();
}
if (obj instanceof List) {
return Optional.>of((List>) obj);
}
final Class> clazz = obj.getClass();
if (clazz.isArray()) {
if (clazz.getComponentType().isPrimitive()) {
return Optional.>of(Coerce.primitiveArrayToList(obj));
}
return Optional.>of(Arrays.asList((Object[]) obj));
}
return Optional.>of(Coerce.parseStringToList(obj.toString()));
}
/**
* Coerce the specified object to a list containing only objects of type
* specified by ofClass
. Also coerces list values where
* possible.
*
* @param obj Object to coerce
* @param ofClass Class to coerce to
* @param type of list (notional)
* @return List of coerced values
*/
@SuppressWarnings("unchecked")
public static List toListOf(@Nullable Object obj, Class ofClass) {
Objects.requireNonNull(ofClass, "ofClass");
final List filteredList = Lists.newArrayList();
for (Object o : Coerce.toList(obj)) {
if (ofClass.isAssignableFrom(o.getClass())) {
filteredList.add((T) o);
} else if (ofClass.equals(String.class)) {
filteredList.add((T) Coerce.toString(o));
} else if (ofClass.equals(Integer.TYPE) || ofClass.equals(Integer.class)) {
filteredList.add((T) (Integer) Coerce.toInteger(o));
} else if (ofClass.equals(Float.TYPE) || ofClass.equals(Float.class)) {
filteredList.add((T) Float.valueOf((float) Coerce.toDouble(o)));
} else if (ofClass.equals(Double.TYPE) || ofClass.equals(Double.class)) {
filteredList.add((T) (Double) Coerce.toDouble(o));
} else if (ofClass.equals(Boolean.TYPE) || ofClass.equals(Boolean.class)) {
filteredList.add((T) (Boolean) Coerce.toBoolean(o));
}
}
return filteredList;
}
/**
* Coerce the supplied object to a boolean, matches strings such as "yes" as
* well as literal boolean values.
*
* @param obj Object to coerce
* @return Object as a boolean, false
if the object is null
*/
public static boolean toBoolean(@Nullable Object obj) {
if (obj == null) {
return false;
}
return (obj instanceof Boolean) ? (Boolean) obj : obj.toString().trim().matches("^(1|true|yes)$");
}
/**
* Gets the given object as a {@link Boolean}.
*
* @param obj The object to translate
* @return The boolean, if available
*/
public static Optional asBoolean(@Nullable Object obj) {
if (obj instanceof Boolean) {
return Optional.of((Boolean) obj);
} else if (obj instanceof Byte) {
return Optional.of((Byte) obj != 0);
}
return Optional.empty();
}
/**
* Coerce the supplied object to an integer, parse it if necessary.
*
* @param obj Object to coerce
* @return Object as an integer, 0
if the object is null or
* cannot be parsed
*/
public static int toInteger(@Nullable Object obj) {
if (obj == null) {
return 0;
}
if (obj instanceof Number) {
return ((Number) obj).intValue();
}
final String strObj = Coerce.sanitiseNumber(obj);
final Integer iParsed = Ints.tryParse(strObj);
if (iParsed != null) {
return iParsed;
}
final Double dParsed = Doubles.tryParse(strObj);
return dParsed != null ? dParsed.intValue() : 0;
}
/**
* Gets the given object as a {@link Integer}.
*
* Note that this does not translate numbers spelled out as strings.
*
* @param obj The object to translate
* @return The integer value, if available
*/
public static Optional asInteger(@Nullable Object obj) {
if (obj == null) {
// fail fast
return Optional.empty();
}
if (obj instanceof Number) {
return Optional.of(((Number) obj).intValue());
}
try {
return Optional.ofNullable(Integer.valueOf(obj.toString()));
} catch (NumberFormatException | NullPointerException e) {
// do nothing
}
final String strObj = Coerce.sanitiseNumber(obj);
final Integer iParsed = Ints.tryParse(strObj);
if (iParsed == null) {
final Double dParsed = Doubles.tryParse(strObj);
// try parsing as double now
return dParsed == null ? Optional.empty() : Optional.of(dParsed.intValue());
}
return Optional.of(iParsed);
}
/**
* Coerce the supplied object to a double-precision floating-point number,
* parse it if necessary.
*
* @param obj Object to coerce
* @return Object as a double, 0.0
if the object is null or
* cannot be parsed
*/
public static double toDouble(@Nullable Object obj) {
if (obj == null) {
return 0.0;
}
if (obj instanceof Number) {
return ((Number) obj).doubleValue();
}
final Double parsed = Doubles.tryParse(Coerce.sanitiseNumber(obj));
return parsed != null ? parsed : 0.0;
}
/**
* Gets the given object as a {@link Double}.
*
* Note that this does not translate numbers spelled out as strings.
*
* @param obj The object to translate
* @return The double value, if available
*/
public static Optional asDouble(@Nullable Object obj) {
if (obj == null) {
// fail fast
return Optional.empty();
}
if (obj instanceof Number) {
return Optional.of(((Number) obj).doubleValue());
}
try {
return Optional.ofNullable(Double.valueOf(obj.toString()));
} catch (NumberFormatException | NullPointerException e) {
// do nothing
}
final String strObj = Coerce.sanitiseNumber(obj);
final Double dParsed = Doubles.tryParse(strObj);
// try parsing as double now
return dParsed == null ? Optional.empty() : Optional.of(dParsed);
}
/**
* Coerce the supplied object to a single-precision floating-point number,
* parse it if necessary.
*
* @param obj Object to coerce
* @return Object as a float, 0.0
if the object is null or
* cannot be parsed
*/
public static float toFloat(@Nullable Object obj) {
if (obj == null) {
return 0.0f;
}
if (obj instanceof Number) {
return ((Number) obj).floatValue();
}
final Float parsed = Floats.tryParse(Coerce.sanitiseNumber(obj));
return parsed != null ? parsed : 0.0f;
}
/**
* Gets the given object as a {@link Float}.
*
* Note that this does not translate numbers spelled out as strings.
*
* @param obj The object to translate
* @return The float value, if available
*/
public static Optional asFloat(@Nullable Object obj) {
if (obj == null) {
// fail fast
return Optional.empty();
}
if (obj instanceof Number) {
return Optional.of(((Number) obj).floatValue());
}
try {
return Optional.ofNullable(Float.valueOf(obj.toString()));
} catch (NumberFormatException | NullPointerException e) {
// do nothing
}
final String strObj = Coerce.sanitiseNumber(obj);
final Double dParsed = Doubles.tryParse(strObj);
return dParsed == null ? Optional.empty() : Optional.of(dParsed.floatValue());
}
/**
* Coerce the supplied object to a short number, parse it if necessary.
*
* @param obj Object to coerce
* @return Object as a short, 0
if the object is null or cannot
* be parsed
*/
public static short toShort(@Nullable Object obj) {
if (obj == null) {
return 0;
}
if (obj instanceof Number) {
return ((Number) obj).shortValue();
}
try {
return Short.parseShort(Coerce.sanitiseNumber(obj));
} catch (NumberFormatException e) {
return 0;
}
}
/**
* Gets the given object as a {@link Short}.
*
* Note that this does not translate numbers spelled out as strings.
*
* @param obj The object to translate
* @return The short value, if available
*/
public static Optional asShort(@Nullable Object obj) {
if (obj == null) {
// fail fast
return Optional.empty();
}
if (obj instanceof Number) {
return Optional.of(((Number) obj).shortValue());
}
try {
return Optional.ofNullable(Short.parseShort(Coerce.sanitiseNumber(obj)));
} catch (NumberFormatException | NullPointerException e) {
// do nothing
}
return Optional.empty();
}
/**
* Coerce the supplied object to a byte number, parse it if necessary.
*
* @param obj Object to coerce
* @return Object as a byte, 0
if the object is null or cannot
* be parsed
*/
public static byte toByte(@Nullable Object obj) {
if (obj == null) {
return 0;
}
if (obj instanceof Number) {
return ((Number) obj).byteValue();
}
try {
return Byte.parseByte(Coerce.sanitiseNumber(obj));
} catch (NumberFormatException e) {
return 0;
}
}
/**
* Gets the given object as a {@link Byte}.
*
* Note that this does not translate numbers spelled out as strings.
*
* @param obj The object to translate
* @return The byte value, if available
*/
public static Optional asByte(@Nullable Object obj) {
if (obj == null) {
// fail fast
return Optional.empty();
}
if (obj instanceof Number) {
return Optional.of(((Number) obj).byteValue());
}
try {
return Optional.ofNullable(Byte.parseByte(Coerce.sanitiseNumber(obj)));
} catch (NumberFormatException | NullPointerException e) {
// do nothing
}
return Optional.empty();
}
/**
* Coerce the supplied object to a long number, parse it if necessary.
*
* @param obj Object to coerce
* @return Object as a long, 0
if the object is null or cannot
* be parsed
*/
public static long toLong(@Nullable Object obj) {
if (obj == null) {
return 0;
}
if (obj instanceof Number) {
return ((Number) obj).longValue();
}
try {
return Long.parseLong(Coerce.sanitiseNumber(obj));
} catch (NumberFormatException e) {
return 0;
}
}
/**
* Gets the given object as a {@link Long}.
*
* Note that this does not translate numbers spelled out as strings.
*
* @param obj The object to translate
* @return The long value, if available
*/
public static Optional asLong(@Nullable Object obj) {
if (obj == null) {
// fail fast
return Optional.empty();
}
if (obj instanceof Number) {
return Optional.of(((Number) obj).longValue());
}
try {
return Optional.ofNullable(Long.parseLong(Coerce.sanitiseNumber(obj)));
} catch (NumberFormatException | NullPointerException e) {
// do nothing
}
return Optional.empty();
}
/**
* Coerce the supplied object to a character, parse it if necessary.
*
* @param obj Object to coerce
* @return Object as a character, '\u0000'
if the object is
* null or cannot be parsed
*/
public static char toChar(@Nullable Object obj) {
if (obj == null) {
return 0;
}
if (obj instanceof Character) {
return (Character) obj;
}
try {
return obj.toString().charAt(0);
} catch (Exception e) {
// do nothing
}
return '\u0000';
}
/**
* Gets the given object as a {@link Character}.
*
* @param obj The object to translate
* @return The character, if available
*/
public static Optional asChar(@Nullable Object obj) {
if (obj == null) {
return Optional.empty();
}
if (obj instanceof Character) {
return Optional.of((Character) obj);
}
try {
return Optional.of(obj.toString().charAt(0));
} catch (Exception e) {
// do nothing
}
return Optional.empty();
}
/**
* Coerce the specified object to an enum of the supplied type, returns the
* first enum constant in the enum if parsing fails.
*
* @param obj Object to coerce
* @param enumClass Enum class to coerce to
* @param enum type
* @return Coerced enum value
*/
public static > E toEnum(@Nullable Object obj, Class enumClass) {
return Coerce.toEnum(obj, enumClass, enumClass.getEnumConstants()[0]);
}
/**
* Coerce the specified object to an enum of the supplied type, returns the
* specified default value if parsing fails.
*
* @param obj Object to coerce
* @param enumClass Enum class to coerce to
* @param defaultValue default value to return if coercion fails
* @param enum type
* @return Coerced enum value
*/
public static > E toEnum(@Nullable Object obj, Class enumClass, E defaultValue) {
Objects.requireNonNull(enumClass, "enumClass");
Objects.requireNonNull(defaultValue, "defaultValue");
if (obj == null) {
return defaultValue;
}
if (enumClass.isAssignableFrom(obj.getClass())) {
@SuppressWarnings("unchecked")
final E enumObj = (E) obj;
return enumObj;
}
final String strObj = obj.toString().trim();
try {
// Efficient but case-sensitive lookup in the constant map
return Enum.valueOf(enumClass, strObj);
} catch (IllegalArgumentException ex) {
// fall through
}
// Try a case-insensitive lookup
for (E value : enumClass.getEnumConstants()) {
if (value.name().equalsIgnoreCase(strObj)) {
return value;
}
}
return defaultValue;
}
/**
* Coerce the specified object to the specified pseudo-enum type using the
* supplied pseudo-enum dictionary class.
*
* @param obj Object to coerce
* @param pseudoEnumClass The pseudo-enum class
* @param dictionaryClass Pseudo-enum dictionary class to look in
* @param defaultValue Value to return if lookup fails
* @param pseudo-enum type
* @return Coerced value or default if coercion fails
*/
public static T toPseudoEnum(@Nullable Object obj, Class pseudoEnumClass, Class> dictionaryClass, T defaultValue) {
Objects.requireNonNull(pseudoEnumClass, "pseudoEnumClass");
Objects.requireNonNull(dictionaryClass, "dictionaryClass");
Objects.requireNonNull(defaultValue, "defaultValue");
if (obj == null) {
return defaultValue;
}
if (pseudoEnumClass.isAssignableFrom(obj.getClass())) {
@SuppressWarnings("unchecked")
final T enumObj = (T) obj;
return enumObj;
}
final String strObj = obj.toString().trim();
try {
for (Field field : dictionaryClass.getFields()) {
if ((field.getModifiers() & Modifier.STATIC) != 0 && pseudoEnumClass.isAssignableFrom(field.getType())) {
final String fieldName = field.getName();
@SuppressWarnings("unchecked")
final T entry = (T) field.get(null);
if (strObj.equalsIgnoreCase(fieldName)) {
return entry;
}
}
}
} catch (Exception ex) {
// well that went badly
}
return defaultValue;
}
/**
* Coerce the supplied object to a Vector2i.
*
* @param obj Object to coerce
* @return Vector2i, returns Vector2i.ZERO if coercion failed
*/
public static Vector2i toVector2i(@Nullable Object obj) {
if (obj == null) {
return Vector2i.ZERO;
}
if (obj instanceof Vectorl) {
obj = ((Vectorl) obj).toInt();
} else if (obj instanceof Vectorf) {
obj = ((Vectorf) obj).toInt();
} else if (obj instanceof Vectord) {
obj = ((Vectord) obj).toInt();
}
if (obj instanceof Vector2i) {
return (Vector2i) obj;
} else if (obj instanceof Vector3i) {
return new Vector2i((Vector3i) obj);
} else if (obj instanceof Vector4i) {
return new Vector2i((Vector4i) obj);
} else if (obj instanceof VectorNi) {
return new Vector2i((VectorNi) obj);
}
final Matcher vecMatch = Coerce.vector2Pattern.matcher(obj.toString());
if (Coerce.listBracketsMatch(vecMatch)) {
return new Vector2i(Integer.parseInt(vecMatch.group(1)), Integer.parseInt(vecMatch.group(2)));
}
final List> list = Coerce.toList(obj);
if (list.size() == 2) {
return new Vector2i(Coerce.toInteger(list.get(0)), Coerce.toInteger(list.get(1)));
}
return Vector2i.ZERO;
}
/**
* Sanitise a string containing a common representation of a number to make
* it parsable. Strips thousand-separating commas and trims later members
* of a comma-separated list. For example the string "(9.5, 10.6, 33.2)"
* will be sanitised to "9.5".
*
* @param obj Object to sanitise
* @return Sanitised number-format string to parse
*/
private static String sanitiseNumber(Object obj) {
String string = obj.toString().trim();
if (string.length() < 1) {
return "0";
}
final Matcher candidate = Coerce.listPattern.matcher(string);
if (Coerce.listBracketsMatch(candidate)) {
string = candidate.group(2).trim();
}
final int decimal = string.indexOf('.');
final int comma = string.indexOf(',', decimal);
if (decimal > -1 && comma > -1) {
return Coerce.sanitiseNumber(string.substring(0, comma));
}
if (string.indexOf('-', 1) != -1) {
return "0";
}
return string.replace(",", "").split(" ", 0)[0];
}
private static boolean listBracketsMatch(Matcher candidate) {
return candidate.matches() && Coerce.listPairings[0].indexOf(candidate.group(1)) == Coerce.listPairings[1].indexOf(candidate.group(3));
}
private static List> primitiveArrayToList(Object obj) {
if (obj instanceof boolean[]) {
return Booleans.asList((boolean[]) obj);
} else if (obj instanceof char[]) {
return Chars.asList((char[]) obj);
} else if (obj instanceof byte[]) {
return Bytes.asList((byte[]) obj);
} else if (obj instanceof short[]) {
return Shorts.asList((short[]) obj);
} else if (obj instanceof int[]) {
return Ints.asList((int[]) obj);
} else if (obj instanceof long[]) {
return Longs.asList((long[]) obj);
} else if (obj instanceof float[]) {
return Floats.asList((float[]) obj);
} else if (obj instanceof double[]) {
return Doubles.asList((double[]) obj);
}
return Collections.emptyList();
}
private static List> parseStringToList(String string) {
final Matcher candidate = Coerce.listPattern.matcher(string);
if (!Coerce.listBracketsMatch(candidate)) {
return Collections.emptyList();
}
final List list = Lists.newArrayList();
for (final String part : candidate.group(2).split(",", -1)) {
list.add(part);
}
return list;
}
}