/*
 * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.loader.enhancer;

import com.caucho.bytecode.*;
import com.caucho.java.WorkDir;
import com.caucho.java.gen.JavaClassGenerator;
import com.caucho.loader.DynamicClassLoader;
import com.caucho.util.L10N;
import com.caucho.vfs.JarPath;
import com.caucho.vfs.Path;
import com.caucho.vfs.ReadStream;
import com.caucho.vfs.Vfs;
import com.caucho.vfs.WriteStream;

import java.net.URL;
import java.util.ArrayList;
import java.util.logging.Logger;

/**
 * Class loader which checks for changes in class files and automatically
 * picks up new jars.
 *
 * <p>DynamicClassLoaders can be chained creating one virtual class loader.
 * From the perspective of the JDK, it's all one classloader.  Internally,
 * the class loader chain searches like a classpath.
 */
public class EnhancerFixup {
  private static final L10N L = new L10N(EnhancerFixup.class);
  private static final Logger log = Logger.getLogger(EnhancerFixup.class.getName());

  private static final int ACC_PUBLIC = 0x1;
  private static final int ACC_PRIVATE = 0x2;
  private static final int ACC_PROTECTED = 0x4;

  private JavaClassGenerator _javaGen = new JavaClassGenerator();

  private JavaClassLoader _jClassLoader;
  private DynamicClassLoader _loader;

  private Path _workPath;

  private String _baseSuffix = "";
  private String _extSuffix = "__ResinExt";

  private ArrayList<ClassEnhancer> _enhancerList =
    new ArrayList<ClassEnhancer>();

  private boolean _isParentStarted;

  /**
   * Sets the class loader.
   */
  public void setClassLoader(DynamicClassLoader loader)
  {
    _loader = loader;
  }

  /**
   * Returns the parsed class loader.
   */
  public void setJavaClassLoader(JavaClassLoader jClassLoader)
  {
    _jClassLoader = jClassLoader;
  }

  /**
   * Returns the parsed class loader.
   */
  public JavaClassLoader getJavaClassLoader()
  {
    return _jClassLoader;
  }

  /**
   * Gets the work path.
   */
  public Path getWorkPath()
  {
    if (_workPath != null)
      return _workPath;
    else
      return WorkDir.getLocalWorkDir();
  }

  /**
   * Sets the work path.
   */
  public void setWorkPath(Path workPath)
  {
    _workPath = workPath;
  }

  /**
   * Gets the work path.
   */
  public final Path getPreWorkPath()
  {
    return getWorkPath().lookup("pre-enhance");
  }

  /**
   * Gets the work path.
   */
  public final Path getPostWorkPath()
  {
    return getWorkPath().lookup("post-enhance");
  }

  /**
   * Adds a class enhancer.
   */
  public void addEnhancer(ClassEnhancer enhancer)
  {
    _enhancerList.add(enhancer);
  }

  protected void fixup(String className, String extClassName)
    throws Exception
  {
    Path prePath = getPreWorkPath();
    Path postPath = getPostWorkPath();

    Path source = getSource(className);

    if (source == null || ! source.canRead())
      return;

    Path ext = prePath.lookup(extClassName.replace('.', '/') + ".class");
    Path target = postPath.lookup(className.replace('.', '/') + ".class");

    try {
      target.getParent().mkdirs();
    } catch (Throwable e) {
    }

    if (source != null)
      mergeClasses(className, target, source, ext);
    else
      mergeClasses(className, target, ext);

    int p = className.lastIndexOf('.');

    Path preTargetDir;
    Path targetDir;
    String classSuffix;
    String prefix = "";

    if (p > 0) {
      prefix = className.substring(0, p).replace('.', '/');
      preTargetDir = prePath.lookup(prefix);
      targetDir = postPath.lookup(prefix);
      classSuffix = className.substring(p + 1);
    }
    else {
      preTargetDir = prePath;
      targetDir = postPath;
      classSuffix = className;
    }

    prefix = prefix.replace('/', '.');
    if (! prefix.equals(""))
      prefix = prefix + ".";

    String extSuffix;
    p = extClassName.lastIndexOf('.');
    if (p > 0)
      extSuffix = extClassName.substring(p + 1);
    else
      extSuffix = extClassName;

    fixupPreSubClasses(preTargetDir, targetDir,
                       extSuffix, classSuffix);

    fixupPostSubClasses(targetDir, prefix, classSuffix);
  }

  private void fixupPreSubClasses(Path preTargetDir, Path targetDir,
                                  String extSuffix, String classSuffix)
    throws Exception
  {
    String []list = preTargetDir.list();

    for (int i = 0; i < list.length; i++) {
      String name = list[i];

      if (name.startsWith(extSuffix + '$') &&
          name.endsWith(".class")) {
        int p = name.indexOf('$');
        String targetClass = (classSuffix + '$' +
                              name.substring(p + 1, name.length() - 6));

        Path subTarget = targetDir.lookup(targetClass + ".class");

        Path extPath = preTargetDir.lookup(name);

        renameSubClass(classSuffix, subTarget, extPath);

        //if (_loader != null)
        //  _loader.addPathClass(prefix + targetClass, subTarget);
      }
      else if (name.startsWith(extSuffix + '-') &&
               name.endsWith(".class")) {
        int p = name.indexOf('-');
        String targetClass = (classSuffix + '-' +
                              name.substring(p + 1, name.length() - 6));

        Path subTarget = targetDir.lookup(targetClass + ".class");

        Path extPath = preTargetDir.lookup(name);

        renameSubClass(classSuffix, subTarget, extPath);

        //if (_loader != null)
        //  _loader.addPathClass(prefix + targetClass, subTarget);
      }
    }
  }

  private void fixupPostSubClasses(Path targetDir,
                                   String prefix,
                                   String classSuffix)
    throws Exception
  {
    String []list = targetDir.list();

    for (int i = 0; i < list.length; i++) {
      String name = list[i];

      if (! name.endsWith(".class"))
        continue;

      String className = name.substring(0, name.length() - ".class".length());

      if (name.startsWith(classSuffix + '$')) {
        if (_loader != null)
          _loader.addPathClass(prefix + className, targetDir.lookup(name));
      }
      else if (name.startsWith(classSuffix + '-')) {
        if (_loader != null)
          _loader.addPathClass(prefix + className, targetDir.lookup(name));
      }
      else if (name.startsWith(classSuffix + '+')) {
        if (_loader != null)
          _loader.addPathClass(prefix + className, targetDir.lookup(name));
      }
    }
  }

  /**
   * Merges the two classes.
   */
  protected void renameSubClass(String className,
                                Path targetPath,
                                Path extPath)
    throws Exception
  {
    JavaClass extClass = null;

    ByteCodeParser parser = new ByteCodeParser();

    parser = new ByteCodeParser();
    parser.setClassLoader(new JavaClassLoader());

    ReadStream is = extPath.openRead();
    try {
      extClass = parser.parse(is);
    } finally {
      if (is != null)
        is.close();
    }

    cleanExtConstantPool(className, extClass);

    WriteStream os = targetPath.openWrite();
    try {
      extClass.write(os);
    } finally {
      os.close();
    }
  }

  /**
   * Renamed the super() methods
   */
  protected void renameExtSuperMethods(String className,
                                       JavaClass baseClass,
                                       JavaClass extClass)
    throws Exception
  {
    ArrayList<ConstantPoolEntry> entries;
    entries = extClass.getConstantPool().getEntries();

    className = className.replace('.', '/');
    String baseName = className + _baseSuffix;
    String extName = className + "__ResinExt";

    for (int i = 0; i < entries.size(); i++) {
      ConstantPoolEntry entry = entries.get(i);

      if (entry instanceof MethodRefConstant) {
        MethodRefConstant methodRef = (MethodRefConstant) entry;

        if (! methodRef.getClassName().equals(baseName))
          continue;

        String methodName = methodRef.getName();
        String type = methodRef.getType();

        if (findMethod(baseClass, methodName, type) == null)
          continue;

        if (findMethod(extClass, methodName, type) == null)
          continue;

        if (methodName.equals("<init>")) {
          methodName = "__init__super";
          // methodRef.setNameAndType(methodName, type);
        }
	/* jpa/0h28, shouldn't automatically change this
        else {
          methodName = methodName + "__super";
          methodRef.setNameAndType(methodName, type);
        }
	*/
      }
    }
  }


  /**
   * Moves all methods in the base to the __super methods
   */
  private void moveSuperMethods(String className,
                                JavaClass baseClass,
                                JavaClass extClass)
  {
    className = className.replace('.', '/');

    ArrayList<JavaMethod> methods = baseClass.getMethodList();

    // Move the __super methods
    ArrayList<JavaMethod> extMethods = extClass.getMethodList();
    for (int i = 0; i < extMethods.size(); i++) {
      JavaMethod extMethod = extMethods.get(i);

      fixupExtMethod(baseClass, extClass, extMethod);

      String baseName = extMethod.getName();

      if (baseName.endsWith("__super"))
        continue;

      String superName = baseName + "__super";

      int j;
      for (j = 0; j < methods.size(); j++) {
        JavaMethod method = methods.get(j);

        String type = method.getDescriptor();

        if (! method.getName().equals(baseName) ||
            ! method.getDescriptor().equals(extMethod.getDescriptor()))
          continue;

        if (baseName.equals("<init>")) {
          baseClass.getConstantPool().addUTF8("__init__super");
          mergeInitMethods(baseClass, method, extClass, extMethod);
          break;
        }

        if (baseName.equals("<clinit>")) {
          concatenateMethods(baseClass, method, extClass, extMethod);
          break;
        }

        baseClass.getConstantPool().addUTF8(superName);
        method.setName(superName);
        baseClass.getConstantPool().addUTF8(type);
        method.setDescriptor(type);

        // set the super methods private
        int flags = method.getAccessFlags();
        flags = (flags & ~ACC_PUBLIC & ~ACC_PROTECTED) | ACC_PRIVATE;
        method.setAccessFlags(flags);
        break;
      }
    }
  }

  /**
   * Concatenates methods
   */
  private void concatenateMethods(JavaClass baseClass, JavaMethod baseMethod,
                                  JavaClass extClass, JavaMethod extMethod)
  {
    extMethod = extMethod.export(extClass, baseClass);

    baseMethod.concatenate(extMethod);
  }

  /**
   * Merges the init methods
   */
  private void mergeInitMethods(JavaClass baseClass, JavaMethod baseMethod,
                                JavaClass extClass, JavaMethod extMethod)
  {
    extMethod = extMethod.export(extClass, baseClass);

    baseMethod.setName("__init__super");

    baseClass.getMethodList().add(extMethod);

    try {
      InitAnalyzer initAnalyzer = new InitAnalyzer();
      CodeEnhancer baseEnhancer = new CodeEnhancer(baseClass, baseMethod.getCode());
      baseEnhancer.analyze(initAnalyzer);

      int offset = initAnalyzer.getOffset();
      byte []code = new byte[offset];
      byte []oldCode = baseEnhancer.getCode();
      System.arraycopy(oldCode, 0, code, 0, offset);

      baseEnhancer.remove(0, offset);
      baseEnhancer.update();

      CodeEnhancer extEnhancer = new CodeEnhancer(baseClass, extMethod.getCode());

      extEnhancer.add(0, code, 0, code.length);

      ExtMethodAnalyzer extMethodAnalyzer
	= new ExtMethodAnalyzer(baseClass, extMethod, offset);
      extEnhancer.analyze(extMethodAnalyzer);
      extEnhancer.update();

      CodeAttribute baseCode = baseMethod.getCode();
      CodeAttribute extCode = extMethod.getCode();

      if (extCode.getMaxStack() < baseCode.getMaxStack())
        extCode.setMaxStack(baseCode.getMaxStack());

      // XXX: needs tests badly
      extCode.removeAttribute("LocalVariableTable");
      extCode.removeAttribute("LineNumberTable");
      baseCode.removeAttribute("LocalVariableTable");
      baseCode.removeAttribute("LineNumberTable");

      /*
        baseMethod.concatenate(extMethod);
      */
    } catch (RuntimeException e) {
      throw e;
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Merges the init methods
   */
  private void fixupExtMethod(JavaClass baseClass,
			      JavaClass extClass,
			      JavaMethod extMethod)
  {
    try {
      if (extMethod.getName().endsWith("__super"))
	return;
      
      CodeEnhancer extEnhancer
	= new CodeEnhancer(extClass, extMethod.getCode());

      ExtMethodAnalyzer extMethodAnalyzer
	= new ExtMethodAnalyzer(baseClass, extMethod, 0);
      extEnhancer.analyze(extMethodAnalyzer);
      extEnhancer.update();
    } catch (RuntimeException e) {
      throw e;
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Adds the methods from the ext to the base
   */
  private void addExtInterfaces(JavaClass baseClass, JavaClass extClass)
  {
    for (String name : extClass.getInterfaceNames()) {
      baseClass.getConstantPool().addClass(name);

      baseClass.addInterface(name);
    }
  }

  /**
   * Adds the methods from the ext to the base
   */
  private void addExtMethods(JavaClass baseClass, JavaClass extClass)
  {
    ArrayList<JavaMethod> methods = baseClass.getMethodList();
    ArrayList<JavaMethod> extMethods = extClass.getMethodList();

    for (int i = 0; i < extMethods.size(); i++) {
      JavaMethod extMethod = extMethods.get(i);

      if (extMethod.getName().equals("<clinit>") &&
          findMethod(baseClass, "<clinit>",
                     extMethod.getDescriptor()) != null) {
        continue;
      }
      else if (extMethod.getName().equals("<init>"))
        continue;
      else if (extMethod.getName().endsWith("__super"))
        continue;

      log.finest("adding extension method: " + extClass.getThisClass() + ":" + extMethod.getName());

      JavaMethod method = extMethod.export(extClass, baseClass);

      methods.add(method);
    }
  }

  /**
   * Adds the inner classes from the ext to the base
   */
  private void addExtClasses(JavaClass baseClass, JavaClass extClass)
  {
    /*
      ArrayList<JavaMethod> methods = baseClass.getMethodList();
      ArrayList<JavaMethod> extMethods = extClass.getMethodList();

      for (int i = 0; i < extMethods.size(); i++) {
      JavaMethod extMethod = extMethods.get(i);

      if (extMethod.getName().equals("<clinit>") &&
      findMethod(baseClass, "<clinit>",
      extMethod.getDescriptor()) != null) {
      continue;
      }
      else if (extMethod.getName().equals("<init>"))
      continue;
      else if (extMethod.getName().endsWith("__super"))
      continue;

      log.finest("adding extension method: " + extClass.getThisClass() + ":" + extMethod.getName());

      JavaMethod method = extMethod.export(extClass, baseClass);

      methods.add(method);
      }
    */
  }

  /**
   * Finds a matching method.
   */
  private static JavaMethod findMethod(JavaClass cl,
				       String name,
				       String descriptor)
  {
    ArrayList<JavaMethod> methods = cl.getMethodList();

    int j;
    for (j = 0; j < methods.size(); j++) {
      JavaMethod method = methods.get(j);

      if (method.getName().equals(name) &&
          method.getDescriptor().equals(descriptor))
        return method;
    }

    return null;
  }

  /**
   * Adds all fields from the ext to the base
   */
  private void moveSuperFields(JavaClass baseClass, JavaClass extClass)
  {
    ArrayList<JavaField> fields = baseClass.getFieldList();
    ArrayList<JavaField> extFields = extClass.getFieldList();

    for (int i = 0; i < extFields.size(); i++) {
    }
  }

  private Path getSource(String className)
  {
    ClassLoader loader = _loader;
    if (loader == null)
      loader = Thread.currentThread().getContextClassLoader();

    URL url = loader.getResource(className.replace('.', '/') + ".class");

    // XXX: workaround for tck
    String s = url.toString();
    int index = s.indexOf("jar!/");
    if (index > 0) {
      s = s.substring(9, index+3);
      Path path = JarPath.create(Vfs.lookup(s));
      path = path.lookup(className.replace('.', '/') + ".class");
      return path;
    }

    return Vfs.lookup(url.toString());
  }

  /**
   * Merges the two classes.
   */
  protected void mergeClasses(String className,
                              Path targetPath,
                              Path sourcePath,
                              Path extPath)
    throws Exception
  {
    JavaClass baseClass = null;
    JavaClass extClass = null;

    ByteCodeParser parser = new ByteCodeParser();
    parser.setClassLoader(getJavaClassLoader());

    ReadStream is = sourcePath.openRead();
    try {
      baseClass = parser.parse(is);
    } finally {
      if (is != null)
        is.close();
    }

    parser = new ByteCodeParser();
    parser.setClassLoader(getJavaClassLoader());

    is = extPath.openRead();
    try {
      extClass = parser.parse(is);
    } finally {
      if (is != null)
        is.close();
    }

    // jpa/0j26
    // XXX: later, need to see if it's possible to keep some of this
    // information for debugging
    fixupLocalVariableTable(extClass);
    fixupLocalVariableTable(baseClass);
    
    // The base class will have the modified class
    mergeClasses(className, baseClass, extClass);

    postEnhance(baseClass);

    WriteStream os = targetPath.openWrite();
    try {
      baseClass.write(os);
    } finally {
      os.close();
    }
  }

  /**
   * Merges the two classes.
   */
  protected void mergeClasses(String className,
                              Path targetPath,
                              Path extPath)
    throws Exception
  {
    JavaClass baseClass = null;
    JavaClass extClass = null;

    ByteCodeParser parser = new ByteCodeParser();
    parser.setClassLoader(getJavaClassLoader());

    ReadStream is = extPath.openRead();
    try {
      extClass = parser.parse(is);
    } finally {
      if (is != null)
        is.close();
    }

    cleanExtConstantPool(className, extClass);

    postEnhance(baseClass);

    WriteStream os = targetPath.openWrite();
    try {
      extClass.write(os);
    } finally {
      os.close();
    }
  }

  /**
   * After enhancement fixup.
   */
  protected void postEnhance(JavaClass baseClass)
    throws Exception
  {
    for (int i = 0; i < _enhancerList.size(); i++) {
      _enhancerList.get(i).postEnhance(baseClass);
    }

    fixupJdk16Methods(baseClass);
  }

  /**
   * Merges the two classes.
   */
  protected void mergeClasses(String className,
                              JavaClass baseClass,
                              JavaClass extClass)
    throws Exception
  {
    if (baseClass.getMajor() < extClass.getMajor()) {
      baseClass.setMajor(extClass.getMajor());
      baseClass.setMinor(extClass.getMinor());
    }

    cleanExtConstantPool(className, extClass);
    renameExtSuperMethods(className, baseClass, extClass);

    cleanExtConstantPool(className, baseClass);

    addExtInterfaces(baseClass, extClass);

    addExtFields(baseClass, extClass);

    moveSuperMethods(className, baseClass, extClass);

    addExtMethods(baseClass, extClass);

    copyExtAnnotations(baseClass);

    addExtClasses(baseClass, extClass);
  }

  /**
   * Cleans the ext constant pool, renaming
   */
  protected void cleanExtConstantPool(String className, JavaClass extClass)
    throws Exception
  {
    extClass.setThisClass(replaceString(className, extClass.getThisClass()));
    extClass.setSuperClass(replaceString(className, extClass.getSuperClassName()));

    ArrayList<ConstantPoolEntry> entries;
    entries = extClass.getConstantPool().getEntries();

    int t = className.lastIndexOf('.');
    if (t > 0)
      className = className.substring(t + 1);

    String baseName = className + _baseSuffix;
    String extName = className + "__ResinExt";

    for (int i = 0; i < entries.size(); i++) {
      ConstantPoolEntry entry = entries.get(i);

      if (entry instanceof Utf8Constant) {
        Utf8Constant utf8 = (Utf8Constant) entry;

        String string = utf8.getValue();

        string = replaceString(className, string);

        utf8.setValue(string);
      }
    }

    ArrayList<JavaField> fields = extClass.getFieldList();
    for (int i = 0; i < fields.size(); i++) {
      JavaField field = fields.get(i);

      field.setName(replaceString(className, field.getName()));
      field.setDescriptor(replaceString(className, field.getDescriptor()));
    }

    ArrayList<JavaMethod> methods = extClass.getMethodList();
    for (int i = 0; i < methods.size(); i++) {
      JavaMethod method = methods.get(i);

      method.setName(replaceString(className, method.getName()));
      method.setDescriptor(replaceString(className, method.getDescriptor()));
    }
  }

  /**
   * Adds the methods from the ext to the base
   */
  private void copyExtAnnotations(JavaClass baseClass)
  {
    for (JavaMethod method : baseClass.getMethodList()) {
      if (method.getName().endsWith("__super")) {
        Attribute ann = method.getAttribute("RuntimeVisibleAnnotations");

        if (ann != null) {
          String name = method.getName();
          name = name.substring(0, name.length() - "__super".length());

          JavaMethod baseMethod;
          baseMethod = findMethod(baseClass, name, method.getDescriptor());

          if (baseMethod != null)
            baseMethod.addAttribute(ann);
        }
      }
    }
  }

  /**
   * Adds the methods from the ext to the base
   */
  private void addExtFields(JavaClass baseClass, JavaClass extClass)
  {
    ArrayList<JavaField> fields = baseClass.getFieldList();

    for (JavaField extField : extClass.getFieldList()) {
      JavaField field = extField.export(extClass, baseClass);

      if (! fields.contains(field))
        fields.add(field);
    }
  }

  /**
   * Remove the StackMapTable
   */
  private void fixupJdk16Methods(JavaClass baseClass)
  {
    for (JavaMethod method : baseClass.getMethodList()) {
      CodeAttribute code = method.getCode();

      code.removeAttribute("StackMapTable");
    }
  }

  /**
   * Remove the LocalVariableTable
   */
  private void fixupLocalVariableTable(JavaClass extClass)
  {
    for (JavaMethod method : extClass.getMethodList()) {
      CodeAttribute code = method.getCode();

      code.removeAttribute("LocalVariableTable");
      code.removeAttribute("LocalVariableTypeTable");
    }
  }

  private String replaceString(String className, String string)
  {
    string = replaceStringInt(className.replace('.', '/'), string);
    string = replaceStringInt(className.replace('.', '$'), string);
    string = replaceStringInt(className.replace('.', '-'), string);

    return string;
  }

  private String replaceStringInt(String className, String string)
  {
    int t = className.lastIndexOf('.');
    if (t > 0)
      className = className.substring(t + 1);

    String baseName = className + _baseSuffix;
    // String extName = className + "__ResinExt";
    String extName = "__ResinExt";

    int p;
    if (! baseName.equals(className)) {
      while ((p = string.indexOf(baseName)) >= 0) {
        String prefix = string.substring(0, p);
        String suffix = string.substring(p + baseName.length());

        string = prefix + className + suffix;
      }
    }

    while ((p = string.indexOf(extName)) >= 0) {
      String prefix = string.substring(0, p);
      String suffix = string.substring(p + extName.length());

      // string = prefix + className + suffix;
      string = prefix + suffix;
    }

    return string;
  }

  private static class InitAnalyzer extends Analyzer {
    int _offset = -1;

    /**
     * Returns the analyzed offset.
     */
    public int getOffset()
    {
      return _offset;
    }

    /**
     * Analyzes the opcode.
     */
    public void analyze(CodeVisitor visitor)
      throws Exception
    {
      if (_offset >= 0)
        return;

      switch (visitor.getOpcode()) {
      case CodeVisitor.INVOKESPECIAL:
        JavaClass javaClass = visitor.getJavaClass();
        ConstantPool cp = javaClass.getConstantPool();
        MethodRefConstant ref = cp.getMethodRef(visitor.getShortArg());

        // ejb/0l00
        // handler "super()" and "this()"
        if (ref.getName().equals("<init>")
	    && (ref.getClassName().equals(javaClass.getThisClass())
		|| ref.getClassName().equals(javaClass.getSuperClassName()))) {
          _offset = visitor.getOffset() + 3;
        }
        break;
      }
    }
  }

  //
  // convert super.foo() calls to foo__super() where appropriate
  //
  private static class ExtMethodAnalyzer extends Analyzer {
    JClass _baseClass;
    JMethod _method;
    int _startOffset;
    boolean _isEnhanced;

    ExtMethodAnalyzer(JClass baseClass, JMethod method, int length)
    {
      _baseClass = baseClass;
      _method = method;
      _startOffset = length;
    }

    /**
     * Analyzes the opcode.
     */
    public void analyze(CodeVisitor visitor)
      throws Exception
    {
      if (_isEnhanced)
        return;

      if (visitor.getOffset() < _startOffset)
        return;

      switch (visitor.getOpcode()) {
      case CodeVisitor.INVOKESPECIAL:
        int index = visitor.getShortArg();

	JavaClass jClass = visitor.getJavaClass();
        ConstantPool cp = jClass.getConstantPool();
        MethodRefConstant ref;
        ref = cp.getMethodRef(index);

	if (ref.getName().endsWith("__super")) {
	  return;
	}
	else if (ref.getName().equals("<init>")
		 && (! ref.getClassName().equals(jClass.getSuperClassName())
		     || ! _method.getName().equals("<init>"))) {
	  return;
	}
	else if (! ref.getName().equals("<init>")) {
	  // private methods are called with invokespecial, but shouldn't
	  // be modified
	  JMethod method = findMethod(jClass,
				      ref.getName(),
				      ref.getType());

	  if (method != null && method.isPrivate())
	    return;
	}

	String superName;
        if (ref.getName().equals("<init>"))
	  superName = "__init__super";
	else
	  superName = ref.getName() + "__super";

        MethodRefConstant newRef;
        newRef = cp.addMethodRef(ref.getClassName(),
                                 superName,
                                 ref.getType());

        visitor.setShortArg(1, newRef.getIndex());

        _isEnhanced = true;
        break;
      }
    }
  }
}
