/*******************************************************************************
 * Copyright (c) 2006 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Matthew Webster - initial implementation
 *******************************************************************************/
package org.aspectj.weaver.tools;

import java.io.File;
import java.lang.reflect.Array;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.regex.Pattern;

import org.aspectj.bridge.IMessage.Kind;

public abstract class AbstractTrace implements Trace {

	private static final Pattern packagePrefixPattern = Pattern.compile("([^.])[^.]*(\\.)");

	protected Class<?> tracedClass;

	private static SimpleDateFormat timeFormat;
	
	protected AbstractTrace (Class clazz) {
		this.tracedClass = clazz;
	}
	
	public abstract void enter (String methodName, Object thiz, Object[] args);
	
	public abstract void enter(String methodName, Object thiz);

	public abstract void exit(String methodName, Object ret);

	public abstract void exit(String methodName, Throwable th);

	/*
	 * Convenience methods
	 */
	public void enter (String methodName) {
		enter(methodName,null,null);
	}

	public void enter (String methodName, Object thiz, Object arg) {
		enter(methodName,thiz,new Object[] { arg });
	}

	public void enter (String methodName, Object thiz, boolean z) {
		enter(methodName,thiz,new Boolean(z));
	}

	public void exit (String methodName, boolean b) {
		exit(methodName,new Boolean(b));
	}

	public void exit (String methodName, int i) {
		exit(methodName,new Integer(i));
	}

	public void event (String methodName, Object thiz, Object arg) {
		event(methodName,thiz,new Object[] { arg });
	}

	public void warn(String message) {
		warn(message,null);
	}

	public void error(String message) {
		error(message,null);
	}

	public void fatal (String message) {
		fatal(message,null);
	}
	
	/*
	 * Formatting
	 */
	protected String formatMessage(String kind, String className, String methodName, Object thiz, Object[] args) {
		StringBuffer message = new StringBuffer();
		Date now = new Date();
		message.append(formatDate(now)).append(" ");
		message.append(Thread.currentThread().getName()).append(" ");
		message.append(kind).append(" ");
		message.append(formatClassName(className));
		message.append(".").append(methodName);
		if (thiz != null) message.append(" ").append(formatObj(thiz));
		if (args != null) message.append(" ").append(formatArgs(args));
		return message.toString();
	}
	
	/**
	 * @param className full dotted class name
	 * @return short version of class name with package collapse to initials
	 */
	private String formatClassName(String className) {
		return packagePrefixPattern.matcher(className).replaceAll("$1.");
	}
	
	protected String formatMessage(String kind, String text, Throwable th) {
		StringBuffer message = new StringBuffer();
		Date now = new Date();
		message.append(formatDate(now)).append(" ");
		message.append(Thread.currentThread().getName()).append(" ");
		message.append(kind).append(" ");
		message.append(text);
		if (th != null) message.append(" ").append(formatObj(th));
		return message.toString();
	}
	
	private static String formatDate (Date date) {
		if (timeFormat == null) {
			timeFormat = new SimpleDateFormat("HH:mm:ss.SSS");
		}
		
		return timeFormat.format(date);
	}

	/**
	 * Format objects safely avoiding toString which can cause recursion,
	 * NullPointerExceptions or highly verbose results.
	 *  
	 * @param obj parameter to be formatted
	 * @return the formatted parameter
	 */
	protected Object formatObj(Object obj) {
		
		/* These classes have a safe implementation of toString() */
		if (obj == null
				|| obj instanceof String
			    || obj instanceof Number
			    || obj instanceof Boolean
			    || obj instanceof Exception
			    || obj instanceof Character
			    || obj instanceof Class
			    || obj instanceof File
			    || obj instanceof StringBuffer
			    || obj instanceof URL
			    || obj instanceof Kind
		    ) return obj;
		else if (obj.getClass().isArray()) {
			return formatArray(obj);
		}
		else if (obj instanceof Collection) {
			return formatCollection((Collection)obj);
		}
		else try {
			
			// Classes can provide an alternative implementation of toString()
			if (obj instanceof Traceable) {
				return ((Traceable)obj).toTraceString();
			}
			
			// classname@hashcode
			else return formatClassName(obj.getClass().getName()) + "@" + Integer.toHexString(System.identityHashCode(obj));
		
		/* Object.hashCode() can be override and may thow an exception */	
		} catch (Exception ex) {
			return obj.getClass().getName() + "@FFFFFFFF";
		}
	}
	
	protected String formatArray (Object obj) {
		return obj.getClass().getComponentType().getName() + "[" + Array.getLength(obj) + "]"; 
	}
	
	protected String formatCollection (Collection c) {
		return c.getClass().getName() + "(" + c.size() + ")"; 
	}

	/** 
	 * Format arguments into a comma separated list
	 * 
	 * @param names array of argument names
	 * @param args array of arguments
	 * @return the formated list
	 */
	protected String formatArgs(Object[] args) {
		StringBuffer sb = new StringBuffer();
		
		for (int i = 0; i < args.length; i++) {
			sb.append(formatObj(args[i]));
			if (i < args.length-1) sb.append(", ");
		}
		
		return sb.toString();
	}
	
	protected Object[] formatObjects(Object[] args) {
		for (int i = 0; i < args.length; i++) {
			args[i] = formatObj(args[i]);
		}
		
		return args;
	}
}