/*
 * 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 flex.messaging.util;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Map;

/**
 * Prettifies the representation of an Object as a String. Complex
 * types are not traversed.
 */
public class BasicPrettyPrinter implements PrettyPrinter {
    protected ObjectTrace trace;

    public BasicPrettyPrinter() {
    }

    /**
     * Prettifies the representation of an Object as a String.
     * <ul>
     *   <li>Simple types are simply toString'ed.</li>
     *   <li>XML strings are formatted with line feeds and indentations.</li>
     *   <li>Complex types report their class names.</li>
     *   <li>Collections, Maps and native Arrays also report their size/length.</li>
     * </ul>
     *
     * @return A prettified version of an Object as a String.
     */
    public String prettify(Object o) {
        try {
            trace = new ObjectTrace();
            internalPrettify(o);
            return trace.toString();
        } catch (Throwable t) {
            return trace.toString();
        } finally {
            trace = null;
        }
    }

    protected void internalPrettify(Object o) {
        if (o == null) {
            trace.writeNull();
        } else if (o instanceof String) {
            String string = (String) o;
            if (string.startsWith("<?xml")) {
                trace.write(StringUtils.prettifyXML(string));
            } else {
                trace.write(string);
            }
        } else if (o instanceof Number || o instanceof Boolean || o instanceof Date
                || o instanceof Calendar || o instanceof Character) {
            trace.write(o);
        } else {
            prettifyComplexType(o);
        }
    }

    protected void prettifyComplexType(Object o) {
        StringBuffer header = new StringBuffer();

        if (o instanceof PrettyPrintable) {
            PrettyPrintable pp = (PrettyPrintable) o;
            header.append(pp.toStringHeader());
        }

        Class c = o.getClass();
        String className = c.getName();

        if (o instanceof Collection) {
            header.append(className).append(" (Collection size:").append(((Collection) o).size()).append(")");
        } else if (o instanceof Map) {
            header.append(className).append(" (Map size:").append(((Map) o).size()).append(")");
        } else if (c.isArray() && c.getComponentType() != null) {
            Class componentType = c.getComponentType();
            className = componentType.getName();
            header.append(className).append("[] (Array length:").append(Array.getLength(o)).append(")");
        } else {
            header.append(className);
        }

        trace.startObject(header.toString());
        trace.endObject();
    }

    /**
     * If the definition of toString is not from java.lang.Object or any class in the
     * java.util.* package then we consider it a custom implementation in which case
     * we'll use it instead of introspecting the class.
     *
     * @param c The class to check for a custom toString definition.
     * @return Whether this class declares a custom toString() method.
     */
    protected boolean hasCustomToStringMethod(Class c) {
        try {
            Method toStringMethod = c.getMethod("toString", (Class[]) null);
            Class declaringClass = toStringMethod.getDeclaringClass();
            if (declaringClass != Object.class
                    && !declaringClass.getName().startsWith("java.util")) {
                return true;
            }
        } catch (Throwable t) {
        }

        return false;
    }

    public Object copy() {
        return new BasicPrettyPrinter();
    }
}
