ArgumentToStringBuilderImpl.java

/*
 * 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.apache.fulcrum.yaafi.interceptor.util;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collection;
import java.util.Dictionary;
import java.util.Iterator;

import org.apache.commons.lang3.StringUtils;

/**
 * Creates a string representation of method argument.
 *
 * @author <a href="mailto:siegfried.goeschl@it20one.at">Siegfried Goeschl</a>
 */
public class ArgumentToStringBuilderImpl implements InterceptorToStringBuilder
{
    /** include the class name in the result */
    public static final int INCLUDE_CLASSNAME = 0x1;

    /** include the hashcode in the result */
    public static final int INCLUDE_HASHCODE = 0x02;

    /** the default mode using class names and hashcode */
    private static int defaultMode = INCLUDE_CLASSNAME & INCLUDE_HASHCODE;

    /** our current formatting mode */
    private int mode;

    /** the maximum length of a dumped argument */
    private static final int MAX_LINE_LENGTH = 2000;

    /** seperator for the arguments in the logfile */
    private static final String SEPERATOR = ";";

    /** the output for a NULL value **/
    private static final String NULL_STRING = "<null>";

    /** the output for a length string **/
    private static final String LENGTH_STRING = "length=";

    /** the output for a value string **/
    private static final String VALUE_STRING = "value=";

    /** maximum line length for dumping arguments */
    private int maxArgLength;

    /** the result of the invocation */
    private StringBuilder buffer;

    /** the target object */
    private Object target;

    /**
     * Constructor
     */
    public ArgumentToStringBuilderImpl()
    {
        this.mode = ArgumentToStringBuilderImpl.defaultMode;
        this.maxArgLength = MAX_LINE_LENGTH;
        this.buffer = new StringBuilder();
    }

    /**
     * Constructor
     *
     * @param target the object to print
     */
    public ArgumentToStringBuilderImpl(Object target)
    {
        this(target,MAX_LINE_LENGTH);
    }

    /**
     * Constructor
     *
     * @param target the object to print
     * @param maxArgLength the maximum length
     */
    public ArgumentToStringBuilderImpl(Object target, int maxArgLength)
    {
        this(target,
            maxArgLength,
            ArgumentToStringBuilderImpl.defaultMode
            );
    }

    /**
     * Constructor
     *
     * @param target the object to print
     * @param maxArgLength the maximum length
     * @param mode the formatting mode to use
     */
    public ArgumentToStringBuilderImpl(Object target, int maxArgLength, int mode)
    {
        this.buffer = new StringBuilder();
        this.target = target;
        this.maxArgLength = maxArgLength;
        this.mode = mode;
    }

    /**
     * @see org.apache.fulcrum.yaafi.interceptor.util.InterceptorToStringBuilder#setMaxArgLength(int)
     */
    public void setMaxArgLength(int maxArgLength)
    {
        this.maxArgLength = maxArgLength;
    }

    /**
     * @see org.apache.fulcrum.yaafi.interceptor.util.InterceptorToStringBuilder#setTarget(java.lang.Object)
     */
    public void setTarget(Object target)
    {
        this.target = target;
    }

    /**
     * @see org.apache.fulcrum.yaafi.interceptor.util.InterceptorToStringBuilder#setMode(int)
     */
    public void setMode(int mode)
    {
        this.mode = mode;
    }

    /**
     * @return Returns the mode.
     */
    public int getMode()
    {
        return this.mode;
    }

        /**
     * @see java.lang.Object#toString()
     */
    public String toString()
    {
        try
        {
            if( this.target == null )
            {
                this.buffer.append(NULL_STRING);
            }
            else if( this.target instanceof Object[] )
            {
                this.appendClassName(target);
                this.appendHashCode(target);
                this.appendChar('[');
                this.append( this.toString((Object[]) this.target) );
                this.appendChar(']');
            }
            else if( this.target instanceof boolean[] )
            {
                this.appendClassName(target);
                this.appendHashCode(target);
                this.appendChar('[');
                this.append( this.toString((boolean[]) this.target) );
                this.appendChar(']');
            }
            else if( this.target instanceof char[] )
            {
                this.appendClassName(target);
                this.appendHashCode(target);
                this.appendChar('[');
                this.append( this.toString((char[]) this.target) );
                this.appendChar(']');
            }
            else if( this.target instanceof byte[] )
            {
                this.appendClassName(target);
                this.appendHashCode(target);
                this.appendChar('[');
                this.append( this.toString((byte[]) this.target) );
                this.appendChar(']');
            }
            else if( this.target instanceof short[] )
            {
                this.appendClassName(target);
                this.appendHashCode(target);
                this.appendChar('[');
                this.append( this.toString((short[]) this.target) );
                this.appendChar(']');
            }
            else if( this.target instanceof int[] )
            {
                this.appendClassName(target);
                this.appendHashCode(target);
                this.appendChar('[');
                this.append( this.toString((int[]) this.target) );
                this.appendChar(']');
            }
            else if( this.target instanceof long[] )
            {
                this.appendClassName(target);
                this.appendHashCode(target);
                this.appendChar('[');
                this.append( this.toString((long[]) this.target) );
                this.appendChar(']');
            }
            else if( this.target instanceof float[] )
            {
                this.appendClassName(target);
                this.appendHashCode(target);
                this.appendChar('[');
                this.append( this.toString((float[]) this.target) );
                this.appendChar(']');
            }
            else if( this.target instanceof double[] )
            {
                this.appendClassName(target);
                this.appendHashCode(target);
                this.appendChar('[');
                this.append( this.toString((double[]) this.target) );
                this.appendChar(']');
            }
            else if( this.target instanceof String )
            {
                this.appendClassName(target);
                this.appendHashCode(target);
                this.appendChar('[');
                this.append( this.toString((String) this.target) );
                this.appendChar(']');
            }
            else if( this.target instanceof Collection )
            {
                this.appendClassName(target);
                this.appendHashCode(target);
                this.appendChar('[');
                this.append( this.toString((Collection<?>) this.target) );
                this.appendChar(']');
            }
            else if( this.target instanceof Dictionary )
            {
                this.appendClassName(target);
                this.appendHashCode(target);
                this.appendChar('[');
                this.append( this.toString((Dictionary<?, ?>) this.target) );
                this.appendChar(']');
            }
            else if( this.target instanceof Throwable )
            {
                this.append( this.toString((Throwable) this.target) );
            }
            else
            {
                this.append( this.toString( (Object) this.target ) );
            }
        }
        catch (Throwable t)
        {
            t.printStackTrace();
            return "<" + t + ">";
        }

        return this.buffer.toString();
    }


    /**
     * Create a String representation for a Throwable.
     *
     * @param throwable the Throwable
     * @return the string representation
     */
    protected String toString(Throwable throwable)
    {
        String result = null;

        if( throwable == null )
        {
            result = NULL_STRING;
        }
        else
        {
            result = this.getStackTrace(throwable);
        }

        return result;
    }

    /**
     * Create a string representation of an object array.
     *
     * @param array the array to print
     * @return the result
     */
    protected String toString(Object[] array)
    {
        StringBuilder temp = new StringBuilder();
        ArgumentToStringBuilderImpl toStringBuilder = null;

        if( array == null )
        {
            return NULL_STRING;
        }
        else
        {
            temp.append(LENGTH_STRING);
            temp.append(array.length);
            temp.append(',');

            for( int i=0; i<array.length; i++ )
            {
                temp.append('[');
                temp.append(i);
                temp.append(']');
                temp.append('=');
                toStringBuilder = new ArgumentToStringBuilderImpl(array[i],this.getMaxArgLength(),this.getMode());
                temp.append(toStringBuilder.toString());

                if( i<array.length-1)
                {
                    temp.append(',');
                }

                if( temp.length() > this.getMaxArgLength() )
                {
                    break;
                }
            }
        }

        return temp.toString();
    }

    /**
     * Create a string representation of a boolean[].
     *
     * @param array the array to print
     * @return the result
     */
    protected String toString(boolean[] array)
    {
        StringBuilder temp = new StringBuilder();

        if( array == null )
        {
            return NULL_STRING;
        }
        else
        {
            temp.append(LENGTH_STRING);
            temp.append(array.length);
            temp.append(',');
            temp.append(VALUE_STRING);

            for( int i=0; i<array.length; i++ )
            {
                temp.append(array[i]);
                if( i<array.length-1)
                {
                    temp.append(',');
                }

                if( temp.length() > this.getMaxArgLength() )
                {
                    break;
                }
            }
        }

        return temp.toString();
    }

    /**
     * Create a string representation of a char[].
     *
     * @param array the array to print
     * @return the result
     */
    protected String toString(char[] array)
    {
        StringBuilder temp = new StringBuilder();

        if( array == null )
        {
            return NULL_STRING;
        }
        else
        {
            temp.append(LENGTH_STRING);
            temp.append(array.length);
            temp.append(',');
            temp.append(VALUE_STRING);

            for( int i=0; i<array.length; i++ )
            {
                temp.append(array[i]);
                if( i<array.length-1)
                {
                    temp.append('.');
                }

                if( temp.length() > this.getMaxArgLength() )
                {
                    break;
                }
            }
        }

        return temp.toString();
    }

    /**
     * Create a string representation of a short[].
     *
     * @param array the array to print
     * @return the result
     */
    protected String toString(short[] array)
    {
        StringBuilder temp = new StringBuilder();

        if( array == null )
        {
            return NULL_STRING;
        }
        else
        {
            temp.append(LENGTH_STRING);
            temp.append(array.length);
            temp.append(',');
            temp.append(VALUE_STRING);

            for( int i=0; i<array.length; i++ )
            {
                temp.append(array[i]);
                if( i<array.length-1)
                {
                    temp.append(',');
                }

                if( temp.length() > this.getMaxArgLength() )
                {
                    break;
                }
            }
        }

        return temp.toString();
    }

    /**
     * Create a string representation of a int[].
     *
     * @param array the array to print
     * @return the result
     */
    protected String toString(int[] array)
    {
        StringBuilder temp = new StringBuilder();

        if( array == null )
        {
            return NULL_STRING;
        }
        else
        {
            temp.append(LENGTH_STRING);
            temp.append(array.length);
            temp.append(',');
            temp.append(VALUE_STRING);

            for( int i=0; i<array.length; i++ )
            {
                temp.append(array[i]);
                if( i<array.length-1)
                {
                    temp.append(',');
                }

                if( temp.length() > this.getMaxArgLength() )
                {
                    break;
                }
            }
        }

        return temp.toString();
    }

    /**
     * Create a string representation of a char[].
     *
     * @param array the array to print
     * @return the result
     */
    protected String toString(long[] array)
    {
        StringBuilder temp = new StringBuilder();

        if( array == null )
        {
            return NULL_STRING;
        }
        else
        {
            temp.append(LENGTH_STRING);
            temp.append(array.length);
            temp.append(',');
            temp.append(VALUE_STRING);

            for( int i=0; i<array.length; i++ )
            {
                temp.append(array[i]);
                if( i<array.length-1)
                {
                    temp.append(',');
                }

                if( temp.length() > this.getMaxArgLength() )
                {
                    break;
                }
            }
        }

        return temp.toString();
    }

    /**
     * Create a string representation of a float[].
     *
     * @param array the array to print
     * @return the result
     */
    protected String toString(float[] array)
    {
        StringBuilder temp = new StringBuilder();

        if( array == null )
        {
            return NULL_STRING;
        }
        else
        {
            temp.append(LENGTH_STRING);
            temp.append(array.length);
            temp.append(',');
            temp.append(VALUE_STRING);

            for( int i=0; i<array.length; i++ )
            {
                temp.append(array[i]);
                if( i<array.length-1)
                {
                    temp.append(',');
                }

                if( temp.length() > this.getMaxArgLength() )
                {
                    break;
                }
            }
        }

        return temp.toString();
    }

    /**
     * Create a string representation of a double[].
     *
     * @param array the array to print
     * @return the result
     */
    protected String toString(double[] array)
    {
        StringBuilder temp = new StringBuilder();

        if( array == null )
        {
            return NULL_STRING;
        }
        else
        {
            temp.append(LENGTH_STRING);
            temp.append(array.length);
            temp.append(',');
            temp.append(VALUE_STRING);

            for( int i=0; i<array.length; i++ )
            {
                temp.append(array[i]);
                if( i<array.length-1)
                {
                    temp.append(',');
                }

                if( temp.length() > this.getMaxArgLength() )
                {
                    break;
                }
            }
        }

        return temp.toString();
    }

    /**
     * Create a string representation of a String.
     *
     * @param string the string to print
     * @return the result
     */
    protected String toString(String string)
    {
        StringBuilder temp = new StringBuilder();

        if( string == null )
        {
            return NULL_STRING;
        }
        else
        {
            temp.append(LENGTH_STRING);
            temp.append(string.length());
            temp.append(',');
            temp.append(VALUE_STRING);
            temp.append(string);
        }

        return temp.toString();
    }

    /**
     * Create a string representation of a char[].
     *
     * @param array the array to print
     * @return the result
     */
    protected String toString(byte[] array)
    {
        StringBuilder temp = new StringBuilder();

        if( array == null )
        {
            temp.append(NULL_STRING);
        }
        else
        {
            temp.append(LENGTH_STRING);
            temp.append(array.length);
        }

        return temp.toString();
    }

    /**
     * Create a string representation of a java.util.Collection.
     *
     * @param collection the collection to print
     * @return the result
     */
    protected String toString(Collection<?> collection)
    {
        int index = 0;
        StringBuilder temp = new StringBuilder();
        ArgumentToStringBuilderImpl toStringBuilder = null;

        if( collection == null )
        {
          return NULL_STRING;
        }
        else
        {
            temp.append(LENGTH_STRING);
            temp.append(collection.size());
            temp.append(',');

            Iterator<?> iterator = collection.iterator();

            while (iterator.hasNext())
            {
                temp.append('[');
                temp.append(index++);
                temp.append(']');
                temp.append('=');

                toStringBuilder = new ArgumentToStringBuilderImpl(
                    iterator.next(),
                    this.getMaxArgLength(),
                    this.getMode()
                    );

                temp.append(toStringBuilder.toString());

                if( index<collection.size()-1)
                {
                    temp.append(',');
                }

                if( temp.length() > this.getMaxArgLength() )
                {
                    break;
                }
            }
        }

        return temp.toString();
    }

    /**
     * Create a string representation of a Dictionary.
     *
     * @param dictionary the collection to print
     * @return the result
     */
    protected String toString(Dictionary<?, ?> dictionary)
    {
        StringBuilder temp = new StringBuilder();

        if( dictionary == null )
        {
            return NULL_STRING;
        }
        else
        {
            temp.append(LENGTH_STRING);
            temp.append(dictionary.size());
            temp.append(',');
            temp.append(VALUE_STRING);
            temp.append(dictionary.toString());
        }

        return temp.toString();
    }

    /**
     * Create a String representation for an arbitrary object.
     *
     * @param object the object
     * @return string representation
     */
    protected String toString(Object object)
    {
        String result = null;
        String temp = null;
        String className = null;

        if( object == null )
        {
            result = NULL_STRING;
        }
        else
        {
            temp = object.toString();

            className = StringUtils.replace(
                object.getClass().getName(),
                "java.lang.", ""
                );

            if( temp.startsWith(className) == false )
            {
                int hashCode = object.hashCode();
                StringBuilder tempBuffer = new StringBuilder();
                tempBuffer.append(className);
                tempBuffer.append('@');
                tempBuffer.append(hashCode);
                tempBuffer.append('[');
                tempBuffer.append(temp);
                tempBuffer.append(']');

                result = tempBuffer.toString();
            }
            else
            {
                result = temp;
            }
        }

        return result;
    }

    /**
     * Append the hash code.
     * @param target the object to print
     */
    protected void appendHashCode(Object target)
    {
            if ((this.mode & INCLUDE_HASHCODE) == 0)
            {
                    return;
            }

        if( this.target != null )
        {
            this.buffer.append('@');
            this.buffer.append(Integer.toHexString(target.hashCode()));
        }
    }

    /**
     * Append the class name.
     * @param target the object to print
     */
    protected void appendClassName(Object target)
    {
        boolean skipClassName = true;

        if ((this.mode & INCLUDE_CLASSNAME) == 0)
        {
            return;
        }

        if( this.target != null )
        {
            String className = target.getClass().getName();

            if( target instanceof boolean[] )
            {
                this.buffer.append("boolean[]");
            }
            else if( target instanceof byte[] )
            {
                this.buffer.append("byte[]");
            }
            else if( target instanceof char[] )
            {
                this.buffer.append("char[]");
            }
            else if( target instanceof short[] )
            {
                this.buffer.append("short[]");
            }
            else if( target instanceof int[] )
            {
                this.buffer.append("int[]");
            }
            else if( target instanceof long[] )
            {
                this.buffer.append("[ong[]");
            }
            else if( target instanceof float[] )
            {
                this.buffer.append("float[]");
            }
            else if( target instanceof double[] )
            {
                this.buffer.append("double[]");
            }
            else if( target instanceof Boolean )
            {
                this.buffer.append("Boolean");
            }
            else if( target instanceof Character )
            {
                this.buffer.append("Character");
            }
            else if( target instanceof Short )
            {
                this.buffer.append("Short");
            }
            else if( target instanceof Integer )
            {
                this.buffer.append("Integer");
            }
            else if( target instanceof Long )
            {
                this.buffer.append("Long");
            }
            else if( target instanceof Float )
            {
                this.buffer.append("Float");
            }
            else if( target instanceof Double )
            {
                this.buffer.append("Double");
            }
            else if( target instanceof String )
            {
                this.buffer.append("String");
            }
            else if( target instanceof Boolean[] )
            {
                this.buffer.append("Boolean[]");
            }
            else if( target instanceof Character[] )
            {
                this.buffer.append("Character[]");
            }
            else if( target instanceof Short[] )
            {
                this.buffer.append("Short[]");
            }
            else if( target instanceof Integer[] )
            {
                this.buffer.append("Integer[]");
            }
            else if( target instanceof Long[] )
            {
                this.buffer.append("Long[]");
            }
            else if( target instanceof Float[] )
            {
                this.buffer.append("Float[]");
            }
            else if( target instanceof Double[] )
            {
                this.buffer.append("Double[]");
            }
            else if( target instanceof String[] )
            {
                this.buffer.append("String[]");
            }
            else
            {
                skipClassName = false;
            }

            if( skipClassName == false )
            {
                className = StringUtils.replace(className, "java.lang.", "");

                if( className.endsWith(";") )
                {
                    this.buffer.append(className.substring(0,className.length()-1));
                }
                else
                {
                    this.buffer.append(className);
                }
            }
        }
    }

    /**
     * Append the hash code.
     * @param ch the object to print
     */
    protected void appendChar(char ch)
    {
        this.buffer.append(ch);
    }

    /**
     * @return Returns the maxLineLength.
     */
    protected int getMaxArgLength()
    {
        return maxArgLength;
    }

    /**
     * <p>Gets the stack trace from a Throwable as a String.</p>
     *
     * @param throwable  the <code>Throwable</code> to be examined
     * @return the stack trace as generated by the exception's
     *  <code>printStackTrace(PrintWriter)</code> method
     */
    protected String getStackTrace(Throwable throwable)
    {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter( sw, true );
        throwable.printStackTrace( pw );
        return sw.getBuffer().toString();
    }

    /**
     * Append a string to the internal buffer
     * @param source the string to append
     */
    protected void append(String source)
    {
        String formattedSource = this.format(source);
        this.buffer.append(formattedSource);
    }

    /**
     * Format the buffer by replacing the whitespaces and cutting
     * away excessive fluff.
     *
     * @param source the source string
     * @return formatted string
     */
    protected String format( String source )
    {
        boolean isTruncated = false;

        // test for null or empty string
        if ( StringUtils.isEmpty(source) )
        	return "";

        // remove the line breaks and tabs for logging output and replace
        StringUtils.replace(source,  "\r",  " ");
        StringUtils.replace(source,  "\n",  " ");
        StringUtils.replace(source,  "\t",  " ");
        StringUtils.replace(source,  SEPERATOR,  " ");

        // Build the output
        StringBuilder stringBuilder = new StringBuilder(source);

        // trim the string to avoid dumping tons of data
        if( stringBuilder.length() > this.getMaxArgLength() )
        {
            stringBuilder.delete(this.getMaxArgLength()-1, stringBuilder.length());
            isTruncated = true;
        }
        
        // show the user that we truncated the ouptut
        if( isTruncated )
        {
            if (source.endsWith("]"))
            {
                stringBuilder.append(" ...]");
            }
            else
            {
                stringBuilder.append(" ...");
            }
        }
        return stringBuilder.toString();
    }
}