/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 *      Portions Copyright 2014-2015 ForgeRock AS
 */
package org.opends.server.plugins.profiler;

import java.io.IOException;

import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.io.ASN1Reader;
import org.forgerock.opendj.io.ASN1Writer;

/**
 * This class defines a data structure that may be used to hold information
 * about a thread stack trace.
 */
public class ProfileStack
{
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();




  /**
   * The line number that will be used for stack frames in which the line number
   * is unknown but it is not a native method.
   */
  public static final int LINE_NUMBER_UNKNOWN = -1;



  /**
   * The line number that will be used for stack frames in which the line number
   * is unknown because it is a native method.
   */
  public static final int LINE_NUMBER_NATIVE = -2;



  /** The number of frames in this stack. */
  private int numFrames;

  /** The source file line numbers for each of the frames in this stack. */
  private int[] lineNumbers;

  /** The class names for each of the frames in this stack. */
  private String[] classNames;

  /** The method names for each of the frames in this stack. */
  private String[] methodNames;



  /**
   * Creates a new profile stack with the provided information.
   *
   * @param  stackElements  The stack trace elements to use to create this
   *                        profile stack.
   */
  public ProfileStack(StackTraceElement[] stackElements)
  {
    numFrames   = stackElements.length;
    classNames  = new String[numFrames];
    methodNames = new String[numFrames];
    lineNumbers = new int[numFrames];

    for (int i=0, j=numFrames-1; i < numFrames; i++,j--)
    {
      classNames[i]  = stackElements[j].getClassName();
      methodNames[i] = stackElements[j].getMethodName();
      lineNumbers[i] = stackElements[j].getLineNumber();

      if (lineNumbers[i] <= 0)
      {
        if (stackElements[j].isNativeMethod())
        {
          lineNumbers[i] = LINE_NUMBER_NATIVE;
        }
        else
        {
          lineNumbers[i] = LINE_NUMBER_UNKNOWN;
        }
      }
    }
  }



  /**
   * Creates a new profile stack with the provided information.
   *
   * @param  classNames   The class names for the frames in this stack.
   * @param  methodNames  The method names for the frames in this stack.
   * @param  lineNumbers  The line numbers for the frames in this stack.
   */
  private ProfileStack(String[] classNames, String[] methodNames,
                       int[] lineNumbers)
  {
    this.numFrames   = classNames.length;
    this.classNames  = classNames;
    this.methodNames = methodNames;
    this.lineNumbers = lineNumbers;
  }



  /**
   * Retrieves the number of frames in this stack.
   *
   * @return  The number of frames in this stack.
   */
  public int getNumFrames()
  {
    return numFrames;
  }



  /**
   * Retrieves the class names in this stack.
   *
   * @return  The class names in this stack.
   */
  public String[] getClassNames()
  {
    return classNames;
  }



  /**
   * Retrieves the class name from the specified frame in the stack.
   *
   * @param  depth  The depth of the frame to retrieve, with the first frame
   *                being frame zero.
   *
   * @return  The class name from the specified frame in the stack.
   */
  public String getClassName(int depth)
  {
    return classNames[depth];
  }



  /**
   * Retrieves the method names in this stack.
   *
   * @return  The method names in this stack.
   */
  public String[] getMethodNames()
  {
    return methodNames;
  }



  /**
   * Retrieves the method name from the specified frame in the stack.
   *
   * @param  depth  The depth of the frame to retrieve, with the first frame
   *                being frame zero.
   *
   * @return  The method name from the specified frame in the stack.
   */
  public String getMethodName(int depth)
  {
    return methodNames[depth];
  }



  /**
   * Retrieves the line numbers in this stack.
   *
   * @return  The line numbers in this stack.
   */
  public int[] getLineNumbers()
  {
    return lineNumbers;
  }



  /**
   * Retrieves the line number from the specified frame in the stack.
   *
   * @param  depth  The depth of the frame for which to retrieve the line
   *                number.
   *
   * @return  The line number from the specified frame in the stack.
   */
  public int getLineNumber(int depth)
  {
    return lineNumbers[depth];
  }



  /**
   * Retrieves the hash code for this profile stack.  It will be the sum of the
   * hash codes for the class and method name and line number for the first
   * frame.
   *
   * @return  The hash code for this profile stack.
   */
  public int hashCode()
  {
    if (numFrames != 0)
    {
      return classNames[0].hashCode() + methodNames[0].hashCode() + lineNumbers[0];
    }
    return 0;
  }



  /**
   * Indicates whether to the provided object is equal to this profile stack.
   *
   * @param  o  The object for which to make the determination.
   *
   * @return  <CODE>true</CODE> if the provided object is a profile stack object
   *          with the same set of class names, method names, and line numbers
   *          as this profile stack, or <CODE>false</CODE> if not.
   */
  public boolean equals(Object o)
  {
    if (o == null)
    {
      return false;
    }
    else if (this == o)
    {
      return true;
    }


    try
    {
      ProfileStack s = (ProfileStack) o;

      if (numFrames != s.numFrames)
      {
        return false;
      }

      for (int i=0; i < numFrames; i++)
      {
        if (lineNumbers[i] != s.lineNumbers[i] ||
            !classNames[i].equals(s.classNames[i]) ||
            !methodNames[i].equals(s.methodNames[i]))
        {
          return false;
        }
      }

      return true;
    }
    catch (Exception e)
    {
      logger.traceException(e);

      return false;
    }
  }



  /**
   * Encodes and writes this profile stack to the capture file.
   *
   * @param  writer The writer to use.
   * @throws IOException if an error occurs while writing.
   */
  public void write(ASN1Writer writer) throws IOException
  {
    writer.writeStartSequence();
    writer.writeInteger(numFrames);
    for (int i=0; i < numFrames; i++)
    {
      writer.writeOctetString(classNames[i]);
      writer.writeOctetString(methodNames[i]);
      writer.writeInteger(lineNumbers[i]);
    }
    writer.writeEndSequence();
  }



  /**
   * Decodes the contents of the provided element as a profile stack.
   *
   * @param  reader  The ASN.1 reader to read the encoded profile stack
   *                 information from.
   *
   * @return  The decoded profile stack.
   * @throws IOException If the element could not be decoded for some reason.
   */
  public static ProfileStack decode(ASN1Reader reader) throws IOException
  {
    reader.readStartSequence();

    int      numFrames   = (int)reader.readInteger();
    String[] classNames  = new String[numFrames];
    String[] methodNames = new String[numFrames];
    int[]    lineNumbers = new int[numFrames];

    int i = 0;
    while(reader.hasNextElement())
    {
      classNames[i]  = reader.readOctetStringAsString();
      methodNames[i] = reader.readOctetStringAsString();
      lineNumbers[i] = (int)reader.readInteger();
      i++;
    }

    reader.readEndSequence();

    return new ProfileStack(classNames, methodNames, lineNumbers);
  }
}

