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

com.squeakysand.commons.lang.ToStringHelper Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2010-2012 Craig S. Dickson (http://craigsdickson.com)
 *
 * 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 com.squeakysand.commons.lang;

import com.squeakysand.commons.beans.BeanHelper;
import com.squeakysand.commons.util.DictionaryMap;
import com.squeakysand.commons.util.IterableArray;
import com.squeakysand.commons.util.IterableEnumeration;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Provides utility methods to help override the {@link Object#toString()} method and/or create more meaningful logging
 * output.
 */
public final class ToStringHelper {

    private static class ArrayStringRenderer implements StringRenderer {

        @Override
        public boolean isAbleToRender(Class klass) {
            return klass.isArray();
        }

        @Override
        public String render(Object target) {
            @SuppressWarnings("rawtypes")
            IterableArray array = new IterableArray(target);
            return ToStringHelper.toString(array);
        }
    }

    private static class ClassStringRenderer implements StringRenderer {

        @Override
        public boolean isAbleToRender(Class klass) {
            return Class.class.isAssignableFrom(klass);
        }

        @Override
        public String render(Object target) {
            return ((Class) target).getName();
        }

    }

    private static class DictionaryStringRenderer implements StringRenderer {

        @Override
        public boolean isAbleToRender(Class klass) {
            return Dictionary.class.isAssignableFrom(klass);
        }

        @Override
        public String render(Object target) {
            Dictionary dictionary = (Dictionary) target;
            Map map = new DictionaryMap(dictionary);
            return ToStringHelper.toString(map);
        }

    }

    private static class EnumerationStringRenderer implements StringRenderer {

        @Override
        public boolean isAbleToRender(Class klass) {
            return Enumeration.class.isAssignableFrom(klass);
        }

        @Override
        public String render(Object target) {
            @SuppressWarnings({"rawtypes", "unchecked"})
            IterableEnumeration iterableEnumeration = new IterableEnumeration((Enumeration) target);
            return ToStringHelper.toString(iterableEnumeration);
        }
    }

    private static class FileStringRenderer implements StringRenderer {

        @Override
        public boolean isAbleToRender(Class klass) {
            return File.class.isAssignableFrom(klass);
        }

        @Override
        public String render(Object target) {
            return ((File) target).getPath();
        }
    }

    private static class FloatStringRenderer implements StringRenderer {

        @Override
        public boolean isAbleToRender(Class klass) {
            return Float.class.isAssignableFrom(klass) || float.class.isAssignableFrom(klass);
        }

        @Override
        public String render(Object target) {
            return Float.toString((Float) target) + 'F';
        }
    }

    private static class IterableStringRenderer implements StringRenderer {

        @Override
        public boolean isAbleToRender(Class klass) {
            return Iterable.class.isAssignableFrom(klass);
        }

        @Override
        public String render(Object target) {
            StringBuilder sb = new StringBuilder("[");
            Iterable iterable = (Iterable) target;
            for (Object o : iterable) {
                String tmp = ToStringHelper.toString(o);
                sb.append(tmp);
                sb.append(", ");
            }
            // chop off the last comma separator
            if (sb.length() > 1) {
                sb.setLength(sb.length() - 2);
            }
            sb.append("]");
            return sb.toString();
        }
    }

    private static class JavaBeanStringRenderer implements StringRenderer {

        @Override
        public boolean isAbleToRender(Class klass) {
            return true;
        }

        @Override
        public String render(Object target) {
            StringBuilder sb = new StringBuilder();
            String name = target.getClass().getName();
            int dotIndex = name.lastIndexOf('.');
            if (dotIndex != -1) {
                name = name.substring(dotIndex + 1);
            }
            sb.append(name);
            String tmp = BeanHelper.getPropertiesString(target, true);
            sb.append(tmp);
            return sb.toString();
        }
    }

    private static class LongStringRenderer implements StringRenderer {

        @Override
        public boolean isAbleToRender(Class klass) {
            return Long.class.isAssignableFrom(klass) || long.class.isAssignableFrom(klass);
        }

        @Override
        public String render(Object target) {
            return Long.toString((Long) target) + 'L';
        }

    }

    private static class MapStringRenderer implements StringRenderer {

        @Override
        public boolean isAbleToRender(Class klass) {
            return Map.class.isAssignableFrom(klass);
        }

        @Override
        public String render(Object target) {
            Map map = (Map) target;
            StringBuilder sb = new StringBuilder("{");
            for (Entry entry : map.entrySet()) {
                Object key = entry.getKey();
                String keyString = ToStringHelper.toString(key);
                Object value = entry.getValue();
                String valueString = ToStringHelper.toString(value);
                sb.append(keyString);
                sb.append(" : ");
                sb.append(valueString);
                sb.append(", ");
            }
            // chop off the last comma separator
            if (sb.length() > 1) {
                sb.setLength(sb.length() - 2);
            }
            sb.append("}");
            return sb.toString();
        }

    }

    private static interface StringRenderer {

        boolean isAbleToRender(Class klass);

        String render(Object target);

    }

    private static class StringStringRenderer implements StringRenderer {

        @Override
        public boolean isAbleToRender(Class klass) {
            return String.class.isAssignableFrom(klass);
        }

        @Override
        public String render(Object target) {
            return '\"' + (String) target + '\"';
        }

    }

    private static class UrlStringRenderer implements StringRenderer {

        @Override
        public boolean isAbleToRender(Class klass) {
            return URL.class.isAssignableFrom(klass);
        }

        @Override
        public String render(Object target) {
            return ((URL) target).toExternalForm();
        }

    }

    private static class ValueOfStringRenderer implements StringRenderer {

        @Override
        public boolean isAbleToRender(Class klass) {
            return klass.isPrimitive() || klass.isEnum() || klass.isAnnotation() || Boolean.class.isAssignableFrom(klass)
                            || Character.class.isAssignableFrom(klass) || Date.class.isAssignableFrom(klass) || Number.class.isAssignableFrom(klass);
        }

        @Override
        public String render(Object target) {
            return String.valueOf(target);
        }

    }

    private static final Logger LOG = LoggerFactory.getLogger(ToStringHelper.class);

    private static final String NULL = "null";

    private static final List RENDERERS = new ArrayList();

    static {
        RENDERERS.add(new StringStringRenderer());
        RENDERERS.add(new FloatStringRenderer());
        RENDERERS.add(new LongStringRenderer());
        RENDERERS.add(new MapStringRenderer());
        RENDERERS.add(new DictionaryStringRenderer());
        RENDERERS.add(new IterableStringRenderer());
        RENDERERS.add(new EnumerationStringRenderer());
        RENDERERS.add(new ArrayStringRenderer());
        RENDERERS.add(new UrlStringRenderer());
        RENDERERS.add(new FileStringRenderer());
        RENDERERS.add(new ClassStringRenderer());
        RENDERERS.add(new ValueOfStringRenderer());
        RENDERERS.add(new JavaBeanStringRenderer());
    }

    private static final ThreadLocal DEPTH_CONTROLLER = new ThreadLocal() {

        /**
         * @see java.lang.ThreadLocal#get()
         */
        @Override
        public Integer get() {
            Integer currentValue = super.get();
            // LOG.debug("getting current depth value of {} for Thread: {}", currentValue,
            // Thread.currentThread().getId());
            return currentValue;
        }

        /**
         * @see java.lang.ThreadLocal#remove()
         */
        @Override
        public void remove() {
            // LOG.debug("removing depth value for Thread: {}", Thread.currentThread().getId());
            super.remove();
        }

        /**
         * @see java.lang.ThreadLocal#set(java.lang.Object)
         */
        @Override
        public void set(Integer value) {
            // LOG.debug("setting depth value of {} for Thread: {}", value, Thread.currentThread().getId());
            super.set(value);
        }

        /**
         * @see java.lang.ThreadLocal#initialValue()
         */
        @Override
        protected Integer initialValue() {
            // LOG.debug("getting new depth value for Thread: {}", Thread.currentThread().getId());
            return 0;
        }

    };

    private static final Integer MAX_DEPTH = 5;

    private static final String DEFAULT_STRING = "...";

    /**
     * Generates a String representation of the passed in Object. This method will handle null values,
     * objects, arrays of primitives and arrays of objects.
     * 
     * @param o
     *            the target object, can be null or an array.
     * @return a String representation of the target object.
     */
    public static String toString(Object o) {
        // LOG.debug("entering: {}", Thread.currentThread().getId());
        String result = null;
        if (o == null) {
            result = NULL;
        } else {
            int currentDepth = DEPTH_CONTROLLER.get();
            try {
                if (currentDepth < MAX_DEPTH) {
                    Class klass = o.getClass();
                    // LOG.debug("called for object of class {}", klass);
                    StringRenderer renderer = findRenderer(klass);
                    DEPTH_CONTROLLER.set(currentDepth + 1);
                    try {
                        result = renderer.render(o);
                    } catch (RuntimeException e) {
                        LOG.error(e.getLocalizedMessage(), e);
                        result = DEFAULT_STRING;
                    } finally {
                        DEPTH_CONTROLLER.set(currentDepth);
                    }
                } else {
                    result = DEFAULT_STRING;
                }
            } finally {
                if (DEPTH_CONTROLLER.get() == 0) {
                    DEPTH_CONTROLLER.remove();
                }
            }
        }
        // LOG.debug("returning result '{}' for Thread {}", result, Thread.currentThread().getId());
        return result;
    }

    private static StringRenderer findRenderer(Class klass) {
        // LOG.debug("entering: {}", Thread.currentThread().getId());
        // check to see if there is a converter directly registered for this class
        StringRenderer result = null;
        for (StringRenderer renderer : RENDERERS) {
            if (renderer.isAbleToRender(klass)) {
                result = renderer;
                break;
            }
        }
        // LOG.debug("for {}, renderer is {} in Thread {}", new Object[] {klass.getName(), result.getClass().getName(),
        // Thread.currentThread().getId()});
        return result;
    }

    private ToStringHelper() {
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy