diff options
-rw-r--r-- | Readme.html | 9 | ||||
-rw-r--r-- | src/main/javassist/ClassPool.java | 5 | ||||
-rw-r--r-- | src/main/javassist/CtClass.java | 18 | ||||
-rw-r--r-- | src/main/javassist/CtClassType.java | 503 | ||||
-rw-r--r-- | src/main/javassist/CtConstructor.java | 1 | ||||
-rw-r--r-- | src/main/javassist/CtField.java | 2 | ||||
-rw-r--r-- | src/main/javassist/CtMember.java | 140 | ||||
-rw-r--r-- | src/main/javassist/CtMethod.java | 9 | ||||
-rw-r--r-- | src/main/javassist/CtNewClass.java | 1 | ||||
-rw-r--r-- | src/main/javassist/CtNewWrappedMethod.java | 3 | ||||
-rw-r--r-- | tutorial/tutorial.html | 58 |
11 files changed, 459 insertions, 290 deletions
diff --git a/Readme.html b/Readme.html index a2847fe7..2a20f8ad 100644 --- a/Readme.html +++ b/Readme.html @@ -284,7 +284,14 @@ see javassist.Dump. <p>-version 3.6 <ul> - <li>The stack map table introduced since Java 6 has been supported. + <li>The stack map table introduced since Java 6 has been supported. + <li>CtClass#getDeclaredBehaviors() now returns a class initializer + as well as methods and constructors. + <li>The default status of automatic pruning was made off. + Instead of pruning, this version of Javassist compresses + the data structure of a class file after toBytecode() is called. + The compressed class file is automatically decompressed when needed. + This saves memory space better than pruning. <li><a href="http://jira.jboss.com/jira/browse/JASSIST-33">JIRA JASSIST-33</a> has been fixed. </ul> diff --git a/src/main/javassist/ClassPool.java b/src/main/javassist/ClassPool.java index aff38282..d97d24d1 100644 --- a/src/main/javassist/ClassPool.java +++ b/src/main/javassist/ClassPool.java @@ -111,12 +111,13 @@ public class ClassPool { * are called. The automatic pruning can be turned on/off individually * for each <code>CtClass</code> object. * - * <p>The initial value is true. + * <p>The initial value is false. * * @see CtClass#prune() * @see CtClass#stopPruning(boolean) + * @see CtClass#detach() */ - public static boolean doPruning = true; + public static boolean doPruning = false; /* releaseUnmodifiedClassFile was introduced for avoiding a bug of JBoss AOP. So the value should be true except for JBoss AOP. diff --git a/src/main/javassist/CtClass.java b/src/main/javassist/CtClass.java index 62616c49..5fdd3495 100644 --- a/src/main/javassist/CtClass.java +++ b/src/main/javassist/CtClass.java @@ -52,7 +52,7 @@ public abstract class CtClass { /** * The version number of this release. */ - public static final String version = "3.6.0BETA"; + public static final String version = "3.6.0beta2"; /** * Prints the version number and the copyright notice. @@ -1126,7 +1126,8 @@ public abstract class CtClass { * save memory consumption. * * <p>If <code>ClassPool.doPruning</code> is true, the automatic pruning - * is on by default. Otherwise, it is off. + * is on by default. Otherwise, it is off. The default value of + * <code>ClassPool.doPruning</code> is false. * * @param stop disallow pruning if true. Otherwise, allow. * @return the previous status of pruning. true if pruning is already stopped. @@ -1150,15 +1151,22 @@ public abstract class CtClass { * are still accessible. * * <p><code>toBytecode()</code>, <code>writeFile()</code>, and - * <code>toClass()</code> internally call this method. + * <code>toClass()</code> internally call this method if + * automatic pruning is on. + * + * <p>According to some experiments, pruning does not really reduce + * memory consumption. Only about 20%. Since pruning takes time, + * it might not pay off. So the automatic pruning is off by default. + * + * @see #stopPruning(boolean) + * @see #detach() + * @see ClassPool#doPruning * * @see #toBytecode() * @see #toClass() * @see #writeFile() * @see #instrument(CodeConverter) * @see #instrument(ExprEditor) - * - * @see #stopPruning(boolean) */ public void prune() {} diff --git a/src/main/javassist/CtClassType.java b/src/main/javassist/CtClassType.java index 9e247061..1585423c 100644 --- a/src/main/javassist/CtClassType.java +++ b/src/main/javassist/CtClassType.java @@ -15,7 +15,10 @@ package javassist; +import java.lang.ref.WeakReference; import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; @@ -60,12 +63,9 @@ class CtClassType extends CtClass { boolean wasPruned; boolean memberRemoved; ClassFile classfile; + byte[] rawClassfile; // backup storage - private CtMember fieldsCache; - private CtMember methodsCache; - private CtMember constructorsCache; - private CtConstructor classInitializerCache; - + private WeakReference memberCache; private AccessorMaker accessors; private FieldInitLink fieldInitializers; @@ -76,17 +76,19 @@ class CtClassType extends CtClass { int getCounter; private static int readCounter = 0; private static final int READ_THRESHOLD = 100; // see getClassFile2() + private static final int GET_THRESHOLD = 2; // see releaseClassFiles() CtClassType(String name, ClassPool cp) { super(name); classPool = cp; wasChanged = wasFrozen = wasPruned = memberRemoved = false; classfile = null; + rawClassfile = null; + memberCache = null; accessors = null; fieldInitializers = null; hiddenMethods = null; uniqueNumberSeed = 0; - eraseCache(); getCounter = 0; } @@ -136,38 +138,25 @@ class CtClassType extends CtClass { buffer.append(" extends ??"); } - CtMember field = getFieldsCache(); - buffer.append(" fields="); - while (field != null) { - buffer.append(field); - buffer.append(", "); - field = field.next; - } - - CtMember c = getConstructorsCache(); - buffer.append(" constructors="); - while (c != null) { - buffer.append(c); - buffer.append(", "); - c = c.next; - } + CtMember.Cache memCache = getMembers(); + exToString(buffer, " fields=", + memCache.fieldHead(), memCache.lastField()); + exToString(buffer, " constructors=", + memCache.consHead(), memCache.lastCons()); + exToString(buffer, " methods=", + memCache.methodHead(), memCache.lastMethod()); + } - CtMember m = getMethodsCache(); - buffer.append(" methods="); - while (m != null) { - buffer.append(m); + private void exToString(StringBuffer buffer, String msg, + CtMember head, CtMember tail) { + buffer.append(msg); + while (head != tail) { + head = head.next(); + buffer.append(head); buffer.append(", "); - m = m.next; } } - protected void eraseCache() { - fieldsCache = null; - constructorsCache = null; - classInitializerCache = null; - methodsCache = null; - } - public AccessorMaker getAccessorMaker() { if (accessors == null) accessors = new AccessorMaker(this); @@ -176,15 +165,29 @@ class CtClassType extends CtClass { } public ClassFile getClassFile2() { - if (classfile != null) - return classfile; + ClassFile cfile = classfile; + if (cfile != null) + return cfile; - if (readCounter++ > READ_THRESHOLD - && ClassPool.releaseUnmodifiedClassFile) { + if (readCounter++ > READ_THRESHOLD) { + getCounter += 2; releaseClassFiles(); readCounter = 0; } + if (rawClassfile != null) { + try { + classfile = new ClassFile(new DataInputStream( + new ByteArrayInputStream(rawClassfile))); + rawClassfile = null; + getCounter = GET_THRESHOLD; + return classfile; + } + catch (IOException e) { + throw new RuntimeException(e.toString(), e); + } + } + InputStream fin = null; try { fin = classPool.openClassfile(getName()); @@ -192,19 +195,20 @@ class CtClassType extends CtClass { throw new NotFoundException(getName()); fin = new BufferedInputStream(fin); - classfile = new ClassFile(new DataInputStream(fin)); - if (!classfile.getName().equals(qualifiedName)) + ClassFile cf = new ClassFile(new DataInputStream(fin)); + if (!cf.getName().equals(qualifiedName)) throw new RuntimeException("cannot find " + qualifiedName + ": " - + classfile.getName() + " found in " + + cf.getName() + " found in " + qualifiedName.replace('.', '/') + ".class"); - return classfile; + classfile = cf; + return cf; } catch (NotFoundException e) { - throw new RuntimeException(e.toString()); + throw new RuntimeException(e.toString(), e); } catch (IOException e) { - throw new RuntimeException(e.toString()); + throw new RuntimeException(e.toString(), e); } finally { if (fin != null) @@ -215,14 +219,41 @@ class CtClassType extends CtClass { } } + /** + * Converts a ClassFile object into a byte array + * for saving memory space. + */ + public synchronized void saveClassFile() { + /* getMembers() and releaseClassFile() are also synchronized. + */ + if (classfile == null || hasMemberCache() != 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) {} + } + + public synchronized void releaseClassFile() { + if (!isModified()) + classfile = null; + } + /* Inherited from CtClass. Called by get() in ClassPool. * * @see javassist.CtClass#incGetCounter() + * @see #toBytecode(DataOutputStream) */ void incGetCounter() { ++getCounter; } /** - * Releases the class files and cached CtBehaviors + * Releases the class files * of the CtClasses that have not been recently used * if they are unmodified. */ @@ -232,10 +263,11 @@ class CtClassType extends CtClass { Object obj = e.nextElement(); if (obj instanceof CtClassType) { CtClassType cct = (CtClassType)obj; - if (cct.getCounter < 2 && !cct.isModified()) { - cct.eraseCache(); - cct.classfile = null; - } + if (cct.getCounter < GET_THRESHOLD) + if (!cct.isModified() && ClassPool.releaseUnmodifiedClassFile) + cct.releaseClassFile(); + else if (cct.isFrozen() && !cct.wasPruned) + cct.saveClassFile(); cct.getCounter = 0; } @@ -314,7 +346,7 @@ class CtClassType extends CtClass { ClassFile cf = getClassFile2(); super.setName(name); cf.setName(name); - eraseCache(); + nameReplaced(); classPool.classNameChanged(oldname, this); } @@ -333,7 +365,7 @@ class CtClassType extends CtClass { super.replaceClassName(classnames); ClassFile cf = getClassFile2(); cf.renameClass(classnames); - eraseCache(); + nameReplaced(); if (newClassName != null) { super.setName(newClassName); @@ -350,7 +382,7 @@ class CtClassType extends CtClass { else { super.replaceClassName(oldname, newname); getClassFile2().renameClass(oldname, newname); - eraseCache(); + nameReplaced(); } } @@ -702,6 +734,69 @@ class CtClassType extends CtClass { return c; } + /* flush cached names. + */ + 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; + } + + 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 { + CtConstructor newCons = new CtConstructor(minfo, this); + cache.addConstructor(newCons); + } + } + } + public CtField[] getFields() { ArrayList alist = new ArrayList(); getFields(alist, this); @@ -726,12 +821,13 @@ class CtClassType extends CtClass { } catch (NotFoundException e) {} - CtMember cf = ((CtClassType)cc).getFieldsCache(); - while (cf != null) { - if (!Modifier.isPrivate(cf.getModifiers())) - alist.add(cf); - - cf = cf.next; + CtMember.Cache memCache = ((CtClassType)cc).getMembers(); + CtMember field = memCache.fieldHead(); + CtMember tail = memCache.lastField(); + while (field != tail) { + field = field.next(); + if (!Modifier.isPrivate(field.getModifiers())) + alist.add(field); } } @@ -766,37 +862,20 @@ class CtClassType extends CtClass { } public CtField[] getDeclaredFields() { - CtMember cf = getFieldsCache(); - int num = CtField.count(cf); + 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 (cf != null) { - cfs[i++] = (CtField)cf; - cf = cf.next; + while (field != tail) { + field = field.next(); + cfs[i++] = (CtField)field; } return cfs; } - protected CtMember getFieldsCache() { - if (fieldsCache == null) { - List list = getClassFile2().getFields(); - int n = list.size(); - CtMember allFields = null; - CtField tail = null; - for (int i = 0; i < n; ++i) { - FieldInfo finfo = (FieldInfo)list.get(i); - CtField newTail = new CtField(finfo, this); - allFields = CtMember.append(allFields, tail, newTail); - tail = newTail; - } - - fieldsCache = allFields; - } - - return fieldsCache; - } - public CtField getDeclaredField(String name) throws NotFoundException { CtField f = getDeclaredField2(name); if (f == null) @@ -806,115 +885,131 @@ class CtClassType extends CtClass { } private CtField getDeclaredField2(String name) { - CtMember cf = getFieldsCache(); - while (cf != null) { - if (cf.getName().equals(name)) - return (CtField)cf; - - cf = cf.next; + CtMember.Cache memCache = getMembers(); + CtMember field = memCache.fieldHead(); + CtMember tail = memCache.lastField(); + while (field != tail) { + field = field.next(); + if (field.getName().equals(name)) + return (CtField)field; } return null; } public CtBehavior[] getDeclaredBehaviors() { - CtMember cc = getConstructorsCache(); - CtMember cm = getMethodsCache(); - int num = CtMember.count(cm) + CtMember.count(cc); - CtBehavior[] cb = new CtBehavior[num]; + CtMember.Cache memCache = getMembers(); + CtMember cons = memCache.consHead(); + CtMember consTail = memCache.lastCons(); + int cnum = CtMember.Cache.count(cons, consTail); + CtMember mth = memCache.methodHead(); + CtMember mthTail = memCache.lastMethod(); + int mnum = CtMember.Cache.count(mth, mthTail); + + CtBehavior[] cb = new CtBehavior[cnum + mnum]; int i = 0; - while (cc != null) { - cb[i++] = (CtBehavior)cc; - cc = cc.next; + while (cons != consTail) { + cons = cons.next(); + cb[i++] = (CtBehavior)cons; } - while (cm != null) { - cb[i++] = (CtBehavior)cm; - cm = cm.next; + while (mth != mthTail) { + mth = mth.next(); + cb[i++] = (CtBehavior)mth; } return cb; } public CtConstructor[] getConstructors() { - CtConstructor[] cons = getDeclaredConstructors(); - if (cons.length == 0) - return cons; + CtMember.Cache memCache = getMembers(); + CtMember cons = memCache.consHead(); + CtMember consTail = memCache.lastCons(); int n = 0; - int i = cons.length; - while (--i >= 0) - if (!Modifier.isPrivate(cons[i].getModifiers())) - ++n; + CtMember mem = cons; + while (mem != consTail) { + mem = mem.next(); + if (isPubCons((CtConstructor)mem)) + n++; + } CtConstructor[] result = new CtConstructor[n]; - n = 0; - i = cons.length; - while (--i >= 0) { - CtConstructor c = cons[i]; - if (!Modifier.isPrivate(c.getModifiers())) - result[n++] = c; + int i = 0; + mem = cons; + while (mem != consTail) { + mem = mem.next(); + CtConstructor cc = (CtConstructor)mem; + if (isPubCons(cc)) + result[i++] = cc; } return result; } + private static boolean isPubCons(CtConstructor cons) { + return !Modifier.isPrivate(cons.getModifiers()) + && cons.isConstructor(); + } + public CtConstructor getConstructor(String desc) throws NotFoundException { - CtConstructor cc = (CtConstructor)getConstructorsCache(); - while (cc != null) { - if (cc.getMethodInfo2().getDescriptor().equals(desc)) + 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; - - cc = (CtConstructor)cc.next; } return super.getConstructor(desc); } public CtConstructor[] getDeclaredConstructors() { - CtMember cc = getConstructorsCache(); - int num = CtMember.count(cc); - CtConstructor[] ccs = new CtConstructor[num]; - int i = 0; - while (cc != null) { - ccs[i++] = (CtConstructor)cc; - cc = cc.next; - } - - return ccs; - } - - protected CtMember getConstructorsCache() { - if (constructorsCache == null) { - List list = getClassFile2().getMethods(); - int n = list.size(); - CtMember allConstructors = null; - CtConstructor tail = null; - for (int i = 0; i < n; ++i) { - MethodInfo minfo = (MethodInfo)list.get(i); - if (minfo.isConstructor()) { - CtConstructor newTail = new CtConstructor(minfo, this); - allConstructors = CtMember.append(allConstructors, tail, newTail); - tail = newTail; - } - } + CtMember.Cache memCache = getMembers(); + CtMember cons = memCache.consHead(); + CtMember consTail = memCache.lastCons(); - constructorsCache = allConstructors; + int n = 0; + CtMember mem = cons; + while (mem != consTail) { + mem = mem.next(); + CtConstructor cc = (CtConstructor)mem; + if (cc.isConstructor()) + n++; } - return constructorsCache; + 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; } public CtConstructor getClassInitializer() { - if (classInitializerCache == null) { - MethodInfo minfo = getClassFile2().getStaticInitializer(); - if (minfo != null) - classInitializerCache = new CtConstructor(minfo, this); + 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 classInitializerCache; + return null; } public CtMethod[] getMethods() { @@ -940,12 +1035,14 @@ class CtClassType extends CtClass { catch (NotFoundException e) {} if (cc instanceof CtClassType) { - CtMember cm = ((CtClassType)cc).getMethodsCache(); - while (cm != null) { - if (!Modifier.isPrivate(cm.getModifiers())) - h.put(((CtMethod)cm).getStringRep(), cm); - - cm = cm.next; + CtMember.Cache memCache = ((CtClassType)cc).getMembers(); + CtMember mth = memCache.methodHead(); + CtMember mthTail = memCache.lastMethod(); + + while (mth != mthTail) { + mth = mth.next(); + if (!Modifier.isPrivate(mth.getModifiers())) + h.put(((CtMethod)mth).getStringRep(), mth); } } } @@ -964,13 +1061,15 @@ class CtClassType extends CtClass { private static CtMethod getMethod0(CtClass cc, String name, String desc) { if (cc instanceof CtClassType) { - CtMethod cm = (CtMethod)((CtClassType)cc).getMethodsCache(); - while (cm != null) { - if (cm.getName().equals(name) - && cm.getMethodInfo2().getDescriptor().equals(desc)) - return cm; - - cm = (CtMethod)cm.next; + 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; } } @@ -998,25 +1097,28 @@ class CtClassType extends CtClass { } public CtMethod[] getDeclaredMethods() { - CtMember cm = getMethodsCache(); - int num = CtMember.count(cm); + 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 (cm != null) { - cms[i++] = (CtMethod)cm; - cm = cm.next; + while (mth != mthTail) { + mth = mth.next(); + cms[i++] = (CtMethod)mth; } return cms; } public CtMethod getDeclaredMethod(String name) throws NotFoundException { - CtMember m = getMethodsCache(); - while (m != null) { - if (m.getName().equals(name)) - return (CtMethod)m; - - m = m.next; + 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 " @@ -1027,40 +1129,21 @@ class CtClassType extends CtClass { throws NotFoundException { String desc = Descriptor.ofParameters(params); - CtMethod m = (CtMethod)getMethodsCache(); - while (m != null) { - if (m.getName().equals(name) - && m.getMethodInfo2().getDescriptor().startsWith(desc)) - return m; + CtMember.Cache memCache = getMembers(); + CtMember mth = memCache.methodHead(); + CtMember mthTail = memCache.lastMethod(); - m = (CtMethod)m.next; + 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()); } - protected CtMember getMethodsCache() { - if (methodsCache == null) { - List list = getClassFile2().getMethods(); - int n = list.size(); - CtMember allMethods = null; - CtMethod tail = null; - for (int i = 0; i < n; ++i) { - MethodInfo minfo = (MethodInfo)list.get(i); - if (minfo.isMethod()) { - CtMethod newTail = new CtMethod(minfo, this); - allMethods = CtMember.append(allMethods, tail, newTail); - tail = newTail; - } - } - - methodsCache = allMethods; - } - - return methodsCache; - } - public void addField(CtField f, String init) throws CannotCompileException { @@ -1091,8 +1174,7 @@ class CtClassType extends CtClass { catch (NotFoundException e) {} } - getFieldsCache(); - fieldsCache = CtField.append(fieldsCache, f); + getMembers().addField(f); getClassFile2().addField(f.getFieldInfo2()); if (init != null) { @@ -1114,7 +1196,7 @@ class CtClassType extends CtClass { FieldInfo fi = f.getFieldInfo2(); ClassFile cf = getClassFile2(); if (cf.getFields().remove(fi)) { - fieldsCache = CtMember.remove(fieldsCache, f); + getMembers().remove(f); memberRemoved = true; } else @@ -1142,8 +1224,7 @@ class CtClassType extends CtClass { if (c.getDeclaringClass() != this) throw new CannotCompileException("cannot add"); - getConstructorsCache(); - constructorsCache = (CtConstructor)CtMember.append(constructorsCache, c); + getMembers().addConstructor(c); getClassFile2().addMethod(c.getMethodInfo2()); } @@ -1152,7 +1233,7 @@ class CtClassType extends CtClass { MethodInfo mi = m.getMethodInfo2(); ClassFile cf = getClassFile2(); if (cf.getMethods().remove(mi)) { - constructorsCache = CtMember.remove(constructorsCache, m); + getMembers().remove(m); memberRemoved = true; } else @@ -1164,8 +1245,7 @@ class CtClassType extends CtClass { if (m.getDeclaringClass() != this) throw new CannotCompileException("cannot add"); - getMethodsCache(); - methodsCache = CtMember.append(methodsCache, m); + getMembers().addMethod(m); getClassFile2().addMethod(m.getMethodInfo2()); if ((m.getModifiers() & Modifier.ABSTRACT) != 0) setModifiers(getModifiers() | Modifier.ABSTRACT); @@ -1176,7 +1256,7 @@ class CtClassType extends CtClass { MethodInfo mi = m.getMethodInfo2(); ClassFile cf = getClassFile2(); if (cf.getMethods().remove(mi)) { - methodsCache = CtMember.remove(methodsCache, m); + getMembers().remove(m); memberRemoved = true; } else @@ -1262,10 +1342,10 @@ class CtClassType extends CtClass { else { classPool.writeClassfile(getName(), out); // to save memory - eraseCache(); - classfile = null; + // classfile = null; } + getCounter = 0; wasFrozen = true; } catch (NotFoundException e) { @@ -1328,6 +1408,9 @@ class CtClassType extends CtClass { 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(); diff --git a/src/main/javassist/CtConstructor.java b/src/main/javassist/CtConstructor.java index 25d25275..54c89669 100644 --- a/src/main/javassist/CtConstructor.java +++ b/src/main/javassist/CtConstructor.java @@ -35,7 +35,6 @@ import javassist.compiler.CompileError; public final class CtConstructor extends CtBehavior { protected CtConstructor(MethodInfo minfo, CtClass declaring) { super(declaring, minfo); - next = null; } /** diff --git a/src/main/javassist/CtField.java b/src/main/javassist/CtField.java index cbc50b8b..ce9c6ddd 100644 --- a/src/main/javassist/CtField.java +++ b/src/main/javassist/CtField.java @@ -97,7 +97,6 @@ public class CtField extends CtMember { throws CannotCompileException { super(clazz); - next = null; ClassFile cf = clazz.getClassFile2(); if (cf == null) throw new CannotCompileException("bad declaring class: " @@ -109,7 +108,6 @@ public class CtField extends CtMember { CtField(FieldInfo fi, CtClass clazz) { super(clazz); fieldInfo = fi; - next = null; } /** diff --git a/src/main/javassist/CtMember.java b/src/main/javassist/CtMember.java index 2f9d8ff0..2fde6cee 100644 --- a/src/main/javassist/CtMember.java +++ b/src/main/javassist/CtMember.java @@ -20,64 +20,120 @@ package javassist; * or a method. */ public abstract class CtMember { - protected CtMember next; // for internal use + CtMember next; // for internal use protected CtClass declaringClass; - protected CtMember(CtClass clazz) { declaringClass = clazz; } + /* Make a circular link of CtMembers declared in the + * same class so that they are garbage-collected together + * at the same time. + */ + static class Cache extends CtMember { + protected void extendToString(StringBuffer buffer) {} + public Object[] getAnnotations() + throws ClassNotFoundException { return null; } + public byte[] getAttribute(String name) { return null; } + public Object[] getAvailableAnnotations() + throws ClassNotFoundException { return null; } + public int getModifiers() { return 0; } + public String getName() { return null; } + public String getSignature() { return null; } + public void setAttribute(String name, byte[] data) {} + public void setModifiers(int mod) {} + + private CtMember methodTail; + private CtMember consTail; // constructor tail + private CtMember fieldTail; - static CtMember append(CtMember list, CtMember previousTail, CtMember tail) { - tail.next = null; - if (list == null) - return tail; - else { - previousTail.next = tail; - return list; + Cache(CtClassType decl) { + super(decl); + methodTail = this; + consTail = this; + fieldTail = this; + fieldTail.next = this; } - } - static CtMember append(CtMember list, CtMember tail) { - tail.next = null; - if (list == null) - return tail; - else { - CtMember lst = list; - while (lst.next != null) - lst = lst.next; - - lst.next = tail; - return list; + CtMember methodHead() { return this; } + CtMember lastMethod() { return methodTail; } + CtMember consHead() { return methodTail; } // may include a static initializer + CtMember lastCons() { return consTail; } + CtMember fieldHead() { return consTail; } + CtMember lastField() { return fieldTail; } + + void addMethod(CtMember method) { + method.next = methodTail.next; + methodTail.next = method; + if (methodTail == consTail) { + consTail = method; + if (methodTail == fieldTail) + fieldTail = method; + } + + methodTail = method; } - } - static int count(CtMember f) { - int n = 0; - while (f != null) { - ++n; - f = f.next; + /* Both constructors and a class initializer. + */ + void addConstructor(CtMember cons) { + cons.next = consTail.next; + consTail.next = cons; + if (consTail == fieldTail) + fieldTail = cons; + + consTail = cons; } - return n; - } + void addField(CtMember field) { + field.next = this; // or fieldTail.next + fieldTail.next = field; + fieldTail = field; + } + + static int count(CtMember head, CtMember tail) { + int n = 0; + while (head != tail) { + n++; + head = head.next; + } + + return n; + } + + void remove(CtMember mem) { + CtMember m = this; + CtMember node; + while ((node = m.next) != this) { + if (node == mem) { + m.next = node.next; + if (node == methodTail) + methodTail = m; + else if (node == consTail) + consTail = m; + else if (node == fieldTail) + fieldTail = m; - static CtMember remove(CtMember list, CtMember m) { - CtMember top = list; - if (list == null) - return null; - else if (list == m) - return list.next; - else - while (list.next != null) { - if (list.next == m) { - list.next = list.next.next; break; } - - list = list.next; + else + m = m.next; } + } + } - return top; + protected CtMember(CtClass clazz) { + declaringClass = clazz; + next = null; } + final CtMember next() { return next; } + + /** + * This method is invoked when setName() or replaceClassName() + * in CtClass is called. + * + * @see CtMethod#nameReplaced() + */ + void nameReplaced() {} + public String toString() { StringBuffer buffer = new StringBuffer(getClass().getName()); buffer.append("@"); diff --git a/src/main/javassist/CtMethod.java b/src/main/javassist/CtMethod.java index 61c29713..158f21af 100644 --- a/src/main/javassist/CtMethod.java +++ b/src/main/javassist/CtMethod.java @@ -32,7 +32,6 @@ public final class CtMethod extends CtBehavior { CtMethod(MethodInfo minfo, CtClass declaring) { super(declaring, minfo); - next = null; cachedStringRep = null; } @@ -135,6 +134,14 @@ public final class CtMethod extends CtBehavior { return getStringRep().hashCode(); } + /** + * This method is invoked when setName() or replaceClassName() + * in CtClass is called. + */ + void nameReplaced() { + cachedStringRep = null; + } + /* This method is also called by CtClassType.getMethods0(). */ final String getStringRep() { diff --git a/src/main/javassist/CtNewClass.java b/src/main/javassist/CtNewClass.java index 3a2ee385..12b18465 100644 --- a/src/main/javassist/CtNewClass.java +++ b/src/main/javassist/CtNewClass.java @@ -28,7 +28,6 @@ class CtNewClass extends CtClassType { boolean isInterface, CtClass superclass) { super(name, cp); wasChanged = true; - eraseCache(); String superName; if (superclass == null) superName = null; diff --git a/src/main/javassist/CtNewWrappedMethod.java b/src/main/javassist/CtNewWrappedMethod.java index 5562e064..45916de9 100644 --- a/src/main/javassist/CtNewWrappedMethod.java +++ b/src/main/javassist/CtNewWrappedMethod.java @@ -152,6 +152,9 @@ class CtNewWrappedMethod { // a stack map is copied. rebuilding it is not needed. classfile.addMethod(body); bodies.put(src, bodyname); + CtMember.Cache cache = clazz.hasMemberCache(); + if (cache != null) + cache.addMethod(new CtMethod(body, clazz)); } return bodyname; diff --git a/tutorial/tutorial.html b/tutorial/tutorial.html index 097d695a..b6876d4c 100644 --- a/tutorial/tutorial.html +++ b/tutorial/tutorial.html @@ -140,19 +140,37 @@ not permitted. This is for warning the developers when they attempt to modify a class file that has been already loaded since the JVM does not allow reloading a class. -<p>When Javassist freezes a <code>CtClass</code> object, it also -prunes the data structure contained in that object. To reduce memory -consumption, Javassist discards unnecessary attributes +<p>A frozen <code>CtClass</code> can be defrost so that +modifications of the class definition will be permitted. For example, + +<ul><pre> +CtClasss cc = ...; + : +cc.writeFile(); +cc.defrost(); +cc.setSuperclass(...); // OK since the class is not frozen. +</pre></ul> + +<p>After <code>defrost()</code> is called, the <code>CtClass</code> +object can be modified again. + +<p>If <code>ClassPool.doPruning</code> is set to <code>true</code>, +then Javassist prunes the data structure contained +in a <code>CtClass</code> object +when Javassist freezes that object. +To reduce memory +consumption, pruning discards unnecessary attributes (<code>attribute_info</code> structures) in that object. For example, <code>Code_attribute</code> structures (method bodies) are discarded. Thus, after a <code>CtClass</code> object is pruned, the bytecode of a method is not -accessible although method names, signatures, and annotations -are still accessible. +accessible except method names, signatures, and annotations. +The pruned <code>CtClass</code> object cannot be defrost again. +The default value of <code>ClassPool.doPruning</code> is <code>false</code>. -<p>To disallow pruning a <code>CtClass</code>, <code>stopPruning()</code> -must be called in advance: +<p>To disallow pruning a particular <code>CtClass</code>, +<code>stopPruning()</code> must be called on that object in advance: <ul><pre> CtClasss cc = ...; @@ -162,29 +180,19 @@ cc.writeFile(); // convert to a class file. // cc is not pruned. </pre></ul> -<p>If a <code>CtClass</code> is not pruned, it can be defrost so that -modifications of the class definition can be permitted. For example, - -<ul><pre> -CtClasss cc = ...; -cc.stopPruning(true); - : -cc.writeFile(); -cc.defrost(); -cc.setSuperclass(...); // OK since the class is not frozen. -</pre></ul> - -<p>To disallow pruning for all the <code>CtClass</code>es, set -<code>ClassPool.doPruning</code> to <code>false</code>. +<p>The <code>CtClass</code> object <code>cc</code> is not pruned. +Thus it can be defrost after <code>writeFile()</code> is called. <ul><b>Note:</b> -While debugging, you might want to temporarily stop pruning and write a modified -class file to a disk drive. <code>debugWriteFile()</code> is a convenient method -for that purpose. It stops pruning, write a class file, defrost it, and turn -pruning on again (if it was initially on). +While debugging, you might want to temporarily stop pruning and freezing +and write a modified class file to a disk drive. +<code>debugWriteFile()</code> is a convenient method +for that purpose. It stops pruning, writes a class file, defrosts it, +and turns pruning on again (if it was initially on). </ul> + <h4>Class search path</h4> <p>The default <code>ClassPool</code> returned |