ilscipio/scipio-erp

View on GitHub
framework/base/src/org/ofbiz/base/util/ObjectType.java

Summary

Maintainability
F
4 days
Test Coverage
/*******************************************************************************
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *******************************************************************************/
package org.ofbiz.base.util;

import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;

import org.ofbiz.base.conversion.ConversionException;
import org.ofbiz.base.conversion.Converter;
import org.ofbiz.base.conversion.Converters;
import org.ofbiz.base.conversion.LocalizedConverter;
import org.ofbiz.base.lang.IsEmpty;
import org.ofbiz.base.lang.SourceMonitored;
import org.w3c.dom.Node;

/**
 * Utilities for analyzing and converting Object types in Java
 * Takes advantage of reflection
 * <p>
 * SCIPIO: <strong>WARNING:</code> 2018-03-26: The behavior of the
 * methods {@link #interfaceOf(Class, Class)} and {@link #instanceOf(Class, Class)}
 * have been modified; they now both behavior closer to the equivalent of a
 * {@code instanceof} operation.
 */
public class ObjectType {

    private static final Debug.OfbizLogger module = Debug.getOfbizLogger(java.lang.invoke.MethodHandles.lookup().lookupClass());

    public static final Object NULL = new NullObject();

    public static final String LANG_PACKAGE = "java.lang."; // We will test both the raw value and this + raw value
    public static final String SQL_PACKAGE = "java.sql.";   // We will test both the raw value and this + raw value

    private static final Map<String, String> classAlias = new HashMap<>();
    private static final Map<String, Class<?>> primitives = new HashMap<>();

    static {
        classAlias.put("Object", "java.lang.Object");
        classAlias.put("String", "java.lang.String");
        classAlias.put("Boolean", "java.lang.Boolean");
        classAlias.put("BigDecimal", "java.math.BigDecimal");
        classAlias.put("Double", "java.lang.Double");
        classAlias.put("Float", "java.lang.Float");
        classAlias.put("Long", "java.lang.Long");
        classAlias.put("Integer", "java.lang.Integer");
        classAlias.put("Short", "java.lang.Short");
        classAlias.put("Byte", "java.lang.Byte");
        classAlias.put("Character", "java.lang.Character");
        classAlias.put("Timestamp", "java.sql.Timestamp");
        classAlias.put("Time", "java.sql.Time");
        classAlias.put("Date", "java.sql.Date");
        classAlias.put("Locale", "java.util.Locale");
        classAlias.put("Collection", "java.util.Collection");
        classAlias.put("List", "java.util.List");
        classAlias.put("Set", "java.util.Set");
        classAlias.put("Map", "java.util.Map");
        classAlias.put("HashMap", "java.util.HashMap");
        classAlias.put("TimeZone", "java.util.TimeZone");
        classAlias.put("TimeDuration", "org.ofbiz.base.util.TimeDuration");
        classAlias.put("GenericValue", "org.ofbiz.entity.GenericValue");
        classAlias.put("GenericPK", "org.ofbiz.entity.GenericPK");
        classAlias.put("GenericEntity", "org.ofbiz.entity.GenericEntity");
        primitives.put("boolean", Boolean.TYPE);
        primitives.put("short", Short.TYPE);
        primitives.put("int", Integer.TYPE);
        primitives.put("long", Long.TYPE);
        primitives.put("float", Float.TYPE);
        primitives.put("double", Double.TYPE);
        primitives.put("byte", Byte.TYPE);
        primitives.put("char", Character.TYPE);
    }

    /**
     * Loads a class with the current thread's context classloader.
     * @param className The name of the class to load
     * @return The requested class
     * @throws ClassNotFoundException
     */
    public static Class<?> loadClass(String className) throws ClassNotFoundException {
        return loadClass(className, null);
    }

    /**
     * Loads a class with the specified classloader.
     * @param className The name of the class to load
     * @param loader The ClassLoader to use
     * @return The requested class
     * @throws ClassNotFoundException
     */
    public static Class<?> loadClass(String className, ClassLoader loader) throws ClassNotFoundException {
        Class<?> theClass = null;
        // if it is a primitive type, return the object from the "primitives" map
        if (primitives.containsKey(className)) {
            return primitives.get(className);
        }

        int genericsStart = className.indexOf("<");
        if (genericsStart != -1) {
            className = className.substring(0, genericsStart);
        }

        // Handle array classes. Details in http://java.sun.com/j2se/1.5.0/docs/guide/jni/spec/types.html#wp16437
        if (className.endsWith("[]")) {
            if (Character.isLowerCase(className.charAt(0)) && className.indexOf(".") < 0) {
                String prefix = className.substring(0, 1).toUpperCase(Locale.getDefault());
               // long and boolean have other prefix than first letter
               if (className.startsWith("long")) {
                   prefix = "J";
               } else if (className.startsWith("boolean")) {
                   prefix = "Z";
               }
               className = "[" + prefix;
            } else {
                Class<?> arrayClass = loadClass(className.replace("[]", ""), loader);
                className = "[L" + arrayClass.getName().replace("[]", "") + ";";
            }
        }

        // if className is an alias (e.g. "String") then replace it with the proper class name (e.g. "java.lang.String")
        if (classAlias.containsKey(className)) {
            className = classAlias.get(className);
        }

        if (loader == null) {
            loader = Thread.currentThread().getContextClassLoader();
        }

        theClass = Class.forName(className, true, loader);

        return theClass;
    }

    /**
     * SCIPIO: Loads a class with the current thread's context classloader; logs ClassNotFoundException, returning null if any.
     * @param className The name of the class to load
     * @return The requested class, or null if not found (error)
     */
    public static Class<?> loadClassSafe(String className) {
        try {
            return loadClass(className, null);
        } catch(ClassNotFoundException e) {
            Debug.logError(e, module);
            return null;
        }
    }

    /**
     * SCIPIO: Loads a class with the specified classloader; logs ClassNotFoundException, returning null if any.
     * @param className The name of the class to load
     * @return The requested class, or null if not found (error)
     */
    public static Class<?> loadClassSafe(String className, ClassLoader loader) {
        try {
            return loadClass(className, loader);
        } catch(ClassNotFoundException e) {
            Debug.logError(e, module);
            return null;
        }
    }

    /**
     * SCIPIO: Loads a class with the current thread's context classloader; ignores ClassNotFoundException, returning null if any.
     * @param className The name of the class to load
     * @return The requested class, or null if not found (error)
     */
    public static Class<?> loadClassOrNull(String className) {
        try {
            return loadClass(className, null);
        } catch(ClassNotFoundException e) {
            return null;
        }
    }

    /**
     * SCIPIO: Loads a class with the specified classloader; ignores ClassNotFoundException, returning null if any.
     * @param className The name of the class to load
     * @return The requested class, or null if not found (error)
     */
    public static Class<?> loadClassOrNull(String className, ClassLoader loader) {
        try {
            return loadClass(className, loader);
        } catch(ClassNotFoundException e) {
            return null;
        }
    }

    /**
     * SCIPIO: Loads a class with the current thread's context classloader; wraps ClassNotFoundException in IllegalArgumentException.
     * @param className The name of the class to load
     * @return The requested class, or null if not found (error)
     * @throws IllegalArgumentException
     */
    public static Class<?> loadClassOrRuntimeEx(String className) throws IllegalArgumentException {
        try {
            return loadClass(className, null);
        } catch(ClassNotFoundException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * SCIPIO: Loads a class with the specified classloader; wraps ClassNotFoundException in IllegalArgumentException.
     * @param className The name of the class to load
     * @return The requested class, or null if not found (error)
     * @throws IllegalArgumentException
     */
    public static Class<?> loadClassOrRuntimeEx(String className, ClassLoader loader) throws IllegalArgumentException {
        try {
            return loadClass(className, loader);
        } catch(ClassNotFoundException e) {
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * Returns an instance of the specified class.  This uses the default
     * no-arg constructor to create the instance.
     * @param className Name of the class to instantiate
     * @return An instance of the named class
     * @throws ClassNotFoundException
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    public static Object getInstance(String className) throws ClassNotFoundException,
            InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class<?> c = loadClass(className);
        Object o = c.getConstructor().newInstance();

        if (Debug.verboseOn()) {
            Debug.logVerbose("Instantiated object: " + o.toString(), module);
        }
        return o;
    }

    /**
     * Tests if a class properly implements the specified interface.
     * @param objectClass Class to test
     * @param interfaceName Name of the interface to test against
     * @return true if interfaceName is an interface of objectClass
     * @throws ClassNotFoundException
     */
    public static boolean interfaceOf(Class<?> objectClass, String interfaceName) throws ClassNotFoundException {
        Class<?> interfaceClass = loadClass(interfaceName);

        return interfaceOf(objectClass, interfaceClass);
    }

    /**
     * Tests if a class properly implements the specified interface.
     * @param objectClass Class to test
     * @param interfaceObject to test against
     * @return true if interfaceObject is an interface of the objectClass
     */
    public static boolean interfaceOf(Class<?> objectClass, Object interfaceObject) {
        Class<?> interfaceClass = interfaceObject.getClass();

        return interfaceOf(objectClass, interfaceClass);
    }

    /**
     * Returns an instance of the specified class using the constructor matching the specified parameters.
     * @param className Name of the class to instantiate
     * @param parameters Parameters passed to the constructor
     * @return An instance of the className
     * @throws ClassNotFoundException
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    public static Object getInstance(String className, Object[] parameters) throws ClassNotFoundException,
            InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Class<?>[] sig = new Class<?>[parameters.length];
        for (int i = 0; i < sig.length; i++) {
            sig[i] = parameters[i].getClass();
        }
        Class<?> c = loadClass(className);
        Constructor<?> con = c.getConstructor(sig);
        Object o = con.newInstance(parameters);

        if (Debug.verboseOn()) {
            Debug.logVerbose("Instantiated object: " + o.toString(), module);
        }
        return o;
    }

    /**
     * Tests if an object properly implements the specified interface.
     * @param obj Object to test
     * @param interfaceName Name of the interface to test against
     * @return true if interfaceName is an interface of obj
     * @throws ClassNotFoundException
     */
    public static boolean interfaceOf(Object obj, String interfaceName) throws ClassNotFoundException {
        Class<?> interfaceClass = loadClass(interfaceName);

        return interfaceOf(obj, interfaceClass);
    }

    /**
     * Tests if an object properly implements the specified interface.
     * @param obj Object to test
     * @param interfaceObject to test against
     * @return true if interfaceObject is an interface of obj
     */
    public static boolean interfaceOf(Object obj, Object interfaceObject) {
        Class<?> interfaceClass = interfaceObject.getClass();

        return interfaceOf(obj, interfaceClass);
    }

    /**
     * Tests if an object properly implements the specified interface.
     * @param obj Object to test
     * @param interfaceClass Class to test against
     * @return true if interfaceClass is an interface of obj
     */
    public static boolean interfaceOf(Object obj, Class<?> interfaceClass) {
        Class<?> objectClass = obj.getClass();

        return interfaceOf(objectClass, interfaceClass);
    }

    /**
     * Tests if a class properly implements the specified interface.
     * <p>
     * SCIPIO: <strong>WARNING:</strong> 2018-03-26: This method's behavior has changed:
     * it now recursively checks all of <code>objectClass</code>'s implemented interfaces
     * to see if <code>interfaceClass</code> is one of them. Previously, it only checked
     * the immediately/explicitly declared super interfaces of <code>objectClass</code>
     * and its parent classes (this is presumably what was meant by "properly implements";
     * however, that behavior caused noticeable limitations in the
     * {@link org.ofbiz.base.conversion.AbstractConverter}s and elsewhere;
     * meanwhile, this method was poorly named and therefore prone to being misused.
     * Due to this change, this method is now effectively performing nothing more than
     * a single simple {@code instanceof} operation. In addition, it also now returns
     * true if objectClass and interfaceClass are the same, an interface.
     * <p>
     * 2018-03-26: As such, this method has now been reduced, for performance reasons, to:
     * {@code typeClass.isAssignableFrom(objectClass)} plus some null checks.
     * Another change 
     * <p>
     * <strong>It is recommended to avoid using this method in client code, and instead
     * use a better library such as {@link org.apache.commons.lang3.ClassUtils}.
     *
     * @param objectClass Class to test
     * @param interfaceClass Class to test against
     * @return true if interfaceClass is an interface of objectClass
     */
    public static boolean interfaceOf(Class<?> objectClass, Class<?> interfaceClass) {
        /* SCIPIO: 2019-01-23: Replaced with a saner check
        while (objectClass != null) {
            // SCIPIO: 2018-03-26: objectClass.getInterfaces() only returned the interfaces directly implemented by the given objectClass.
            // That was wrong because it didn't look for interfaces implemented in parent classes or implemented within those explicit
            // implemented interfaces.
            //Class<?>[] ifaces = objectClass.getInterfaces();
            List<Class<?>> ifaces = ClassUtils.getAllInterfaces(objectClass);

            for (Class<?> iface: ifaces) {
                if (iface == interfaceClass) {
                    return true;
                }
            }
            objectClass = objectClass.getSuperclass();
        }
        return false; */
        return (objectClass != null && interfaceClass != null &&
                interfaceClass.isInterface() && interfaceClass.isAssignableFrom(objectClass));
    }

    /**
     * Tests if a class is a class of or a sub-class of the parent.
     * @param objectClass Class to test
     * @param parentName Name of the parent class to test against
     * @return true if objectClass is a class of or a sub-class of the parent
     * @throws ClassNotFoundException
     */
    public static boolean isOrSubOf(Class<?> objectClass, String parentName) throws ClassNotFoundException {
        Class<?> parentClass = loadClass(parentName);

        return isOrSubOf(objectClass, parentClass);
    }

    /**
     * Tests if a class is a class of or a sub-class of the parent.
     * @param objectClass Class to test
     * @param parentObject Object to test against
     * @return true if objectClass is a class of or a sub-class of the parent
     */
    public static boolean isOrSubOf(Class<?> objectClass, Object parentObject) {
        Class<?> parentClass = parentObject.getClass();

        return isOrSubOf(objectClass, parentClass);
    }

    /**
     * Tests if an object is an instance of or a sub-class of the parent.
     * @param obj Object to test
     * @param parentName Name of the parent class to test against
     * @return true if obj is an instance of or a sub-class of the parent
     * @throws ClassNotFoundException
     */
    public static boolean isOrSubOf(Object obj, String parentName) throws ClassNotFoundException {
        Class<?> parentClass = loadClass(parentName);

        return isOrSubOf(obj, parentClass);
    }

    /**
     * Tests if an object is an instance of or a sub-class of the parent.
     * @param obj Object to test
     * @param parentObject Object to test against
     * @return true if obj is an instance of or a sub-class of the parent
     */
    public static boolean isOrSubOf(Object obj, Object parentObject) {
        Class<?> parentClass = parentObject.getClass();

        return isOrSubOf(obj, parentClass);
    }

    /**
     * Tests if an object is an instance of or a sub-class of the parent.
     * @param obj Object to test
     * @param parentClass Class to test against
     * @return true if obj is an instance of or a sub-class of the parent
     */
    public static boolean isOrSubOf(Object obj, Class<?> parentClass) {
        Class<?> objectClass = obj.getClass();

        return isOrSubOf(objectClass, parentClass);
    }

    /**
     * Tests if a class is a class of or a sub-class of the parent.
     * @param objectClass Class to test
     * @param parentClass Class to test against
     * @return true if objectClass is a class of or a sub-class of the parent
     */
    public static boolean isOrSubOf(Class<?> objectClass, Class<?> parentClass) {
        while (objectClass != null) {
            if (objectClass == parentClass) {
                return true;
            }
            objectClass = objectClass.getSuperclass();
        }
        return false;
    }

    /**
     * Tests if a class is a class of a sub-class of or properly implements an interface.
     * @param objectClass Class to test
     * @param typeObject Object to test against
     * @return true if objectClass is a class of a sub-class of, or properly implements an interface
     */
    public static boolean instanceOf(Class<?> objectClass, Object typeObject) {
        Class<?> typeClass = typeObject.getClass();

        return instanceOf(objectClass, typeClass);
    }

    /**
     * Tests if a class is a class of a sub-class of or properly implements an interface.
     * @param objectClass Class to test
     * @param typeName name to test against
     * @return true if objectClass is a class or a sub-class of, or properly implements an interface
     */
    public static boolean instanceOf(Class<?> objectClass, String typeName) {
        return instanceOf(objectClass, typeName, null);
    }

    /**
     * Tests if an object is an instance of a sub-class of or properly implements an interface.
     * @param obj Object to test
     * @param typeObject Object to test against
     * @return true if obj is an instance of a sub-class of, or properly implements an interface
     */
    public static boolean instanceOf(Object obj, Object typeObject) {
        Class<?> typeClass = typeObject.getClass();

        return instanceOf(obj, typeClass);
    }

    /**
     * Tests if an object is an instance of a sub-class of or properly implements an interface.
     * @param obj Object to test
     * @param typeName name to test against
     * @return true if obj is an instance of a sub-class of, or properly implements an interface
     */
    public static boolean instanceOf(Object obj, String typeName) {
        return instanceOf(obj, typeName, null);
    }

    /**
     * Tests if a class is a class of a sub-class of or properly implements an interface.
     * @param objectClass Class to test
     * @param typeName Object to test against
     * @param loader
     * @return true if objectClass is a class of a sub-class of, or properly implements an interface
     */
    public static boolean instanceOf(Class<?> objectClass, String typeName, ClassLoader loader) {
        Class<?> infoClass = loadInfoClass(typeName, loader);

        if (infoClass == null) {
            throw new IllegalArgumentException("Illegal type found in info map (could not load class for specified type)");
        }

        return instanceOf(objectClass, infoClass);
    }

    /**
     * Tests if an object is an instance of a sub-class of or properly implements an interface.
     * @param obj Object to test
     * @param typeName Object to test against
     * @param loader
     * @return true if obj is an instance of a sub-class of, or properly implements an interface
     */
    public static boolean instanceOf(Object obj, String typeName, ClassLoader loader) {
        Class<?> infoClass = loadInfoClass(typeName, loader);

        if (infoClass == null) {
            throw new IllegalArgumentException("Illegal type found in info map (could not load class for specified type)");
        }

        return instanceOf(obj, infoClass);
    }

    public static Class<?> loadInfoClass(String typeName, ClassLoader loader) {
        try {
            return loadClass(typeName, loader);
        } catch (SecurityException se1) {
            throw new IllegalArgumentException("Problems with classloader: security exception (" +
                    se1.getMessage() + ")");
        } catch (ClassNotFoundException e1) {
            try {
                return loadClass(LANG_PACKAGE + typeName, loader);
            } catch (SecurityException se2) {
                throw new IllegalArgumentException("Problems with classloader: security exception (" +
                        se2.getMessage() + ")");
            } catch (ClassNotFoundException e2) {
                try {
                    return loadClass(SQL_PACKAGE + typeName, loader);
                } catch (SecurityException se3) {
                    throw new IllegalArgumentException("Problems with classloader: security exception (" +
                            se3.getMessage() + ")");
                } catch (ClassNotFoundException e3) {
                    throw new IllegalArgumentException("Cannot find and load the class of type: " + typeName +
                            " or of type: " + LANG_PACKAGE + typeName + " or of type: " + SQL_PACKAGE + typeName +
                            ":  (" + e3.getMessage() + ")");
                }
            }
        }
    }

    /**
     * Tests if an object is an instance of a sub-class of or properly implements an interface.
     * @param obj Object to test
     * @param typeClass Class to test against
     * @return true if obj is an instance of a sub-class of typeClass
     */
    public static boolean instanceOf(Object obj, Class<?> typeClass) {
        if (obj == null) {
            return true;
        }
        Class<?> objectClass = obj.getClass();
        return instanceOf(objectClass, typeClass);
    }

    /**
     * Tests if a class is a class of a sub-class of or properly implements an interface.
     * <p>
     * SCIPIO: <strong>WARNING:</strong> 2018-03-26: The behavior of this method has changed, due to the
     * modification of the {@link #interfaceOf(Class, Class)} method (above) on which it depended,
     * which previously only returned true on matching <code>typeClass</code> to the immediate/explicit
     * super interfaces of the <code>objectClass</code> or its parent classes, rather than recursively
     * checking all interfaces of those interfaces...
     * Due to this change, this method is now effectively performing nothing more than
     * a single simple {@code instanceof} operation.
     * <p>
     * 2018-03-26: As such, this method has now been reduced, for performance reasons, to:
     * {@code typeClass.isAssignableFrom(objectClass)}.
     * <p>
     * <strong>It is recommended to avoid using this method in client code, and instead
     * use a better library such as {@link org.apache.commons.lang3.ClassUtils}.
     *
     * @param objectClass Class to test
     * @param typeClass Class to test against
     * @return true if objectClass is a class or sub-class of, or implements typeClass
     */
    public static boolean instanceOf(Class<?> objectClass, Class<?> typeClass) {
        /* SCIPIO: 2019-01-28: Since we changed the implementation of
         *     boolean interfaceOf(Class<?> objectClass, Class<?> interfaceClass)
         * to fully recurse the interfaces, this method - which was always extremely
         * slow - can now be reduced to a single simple "instanceof" test.
         * This should help improve performance in service interface validation and numerous others.
        if (typeClass.isInterface() && !objectClass.isInterface()) {
            return interfaceOf(objectClass, typeClass);
        }
        return isOrSubOf(objectClass, typeClass); */
        return typeClass.isAssignableFrom(objectClass);
    }

    public static Object simpleTypeConvert(Object obj, String type, String format, Locale locale, boolean noTypeFail) throws GeneralException {
        return simpleTypeConvert(obj, type, format, null, locale, noTypeFail);
    }

    /**
     * Converts the passed object to the named type.
     * Initially created for only simple types but actually handle more types and not all simple types.
     * See ObjectTypeTests class for more, and (normally) up to date information
     *
     * Supported types:
     * - All primitives
     * - Simple types: String, Boolean, Double, Float, Long, Integer, BigDecimal.
     * - Other Objects: List, Map, Set, Calendar, Date (java.sql.Date), Time, Timestamp, TimeZone, Date (util.Date and sql.Date)
     * - Simple types (maybe) not handled: Short, BigInteger, Byte, Character, ObjectName and Void...
     *
     * @param obj Object to convert
     * @param type Optional Java class name of type to convert to. A <code>null</code> or empty <code>String</code> will return the original object.
     * @param format Optional (can be null) format string for Date, Time, Timestamp
     * @param timeZone Optional (can be null) TimeZone for converting dates and times
     * @param locale Optional (can be null) Locale for formatting and parsing Double, Float, Long, Integer
     * @param noTypeFail Fail (Exception) when no type conversion is available, false will return the primary object
     * @return the converted value
     * @throws GeneralException
     */
    @SourceMonitored
    @SuppressWarnings("unchecked")
    public static Object simpleTypeConvert(Object obj, String type, String format, TimeZone timeZone, Locale locale, boolean noTypeFail) throws GeneralException {
        if (obj == null || UtilValidate.isEmpty(type) || "Object".equals(type) || "java.lang.Object".equals(type)) {
            return obj;
        }
        if ("PlainString".equals(type)) {
            return obj.toString();
        }
        if (obj instanceof Node) {
            Node node = (Node) obj;
            String nodeValue =  node.getTextContent();
            if ("String".equals(type) || "java.lang.String".equals(type)) {
                return nodeValue;
            }
            return simpleTypeConvert(nodeValue, type, format, timeZone, locale, noTypeFail);
        }
        int genericsStart = type.indexOf("<");
        if (genericsStart != -1) {
            type = type.substring(0, genericsStart);
        }
        Class<?> sourceClass = obj.getClass();
        Class<?> targetClass = null;
        try {
            targetClass = loadClass(type);
        } catch (ClassNotFoundException e) {
            throw new GeneralException("Conversion from " + sourceClass.getName() + " to " + type + " not currently supported", e);
        }
        if (sourceClass.equals(targetClass)) {
            return obj;
        }
        if (obj instanceof String && ((String) obj).length() == 0) {
            return null;
        }
        Converter<Object, Object> converter = null;
        try {
            converter = (Converter<Object, Object>) Converters.getConverter(sourceClass, targetClass);
        } catch (ClassNotFoundException e) {
            // SCIPIO: 2018-08-30: do not do this for now, callers may not expect it
            //Debug.logError(e, module);
            if (Debug.verboseOn()) {
                Debug.logWarning("Could not convert object: " + e.toString(), module);
            }
        }

        if (converter != null) {
            if (converter instanceof LocalizedConverter) {
                LocalizedConverter<Object, Object> localizedConverter = (LocalizedConverter<Object, Object>) converter;
                if (timeZone == null) {
                    timeZone = TimeZone.getDefault();
                }
                if (locale == null) {
                    locale = Locale.getDefault();
                }
                try {
                    return localizedConverter.convert(obj, locale, timeZone, format);
                } catch (ConversionException e) {
                    Debug.logWarning(e, "Exception thrown while converting type: ", module);
                    throw new GeneralException(e.getMessage(), e);
                }
            }
            try {
                return converter.convert(obj);
            } catch (ConversionException e) {
                Debug.logWarning(e, "Exception thrown while converting type: ", module);
                throw new GeneralException(e.getMessage(), e);
            }
        }
        // we can pretty much always do a conversion to a String, so do that here
        if (targetClass.equals(String.class)) {
            Debug.logWarning("No special conversion available for " + obj.getClass().getName() + " to String, returning object.toString().", module);
            return obj.toString();
        }
        if (noTypeFail) {
            throw new GeneralException("Conversion from " + obj.getClass().getName() + " to " + type + " not currently supported");
        }
        if (Debug.infoOn()) {
            Debug.logInfo("No type conversion available for " + obj.getClass().getName() + " to " + targetClass.getName() + ", returning original object.", module);
        }
        return obj;
    }

    public static Object simpleTypeConvert(Object obj, String type, String format, Locale locale) throws GeneralException {
        return simpleTypeConvert(obj, type, format, locale, true);
    }

    // SCIPIO: Compatibility wrappers

    public static Object simpleTypeOrObjectConvert(Object obj, String type, String format, TimeZone timeZone, Locale locale, boolean noTypeFail) throws GeneralException {
        return simpleTypeConvert(obj, type, format, timeZone, locale, noTypeFail);
    }

    public static Object simpleTypeOrObjectConvert(Object obj, String type, String format, Locale locale, boolean noTypeFail) throws GeneralException {
        return simpleTypeOrObjectConvert(obj, type, format, null, locale, noTypeFail);
    }

    public static Object simpleTypeOrObjectConvert(Object obj, String type, String format, Locale locale) throws GeneralException {
        return simpleTypeConvert(obj, type, format, locale, true);
    }

    public static Boolean doRealCompare(Object value1, Object value2, String operator, String type, String format,
        List<Object> messages, Locale locale, ClassLoader loader, boolean value2InlineConstant) {
        boolean verboseOn = Debug.verboseOn();

        if (verboseOn) {
            Debug.logVerbose("Comparing value1: \"" + value1 + "\" " + operator + " value2:\"" + value2 + "\"", module);
        }

        try {
            if (!"PlainString".equals(type)) {
                Class<?> clz = loadClass(type, loader);
                type = clz.getName();
            }
        } catch (ClassNotFoundException e) {
            Debug.logWarning("The specified type [" + type + "] is not a valid class or a known special type, may see more errors later because of this: " + e.getMessage(), module);
        }

        if (value1 == null) {
            // some default behavior for null values, results in a bit cleaner operation
            if ("is-null".equals(operator)) {
                return Boolean.TRUE;
            } else if ("is-not-null".equals(operator)) {
                return Boolean.FALSE;
            } else if ("is-empty".equals(operator)) {
                return Boolean.TRUE;
            } else if ("is-not-empty".equals(operator)) {
                return Boolean.FALSE;
            } else if ("contains".equals(operator)) {
                return Boolean.FALSE;
            }
        }

        int result = 0;

        Object convertedValue2 = null;
        if (value2 != null) {
            Locale value2Locale = locale;
            if (value2InlineConstant) {
                value2Locale = UtilMisc.parseLocale("en");
            }
            try {
                convertedValue2 = simpleTypeConvert(value2, type, format, value2Locale);
            } catch (GeneralException e) {
                Debug.logError(e, module);
                messages.add("Could not convert value2 for comparison: " + e.getMessage());
                return null;
            }
        }

        // have converted value 2, now before converting value 1 see if it is a Collection and we are doing a contains comparison
        if ("contains".equals(operator) && value1 instanceof Collection<?>) {
            Collection<?> col1 = (Collection<?>) value1;
            return col1.contains(convertedValue2) ? Boolean.TRUE : Boolean.FALSE;
        }

        Object convertedValue1 = null;
        try {
            convertedValue1 = simpleTypeConvert(value1, type, format, locale);
        } catch (GeneralException e) {
            Debug.logError(e, module);
            messages.add("Could not convert value1 for comparison: " + e.getMessage());
            return null;
        }

        // handle null values...
        if (convertedValue1 == null || convertedValue2 == null) {
            if ("equals".equals(operator)) {
                return convertedValue1 == null && convertedValue2 == null ? Boolean.TRUE : Boolean.FALSE;
            } else if ("not-equals".equals(operator)) {
                return convertedValue1 == null && convertedValue2 == null ? Boolean.FALSE : Boolean.TRUE;
            } else if ("is-not-empty".equals(operator) || "is-empty".equals(operator)) {
                // do nothing, handled later...
            } else {
                if (convertedValue1 == null) {
                    messages.add("Left value is null, cannot complete compare for the operator " + operator);
                    return null;
                }
                if (convertedValue2 == null) {
                    messages.add("Right value is null, cannot complete compare for the operator " + operator);
                    return null;
                }
            }
        }

        if ("contains".equals(operator)) {
            if ("java.lang.String".equals(type) || "PlainString".equals(type)) {
                String str1 = (String) convertedValue1;
                String str2 = (String) convertedValue2;

                return str1.indexOf(str2) < 0 ? Boolean.FALSE : Boolean.TRUE;
            }
            messages.add("Error in XML file: cannot do a contains compare between a String and a non-String type");
            return null;
        } else if ("is-empty".equals(operator)) {
            if (convertedValue1 == null) {
                return Boolean.TRUE;
            }
            if (convertedValue1 instanceof String && ((String) convertedValue1).length() == 0) {
                return Boolean.TRUE;
            }
            if (convertedValue1 instanceof List<?> && ((List<?>) convertedValue1).size() == 0) {
                return Boolean.TRUE;
            }
            if (convertedValue1 instanceof Map<?, ?> && ((Map<?, ?>) convertedValue1).size() == 0) {
                return Boolean.TRUE;
            }
            return Boolean.FALSE;
        } else if ("is-not-empty".equals(operator)) {
            if (convertedValue1 == null) {
                return Boolean.FALSE;
            }
            if (convertedValue1 instanceof String && ((String) convertedValue1).length() == 0) {
                return Boolean.FALSE;
            }
            if (convertedValue1 instanceof List<?> && ((List<?>) convertedValue1).size() == 0) {
                return Boolean.FALSE;
            }
            if (convertedValue1 instanceof Map<?, ?> && ((Map<?, ?>) convertedValue1).size() == 0) {
                return Boolean.FALSE;
            }
            return Boolean.TRUE;
        }

        if ("java.lang.String".equals(type) || "PlainString".equals(type)) {
            String str1 = (String) convertedValue1;
            String str2 = (String) convertedValue2;

            if (str1.length() == 0 || str2.length() == 0) {
                if ("equals".equals(operator)) {
                    return str1.length() == 0 && str2.length() == 0 ? Boolean.TRUE : Boolean.FALSE;
                } else if ("not-equals".equals(operator)) {
                    return str1.length() == 0 && str2.length() == 0 ? Boolean.FALSE : Boolean.TRUE;
                } else {
                    messages.add("ERROR: Could not do a compare between strings with one empty string for the operator " + operator);
                    return null;
                }
            }
            result = str1.compareTo(str2);
        } else if ("java.lang.Double".equals(type) || "java.lang.Float".equals(type) || "java.lang.Long".equals(type) || "java.lang.Integer".equals(type) || "java.math.BigDecimal".equals(type)) {
            Number tempNum = (Number) convertedValue1;
            double value1Double = tempNum.doubleValue();

            tempNum = (Number) convertedValue2;
            double value2Double = tempNum.doubleValue();

            if (value1Double < value2Double) {
                result = -1;
            } else if (value1Double > value2Double) {
                result = 1;
            } else {
                result = 0;
            }
        } else if ("java.sql.Date".equals(type)) {
            java.sql.Date value1Date = (java.sql.Date) convertedValue1;
            java.sql.Date value2Date = (java.sql.Date) convertedValue2;
            result = value1Date.compareTo(value2Date);
        } else if ("java.sql.Time".equals(type)) {
            java.sql.Time value1Time = (java.sql.Time) convertedValue1;
            java.sql.Time value2Time = (java.sql.Time) convertedValue2;
            result = value1Time.compareTo(value2Time);
        } else if ("java.sql.Timestamp".equals(type)) {
            java.sql.Timestamp value1Timestamp = (java.sql.Timestamp) convertedValue1;
            java.sql.Timestamp value2Timestamp = (java.sql.Timestamp) convertedValue2;
            result = value1Timestamp.compareTo(value2Timestamp);
        } else if ("java.lang.Boolean".equals(type)) {
            Boolean value1Boolean = (Boolean) convertedValue1;
            Boolean value2Boolean = (Boolean) convertedValue2;
            if ("equals".equals(operator)) {
                if ((value1Boolean && value2Boolean) || (!value1Boolean && !value2Boolean)) {
                    result = 0;
                } else {
                    result = 1;
                }
            } else if ("not-equals".equals(operator)) {
                if ((!value1Boolean && value2Boolean) || (value1Boolean && !value2Boolean)) {
                    // SCIPIO: 2018-11-02: stock bugfix: fixed inverted results!
                    //result = 0;
                    result = 1;
                } else {
                    //result = 1;
                    result = 0;
                }
            } else {
                messages.add("Can only compare Booleans using the operators 'equals' or 'not-equals'");
                return null;
            }
        } else if ("java.lang.Object".equals(type)) {
            if (convertedValue1.equals(convertedValue2)) {
                result = 0;
            } else {
                result = 1;
            }
        } else {
            messages.add("Type \"" + type + "\" specified for compare not supported.");
            return null;
        }

        if (verboseOn) {
            Debug.logVerbose("Got Compare result: " + result + ", operator: " + operator, module);
        }
        if ("less".equals(operator)) {
            if (result >= 0) {
                return Boolean.FALSE;
            }
        } else if ("greater".equals(operator)) {
            if (result <= 0) {
                return Boolean.FALSE;
            }
        } else if ("less-equals".equals(operator)) {
            if (result > 0) {
                return Boolean.FALSE;
            }
        } else if ("greater-equals".equals(operator)) {
            if (result < 0) {
                return Boolean.FALSE;
            }
        } else if ("equals".equals(operator)) {
            if (result != 0) {
                return Boolean.FALSE;
            }
        } else if ("not-equals".equals(operator)) {
            if (result == 0) {
                return Boolean.FALSE;
            }
        } else {
            messages.add("Specified compare operator \"" + operator + "\" not known.");
            return null;
        }

        if (verboseOn) {
            Debug.logVerbose("Returning true", module);
        }
        return Boolean.TRUE;
    }

    //@SuppressWarnings("unchecked") // SCIPIO: Not necessary for javac or IDEA - NOTE: Eclipse may report a warning
    public static boolean isEmpty(Object value) {
        if (value == null) {
            return true;
        }

        if (value instanceof CharSequence) {
            return ((CharSequence) value).length() == 0;
        }
        if (value instanceof Collection) {
            return ((Collection<? extends Object>) value).size() == 0;
        }
        if (value instanceof Map) {
            return ((Map<? extends Object, ? extends Object>) value).size() == 0;
        }
        if (value instanceof IsEmpty) {
            return ((IsEmpty) value).isEmpty();
        }

        // These types would flood the log
        // Number covers: BigDecimal, BigInteger, Byte, Double, Float, Integer, Long, Short
        if (value instanceof Boolean) {
            return false;
        }
        if (value instanceof Number) {
            return false;
        }
        if (value instanceof Character) {
            return false;
        }
        if (value instanceof java.util.Date) {
            return false;
        }
        // SCIPIO: Check array length
        if (value.getClass().isArray()) {
            return Array.getLength(value) == 0;
        }
        // SCIPIO: Check Iterable (NOTE: inefficient, should probably be avoided)
        if (value instanceof Iterable) {
            return !((Iterable<?>) value).iterator().hasNext();
        }

        if (Debug.verboseOn()) {
            Debug.logVerbose("In ObjectType.isEmpty(Object value) returning false for " + value.getClass() + " Object.", module);
        }
        return false;
    }

    @SuppressWarnings("serial")
    public static final class NullObject implements Serializable {
        public NullObject() { }

        @Override
        public String toString() {
            return "ObjectType.NullObject";
        }

        @Override
        public int hashCode() {
            return toString().hashCode();
        }

        @Override
        public boolean equals(Object other) {
            // should do equality of object? don't think so, just same type
            return other instanceof NullObject;
        }
    }

    /**
     * Gets declared fields from this class (first) and its superclasses (last).
     *
     * <p>SCIPIO: 3.0.0: Added.</p>
     */
    public static List<Field> getAllDeclaredFields(List<Field> fields, Class<?> type) {
        fields.addAll(Arrays.asList(type.getDeclaredFields()));
        if (type.getSuperclass() != null) {
            getAllDeclaredFields(fields, type.getSuperclass());
        }
        return fields;
    }

    /**
     * Gets declared fields from this class's superclasses (first) and itself (last).
     *
     * <p>SCIPIO: 3.0.0: Added.</p>
     */
    public static List<Field> getAllDeclaredFieldsSuperFirst(List<Field> fields, Class<?> type) {
        if (type.getSuperclass() != null) {
            getAllDeclaredFieldsSuperFirst(fields, type.getSuperclass());
        }
        fields.addAll(Arrays.asList(type.getDeclaredFields()));
        return fields;
    }

}