org.jtester.module.utils.ObjectFormatter.java Source code

Java tutorial

Introduction

Here is the source code for org.jtester.module.utils.ObjectFormatter.java

Source

/*
 * Copyright 2008,  Unitils.org
 *
 * Licensed 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.jtester.module.utils;

import static org.apache.commons.lang.ClassUtils.getShortClassName;
import static org.jtester.hamcrest.reflection.HibernateUtil.getUnproxiedValue;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import static java.lang.reflect.Modifier.isStatic;
import static java.lang.reflect.Modifier.isTransient;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Map;

/**
 * A class for generating a string representation of any object, array or primitive value.
 * <p/>
 * Non-primitive objects are processed recursively so that a string representation of inner objects is also generated.
 * Too avoid too much output, this recursion is limited with a given maximum depth.
 *
 * @author Tim Ducheyne
 * @author Filip Neven
 */
public class ObjectFormatter {

    /**
     * The maximum recursion depth
     */
    protected int maxDepth;

    /**
     * Creates a formatter with a maximum recursion depth of 5.
     */
    public ObjectFormatter() {
        this(5);
    }

    /**
     * Creates a formatter with the given maximum recursion depth.
     * <p/>
     * NOTE: there is no cycle detection. A large max depth value can cause lots of output in case of a cycle.
     *
     * @param maxDepth The max depth > 0
     */
    public ObjectFormatter(int maxDepth) {
        this.maxDepth = maxDepth;
    }

    /**
     * Gets the string representation of the given object.
     *
     * @param object The instance
     * @return The string representation, not null
     */
    public String format(Object object) {
        StringBuilder result = new StringBuilder();
        formatImpl(object, 0, result);
        return result.toString();
    }

    /**
     * Actual implementation of the formatting.
     *
     * @param object       The instance
     * @param currentDepth The current recursion depth
     * @param result       The builder to append the result to, not null
     */
    protected void formatImpl(Object object, int currentDepth, StringBuilder result) {
        // get the actual value if the value is wrapped by a Hibernate proxy
        object = getUnproxiedValue(object);

        if (object == null) {
            result.append(String.valueOf(object));
            return;
        }
        if (object instanceof String) {
            result.append('"');
            result.append(object);
            result.append('"');
            return;
        }
        if (object instanceof Number || object instanceof Date) {
            result.append(String.valueOf(object));
            return;
        }
        if (object instanceof Character) {
            result.append('\'');
            result.append(String.valueOf(object));
            result.append('\'');
            return;
        }
        Class<?> dummyObjectClass = getDummyObjectClass();
        if (dummyObjectClass != null && dummyObjectClass.isAssignableFrom(object.getClass())) {
            result.append("Dummy<");
            result.append(object.toString());
            result.append(">");
            return;
        }
        Class<?> type = object.getClass();
        if (type.isPrimitive() || type.isEnum()) {
            result.append(String.valueOf(object));
            return;
        }
        if (formatMock(object, result)) {
            return;
        }
        if (formatProxy(object, result)) {
            return;
        }
        if (type.getName().startsWith("java.lang")) {
            result.append(String.valueOf(object));
            return;
        }
        if (type.isArray()) {
            formatArray(object, currentDepth, result);
            return;
        }
        if (object instanceof Collection) {
            formatCollection((Collection<?>) object, currentDepth, result);
            return;
        }
        if (object instanceof Map) {
            formatMap((Map<?, ?>) object, currentDepth, result);
            return;
        }
        if (currentDepth >= maxDepth) {
            result.append(getShortClassName(type));
            result.append("<...>");
            return;
        }
        formatObject(object, currentDepth, result);
    }

    /**
     * Formats the given array.
     *
     * @param array        The array, not null
     * @param currentDepth The current recursion depth
     * @param result       The builder to append the result to, not null
     */
    protected void formatArray(Object array, int currentDepth, StringBuilder result) {
        if (array instanceof byte[]) {
            result.append(Arrays.toString((byte[]) array));
            return;
        }
        if (array instanceof short[]) {
            result.append(Arrays.toString((short[]) array));
            return;
        }
        if (array instanceof int[]) {
            result.append(Arrays.toString((int[]) array));
            return;
        }
        if (array instanceof long[]) {
            result.append(Arrays.toString((long[]) array));
            return;
        }
        if (array instanceof char[]) {
            result.append(Arrays.toString((char[]) array));
            return;
        }
        if (array instanceof float[]) {
            result.append(Arrays.toString((float[]) array));
            return;
        }
        if (array instanceof double[]) {
            result.append(Arrays.toString((double[]) array));
            return;
        }
        if (array instanceof boolean[]) {
            result.append(Arrays.toString((boolean[]) array));
            return;
        }

        // format an object array
        result.append("[");
        boolean notFirst = false;
        for (Object element : (Object[]) array) {
            if (notFirst) {
                result.append(", ");
            } else {
                notFirst = true;
            }
            formatImpl(element, currentDepth + 1, result);
        }
        result.append("]");
    }

    /**
     * Formats the given collection.
     *
     * @param collection   The collection, not null
     * @param currentDepth The current recursion depth
     * @param result       The builder to append the result to, not null
     */
    protected void formatCollection(Collection<?> collection, int currentDepth, StringBuilder result) {
        result.append("[");
        boolean notFirst = false;
        for (Object element : collection) {
            if (notFirst) {
                result.append(", ");
            } else {
                notFirst = true;
            }
            formatImpl(element, currentDepth + 1, result);
        }
        result.append("]");
    }

    /**
     * Formats the given map.
     *
     * @param map          The map, not null
     * @param currentDepth The current recursion depth
     * @param result       The builder to append the result to, not null
     */
    protected void formatMap(Map<?, ?> map, int currentDepth, StringBuilder result) {
        result.append("{");
        boolean notFirst = false;
        for (Map.Entry<?, ?> element : map.entrySet()) {
            if (notFirst) {
                result.append(", ");
            } else {
                notFirst = true;
            }
            formatImpl(element.getKey(), currentDepth, result);
            result.append("=");
            formatImpl(element.getValue(), currentDepth + 1, result);
        }
        result.append("}");
    }

    /**
     * Formats the given object by formatting the inner fields.
     *
     * @param object       The object, not null
     * @param currentDepth The current recursion depth
     * @param result       The builder to append the result to, not null
     */
    protected void formatObject(Object object, int currentDepth, StringBuilder result) {
        Class<?> type = object.getClass();
        result.append(getShortClassName(type));
        result.append("<");
        formatFields(object, type, currentDepth, result);
        result.append(">");
    }

    /**
     * Formats the field values of the given object.
     *
     * @param object       The object, not null
     * @param clazz        The class for which to format the fields, not null
     * @param currentDepth The current recursion depth
     * @param result       The builder to append the result to, not null
     */
    protected void formatFields(Object object, Class<?> clazz, int currentDepth, StringBuilder result) {
        Field[] fields = clazz.getDeclaredFields();
        AccessibleObject.setAccessible(fields, true);

        for (int i = 0; i < fields.length; i++) {
            // skip transient and static fields
            Field field = fields[i];
            if (isTransient(field.getModifiers()) || isStatic(field.getModifiers()) || field.isSynthetic()) {
                continue;
            }
            try {
                if (i > 0) {
                    result.append(", ");
                }
                result.append(field.getName());
                result.append("=");
                formatImpl(field.get(object), currentDepth + 1, result);

            } catch (IllegalAccessException e) {
                // this can't happen. Would get a Security exception instead
                // throw a runtime exception in case the impossible happens.
                throw new InternalError("Unexpected IllegalAccessException");
            }
        }

        // format fields declared in superclass
        Class<?> superclazz = clazz.getSuperclass();
        while (superclazz != null && !superclazz.getName().startsWith("java.lang")) {
            formatFields(object, superclazz, currentDepth, result);
            superclazz = superclazz.getSuperclass();
        }
    }

    protected boolean formatMock(Object object, StringBuilder result) {
        try {
            Class<?> proxyUtilsClass = getProxyUtilsClass();
            if (proxyUtilsClass == null) {
                return false;
            }
            String mockName = (String) proxyUtilsClass.getMethod("getMockName", Object.class).invoke(null, object);
            if (mockName == null) {
                return false;
            }
            result.append("Mock<");
            result.append(mockName);
            result.append(">");
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    protected boolean formatProxy(Object object, StringBuilder result) {
        String className = getShortClassName(object.getClass());
        int index = className.indexOf("..EnhancerByCGLIB..");
        if (index > 0) {
            result.append("Proxy<");
            result.append(className.substring(0, index));
            result.append(">");
            return true;
        }
        return false;
    }

    /**
     * @return The interface that represents a dummy object. If the DummyObject interface is not in the
     *         classpath, null is returned.
     */
    protected Class<?> getDummyObjectClass() {
        try {
            return Class.forName("org.unitils.mock.dummy.DummyObject");
        } catch (ClassNotFoundException e) {
            return null;
        }
    }

    /**
     * @return The proxy utils. null if not in classpath
     */
    protected Class<?> getProxyUtilsClass() {
        try {

            return Class.forName("org.unitils.mock.core.proxy.ProxyUtils");
        } catch (ClassNotFoundException e) {
            return null;
        }
    }

}