/*
* The MIT License (MIT)
*
* Copyright (c) 2015 Lachlan Dowding
*
* 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 permafrost.tundra.lang;
import com.wm.data.IData;
import com.wm.data.IDataPortable;
import com.wm.data.IDataUtil;
import com.wm.lang.ns.NSNode;
import com.wm.util.Table;
import com.wm.util.coder.IDataCodable;
import com.wm.util.coder.ValuesCodable;
import permafrost.tundra.data.IDataComparisonType;
import permafrost.tundra.data.IDataHelper;
import permafrost.tundra.data.transform.TransformerMode;
import permafrost.tundra.io.InputStreamHelper;
import permafrost.tundra.io.ReaderHelper;
import permafrost.tundra.io.filter.FilenameFilterType;
import permafrost.tundra.math.BigDecimalHelper;
import permafrost.tundra.math.BigIntegerHelper;
import permafrost.tundra.math.ByteHelper;
import permafrost.tundra.math.DoubleHelper;
import permafrost.tundra.math.FloatHelper;
import permafrost.tundra.math.IntegerHelper;
import permafrost.tundra.math.LongHelper;
import permafrost.tundra.math.RoundingModeHelper;
import permafrost.tundra.math.ShortHelper;
import permafrost.tundra.mime.MIMETypeHelper;
import permafrost.tundra.net.http.HTTPMethod;
import permafrost.tundra.security.MessageDigestHelper;
import permafrost.tundra.server.NodePermission;
import permafrost.tundra.time.DateTimeHelper;
import permafrost.tundra.time.DurationHelper;
import permafrost.tundra.time.DurationPattern;
import permafrost.tundra.xml.namespace.IDataNamespaceContext;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import javax.activation.MimeType;
import javax.xml.datatype.Duration;
/**
* A collection of convenience methods for working with Objects.
*/
public final class ObjectHelper {
/**
* Disallow instantiation of this class.
*/
private ObjectHelper() {}
/**
* Returns true if the given objects are considered equal; correctly supports comparing com.wm.data.IData objects.
*
* @param firstObject The first object in the comparison.
* @param secondObject The seconds object in the comparison.
* @return True if the two objects are equal, otherwise false.
*/
public static boolean equal(Object firstObject, Object secondObject) {
boolean result;
if (firstObject != null && secondObject != null) {
if (firstObject instanceof IData && secondObject instanceof IData) {
result = IDataUtil.equals((IData)firstObject, (IData)secondObject);
} else {
result = firstObject.equals(secondObject);
}
} else {
result = (firstObject == null && secondObject == null);
}
return result;
}
/**
* Returns the first non-null argument.
*
* @param objects A list of arguments to be coalesced.
* @param <T> The type of the arguments.
* @return The first non-null argument, or null.
*/
public static <T> T coalesce(T... objects) {
if (objects == null) return null;
T output = null;
for (T item : objects) {
if (item != null) {
output = item;
break;
}
}
return output;
}
/**
* Returns true if the given object is considered empty.
*
* @param object The object to check the emptiness of.
* @return True if the object is considered empty.
*/
public static boolean isEmpty(Object object) {
return object == null ||
(object instanceof String && object.equals("")) ||
(object instanceof Object[] && ((Object[])object).length == 0) ||
(object instanceof Collection && ((Collection)object).size() == 0) ||
(object instanceof IData && IDataHelper.size((IData)object) == 0);
}
/**
* Returns true if the given object is an instance of the given class.
*
* @param object The object to be checked against the given class.
* @param className The name of the class the given object will be tested against.
* @return True if the given object is an instance of the given class.
* @throws ClassNotFoundException If the given class name is not found.
*/
public static boolean instance(Object object, String className) throws ClassNotFoundException {
return className != null && instance(object, Class.forName(className));
}
/**
* Returns true if the given object is an instance of the given class.
*
* @param object The object to be checked against the given class.
* @param klass The class the given object will be tested against.
* @return True if the given object is an instance of the given class.
*/
public static boolean instance(Object object, Class klass) {
return object != null && klass != null && klass.isInstance(object);
}
/**
* Returns a string representation of the given object.
*
* @param object The object to stringify.
* @return A string representation of the given object.
*/
public static String stringify(Object object) {
if (object == null) return null;
String output;
if (object instanceof NSNode) {
output = ((NSNode)object).getNSName().toString();
} else if (object instanceof IData[] || object instanceof Table || object instanceof IDataCodable[] || object instanceof IDataPortable[] || object instanceof ValuesCodable[]) {
output = ArrayHelper.stringify(IDataHelper.toIDataArray(object));
} else if (object instanceof IData || object instanceof IDataCodable || object instanceof IDataPortable || object instanceof ValuesCodable) {
output = IDataHelper.toIData(object).toString();
} else if (object instanceof Object[][]) {
output = TableHelper.stringify((Object[][])object);
} else if (object instanceof Object[]) {
output = ArrayHelper.stringify((Object[])object);
} else {
output = object.toString();
}
return output;
}
/**
* Returns the given item if it is already an array, or a new array with the given type whose first element is the
* given item.
*
* @param item The item to be converted to an array.
* @return Either the item itself if it is already an array or a new array containing the item as its only element.
*/
public static Object[] arrayify(Object item) {
Object[] array = null;
if (item instanceof Object[]) {
array = (Object[])item;
} else if (item != null) {
array = (Object[])Array.newInstance(item.getClass(), 1);
array[0] = item;
}
return array;
}
/**
* Returns a List containing all elements in the given item if it is an array, or a List containing the item itself
* if it is not an array.
*
* @param item The item to be converted to a List.
* @return Either a List containing all elements in the given item if it is an array, or a List containing the item
* itself if it is not an array.
*/
public static List<Object> listify(Object item) {
List<Object> list = new ArrayList<Object>();
if (item instanceof Object[]) {
list.addAll(ArrayHelper.toList((Object[])item));
} else if (item != null) {
list.add(item);
}
return list;
}
/**
* Returns the nearest class which is an ancestor to the classes of the given objects.
*
* @param objects One or more objects to return the nearest ancestor for.
* @return The nearest ancestor class which is an ancestor to the classes of the given objects.
*/
public static Class<?> getNearestAncestor(Object... objects) {
return ClassHelper.getNearestAncestor(toClassSet(objects));
}
/**
* Returns the nearest class which is an ancestor to the classes of the given objects.
*
* @param objects One or more objects to return the nearest ancestor for.
* @return The nearest ancestor class which is an ancestor to the classes of the given objects.
*/
public static Class<?> getNearestAncestor(Collection<?> objects) {
return ClassHelper.getNearestAncestor(toClassSet(objects));
}
/**
* Returns all the ancestor classes from nearest to furthest for the given class.
*
* @param objects One or more objects to fetch the ancestors classes of.
* @return All the ancestor classes from nearest to furthest for the class of the given object.
*/
public static Set<Class<?>> getAncestors(Object... objects) {
return ClassHelper.getAncestors(toClassSet(objects));
}
/**
* Returns all the ancestor classes from nearest to furthest for the given class.
*
* @param object An object to fetch the ancestors classes of.
* @return All the ancestor classes from nearest to furthest for the class of the given object.
*/
public static Set<Class<?>> getAncestors(Object object) {
return object == null ? new TreeSet<Class<?>>() : ClassHelper.getAncestors(object.getClass());
}
/**
* Converts the given list of objects to a set of classes.
*
* @param objects One or more objects to return a set of classes for.
* @return The set of classes for the given list of objects.
*/
private static Set<Class<?>> toClassSet(Object... objects) {
Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
if (objects != null) {
for (Object object : objects) {
if (object != null) classes.add(object.getClass());
}
} else {
classes.add(Object.class);
}
return classes;
}
/**
* Converts the given list of objects to a set of classes.
*
* @param objects One or more objects to return a set of classes for.
* @return The set of classes for the given list of objects.
*/
private static Set<Class<?>> toClassSet(Collection<?> objects) {
Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
if (objects != null) {
for (Object object : objects) {
if (object != null) classes.add(object.getClass());
}
} else {
classes.add(Object.class);
}
return classes;
}
/**
* Converts a string, byte array or stream to a string, byte array or stream.
*
* @param object The object to be converted.
* @param charsetName The character set to use.
* @param mode The desired return type of the object.
* @return The converted object.
* @throws IOException If an I/O problem occurs.
*/
public static Object convert(Object object, String charsetName, String mode) throws IOException {
return convert(object, CharsetHelper.normalize(charsetName), mode);
}
/**
* Converts a string, byte array or stream to a string, byte array or stream.
*
* @param object The object to be converted.
* @param charset The character set to use.
* @param mode The desired return type of the object.
* @return The converted object.
* @throws IOException If an I/O problem occurs.
*/
public static Object convert(Object object, Charset charset, String mode) throws IOException {
return convert(object, charset, ObjectConvertMode.normalize(mode));
}
/**
* Converts a string, byte array or stream to a string, byte array or stream.
*
* @param object The object to be converted.
* @param mode The desired return type of the object.
* @return The converted object.
* @throws IOException If an I/O problem occurs.
*/
public static Object convert(Object object, String mode) throws IOException {
return convert(object, ObjectConvertMode.normalize(mode));
}
/**
* Converts a string, byte array or stream to a string, byte array or stream.
*
* @param object The object to be converted.
* @param mode The desired return type of the object.
* @return The converted object.
* @throws IOException If an I/O problem occurs.
*/
public static Object convert(Object object, ObjectConvertMode mode) throws IOException {
return convert(object, CharsetHelper.DEFAULT_CHARSET, mode);
}
/**
* Converts a string, byte array or stream to a string, byte array or stream.
*
* @param object The object to be converted.
* @param charset The character set to use.
* @param mode The desired return type of the object.
* @return The converted object.
* @throws IOException If an I/O problem occurs.
*/
public static Object convert(Object object, Charset charset, ObjectConvertMode mode) throws IOException {
if (object == null) return null;
mode = ObjectConvertMode.normalize(mode);
if (mode == ObjectConvertMode.BYTES) {
object = BytesHelper.normalize(object, charset);
} else if (mode == ObjectConvertMode.STRING) {
object = StringHelper.normalize(object, charset);
} else if (mode == ObjectConvertMode.BASE64) {
object = BytesHelper.base64Encode(BytesHelper.normalize(object, charset));
} else if (mode == ObjectConvertMode.STREAM) {
object = InputStreamHelper.normalize(object, charset);
} else {
throw new IllegalArgumentException("Unsupported conversion mode specified: " + mode);
}
return object;
}
/**
* Converts the given object to the given class.
*
* @param object The object to be converted.
* @param klass The class to convert the object to.
* @param <T> The class to convert the object to.
* @return The given object converted to the given class.
*/
@SuppressWarnings("unchecked")
public static <T> T convert(Object object, Class<T> klass) {
return convert(object, null, klass);
}
/**
* Converts the given object to the given class.
*
* @param object The object to be converted.
* @param charset The charset to use for string related conversions.
* @param klass The class to convert the object to.
* @param <T> The class to convert the object to.
* @return The given object converted to the given class.
*/
@SuppressWarnings("unchecked")
public static <T> T convert(Object object, Charset charset, Class<T> klass) {
return convert(object, charset, klass, false);
}
/**
* Converts the given object to the given class.
*
* @param object The object to be converted.
* @param klass The class to convert the object to.
* @param required If true and the object could not be converted, throws an exception.
* @param <T> The class to convert the object to.
* @return The given object converted to the given class.
*/
@SuppressWarnings("unchecked")
public static <T> T convert(Object object, Class<T> klass, boolean required) {
return convert(object, null, klass, required);
}
/**
* Converts the given object to the given class.
*
* @param object The object to be converted.
* @param charset The charset to use for string related conversions.
* @param klass The class to convert the object to.
* @param required If true and the object could not be converted, throws an exception.
* @param <T> The class to convert the object to.
* @return The given object converted to the given class.
*/
@SuppressWarnings("unchecked")
public static <T> T convert(Object object, Charset charset, Class<T> klass, boolean required) {
if (required) {
if (object == null) {
throw new NullPointerException("object must not be null");
} else if (klass == null) {
throw new NullPointerException("klass must not be null");
}
} else if (object == null || klass == null) return null;
T value = null;
try {
if (klass.isInstance(object)) {
value = (T)object;
} else if (klass.isAssignableFrom(byte[].class)) {
value = (T)BytesHelper.normalize(object, charset);
} else if (klass.isAssignableFrom(InputStream.class)) {
value = (T)InputStreamHelper.normalize(object, charset);
} else if (klass.isAssignableFrom(String.class)) {
value = (T)StringHelper.normalize(object, charset);
} else if (klass.isAssignableFrom(Boolean.class)) {
value = (T)BooleanHelper.normalize(object);
} else if (klass.isAssignableFrom(BigDecimal.class)) {
value = (T)BigDecimalHelper.normalize(object);
} else if (klass.isAssignableFrom(BigInteger.class)) {
value = (T)BigIntegerHelper.normalize(object);
} else if (klass.isAssignableFrom(Double.class)) {
value = (T)DoubleHelper.normalize(object);
} else if (klass.isAssignableFrom(Float.class)) {
value = (T)FloatHelper.normalize(object);
} else if (klass.isAssignableFrom(Long.class)) {
value = (T)LongHelper.normalize(object);
} else if (klass.isAssignableFrom(Integer.class)) {
value = (T)IntegerHelper.normalize(object);
} else if (klass.isAssignableFrom(Short.class)) {
value = (T)ShortHelper.normalize(object);
} else if (klass.isAssignableFrom(Byte.class)) {
value = (T)ByteHelper.normalize(object);
} else if (klass.isAssignableFrom(Byte.class)) {
value = (T)ByteHelper.normalize(object);
} else if (klass.isAssignableFrom(Class.class)) {
value = (T)ClassHelper.normalize(object);
} else if (klass.isEnum()) {
value = EnumHelper.normalize(object, klass);
} else if (klass.isAssignableFrom(IData.class) && (object instanceof IDataCodable || object instanceof IDataPortable || object instanceof ValuesCodable || object instanceof Map)) {
value = (T)IDataHelper.toIData(object);
} else if (klass.isAssignableFrom(Character.class) && object instanceof String && ((String)object).length() > 0) {
value = (T)Character.valueOf(((String)object).charAt(0));
} else if (klass.isAssignableFrom(Calendar.class) && object instanceof String) {
value = (T)DateTimeHelper.parse((String)object);
} else if (klass.isAssignableFrom(Duration.class) && object instanceof String) {
value = (T)DurationHelper.parse((String)object);
} else if (klass.isAssignableFrom(Charset.class) && object instanceof String) {
value = (T)CharsetHelper.normalize((String)object);
} else if (klass.isAssignableFrom(MimeType.class) && object instanceof String) {
value = (T)MIMETypeHelper.of((String)object);
} else if (klass.isAssignableFrom(NodePermission.class) && object instanceof String) {
value = (T)NodePermission.normalize((String)object);
} else if (klass.isAssignableFrom(IDataNamespaceContext.class) && object instanceof IData) {
value = (T)IDataNamespaceContext.of((IData)object);
} else if (klass.isAssignableFrom(Locale.class) && object instanceof IData) {
value = (T)LocaleHelper.toLocale((IData)object);
} else if (klass.isAssignableFrom(MessageDigest.class) && object instanceof String) {
value = (T)MessageDigestHelper.normalize((String)object);
}
} catch (IOException ex) {
throw new RuntimeException(ex);
} catch (NoSuchAlgorithmException ex) {
throw new RuntimeException(ex);
}
if (required && value == null) {
throw new UnsupportedOperationException(MessageFormat.format("Unsupported class conversion from {0} to {1}", object.getClass().getName(), klass.getName()));
}
return value;
}
}