/**
   * Finds the original field name(s), appending the first one to the out line, and any additional
   * alternatives to the extra lines.
   */
  private void originalFieldName(
      String className,
      String obfuscatedFieldName,
      String type,
      StringBuffer outLine,
      List extraOutLines) {
    int extraIndent = -1;

    // Class name -> obfuscated field names.
    Map fieldMap = (Map) classFieldMap.get(className);
    if (fieldMap != null) {
      // Obfuscated field names -> fields.
      Set fieldSet = (Set) fieldMap.get(obfuscatedFieldName);
      if (fieldSet != null) {
        // Find all matching fields.
        Iterator fieldInfoIterator = fieldSet.iterator();
        while (fieldInfoIterator.hasNext()) {
          FieldInfo fieldInfo = (FieldInfo) fieldInfoIterator.next();
          if (fieldInfo.matches(type)) {
            // Is this the first matching field?
            if (extraIndent < 0) {
              extraIndent = outLine.length();

              // Append the first original name.
              if (verbose) {
                outLine.append(fieldInfo.type).append(' ');
              }
              outLine.append(fieldInfo.originalName);
            } else {
              // Create an additional line with the proper
              // indentation.
              StringBuffer extraBuffer = new StringBuffer();
              for (int counter = 0; counter < extraIndent; counter++) {
                extraBuffer.append(' ');
              }

              // Append the alternative name.
              if (verbose) {
                extraBuffer.append(fieldInfo.type).append(' ');
              }
              extraBuffer.append(fieldInfo.originalName);

              // Store the additional line.
              extraOutLines.add(extraBuffer);
            }
          }
        }
      }
    }

    // Just append the obfuscated name if we haven't found any matching
    // fields.
    if (extraIndent < 0) {
      outLine.append(obfuscatedFieldName);
    }
  }
  /** Returns the original argument types. */
  private String originalArguments(String obfuscatedArguments) {
    StringBuffer originalArguments = new StringBuffer();

    int startIndex = 0;
    while (true) {
      int endIndex = obfuscatedArguments.indexOf(',', startIndex);
      if (endIndex < 0) {
        break;
      }

      originalArguments
          .append(originalType(obfuscatedArguments.substring(startIndex, endIndex).trim()))
          .append(',');

      startIndex = endIndex + 1;
    }

    originalArguments.append(originalType(obfuscatedArguments.substring(startIndex).trim()));

    return originalArguments.toString();
  }
  /** Performs the subsequent ReTrace operations. */
  public void execute() throws IOException {
    // Read the mapping file.
    MappingReader mappingReader = new MappingReader(mappingFile);
    mappingReader.pump(this);

    StringBuffer expressionBuffer = new StringBuffer(regularExpression.length() + 32);
    char[] expressionTypes = new char[32];
    int expressionTypeCount = 0;
    int index = 0;
    while (true) {
      int nextIndex = regularExpression.indexOf('%', index);
      if (nextIndex < 0
          || nextIndex == regularExpression.length() - 1
          || expressionTypeCount == expressionTypes.length) {
        break;
      }

      expressionBuffer.append(regularExpression.substring(index, nextIndex));
      expressionBuffer.append('(');

      char expressionType = regularExpression.charAt(nextIndex + 1);
      switch (expressionType) {
        case 'c':
          expressionBuffer.append(REGEX_CLASS);
          break;

        case 'C':
          expressionBuffer.append(REGEX_CLASS_SLASH);
          break;

        case 'l':
          expressionBuffer.append(REGEX_LINE_NUMBER);
          break;

        case 't':
          expressionBuffer.append(REGEX_TYPE);
          break;

        case 'f':
          expressionBuffer.append(REGEX_MEMBER);
          break;

        case 'm':
          expressionBuffer.append(REGEX_MEMBER);
          break;

        case 'a':
          expressionBuffer.append(REGEX_ARGUMENTS);
          break;
      }

      expressionBuffer.append(')');

      expressionTypes[expressionTypeCount++] = expressionType;

      index = nextIndex + 2;
    }

    expressionBuffer.append(regularExpression.substring(index));

    Pattern pattern = Pattern.compile(expressionBuffer.toString());

    // Read the stack trace file.
    LineNumberReader reader =
        new LineNumberReader(
            stackTraceFile == null
                ? (Reader) new InputStreamReader(System.in)
                : (Reader) new BufferedReader(new FileReader(stackTraceFile)));

    try {
      StringBuffer outLine = new StringBuffer(256);
      List extraOutLines = new ArrayList();

      String className = null;

      // Read the line in the stack trace.
      while (true) {
        String line = reader.readLine();
        if (line == null) {
          break;
        }

        Matcher matcher = pattern.matcher(line);

        if (matcher.matches()) {
          int lineNumber = 0;
          String type = null;
          String arguments = null;

          // Figure out a class name, line number, type, and
          // arguments beforehand.
          for (int expressionTypeIndex = 0;
              expressionTypeIndex < expressionTypeCount;
              expressionTypeIndex++) {
            int startIndex = matcher.start(expressionTypeIndex + 1);
            if (startIndex >= 0) {
              String match = matcher.group(expressionTypeIndex + 1);

              char expressionType = expressionTypes[expressionTypeIndex];
              switch (expressionType) {
                case 'c':
                  className = originalClassName(match);
                  break;

                case 'C':
                  className = originalClassName(ClassUtil.externalClassName(match));
                  break;

                case 'l':
                  lineNumber = Integer.parseInt(match);
                  break;

                case 't':
                  type = originalType(match);
                  break;

                case 'a':
                  arguments = originalArguments(match);
                  break;
              }
            }
          }

          // Actually construct the output line.
          int lineIndex = 0;

          outLine.setLength(0);
          extraOutLines.clear();

          for (int expressionTypeIndex = 0;
              expressionTypeIndex < expressionTypeCount;
              expressionTypeIndex++) {
            int startIndex = matcher.start(expressionTypeIndex + 1);
            if (startIndex >= 0) {
              int endIndex = matcher.end(expressionTypeIndex + 1);
              String match = matcher.group(expressionTypeIndex + 1);

              // Copy a literal piece of input line.
              outLine.append(line.substring(lineIndex, startIndex));

              char expressionType = expressionTypes[expressionTypeIndex];
              switch (expressionType) {
                case 'c':
                  className = originalClassName(match);
                  outLine.append(className);
                  break;

                case 'C':
                  className = originalClassName(ClassUtil.externalClassName(match));
                  outLine.append(ClassUtil.internalClassName(className));
                  break;

                case 'l':
                  lineNumber = Integer.parseInt(match);
                  outLine.append(match);
                  break;

                case 't':
                  type = originalType(match);
                  outLine.append(type);
                  break;

                case 'f':
                  originalFieldName(className, match, type, outLine, extraOutLines);
                  break;

                case 'm':
                  originalMethodName(
                      className, match, lineNumber, type, arguments, outLine, extraOutLines);
                  break;

                case 'a':
                  arguments = originalArguments(match);
                  outLine.append(arguments);
                  break;
              }

              // Skip the original element whose processed version
              // has just been appended.
              lineIndex = endIndex;
            }
          }

          // Copy the last literal piece of input line.
          outLine.append(line.substring(lineIndex));

          // Print out the main line.
          System.out.println(outLine);

          // Print out any additional lines.
          for (int extraLineIndex = 0; extraLineIndex < extraOutLines.size(); extraLineIndex++) {
            System.out.println(extraOutLines.get(extraLineIndex));
          }
        } else {
          // Print out the original line.
          System.out.println(line);
        }
      }
    } catch (IOException ex) {
      throw new IOException("Can't read stack trace (" + ex.getMessage() + ")");
    } finally {
      if (stackTraceFile != null) {
        try {
          reader.close();
        } catch (IOException ex) {
          // This shouldn't happen.
        }
      }
    }
  }
  /**
   * Finds the original method name(s), appending the first one to the out line, and any additional
   * alternatives to the extra lines.
   */
  private void originalMethodName(
      String className,
      String obfuscatedMethodName,
      int lineNumber,
      String type,
      String arguments,
      StringBuffer outLine,
      List extraOutLines) {
    int extraIndent = -1;

    // Class name -> obfuscated method names.
    Map methodMap = (Map) classMethodMap.get(className);
    if (methodMap != null) {
      // Obfuscated method names -> methods.
      Set methodSet = (Set) methodMap.get(obfuscatedMethodName);
      if (methodSet != null) {
        // Find all matching methods.
        Iterator methodInfoIterator = methodSet.iterator();
        while (methodInfoIterator.hasNext()) {
          MethodInfo methodInfo = (MethodInfo) methodInfoIterator.next();
          if (methodInfo.matches(lineNumber, type, arguments)) {
            // Is this the first matching method?
            if (extraIndent < 0) {
              extraIndent = outLine.length();

              // Append the first original name.
              if (verbose) {
                outLine.append(methodInfo.type).append(' ');
              }
              outLine.append(methodInfo.originalName);
              if (verbose) {
                outLine.append('(').append(methodInfo.arguments).append(')');
              }
            } else {
              // Create an additional line with the proper
              // indentation.
              StringBuffer extraBuffer = new StringBuffer();
              for (int counter = 0; counter < extraIndent; counter++) {
                extraBuffer.append(' ');
              }

              // Append the alternative name.
              if (verbose) {
                extraBuffer.append(methodInfo.type).append(' ');
              }
              extraBuffer.append(methodInfo.originalName);
              if (verbose) {
                extraBuffer.append('(').append(methodInfo.arguments).append(')');
              }

              // Store the additional line.
              extraOutLines.add(extraBuffer);
            }
          }
        }
      }
    }

    // Just append the obfuscated name if we haven't found any matching
    // methods.
    if (extraIndent < 0) {
      outLine.append(obfuscatedMethodName);
    }
  }