/*
 *  @(#)StackTraceLineParser.java
 *
 * Copyright (C) 2002-2003 Matt Albrecht
 * groboclown@users.sourceforge.net
 * http://groboutils.sourceforge.net
 *
 *  Part of the GroboUtils package at:
 *  http://groboutils.sourceforge.net
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a
 *  copy of this software and associated documentation files (the "Software"),
 *  to deal in the Software without restriction, including without limitation
 *  the rights to use, copy, modify, merge, publish, distribute, sublicense,
 *  and/or sell copies of the Software, and to permit persons to whom the 
 *  Software is furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in 
 *  all copies or substantial portions of the Software. 
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL 
 *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 *  DEALINGS IN THE SOFTWARE.
 */
package net.sourceforge.groboutils.util.throwable.v1;


import java.lang.reflect.Method;

//import org.apache.log4j.Logger;


/**
 * Encompasses the line on which a Stack Trace occurs.  Should not call
 * this "StackTraceElement", since that's a JDK 1.4 class in the java.lang
 * package.
 *
 * @author     Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
 * @since      March 17, 2002
 * @version    $Date: 2003/09/29 21:10:36 $
 */
public class StackTraceLineParser
{
    //private static final Logger LOG = Logger.getLogger(
    //    StackTraceLineParser.class.getName() );
    
    private String className;
    private String methodName;
    private int lineNumber;
    
    private static final String JDK14_STE_CLASS = "java.lang.StackTraceElement";
    private static final String JDK14_STE_GCN = "getClassName";
    private static final Class[] JDK14_STE_GCN_ARGS = new Class[0];
    private static final String JDK14_STE_GLN = "getLineNumber";
    private static final Class[] JDK14_STE_GLN_ARGS = new Class[0];
    private static final String JDK14_STE_GMN = "getMethodName";
    private static final Class[] JDK14_STE_GMN_ARGS = new Class[0];
    
    private static final String ST_START = "at ";
    
    
    // Check if we are running in IBM's visual age.
    // - it has a different stack trace
    private static boolean IN_VISUAL_AGE = false;
    static {
        try
        {
            Class dummy = Class.forName("com.ibm.uvm.tools.DebugSupport");
            IN_VISUAL_AGE = true;
            //LOG.info("Using VisualAge style stack traces.");
        }
        catch (Throwable e)
        {
            // ignore.  We're not in visual age.
            //LOG.info("Using standard JDK style stack traces.");
        }
    }

    
    
    public StackTraceLineParser( String className, String methodName )
    {
        this( className, methodName, -1 );
    }
    
    
    public StackTraceLineParser( String className, String methodName,
            int lineNumber )
    {
        if (className == null || methodName == null)
        {
            throw new IllegalArgumentException("no null arguments");
        }
        if (lineNumber < 0)
        {
            lineNumber = -1;
        }
        this.lineNumber = lineNumber;
        this.className = className;
        this.methodName = methodName;
    }
    
    
    /**
     * Parses a JDK 1.4 StackTraceElement or a stack trace line.
     *
     * @exception IllegalArgumentException if this is not a recognizable stack
     *      trace line, or is <tt>null</tt>, or is not a StackTraceElement.
     */
    public StackTraceLineParser( Object obj )
    {
        if (obj == null)
        {
            throw new IllegalArgumentException("no null arguments");
        }
        
        if (obj instanceof String)
        {
            parseLine( (String)obj );
        }
        else
        {
            parseElement( obj );
        }
    }
    
    
    public String getClassName()
    {
        return this.className;
    }
    
    
    public String getMethodName()
    {
        return this.methodName;
    }
    
    
    public int getLineNumber()
    {
        return this.lineNumber;
    }
    
    
    public String toString()
    {
        return getClassName() + '#' + getMethodName() + " (line " +
            getLineNumber() + ')';
    }
    
    
    //-------------------------------------------------------------------------
    // Protected methods
    
    
    protected void parseLine( String line )
    {
        String nl = line.trim();
        //LOG.debug("parsing trace line '"+line+"'.");
        int startPos = 0;
        if (!IN_VISUAL_AGE)
        {
            if (nl.startsWith(ST_START))
            {
                startPos += ST_START.length();
            }
            else
            {
                //LOG.debug("trace line does not start with '"+ST_START+"'.");
                notValidStackTraceLine( line );
            }
        }
        
        int endNamePos = nl.lastIndexOf('(');
        if (endNamePos < startPos)
        {
            //LOG.debug("no '(' in trace line.");
            notValidStackTraceLine( line );
        }
        
        int lastDot = nl.lastIndexOf('.', endNamePos );
        if (lastDot < startPos)
        {
            //LOG.debug("No '.' in trace line before last '(', which is at "+
            //    endNamePos+".");
            notValidStackTraceLine( line );
        }
        
        this.methodName = findMethodName( nl, startPos, lastDot, endNamePos );
        this.className = findClassName( nl, startPos, lastDot, endNamePos );
        this.lineNumber = findLineNumber( nl, startPos, lastDot, endNamePos );
    }
    
    
    /**
     * 
     */
    protected String findMethodName( String line, int startPos, int lastDot,
            int endNamePos )
    {
        String name = line.substring( lastDot+1, endNamePos ).trim();
        //LOG.debug("Parsed method name out to '"+name+"'.");
        return name;
    }
    
    
    /**
     * 
     */
    protected String findClassName( String line, int startPos, int lastDot,
            int endNamePos )
    {
        String name = line.substring( startPos, lastDot ).trim();
        //LOG.debug("Parsed class name out to '"+name+"'.");
        return name;
    }
    
    
    /**
     * 
     */
    protected int findLineNumber( String line, int startPos, int lastDot,
            int endNamePos )
    {
        int ln = -1;
        if (!IN_VISUAL_AGE)
        {
            int lineNumberStart = line.indexOf( ':', endNamePos );
            if (lineNumberStart > 0)
            {
                //LOG.debug("line number is at position "+lineNumberStart+".");
                int lineNumberEnd = line.indexOf( ')', lineNumberStart );
                if (lineNumberEnd > 0)
                {
                    //LOG.debug("final ')', after line number, is at "+
                    //    lineNumberEnd+".");
                    try
                    {
                        String lineNumber = line.substring(
                            lineNumberStart+1, lineNumberEnd );
                        //LOG.debug("line number text is "+lineNumber+".");
                        ln = Integer.parseInt( lineNumber );
                    }
                    catch (NumberFormatException e)
                    {
                        //LOG.debug("line number text is not a number.");
                        ln = -1;
                    }
                }
                else
                {
                    //LOG.debug("stack trace text does not have a final ')'");
                }
            }
            else
            {
                //LOG.debug("stack trace text did not provide line number "+
                //    "information.");
            }
        }
        return ln;
    }
    
    
    /**
     * We may not be in JDK 1.4 during compilation or runtime, but we
     * can support it through reflection anyway.
     */
    protected void parseElement( Object el )
    {
        Class c = el.getClass();
        if (String.class.equals( c ))
        {
            // specialized method for internal usage checks.
            throw new IllegalStateException("Incorrect method invoked.");
        }
        if (!JDK14_STE_CLASS.equals( c.getName() ))
        {
            throw new IllegalArgumentException( "Object "+el+
                " is not of type "+JDK14_STE_CLASS+
                ", but rather of type "+c.getName() );
        }
        
        Method gcn;
        Method gln;
        Method gmn;
        try
        {
            gcn = c.getDeclaredMethod( JDK14_STE_GCN,
                JDK14_STE_GCN_ARGS );
            gln = c.getDeclaredMethod( JDK14_STE_GLN,
                JDK14_STE_GLN_ARGS );
            gmn = c.getDeclaredMethod( JDK14_STE_GMN,
                JDK14_STE_GMN_ARGS );
        }
        catch (NoSuchMethodException nsme)
        {
            throw new IllegalArgumentException( "Object "+el+
                " does not support the JDK 1.4 specification of "+
                    JDK14_STE_CLASS );
        }
        
        String cn;
        int ln;
        String mn;
        try
        {
            cn = (String)gcn.invoke( el, null );
            ln = ((Integer)gln.invoke( el, null )).intValue();
            mn = (String)gmn.invoke( el, null );
        }
        catch (Exception e)
        {
            throw new IllegalArgumentException( "Object "+el+
                " could not retrieve values for JDK 1.4 specification of "+
                    JDK14_STE_CLASS );
        }
        
        this.className = cn;
        this.lineNumber = ln;
        this.methodName = mn;
    }
    
    
    protected void notValidStackTraceLine( String line )
    {
        throw new IllegalArgumentException("Line ["+
            line+"] is not a valid stack-trace line");
    }
}

