All Downloads are FREE. Search and download functionalities are using the official Maven repository.

xapi.bytecode.CtClassType Maven / Gradle / Ivy

Go to download

Everything needed to run a comprehensive dev environment. Just type X_ and pick a service from autocomplete; new dev modules will be added as they are built. The only dev service not included in the uber jar is xapi-dev-maven, as it includes all runtime dependencies of maven, adding ~4 seconds to build time, and 6 megabytes to the final output jar size (without xapi-dev-maven, it's ~1MB).

The newest version!
/*
 * Javassist, a Java-bytecode translator toolkit.
 * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License.  Alternatively, the contents of this file may be used under
 * the terms of the GNU Lesser General Public License Version 2.1 or later,
 * or the Apache License Version 2.0.
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * MODIFIED BY James Nelson of We The Internet, 2013.
 * Repackaged to avoid conflicts with different versions of Javassist,
 * and modified Javassist APIs to make them more accessible to outside code.
 */
package xapi.bytecode;

import static xapi.source.X_Modifier.ABSTRACT;
import static xapi.source.X_Modifier.INTERFACE;
import static xapi.source.X_Modifier.PUBLIC;
import static xapi.source.X_Modifier.isPrivate;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Set;

import xapi.bytecode.CtMember.Cache;
import xapi.bytecode.annotation.Annotation;
import xapi.bytecode.annotation.AnnotationsAttribute;
import xapi.bytecode.attributes.AttributeInfo;
import xapi.bytecode.attributes.InnerClassesAttribute;
import xapi.bytecode.attributes.ParameterAnnotationsAttribute;
import xapi.source.X_Modifier;

@SuppressWarnings({"unchecked", "rawtypes"})
public class CtClassType extends CtClass {
  ClassPool classPool;
  boolean wasChanged;
  boolean wasPruned;
  boolean gcConstPool; // if true, the constant pool entries will be garbage
                       // collected.
  ClassFile classfile;
  byte[] rawClassfile; // backup storage

  private WeakReference memberCache;

  private Hashtable hiddenMethods; // must be synchronous
  private int uniqueNumberSeed;

  private boolean doPruning = ClassPool.doPruning;
  private int getCount;
  private static final int GET_THRESHOLD = 2; // see compress()

  CtClassType(String name, ClassPool cp) {
    super(name);
    classPool = cp;
    wasChanged = wasPruned = gcConstPool = false;
    classfile = null;
    rawClassfile = null;
    memberCache = null;
    hiddenMethods = null;
    uniqueNumberSeed = 0;
    getCount = 0;
  }

  public CtClassType(InputStream ins, ClassPool cp) throws IOException {
    this((String) null, cp);
    classfile = new ClassFile(new DataInputStream(ins));
    qualifiedName = classfile.getName();
  }

  @Override
  public ClassFile getClassFile2() {
    ClassFile cfile = classfile;
    if (cfile != null) {
      return cfile;
    }

    classPool.compress();
    if (rawClassfile != null) {
      try {
        classfile = new ClassFile(new DataInputStream(new ByteArrayInputStream(
            rawClassfile)));
        rawClassfile = null;
        getCount = GET_THRESHOLD;
        return classfile;
      } catch (IOException e) {
        throw new RuntimeException(e.toString(), e);
      }
    }

    InputStream fin = null;
    try {
      fin = classPool.openClassfile(getName());
      if (fin == null) {
        throw new NotFoundException(getName());
      }

      fin = new BufferedInputStream(fin);
      ClassFile cf = new ClassFile(new DataInputStream(fin));
      if (!cf.getName().equals(qualifiedName)) {
        throw new RuntimeException("cannot find " + qualifiedName + ": "
            + cf.getName() + " found in " + qualifiedName.replace('.', '/')
            + ".class");
      }

      classfile = cf;
      return cf;
    } catch (NotFoundException e) {
      throw new RuntimeException(e.toString(), e);
    } catch (IOException e) {
      throw new RuntimeException(e.toString(), e);
    } finally {
      if (fin != null) {
        try {
          fin.close();
        } catch (IOException e) {
        }
      }
    }
  }

  /*
   * Inherited from CtClass. Called by get() in ClassPool.
   *
   * @see javassist.CtClass#incGetCounter()
   *
   * @see #toBytecode(DataOutputStream)
   */
  @Override
  final void incGetCounter() {
    ++getCount;
  }

  /**
   * Invoked from ClassPool#compress(). It releases the class files that have
   * not been recently used if they are unmodified.
   */
  @Override
  void compress() {
    if (getCount < GET_THRESHOLD) {
      if (!isModified() && ClassPool.releaseUnmodifiedClassFile) {
        removeClassFile();
      } else if (isFrozen() && !wasPruned) {
        saveClassFile();
      }
    }

    getCount = 0;
  }

  /**
   * Converts a ClassFile object into a byte array for saving memory space.
   */
  private synchronized void saveClassFile() {
    /*
     * getMembers() and releaseClassFile() are also synchronized.
     */
    if (classfile == null) {
      return;
    }

    ByteArrayOutputStream barray = new ByteArrayOutputStream();
    DataOutputStream out = new DataOutputStream(barray);
    try {
      classfile.write(out);
      barray.close();
      rawClassfile = barray.toByteArray();
      classfile = null;
    } catch (IOException e) {
    }
  }

  private synchronized void removeClassFile() {
    if (classfile != null && !isModified()) {
      classfile = null;
    }
  }

  @Override
  public ClassPool getClassPool() {
    return classPool;
  }

  void setClassPool(ClassPool cp) {
    classPool = cp;
  }

  @Override
  public URL getURL() throws NotFoundException {
    URL url = classPool.find(getName());
    if (url == null) {
      throw new NotFoundException(getName());
    } else {
      return url;
    }
  }

  @Override
  public boolean isModified() {
    return wasChanged;
  }

  @Override
  public boolean subtypeOf(CtClass clazz) throws NotFoundException {
    int i;
    String cname = clazz.getName();
    if (this == clazz || getName().equals(cname)) {
      return true;
    }

    ClassFile file = getClassFile2();
    String supername = file.getSuperclass();
    if (supername != null && supername.equals(cname)) {
      return true;
    }

    String[] ifs = file.getInterfaces();
    int num = ifs.length;
    for (i = 0; i < num; ++i) {
      if (ifs[i].equals(cname)) {
        return true;
      }
    }

    if (supername != null && classPool.get(supername).subtypeOf(clazz)) {
      return true;
    }

    for (i = 0; i < num; ++i) {
      if (classPool.get(ifs[i]).subtypeOf(clazz)) {
        return true;
      }
    }

    return false;
  }

  // @Override
  @Override
  public void setName(String name) throws RuntimeException {
    String oldname = getName();
    if (name.equals(oldname)) {
      return;
    }

    // check this in advance although classNameChanged() below does.
    classPool.checkNotFrozen(name);
    ClassFile cf = getClassFile2();
    super.setName(name);
    cf.setName(name);
    nameReplaced();
    classPool.classNameChanged(oldname, this);
  }

  private void nameReplaced() {
    CtMember.Cache cache = hasMemberCache();
    if (cache != null) {
      CtMember mth = cache.methodHead();
      CtMember tail = cache.lastMethod();
      while (mth != tail) {
        mth = mth.next();
        mth.nameReplaced();
      }
    }
  }

  /**
   * Returns null if members are not cached.
   */
  protected CtMember.Cache hasMemberCache() {
    if (memberCache != null) {
      return (CtMember.Cache) memberCache.get();
    } else {
      return null;
    }
  }

  @Override
  public boolean isInterface() {
    return X_Modifier.isInterface(getModifiers());
  }

  @Override
  public boolean isAnnotation() {
    return X_Modifier.isAnnotation(getModifiers());
  }

  @Override
  public boolean isEnum() {
    return X_Modifier.isEnum(getModifiers());
  }

  @Override
  public int getModifiers() {
    ClassFile cf = getClassFile2();
    int acc = cf.getAccessFlags();

    acc = X_Modifier.clear(acc, X_Modifier.SUPER);
    int inner = cf.getInnerAccessFlags();
    if (inner != -1 && (inner & X_Modifier.STATIC) != 0) {
      acc |= X_Modifier.STATIC;
    }

    return acc;
  }

  @Override
  public CtClass[] getNestedClasses() throws NotFoundException {
    ClassFile cf = getClassFile2();
    InnerClassesAttribute ica = (InnerClassesAttribute) cf
        .getAttribute(InnerClassesAttribute.tag);
    if (ica == null) {
      return new CtClass[0];
    }

    String thisName = cf.getName();
    int n = ica.tableLength();
    ArrayList list = new ArrayList(n);
    for (int i = 0; i < n; i++) {
      String outer = ica.outerClass(i);
      /*
       * If a nested class is local or anonymous, the outer_class_info_index is
       * 0.
       */
      if (outer == null || outer.equals(thisName)) {
        String inner = ica.innerClass(i);
        if (inner != null) {
          list.add(classPool.get(inner));
        }
      }
    }

    return (CtClass[]) list.toArray(new CtClass[list.size()]);
  }

  @Override
  public void setModifiers(int mod) {
    ClassFile cf = getClassFile2();
    if (X_Modifier.isStatic(mod)) {
      int flags = cf.getInnerAccessFlags();
      if (flags != -1 && (flags & X_Modifier.STATIC) != 0) {
        mod = mod & ~X_Modifier.STATIC;
      } else {
        throw new RuntimeException("cannot change " + getName()
            + " into a static class");
      }
    }

    checkModify();
    cf.setAccessFlags(mod);
  }

  @Override
  public boolean hasAnnotation(Class clz) {
    ClassFile cf = getClassFile2();
    AnnotationsAttribute ainfo = (AnnotationsAttribute) cf
        .getAttribute(AnnotationsAttribute.invisibleTag);
    AnnotationsAttribute ainfo2 = (AnnotationsAttribute) cf
        .getAttribute(AnnotationsAttribute.visibleTag);
    return hasAnnotationType(clz, getClassPool(), ainfo, ainfo2);
  }

  static boolean hasAnnotationType(Class clz, ClassPool cp,
      AnnotationsAttribute a1, AnnotationsAttribute a2) {
    Annotation[] anno1, anno2;

    if (a1 == null) {
      anno1 = null;
    } else {
      anno1 = a1.getAnnotations();
    }

    if (a2 == null) {
      anno2 = null;
    } else {
      anno2 = a2.getAnnotations();
    }

    String typeName = clz.getName();
    if (anno1 != null) {
      for (int i = 0; i < anno1.length; i++) {
        if (anno1[i].getTypeName().equals(typeName)) {
          return true;
        }
      }
    }

    if (anno2 != null) {
      for (int i = 0; i < anno2.length; i++) {
        if (anno2[i].getTypeName().equals(typeName)) {
          return true;
        }
      }
    }

    return false;
  }

  @Override
  public Object getAnnotation(Class clz) throws ClassNotFoundException {
    ClassFile cf = getClassFile2();
    AnnotationsAttribute ainfo = (AnnotationsAttribute) cf
        .getAttribute(AnnotationsAttribute.invisibleTag);
    AnnotationsAttribute ainfo2 = (AnnotationsAttribute) cf
        .getAttribute(AnnotationsAttribute.visibleTag);
    return getAnnotationType(clz, getClassPool(), ainfo, ainfo2);
  }

  static Object getAnnotationType(Class clz, ClassPool cp,
      AnnotationsAttribute a1, AnnotationsAttribute a2)
      throws ClassNotFoundException {
    Annotation[] anno1, anno2;

    if (a1 == null) {
      anno1 = null;
    } else {
      anno1 = a1.getAnnotations();
    }

    if (a2 == null) {
      anno2 = null;
    } else {
      anno2 = a2.getAnnotations();
    }

    String typeName = clz.getName();
    if (anno1 != null) {
      for (int i = 0; i < anno1.length; i++) {
        if (anno1[i].getTypeName().equals(typeName)) {
          return toAnnoType(anno1[i], cp);
        }
      }
    }

    if (anno2 != null) {
      for (int i = 0; i < anno2.length; i++) {
        if (anno2[i].getTypeName().equals(typeName)) {
          return toAnnoType(anno2[i], cp);
        }
      }
    }

    return null;
  }

  @Override
  public Object[] getAnnotations() throws ClassNotFoundException {
    return getAnnotations(false);
  }

  @Override
  public Object[] getAvailableAnnotations() {
    try {
      return getAnnotations(true);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException("Unexpected exception ", e);
    }
  }

  private Object[] getAnnotations(boolean ignoreNotFound)
      throws ClassNotFoundException {
    ClassFile cf = getClassFile2();
    AnnotationsAttribute ainfo = (AnnotationsAttribute) cf
        .getAttribute(AnnotationsAttribute.invisibleTag);
    AnnotationsAttribute ainfo2 = (AnnotationsAttribute) cf
        .getAttribute(AnnotationsAttribute.visibleTag);
    return toAnnotationType(ignoreNotFound, getClassPool(), ainfo, ainfo2);
  }

  static Object[] toAnnotationType(boolean ignoreNotFound, ClassPool cp,
      AnnotationsAttribute a1, AnnotationsAttribute a2)
      throws ClassNotFoundException {
    Annotation[] anno1, anno2;
    int size1, size2;

    if (a1 == null) {
      anno1 = null;
      size1 = 0;
    } else {
      anno1 = a1.getAnnotations();
      size1 = anno1.length;
    }

    if (a2 == null) {
      anno2 = null;
      size2 = 0;
    } else {
      anno2 = a2.getAnnotations();
      size2 = anno2.length;
    }

    if (!ignoreNotFound) {
      Object[] result = new Object[size1 + size2];
      for (int i = 0; i < size1; i++) {
        result[i] = toAnnoType(anno1[i], cp);
      }

      for (int j = 0; j < size2; j++) {
        result[j + size1] = toAnnoType(anno2[j], cp);
      }

      return result;
    } else {
      ArrayList annotations = new ArrayList();
      for (int i = 0; i < size1; i++) {
        try {
          annotations.add(toAnnoType(anno1[i], cp));
        } catch (ClassNotFoundException e) {
        }
      }
      for (int j = 0; j < size2; j++) {
        try {
          annotations.add(toAnnoType(anno2[j], cp));
        } catch (ClassNotFoundException e) {
        }
      }

      return annotations.toArray();
    }
  }

  static Object[][] toAnnotationType(boolean ignoreNotFound, ClassPool cp,
      ParameterAnnotationsAttribute a1, ParameterAnnotationsAttribute a2,
      MethodInfo minfo) throws ClassNotFoundException {
    int numParameters = 0;
    if (a1 != null) {
      numParameters = a1.numParameters();
    } else if (a2 != null) {
      numParameters = a2.numParameters();
    } else {
      numParameters = Descriptor.numOfParameters(minfo.getDescriptor());
    }

    Object[][] result = new Object[numParameters][];
    for (int i = 0; i < numParameters; i++) {
      Annotation[] anno1, anno2;
      int size1, size2;

      if (a1 == null) {
        anno1 = null;
        size1 = 0;
      } else {
        anno1 = a1.getAnnotations()[i];
        size1 = anno1.length;
      }

      if (a2 == null) {
        anno2 = null;
        size2 = 0;
      } else {
        anno2 = a2.getAnnotations()[i];
        size2 = anno2.length;
      }

      if (!ignoreNotFound) {
        result[i] = new Object[size1 + size2];
        for (int j = 0; j < size1; ++j) {
          result[i][j] = toAnnoType(anno1[j], cp);
        }

        for (int j = 0; j < size2; ++j) {
          result[i][j + size1] = toAnnoType(anno2[j], cp);
        }
      } else {
        ArrayList annotations = new ArrayList();
        for (int j = 0; j < size1; j++) {
          try {
            annotations.add(toAnnoType(anno1[j], cp));
          } catch (ClassNotFoundException e) {
          }
        }
        for (int j = 0; j < size2; j++) {
          try {
            annotations.add(toAnnoType(anno2[j], cp));
          } catch (ClassNotFoundException e) {
          }
        }

        result[i] = annotations.toArray();
      }
    }

    return result;
  }

  private static Object toAnnoType(Annotation anno, ClassPool cp)
      throws ClassNotFoundException {
    try {
      ClassLoader cl = cp.getClassLoader();
      return anno.toAnnotationType(cl, cp);
    } catch (ClassNotFoundException e) {
      ClassLoader cl2 = cp.getClass().getClassLoader();
      return anno.toAnnotationType(cl2, cp);
    }
  }

  @Override
  public boolean subclassOf(CtClass superclass) {
    if (superclass == null) {
      return false;
    }

    String superName = superclass.getName();
    CtClass curr = this;
    try {
      while (curr != null) {
        if (curr.getName().equals(superName)) {
          return true;
        }

        curr = curr.getSuperclass();
      }
    } catch (Exception ignored) {
    }
    return false;
  }

  @Override
  public CtClass getSuperclass() throws NotFoundException {
    String supername = getClassFile2().getSuperclass();
    if (supername == null) {
      return null;
    } else {
      return classPool.get(supername);
    }
  }

  @Override
  public void setSuperclass(CtClass clazz) {
    checkModify();
    if (isInterface()) {
      addInterface(clazz);
    } else {
      getClassFile2().setSuperclass(clazz.getName());
    }
  }

  @Override
  public CtClass[] getInterfaces() throws NotFoundException {
    String[] ifs = getClassFile2().getInterfaces();
    int num = ifs.length;
    CtClass[] ifc = new CtClass[num];
    for (int i = 0; i < num; ++i) {
      ifc[i] = classPool.get(ifs[i]);
    }

    return ifc;
  }

  @Override
  public void setInterfaces(CtClass[] list) {
    checkModify();
    String[] ifs;
    if (list == null) {
      ifs = new String[0];
    } else {
      int num = list.length;
      ifs = new String[num];
      for (int i = 0; i < num; ++i) {
        ifs[i] = list[i].getName();
      }
    }

    getClassFile2().setInterfaces(ifs);
  }

  @Override
  public void addInterface(CtClass anInterface) {
    checkModify();
    if (anInterface != null) {
      getClassFile2().addInterface(anInterface.getName());
    }
  }

  @Override
  public CtClass getDeclaringClass() throws NotFoundException {
    ClassFile cf = getClassFile2();
    InnerClassesAttribute ica = (InnerClassesAttribute) cf
        .getAttribute(InnerClassesAttribute.tag);
    if (ica == null) {
      return null;
    }

    String name = getName();
    int n = ica.tableLength();
    for (int i = 0; i < n; ++i) {
      if (name.equals(ica.innerClass(i))) {
        String outName = ica.outerClass(i);
        if (outName != null) {
          return classPool.get(outName);
        } else {
          // we don't support anonymous or local classes
          // // maybe anonymous or local class.
          // EnclosingMethodAttribute ema
          // = (EnclosingMethodAttribute)cf.getAttribute(
          // EnclosingMethodAttribute.tag);
          // if (ema != null)
          // return classPool.get(ema.className());
        }
      }
    }

    return null;
  }

  //
  // @Override
  // public CtMethod getEnclosingMethod() throws NotFoundException {
  // ClassFile cf = getClassFile2();
  // EnclosingMethodAttribute ema
  // = (EnclosingMethodAttribute)cf.getAttribute(
  // EnclosingMethodAttribute.tag);
  // if (ema != null) {
  // CtClass enc = classPool.get(ema.className());
  // return enc.getMethod(ema.methodName(), ema.methodDescriptor());
  // }
  //
  // return null;
  // }

  @Override
  public CtClass makeNestedClass(String name, boolean isStatic) {
    if (!isStatic) {
      throw new RuntimeException("sorry, only nested static class is supported");
    }

    checkModify();
    CtClass c = classPool.makeNestedClass(getName() + "$" + name);
    ClassFile cf = getClassFile2();
    ClassFile cf2 = c.getClassFile2();
    InnerClassesAttribute ica = (InnerClassesAttribute) cf
        .getAttribute(InnerClassesAttribute.tag);
    if (ica == null) {
      ica = new InnerClassesAttribute(cf.getConstPool());
      cf.addAttribute(ica);
    }

    ica.append(c.getName(), this.getName(), name,
        (cf2.getAccessFlags() & ~X_Modifier.SUPER) | X_Modifier.STATIC);
    cf2.addAttribute(ica.copy(cf2.getConstPool(), null));
    return c;
  }

  private static boolean isPubCons(CtConstructor cons) {
    return !X_Modifier.isPrivate(cons.getModifiers()) && cons.isConstructor();
  }

  @Override
  public CtConstructor getConstructor(String desc) throws NotFoundException {
    CtMember.Cache memCache = getMembers();
    CtMember cons = memCache.consHead();
    CtMember consTail = memCache.lastCons();

    while (cons != consTail) {
      cons = cons.next();
      CtConstructor cc = (CtConstructor) cons;
      if (cc.getMethodInfo2().getDescriptor().equals(desc)
          && cc.isConstructor()) {
        return cc;
      }
    }

    return super.getConstructor(desc);
  }

  @Override
  public CtConstructor[] getDeclaredConstructors() {
    CtMember.Cache memCache = getMembers();
    CtMember cons = memCache.consHead();
    CtMember consTail = memCache.lastCons();

    int n = 0;
    CtMember mem = cons;
    while (mem != consTail) {
      mem = mem.next();
      CtConstructor cc = (CtConstructor) mem;
      if (cc.isConstructor()) {
        n++;
      }
    }

    CtConstructor[] result = new CtConstructor[n];
    int i = 0;
    mem = cons;
    while (mem != consTail) {
      mem = mem.next();
      CtConstructor cc = (CtConstructor) mem;
      if (cc.isConstructor()) {
        result[i++] = cc;
      }
    }

    return result;
  }

  @Override
  public CtConstructor getClassInitializer() {
    CtMember.Cache memCache = getMembers();
    CtMember cons = memCache.consHead();
    CtMember consTail = memCache.lastCons();

    while (cons != consTail) {
      cons = cons.next();
      CtConstructor cc = (CtConstructor) cons;
      if (cc.isClassInitializer()) {
        return cc;
      }
    }

    return null;
  }

  @Override
  public CtMethod[] getMethods() {
    HashMap h = new HashMap();
    getMethods0(h, this);
    return (CtMethod[]) h.values().toArray(new CtMethod[h.size()]);
  }

  private static void getMethods0(HashMap h, CtClass cc) {
    try {
      CtClass[] ifs = cc.getInterfaces();
      int size = ifs.length;
      for (int i = 0; i < size; ++i) {
        getMethods0(h, ifs[i]);
      }
    } catch (NotFoundException e) {
    }

    try {
      CtClass s = cc.getSuperclass();
      if (s != null) {
        getMethods0(h, s);
      }
    } catch (NotFoundException e) {
    }

    if (cc instanceof CtClassType) {
      CtMember.Cache memCache = ((CtClassType) cc).getMembers();
      CtMember mth = memCache.methodHead();
      CtMember mthTail = memCache.lastMethod();

      while (mth != mthTail) {
        mth = mth.next();
        if (!X_Modifier.isPrivate(mth.getModifiers())) {
          h.put(((CtMethod) mth).getStringRep(), mth);
        }
      }
    }
  }

  protected synchronized CtMember.Cache getMembers() {
    CtMember.Cache cache = null;
    if (memberCache == null
        || (cache = (CtMember.Cache) memberCache.get()) == null) {
      cache = new CtMember.Cache(this);
      makeFieldCache(cache);
      makeBehaviorCache(cache);
      memberCache = new WeakReference(cache);
    }

    return cache;
  }

  private void makeFieldCache(CtMember.Cache cache) {
    List list = getClassFile2().getFields();
    int n = list.size();
    for (int i = 0; i < n; ++i) {
       FieldInfo finfo = (FieldInfo)list.get(i);
       CtField newField = new CtField(finfo, this);
       cache.addField(newField);
    }
  }

  private void makeBehaviorCache(CtMember.Cache cache) {
    List list = getClassFile2().getMethods();
    int n = list.size();
    for (int i = 0; i < n; ++i) {
      MethodInfo minfo = (MethodInfo) list.get(i);
      if (minfo.isMethod()) {
        CtMethod newMethod = new CtMethod(minfo, this);
        cache.addMethod(newMethod);
      } else {
        // TODO do something with ctors
        // CtConstructor newCons = new CtConstructor(minfo, this);
        // cache.addConstructor(newCons);
      }
    }
  }

  @Override
  public CtMethod getMethod(String name, String desc) throws NotFoundException {
    CtMethod m = getMethod0(this, name, desc);
    if (m != null) {
      return m;
    } else {
      throw new NotFoundException(name + "(..) is not found in " + getName());
    }
  }

  private static CtMethod getMethod0(CtClass cc, String name, String desc) {
    if (cc instanceof CtClassType) {
      CtMember.Cache memCache = ((CtClassType) cc).getMembers();
      CtMember mth = memCache.methodHead();
      CtMember mthTail = memCache.lastMethod();

      while (mth != mthTail) {
        mth = mth.next();
        if (mth.getName().equals(name)
            && ((CtMethod) mth).getMethodInfo2().getDescriptor().equals(desc)) {
          return (CtMethod) mth;
        }
      }
    }

    try {
      CtClass s = cc.getSuperclass();
      if (s != null) {
        CtMethod m = getMethod0(s, name, desc);
        if (m != null) {
          return m;
        }
      }
    } catch (NotFoundException e) {
    }

    try {
      CtClass[] ifs = cc.getInterfaces();
      int size = ifs.length;
      for (int i = 0; i < size; ++i) {
        CtMethod m = getMethod0(ifs[i], name, desc);
        if (m != null) {
          return m;
        }
      }
    } catch (NotFoundException e) {
    }
    return null;
  }

  @Override
  public CtMethod[] getDeclaredMethods() {
    CtMember.Cache memCache = getMembers();
    CtMember mth = memCache.methodHead();
    CtMember mthTail = memCache.lastMethod();
    int num = CtMember.Cache.count(mth, mthTail);
    CtMethod[] cms = new CtMethod[num];
    int i = 0;
    while (mth != mthTail) {
      mth = mth.next();
      cms[i++] = (CtMethod) mth;
    }

    return cms;
  }

  @Override
  public CtMethod getDeclaredMethod(String name) throws NotFoundException {
    CtMember.Cache memCache = getMembers();
    CtMember mth = memCache.methodHead();
    CtMember mthTail = memCache.lastMethod();
    while (mth != mthTail) {
      mth = mth.next();
      if (mth.getName().equals(name)) {
        return (CtMethod) mth;
      }
    }

    throw new NotFoundException(name + "(..) is not found in " + getName());
  }

  @Override
  public CtMethod getDeclaredMethod(String name, CtClass[] params)
      throws NotFoundException {
    String desc = Descriptor.ofParameters(params);
    CtMember.Cache memCache = getMembers();
    CtMember mth = memCache.methodHead();
    CtMember mthTail = memCache.lastMethod();

    while (mth != mthTail) {
      mth = mth.next();
      if (mth.getName().equals(name)
          && ((CtMethod) mth).getMethodInfo2().getDescriptor().startsWith(desc)) {
        return (CtMethod) mth;
      }
    }

    throw new NotFoundException(name + "(..) is not found in " + getName());
  }


  @Override
  public CtField[] getFields() {
      ArrayList alist = new ArrayList();
      getFields(alist, this);
      return (CtField[])alist.toArray(new CtField[alist.size()]);
  }

  private static void getFields(ArrayList alist, CtClass cc) {
      int i, num;
      if (cc == null) {
        return;
      }

      try {
          getFields(alist, cc.getSuperclass());
      }
      catch (NotFoundException e) {}

      try {
          CtClass[] ifs = cc.getInterfaces();
          num = ifs.length;
          for (i = 0; i < num; ++i) {
            getFields(alist, ifs[i]);
          }
      }
      catch (NotFoundException e) {}

      CtMember.Cache memCache = ((CtClassType)cc).getMembers();
      CtMember field = memCache.fieldHead();
      CtMember tail = memCache.lastField();
      while (field != tail) {
          field = field.next();
          if (!isPrivate(field.getModifiers())) {
            alist.add(field);
          }
      }
  }

  @Override
  public CtField getField(String name) throws NotFoundException {
      return getField(name, null);
  }

  public CtField getField(String name, String desc) throws NotFoundException {
    CtField f = getField2(name, desc);
    return checkGetField(f, name, desc);
  }

  private CtField checkGetField(CtField f, String name, String desc)
      throws NotFoundException
  {
      if (f == null) {
          String msg = "field: " + name;
          if (desc != null) {
            msg += " type " + desc;
          }

          throw new NotFoundException(msg + " in " + getName());
      } else {
        return f;
      }
  }

  @Override
  CtField getField2(String name, String desc) {
      CtField df = getDeclaredField2(name, desc);
      if (df != null) {
        return df;
      }

      try {
          CtClass[] ifs = getInterfaces();
          int num = ifs.length;
          for (int i = 0; i < num; ++i) {
              CtField f = ifs[i].getField2(name, desc);
              if (f != null) {
                return f;
              }
          }

          CtClass s = getSuperclass();
          if (s != null) {
            return s.getField2(name, desc);
          }
      }
      catch (NotFoundException e) {}
      return null;
  }

  @Override
  public CtField[] getDeclaredFields() {
      CtMember.Cache memCache = getMembers();
      CtMember field = memCache.fieldHead();
      CtMember tail = memCache.lastField();
      int num = CtMember.Cache.count(field, tail);
      CtField[] cfs = new CtField[num];
      int i = 0;
      while (field != tail) {
          field = field.next();
          cfs[i++] = (CtField)field;
      }

      return cfs;
  }

  @Override
  public CtField getDeclaredField(String name) throws NotFoundException {
      return getDeclaredField(name, null);
  }

  public CtField getDeclaredField(String name, String desc) throws NotFoundException {
      CtField f = getDeclaredField2(name, desc);
      return checkGetField(f, name, desc);
  }

  private CtField getDeclaredField2(String name, String desc) {
      CtMember.Cache memCache = getMembers();
      CtMember field = memCache.fieldHead();
      CtMember tail = memCache.lastField();
      while (field != tail) {
          field = field.next();
          if (field.getName().equals(name)
              && (desc == null || desc.equals(field.getSignature()))) {
            return (CtField)field;
          }
      }

      return null;
  }

   @Override
   public void removeField(CtField f) throws NotFoundException {
   checkModify();
   FieldInfo fi = f.getFieldInfo2();
   ClassFile cf = getClassFile2();
   if (cf.getFields().remove(fi)) {
   getMembers().remove(f);
   gcConstPool = true;
   } else {
    throw new NotFoundException(f.toString());
  }
   }

   @Override
   public void addConstructor(CtConstructor c)
   throws CannotCompileException
   {
   checkModify();
   if (c.getDeclaringClass() != this) {
    throw new CannotCompileException("cannot add");
  }

   getMembers().addConstructor(c);
   getClassFile2().addMethod(c.getMethodInfo2());
   }

   @Override
   public void removeConstructor(CtConstructor m) throws NotFoundException {
   checkModify();
   MethodInfo mi = m.getMethodInfo2();
   ClassFile cf = getClassFile2();
   if (cf.getMethods().remove(mi)) {
   getMembers().remove(m);
   gcConstPool = true;
   } else {
    throw new NotFoundException(m.toString());
  }
   }

   @Override
   public void addMethod(CtMethod m) throws CannotCompileException {
   checkModify();
   if (m.getDeclaringClass() != this) {
    throw new CannotCompileException("bad declaring class");
  }

   int mod = m.getModifiers();
   if ((getModifiers() & INTERFACE) != 0) {
   m.setModifiers(mod | PUBLIC);
   if ((mod & ABSTRACT) == 0) {
    throw new CannotCompileException(
     "an interface method must be abstract: " + m.toString());
  }
   }

   getMembers().addMethod(m);
   getClassFile2().addMethod(m.getMethodInfo2());
   if ((mod & ABSTRACT) != 0) {
    setModifiers(getModifiers() | ABSTRACT);
  }
   }

   @Override
   public void removeMethod(CtMethod m) throws NotFoundException {
   checkModify();
   MethodInfo mi = m.getMethodInfo2();
   ClassFile cf = getClassFile2();
   if (cf.getMethods().remove(mi)) {
   getMembers().remove(m);
   gcConstPool = true;
   } else {
    throw new NotFoundException(m.toString());
  }
   }

  @Override
  public byte[] getAttribute(String name) {
    AttributeInfo ai = getClassFile2().getAttribute(name);
    if (ai == null) {
      return null;
    } else {
      return ai.get();
    }
  }

  @Override
  public void setAttribute(String name, byte[] data) {
    checkModify();
    ClassFile cf = getClassFile2();
    cf.addAttribute(new AttributeInfo(cf.getConstPool(), name, data));
  }

  /**
   * @see javassist.CtClass#prune()
   * @see javassist.CtClass#stopPruning(boolean)
   */
  @Override
  public void prune() {
    if (wasPruned) {
      return;
    }

    wasPruned = true;
    getClassFile2().prune();
  }

  @Override
  public void rebuildClassFile() {
    gcConstPool = true;
  }

  // @Override
  // public void toBytecode(DataOutputStream out)
  // throws CannotCompileException, IOException
  // {
  // try {
  // if (isModified()) {
  // checkPruned("toBytecode");
  // ClassFile cf = getClassFile2();
  // if (gcConstPool) {
  // cf.compact();
  // gcConstPool = false;
  // }
  //
  // modifyClassConstructor(cf);
  // modifyConstructors(cf);
  // cf.write(out);
  // out.flush();
  // fieldInitializers = null;
  // if (doPruning) {
  // // to save memory
  // cf.prune();
  // wasPruned = true;
  // }
  // }
  // else {
  // classPool.writeClassfile(getName(), out);
  // // to save memory
  // // classfile = null;
  // }
  //
  // getCount = 0;
  // wasFrozen = true;
  // }
  // catch (NotFoundException e) {
  // throw new CannotCompileException(e);
  // }
  // catch (IOException e) {
  // throw new CannotCompileException(e);
  // }
  // }

  @Override
  public boolean stopPruning(boolean stop) {
    boolean prev = !doPruning;
    doPruning = !stop;
    return prev;
  }

  // private void modifyClassConstructor(ClassFile cf)
  // throws CannotCompileException, NotFoundException
  // {
  // if (fieldInitializers == null)
  // return;
  //
  // Bytecode code = new Bytecode(cf.getConstPool(), 0, 0);
  // Javac jv = new Javac(code, this);
  // int stacksize = 0;
  // boolean doInit = false;
  // for (FieldInitLink fi = fieldInitializers; fi != null; fi = fi.next) {
  // CtField f = fi.field;
  // if (AccessFlagisStatic(f.getModifiers())) {
  // doInit = true;
  // int s = fi.init.compileIfStatic(f.getType(), f.getName(),
  // code, jv);
  // if (stacksize < s)
  // stacksize = s;
  // }
  // }
  //
  // if (doInit) // need an initializer for static fileds.
  // modifyClassConstructor(cf, code, stacksize, 0);
  // }
  //
  // private void modifyClassConstructor(ClassFile cf, Bytecode code,
  // int stacksize, int localsize)
  // throws CannotCompileException
  // {
  // MethodInfo m = cf.getStaticInitializer();
  // if (m == null) {
  // code.add(Bytecode.RETURN);
  // code.setMaxStack(stacksize);
  // code.setMaxLocals(localsize);
  // m = new MethodInfo(cf.getConstPool(), "", "()V");
  // m.setAccessFlags(AccessFlag.STATIC);
  // m.setCodeAttribute(code.toCodeAttribute());
  // cf.addMethod(m);
  // CtMember.Cache cache = hasMemberCache();
  // if (cache != null)
  // cache.addConstructor(new CtConstructor(m, this));
  // }
  // else {
  // CodeAttribute codeAttr = m.getCodeAttribute();
  // if (codeAttr == null)
  // throw new CannotCompileException("empty ");
  //
  // try {
  // CodeIterator it = codeAttr.iterator();
  // int pos = it.insertEx(code.get());
  // it.insert(code.getExceptionTable(), pos);
  // int maxstack = codeAttr.getMaxStack();
  // if (maxstack < stacksize)
  // codeAttr.setMaxStack(stacksize);
  //
  // int maxlocals = codeAttr.getMaxLocals();
  // if (maxlocals < localsize)
  // codeAttr.setMaxLocals(localsize);
  // }
  // catch (BadBytecode e) {
  // throw new CannotCompileException(e);
  // }
  // }
  //
  // try {
  // m.rebuildStackMapIf6(classPool, cf);
  // }
  // catch (BadBytecode e) {
  // throw new CannotCompileException(e);
  // }
  // }
  //
  // private void modifyConstructors(ClassFile cf)
  // throws CannotCompileException, NotFoundException
  // {
  // if (fieldInitializers == null)
  // return;
  //
  // ConstPool cp = cf.getConstPool();
  // List list = cf.getMethods();
  // int n = list.size();
  // for (int i = 0; i < n; ++i) {
  // MethodInfo minfo = (MethodInfo)list.get(i);
  // if (minfo.isConstructor()) {
  // CodeAttribute codeAttr = minfo.getCodeAttribute();
  // if (codeAttr != null)
  // try {
  // Bytecode init = new Bytecode(cp, 0,
  // codeAttr.getMaxLocals());
  // CtClass[] params
  // = Descriptor.getParameterTypes(
  // minfo.getDescriptor(),
  // classPool);
  // int stacksize = makeFieldInitializer(init, params);
  // insertAuxInitializer(codeAttr, init, stacksize);
  // minfo.rebuildStackMapIf6(classPool, cf);
  // }
  // catch (BadBytecode e) {
  // throw new CannotCompileException(e);
  // }
  // }
  // }
  // }
  //
  // private static void insertAuxInitializer(CodeAttribute codeAttr,
  // Bytecode initializer,
  // int stacksize)
  // throws BadBytecode
  // {
  // CodeIterator it = codeAttr.iterator();
  // int index = it.skipSuperConstructor();
  // if (index < 0) {
  // index = it.skipThisConstructor();
  // if (index >= 0)
  // return; // this() is called.
  //
  // // Neither this() or super() is called.
  // }
  //
  // int pos = it.insertEx(initializer.get());
  // it.insert(initializer.getExceptionTable(), pos);
  // int maxstack = codeAttr.getMaxStack();
  // if (maxstack < stacksize)
  // codeAttr.setMaxStack(stacksize);
  // }
  //
  // private int makeFieldInitializer(Bytecode code, CtClass[] parameters)
  // throws CannotCompileException, NotFoundException
  // {
  // int stacksize = 0;
  // Javac jv = new Javac(code, this);
  // try {
  // jv.recordParams(parameters, false);
  // }
  // catch (CompileError e) {
  // throw new CannotCompileException(e);
  // }
  //
  // for (FieldInitLink fi = fieldInitializers; fi != null; fi = fi.next) {
  // CtField f = fi.field;
  // if (!AccessFlagisStatic(f.getModifiers())) {
  // int s = fi.init.compile(f.getType(), f.getName(), code,
  // parameters, jv);
  // if (stacksize < s)
  // stacksize = s;
  // }
  // }
  //
  // return stacksize;
  // }

  // Methods used by CtNewWrappedMethod

  Hashtable getHiddenMethods() {
    if (hiddenMethods == null) {
      hiddenMethods = new Hashtable();
    }

    return hiddenMethods;
  }

  int getUniqueNumber() {
    return uniqueNumberSeed++;
  }

  @Override
  public String makeUniqueName(String prefix) {
    HashMap table = new HashMap();
    makeMemberList(table);
    Set keys = table.keySet();
    String[] methods = new String[keys.size()];
    keys.toArray(methods);

    if (notFindInArray(prefix, methods)) {
      return prefix;
    }

    int i = 100;
    String name;
    do {
      if (i > 999) {
        throw new RuntimeException("too many unique name");
      }

      name = prefix + i++;
    } while (!notFindInArray(name, methods));
    return name;
  }

  private static boolean notFindInArray(String prefix, String[] values) {
    int len = values.length;
    for (int i = 0; i < len; i++) {
      if (values[i].startsWith(prefix)) {
        return false;
      }
    }

    return true;
  }

  private void makeMemberList(HashMap table) {
    int mod = getModifiers();
    if (X_Modifier.isAbstract(mod) || X_Modifier.isInterface(mod)) {
      try {
        CtClass[] ifs = getInterfaces();
        int size = ifs.length;
        for (int i = 0; i < size; i++) {
          CtClass ic = ifs[i];
          if (ic != null && ic instanceof CtClassType) {
            ((CtClassType) ic).makeMemberList(table);
          }
        }
      } catch (NotFoundException e) {
      }
    }

    try {
      CtClass s = getSuperclass();
      if (s != null && s instanceof CtClassType) {
        ((CtClassType) s).makeMemberList(table);
      }
    } catch (NotFoundException e) {
    }

    List list = getClassFile2().getMethods();
    int n = list.size();
    for (int i = 0; i < n; i++) {
      MethodInfo minfo = (MethodInfo) list.get(i);
      table.put(minfo.getName(), this);
    }

    list = getClassFile2().getFields();
    n = list.size();
    for (int i = 0; i < n; i++) {
      FieldInfo finfo = (FieldInfo) list.get(i);
      table.put(finfo.getName(), this);
    }
  }
}

// class FieldInitLink {
// FieldInitLink next;
// CtField field;
// CtField.Initializer init;
//
// FieldInitLink(CtField f, CtField.Initializer i) {
// next = null;
// field = f;
// init = i;
// }
// }