text: /* ************************************************************************
  
     qooxdoo - the new era of web development
  
     http://qooxdoo.org
  
     Copyright:
       2007-2008 1&1 Internet AG, Germany, http://www.1und1.de
  
     License:
       MIT: https://opensource.org/licenses/MIT
       See the LICENSE file in the project's top-level directory for details.
  
     Authors:
       * Fabian Jakobs (fjakobs)
  
  ************************************************************************ */
  
  /**
   * Methods to get information about the JavaScript call stack.
   *
   * @require(qx.lang.normalize.String)
   * @ignore(qx.bom.client.EcmaScript.*)
   * @ignore(qx.bom.client)
   * @ignore(qx.bom)
   * @ignore(qx.Class.*)
   */
  qx.Bootstrap.define("qx.dev.StackTrace",
  {
    statics:
    {
  
      /**
       * Optional user-defined function to convert source file names into readable
       * class names. Will be called with the source file name extracted from the
       * browser's stack trace information as the only argument. The returned
       * string is used in the output of {@link #getStackTraceFromError}
       */
      FILENAME_TO_CLASSNAME : null,
  
      /**
       * Optional user-defined formatting function for stack trace information.
       * Will be called by with an array of strings representing the calls in the
       * stack trace. {@link #getStackTraceFromError} will return the output of
       * this function. Must return an array of strings.
       */
      FORMAT_STACKTRACE : null,
  
      /**
       * Get a stack trace of the current position in the code.
       *
       * Browser compatibility:
       * <ul>
       *   <li>In new versions of Gecko, WebKit and Opera, the output of
       *   {@link #getStackTraceFromError} and {@link #getStackTraceFromCaller} is
       *   combined to generate the richest trace, including line numbers.</li>
       *   <li>For Internet Explorer (and other engines that do not provide stack
       *    traces), {@link #getStackTraceFromCaller} is used</li>
       * </ul>
       *
       * @return {String[]} Stack trace of the current position in the code. Each line in the array
       *     represents one call in the stack trace.
       */
      getStackTrace : function()
      {
        var trace = [];
        try {
          throw new Error();
        }
        catch(ex) {
          if (qx.dev.StackTrace.hasEnvironmentCheck &&
              qx.core.Environment.get("ecmascript.error.stacktrace"))
          {
            var errorTrace = qx.dev.StackTrace.getStackTraceFromError(ex);
            var callerTrace = qx.dev.StackTrace.getStackTraceFromCaller(arguments);
            qx.lang.Array.removeAt(errorTrace, 0);
  
            trace = callerTrace.length > errorTrace.length ? callerTrace : errorTrace;
            for (var i=0; i<Math.min(callerTrace.length, errorTrace.length); i++)
            {
              var callerCall = callerTrace[i];
              if (callerCall.indexOf("anonymous") >= 0) {
                continue;
              }
  
              var methodName = null;
              var callerArr = callerCall.split(".");
              var mO = /(.*?)\(/.exec(callerArr[callerArr.length - 1]);
              if (mO && mO.length == 2) {
                methodName = mO[1];
                callerArr.pop();
              }
              if (callerArr[callerArr.length - 1] == "prototype") {
                callerArr.pop();
              }
              var callerClassName = callerArr.join(".");
  
              var errorCall = errorTrace[i];
              var errorArr = errorCall.split(":");
              var errorClassName = errorArr[0];
              var lineNumber = errorArr[1];
              var columnNumber;
              if (errorArr[2]) {
                columnNumber = errorArr[2];
              }
  
              var className = null;
              if (qx.Class && qx.Class.getByName(errorClassName)) {
                className = errorClassName;
              } else {
                className = callerClassName;
              }
              var line = className;
              if (methodName) {
                line += "." + methodName;
              }
              line += ":" + lineNumber;
              if (columnNumber) {
                line += ":" + columnNumber;
              }
              trace[i] = line;
            }
          }
          else {
            trace = this.getStackTraceFromCaller(arguments);
          }
        }
  
        return trace;
      },
  
  
      /**
       * Get a stack trace from the arguments special variable using the
       * <code>caller</code> property.
       *
       * This methods returns class/mixin and function names of each step
       * in the call stack.
       *
       * Recursion is not supported.
       *
       * @param args {arguments} arguments variable.
       * @return {String[]} Stack trace of caller of the function the arguments variable belongs to.
       *     Each line in the array represents one call in the stack trace.
       * @signature function(args)
       */
      getStackTraceFromCaller : function(args)
      {
        var isStrictMode = function () {
          return (typeof this == 'undefined');
        };
  
        var trace = [];
        var fcn = null;
        if (!isStrictMode()) {
          try {
            fcn = qx.lang.Function.getCaller(args);
          }catch(ex) {
            // Nothing
          }
        }
        var knownFunction = {};
        while (fcn)
        {
          var fcnName = qx.lang.Function.getName(fcn);
          trace.push(fcnName);
  
          try {
            fcn = fcn.caller;
          } catch(ex) {
            break;
          }
  
          if (!fcn) {
            break;
          }
  
          // avoid infinite recursion
          var hash = qx.core.ObjectRegistry.toHashCode(fcn);
          if (knownFunction[hash]) {
            trace.push("...");
            break;
          }
          knownFunction[hash] = fcn;
        }
        return trace;
      },
  
  
      /**
       * Try to get a stack trace from an Error object. Mozilla sets the field
       * <code>stack</code> for Error objects thrown using <code>throw new Error()</code>.
       * From this field it is possible to get a stack trace from the position
       * the exception was thrown at.
       *
       * This will get the JavaScript file names and the line numbers of each call.
       * The file names are converted into qooxdoo class names if possible (customizable
       * via {@link #FILENAME_TO_CLASSNAME}).
       *
       * The stack trace can be custom formatted using {@link #FORMAT_STACKTRACE}.
       *
       * This works reliably in Gecko-based browsers. Later Opera versions and
       * Chrome also provide a useful stack trace. For Safari, only the class or
       * file name and line number where the error occurred are returned.
       * IE 6/7/8/9 does not attach any stack information to error objects so an
       * empty array is returned.
       *
       * @param error {Error} Error exception instance.
       * @return {String[]} Stack trace of the exception. Each line in the array
       *     represents one call in the stack trace.
       */
      getStackTraceFromError : function(error)
      {
        var trace = [];
        var lineRe,
            hit,
            className,
            lineNumber,
            columnNumber,
            fileName,
            url;
  
        var traceProp = qx.dev.StackTrace.hasEnvironmentCheck ?
            qx.core.Environment.get("ecmascript.error.stacktrace") : null;
  
        if (traceProp === "stack") {
          if (!error.stack) {
            return trace;
          }
          // Gecko style, e.g. "()@http://localhost:8080/webcomponent-test-SNAPSHOT/webcomponent/js/com/ptvag/webcomponent/common/log/Logger:253"
          lineRe = /@(.+):(\d+)$/gm;
  
          while ((hit = lineRe.exec(error.stack)) != null)
          {
            url = hit[1];
            lineNumber = hit[2];
  
            className = this.__fileNameToClassName(url);
            trace.push(className + ":" + lineNumber);
          }
  
          if (trace.length > 0) {
            return this.__formatStackTrace(trace);
          }
          /*
           * Chrome trace info comes in two flavors:
           * at [jsObject].function (fileUrl:line:char)
           * at fileUrl:line:char
           */
          lineRe = /at (.*)/gm;
          var fileReParens = /\((.*?)(:[\d:]+)\)/;
          var fileRe = /(.*?)(:[\d:]+$)/;
          while ((hit = lineRe.exec(error.stack)) != null) {
            var fileMatch = fileReParens.exec(hit[1]);
            if (!fileMatch) {
              fileMatch = fileRe.exec(hit[1]);
            }
  
            if (fileMatch) {
              className = this.__fileNameToClassName(fileMatch[1]);
              trace.push(className + fileMatch[2]);
            } else {
              trace.push(hit[1]);
            }
          }
        }
        else if (traceProp === "stacktrace")
        {
          // Opera
          var stacktrace = error.stacktrace;
          if (!stacktrace) {
            return trace;
          }
          if (stacktrace.indexOf("Error created at") >= 0) {
            stacktrace = stacktrace.split("Error created at")[0];
          }
  
          // new Opera style (10.6+)
          lineRe = /line\ (\d+?),\ column\ (\d+?)\ in\ (?:.*?)\ in\ (.*?):[^\/]/gm;
          while ((hit = lineRe.exec(stacktrace)) != null) {
            lineNumber = hit[1];
            columnNumber = hit[2];
            url = hit[3];
            className = this.__fileNameToClassName(url);
            trace.push(className + ":" + lineNumber + ":" + columnNumber);
          }
  
          if (trace.length > 0) {
            return this.__formatStackTrace(trace);
          }
  
          // older Opera style
          lineRe = /Line\ (\d+?)\ of\ linked\ script\ (.*?)$/gm;
          while ((hit = lineRe.exec(stacktrace)) != null) {
            lineNumber = hit[1];
            url = hit[2];
            className = this.__fileNameToClassName(url);
            trace.push(className + ":" + lineNumber);
          }
        }
        else if (error.message && error.message.indexOf("Backtrace:") >= 0) {
          // Some old Opera versions append the trace to the message property
          var traceString = error.message.split("Backtrace:")[1].trim();
          var lines = traceString.split("\n");
          for (var i=0; i<lines.length; i++)
          {
            var reResult = lines[i].match(/\s*Line ([0-9]+) of.* (\S.*)/);
            if (reResult && reResult.length >= 2) {
              lineNumber = reResult[1];
              fileName = this.__fileNameToClassName(reResult[2]);
              trace.push(fileName + ":" + lineNumber);
            }
          }
        }
        else if (error.sourceURL && error.line) {
          // Safari
          trace.push(this.__fileNameToClassName(error.sourceURL) + ":" + error.line);
        }
  
        return this.__formatStackTrace(trace);
      },
  
      /**
       * Converts the URL of a JavaScript file to a class name using either a
       * user-defined ({@link #FILENAME_TO_CLASSNAME}) or default
       * ({@link #__fileNameToClassNameDefault}) converter
       *
       * @param fileName {String} URL of the JavaScript file
       * @return {String} Result of the conversion
       */
      __fileNameToClassName : function(fileName)
      {
        if (typeof qx.dev.StackTrace.FILENAME_TO_CLASSNAME == "function") {
          var convertedName = qx.dev.StackTrace.FILENAME_TO_CLASSNAME(fileName);
          if (qx.core.Environment.get("qx.debug") &&
            !qx.lang.Type.isString(convertedName))
          {
            throw new Error("FILENAME_TO_CLASSNAME must return a string!");
          }
          return convertedName;
        }
  
        return qx.dev.StackTrace.__fileNameToClassNameDefault(fileName);
      },
  
  
      /**
       * Converts the URL of a JavaScript file to a class name if the file is
       * named using the qooxdoo naming conventions.
       *
       * @param fileName {String} URL of the JavaScript file
       * @return {String} class name of the file if conversion was possible.
       * Otherwise the fileName is returned unmodified.
       */
      __fileNameToClassNameDefault : function(fileName)
      {
        var scriptDir = "/source/class/";
        var jsPos = fileName.indexOf(scriptDir);
        var paramPos = fileName.indexOf("?");
        if (paramPos >= 0) {
          fileName = fileName.substring(0, paramPos);
        }
        var className = (jsPos == -1) ? fileName : fileName.substring(jsPos + scriptDir.length).replace(/\//g, ".").replace(/\.js$/, "");
        return className;
      },
  
  
      /**
       * Runs the given stack trace array through the formatter function
       * ({@link #FORMAT_STACKTRACE}) if available and returns it. Otherwise, the
       * original array is returned
       *
       * @param trace {String[]} Stack trace information
       * @return {String[]} Formatted stack trace info
       */
      __formatStackTrace : function(trace)
      {
        if (typeof qx.dev.StackTrace.FORMAT_STACKTRACE == "function") {
          trace = qx.dev.StackTrace.FORMAT_STACKTRACE(trace);
          // Can't use qx.core.Assert here since it throws an AssertionError which
          // calls getStackTrace in its constructor, leading to infinite recursion
          if (qx.core.Environment.get("qx.debug") && !qx.lang.Type.isArray(trace)) {
            throw new Error("FORMAT_STACKTRACE must return an array of strings!");
          }
        }
        return trace;
      }
    },
  
    defer : function(statics)
    {
      // This is necessary to avoid an infinite loop when logging the absence
      // of the "ecmascript.error.stacktrace" environment key.
      statics.hasEnvironmentCheck = qx.bom && qx.bom.client &&
        qx.bom.client.EcmaScript && qx.bom.client.EcmaScript.getStackTrace;
    }
  });
  