diff options
166 files changed, 33356 insertions, 0 deletions
diff --git a/License.html b/License.html new file mode 100644 index 00000000..dda30cc9 --- /dev/null +++ b/License.html @@ -0,0 +1,18 @@ +<HTML>
+<HEAD>
+<TITLE>Javassist License</TITLE>
+</HEAD>
+
+<BODY text=#000000 vLink=#551a8b aLink=#ff0000 link=#0000ee bgColor=#ffffff>
+<p>
+Javassist, a Java-bytecode translator toolkit.
+Copyright (C) 1999-2003 Shigeru Chiba. All Rights Reserved.
+</p>
+<p>
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+</p>
+</BODY>
+</HTML>
diff --git a/Readme.html b/Readme.html new file mode 100644 index 00000000..cfb73123 --- /dev/null +++ b/Readme.html @@ -0,0 +1,453 @@ +<html>
+<HEAD>
+ <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
+ <TITLE>Read Me First</TITLE>
+</HEAD>
+<body>
+
+<h1>Javassist version 2</h1>
+
+<h3>in February, 2003.
+<br>Copyright (C) 2000-2003 by Shigeru Chiba, All rights reserved.</h3>
+
+<p><br></p>
+
+<p>Javassist (JAVA programming ASSISTant) is yet another reflective
+system for Java. It is a class library for editing bytecodes in Java;
+it enables Java programs to define a new class at runtime and to
+modify a class file when the JVM loads it. Unlike other
+similar bytecode editors, Javassist provides two levels of API:
+source level and bytecode level. If the users use the source-level
+API, they can edit a class file without knowledge of the specifications
+of the Java bytecode. The whole API is designed with only the vocabulary
+of the Java language. On the other hand, the bytecode-level API allows
+the users to directly edit a class file.
+
+<p><br>
+
+<h2>Files</h2>
+
+<ul>
+<table>
+<tr>
+<td><li><tt><a href="License.html">License.html</a></tt></td>
+<td>License file</td>
+</tr>
+
+<tr>
+<td><li><tt><a href="tutorial/tutorial.html">tutorial/tutorial.html</a></tt></td>
+<td>Tutorial</td>
+</tr>
+
+<tr>
+<td><li><tt>./javassist.jar</tt></td>
+<td>The Javassist jar file (class files)</td>
+</tr>
+
+<tr>
+<td><li><tt>./javassist-src.zip</tt></td>
+<td>The source archive</td>
+</tr>
+
+<tr>
+<td><li><tt><a href="html/index.html">html/index.html</a></tt></tr>
+<td>The top page of the Javassist API document.</td>
+</tr>
+
+<tr>
+<td><li><tt>./sample/</tt></td>
+<td>Sample programs</td>
+</tr>
+</table>
+</ul>
+
+<p>To extract source files from the archive, use the jar command:<br>
+
+<ul><pre>% jar xvf javassist-src.zip</pre></ul>
+
+
+<p><br>
+
+<h2>How to run sample programs</h2>
+
+<p>JDK 1.2.2 or later is needed.
+
+<h3>1. Move to the directory where this Readme.html file is located.</h3>
+
+<p>In the description below, we assume that the platform is JDK 1.2
+(or later) for Solaris. If the platform is JDK 1.2 (or later) for
+Windows, the class-path option is:
+
+<ul><pre>
+-classpath ".;javassist.jar"
+</pre></ul>
+
+<p>If you don't want to put the class-path option, copy
+<tt>./javassist.jar</tt> to
+
+<ul><<i>java-home</i>><tt>/jre/lib/ext/</tt>.</ul>
+
+<p><<i>java-home</i>> depends on the system. It is usually
+<tt>/usr/local/java</tt>, <tt>c:\jdk1.2\</tt>, etc.
+
+<p>If you don't use javassist.jar, first compile Javassist:
+<ul><pre>
+% cd src
+% javac -d .. javassist/*.java javassist/*/*.java
+% cd ..
+</pre></ul>
+
+<p>Then you can compile and run the sample programs without the class-path
+option.
+
+<h3>2. sample/Test.java</h3>
+
+<p> This is a very simple program using Javassist.
+
+<p> To run, type the commands:
+
+<ul><pre>
+% javac -classpath ".:javassist.jar" sample/Test.java
+% java -cp ".:javassist.jar" sample.Test
+</pre></ul>
+
+<p> For more details, see <a type="text/plain" href="sample/Test.java">sample/Test.java</a>
+
+<h3>3. sample/reflect/*.java</h3>
+
+<p> This is the "verbose metaobject" example well known in reflective
+ programming. This program dynamically attaches a metaobject to
+ a Person object. The metaobject prints a message if a method
+ is called on the Person object.
+
+<p> To run, type the commands:
+
+<ul><pre>
+% javac -classpath ".:javassist.jar" sample/reflect/*.java
+% java -cp ".:javassist.jar" javassist.reflect.Loader sample.reflect.Main Joe
+</pre></ul>
+
+<p>Compare this result with that of the regular execution without reflection:
+
+<ul><pre>% java -cp ".:javassist.jar" sample.reflect.Person Joe</pre></ul>
+
+<p> For more details, see <a type="text/plain" href="sample/reflect/Main.java">sample/reflect/Main.java</a>
+
+<p> Furthermore, the Person class can be statically modified so that
+ all the Person objects become reflective without sample.reflect.Main.
+ To do this, type the commands:
+
+<ul><pre>
+% java -cp ".:javassist.jar" javassist.reflect.Compiler sample.reflect.Person -m sample.reflect.VerboseMetaobj
+</pre></ul>
+
+<p> Then,
+<ul><pre>
+% java -cp ".:javassist.jar" sample.reflect.Person Joe
+</pre></ul>
+
+<h3>4. sample/duplicate/*.java</h3>
+
+<p> This is another example of reflective programming.
+
+<p> To run, type the commands:
+
+<ul><pre>
+% javac -classpath ".:javassist.jar" sample/duplicate/*.java
+% java -cp ".:javassist.jar" sample.duplicate.Main
+</pre></ul>
+
+<p>Compare this result with that of the regular execution without reflection:
+
+<ul><pre>% java sample.duplicate.Viewer</pre></ul>
+
+<p>For more details, see
+<a type="text/plain" href="sample/duplicate/Main.java">sample/duplicate/Main.java</a>
+
+<h3>5. sample/vector/*.java</h3>
+
+<p>This example shows the use of Javassit for producing a class representing
+a vector of a given type at compile time.
+This is a demonstration of the use of <tt>javassist.preproc</tt> package.
+
+<p> To run, type the commands:
+<ul><pre>
+% javac -classpath ".:javassist.jar" sample/vector/*.java
+% java -cp ".:javassist.jar" javassist.preproc.Compiler sample/vector/Test.j
+% javac sample/vector/Test.java
+% java sample.vector.Test
+</pre></ul>
+
+<p>For more details, see
+<a type="text/plain" href="sample/vector/Test.j">sample/vector/Test.j</a>
+and <a type="text/plain" href="sample/vector/VectorAssistant.java">sample/vector/VectorAssistant.java</a>
+
+<h3>6. sample/rmi/*.java</h3>
+
+<p> This demonstrates the javassist.rmi package.
+
+<p> To run, type the commands:
+<ul><pre>
+% javac -classpath ".:javassist.jar" sample/rmi/*.java
+% java -cp ".:javassist.jar" sample.rmi.Counter 5001
+</pre></ul>
+
+<p> The second line starts a web server listening to port 5001.
+
+<p> Then, open <a href="sample/rmi/webdemo.html">sample/rmi/webdemo.html</a>
+with a web browser running
+ on the local host. (<tt>webdemo.html</tt> trys to fetch an applet from
+ <tt>http://localhost:5001/</tt>, which is the web server we started above.)
+
+<p> Otherwise, run sample.rmi.CountApplet as an application:
+
+<ul><pre>
+% java -cp ".:javassist.jar" javassist.web.Viewer localhost 5001 sample.rmi.CountApplet
+</pre></ul>
+
+<h3>7. sample/evolve/*.java</h3>
+
+<p> This is a demonstration of the class evolution mechanism implemented
+ with Javassist. This mechanism enables a Java program to reload an
+ existing class file under some restriction.
+
+<p> To run, type the commands:
+<ul><pre>
+% javac -classpath ".:javassist.jar" sample/evolve/*.java
+% java -cp ".:javassist.jar" sample.evolve.DemoLoader 5003
+</pre></ul>
+
+<p> The second line starts a class loader DemoLoader, which runs a web
+ server DemoServer listening to port 5003.
+
+<p> Then, open <a href="http://localhost:5003/demo.html">http://localhost:5003/demo.html</a> with a web browser running
+ on the local host.
+(Or, see <a href="sample/evolve/start.html">sample/evolve/start.html</a>.)
+
+<h3>8. Hints</h3>
+
+<p>Javassist provides a class file viewer for debugging. For more details,
+see javassist.Dump.
+
+<p><br>
+
+<h2>Changes</h2>
+
+<p>- version 2.4 in February, 2003.
+<ul>
+ <li>The compiler included in Javassist did not correctly work with
+ interface methods. This bug was fixed.
+ <li>Now javassist.bytecode.Bytecode allows more than 255 local
+ variables in the same method.
+ <li>javassist.expr.Instanceof and Cast have been added.
+ <li>javassist.expr.{MethodCall,NewExpr,FieldAccess,Instanceof,Cast}.where()
+ have been added. They return the caller-side method surrounding the
+ expression.
+ <li>javassist.expr.{MethodCall,NewExpr,FieldAccess,Instanceof,Cast}.mayThrow()
+ have been added.
+ <li>$class has been introduced.
+ <li>The parameters to replaceFieldRead(), replaceFieldWrite(),
+ and redirectFieldAccess() in javassist.CodeConverter are changed.
+ <li>The compiler could not correctly handle a try-catch statement.
+ This bug has been fixed.
+</ul>
+
+<p>- version 2.3 in December, 2002.
+<ul>
+ <li>The tutorial has been revised a bit.
+ <li>SerialVersionUID class was donated by Bob Lee. Thanks.
+ <li>CtMethod.setBody() and CtConstructor.setBody() have been added.
+ <li>javassist.reflect.ClassMetaobject.useContextClassLoader has been added.
+ If true, the reflection package does not use Class.forName() but uses
+ a context class loader specified by the user.
+ <li>$sig and $type are now available.
+ <li>Bugs in Bytecode.write() and read() have been fixed.
+</ul>
+
+<p>- version 2.2 in October, 2002.
+<ul>
+ <li>The tutorial has been revised.
+ <li>A new package <code>javassist.expr</code> has been added.
+ This is replacement of classic <code>CodeConverter</code>.
+ <li>javassist.ConstParameter was changed into
+ javassist.CtMethod.ConstParameter.
+ <li>javassist.FieldInitializer was renamed into
+ javassist.CtField.Initializer.
+ <li>A bug in javassist.bytecode.Bytecode.addInvokeinterface() has been
+ fixed.
+ <li>In javassist.bytecode.Bytecode, addGetfield(), addGetstatic(),
+ addInvokespecial(), addInvokestatic(), addInvokevirtual(),
+ and addInvokeinterface()
+ have been modified to update the current statck depth.
+</ul>
+
+<p>- version 2.1 in July, 2002.
+<ul>
+ <li>javassist.CtMember and javassist.CtBehavior have been added.
+ <li>javassist.CtClass.toBytecode() has been added.
+ <li>javassist.CtClass.toClass() and javassist.ClassPool.writeAsClass()
+ has been added.
+ <li>javassist.ByteArrayClassPath has been added.
+ <li>javassist.bytecode.Mnemonic has been added.
+ <li>Several bugs have been fixed.
+</ul>
+
+<p>- version 2.0 (major update) in November, 2001.
+<ul>
+ <li>The javassist.bytecode package has been provided. It is a
+ lower-level API for directly modifying a class file although
+ the users must have detailed knowledge of the Java bytecode.
+
+ <li>The mechanism for creating CtClass objects have been changed.
+
+ <li>javassist.tool.Dump moves to the javassist package.
+</ul>
+
+<p>version 1.0 in July, 2001.
+<ul>
+ <li>javassist.reflect.Metaobject and ClassMetaobject was changed.
+ Now they throw the same exception that they receive from a
+ base-level object.
+</ul>
+
+<p>- version 0.8
+<ul>
+ <li>javassist.tool.Dump was added. It is a class file viewer.
+
+ <li>javassist.FiledInitializer.byNewArray() was added. It is for
+ initializing a field with an array object.
+
+ <li>javassist.CodeConverter.redirectMethodCall() was added.
+
+ <li>javassist.Run was added.
+</ul>
+
+<p>- version 0.7
+<ul>
+ <li>javassit.Loader was largely modified. javassist.UserLoader was
+ deleted. Instead, Codebase was renamed to ClassPath
+ and UserClassPath was added. Now programmers who want to
+ customize Loader must write a class implementing UserClassPath
+ instead of UserLoader. This change is for sharing class search paths
+ between Loader and CtClass.CtClass(String).
+
+ <li>CtClass.addField(), addMethod(), addConstructor(), addWrapper() were
+ also largely modified so that it receives CtNewMethod, CtNewConstructor,
+ or CtNewField. The static methods for creating these objects were
+ added to the API.
+
+ <li>Constructors are now represented by CtConstructor objects.
+ CtConstructor is a subclass of CtMethod.
+
+ <li>CtClass.getUserAttribute() was removed. Use CtClass.getAttribute().
+
+ <li>javassist.rmi.RmiLoader was added.
+
+ <li>javassist.reflect.Metalevel._setMetaobject() was added. Now
+ metaobjects can be replaced at runtime.
+</ul>
+
+<p>- version 0.6
+<ul>
+ <li>Javassist was modified to correctly deal with array types appearing
+ in signatures.
+
+ <li>A bug crashed resulting bytecode if a class includes a private static
+ filed. It has been fixed.
+
+ <li>javassist.CtNewInterface was added.
+
+ <li>javassist.Loader.recordClass() was renamed into makeClass().
+
+ <li>javassist.UserLoader.loadClass() was changed to take the second
+ parameter.
+</ul>
+
+<p>- version 0.5
+<ul>
+ <li>a bug-fix version.
+</ul>
+
+<p>- version 0.4
+<ul>
+ <li>Major update again. Many classes and methods were changed.
+ Most of methods taking java.lang.Class have been changed to
+ take javassist.CtClass.
+</ul>
+
+<p>- version 0.3
+<ul>
+ <li>Major update. Many classes and methods were changed.
+</ul>
+
+<p>- version 0.2
+<ul>
+ <li>Jar/zip files are supported.
+</ul>
+
+<p>-version 0.1 in April, 1999.
+<ul>
+ <li>The first release.
+</ul>
+
+<p><br>
+
+<h2>Bug reports etc.</h2>
+
+<dl>
+<dt>Bug reports:
+<dd><tt><a href="mailto:chiba@acm.org">chiba@acm.org</a></tt>
+<br>or
+<tt><a href="mailto:chiba@is.titech.ac.jp">chiba@is.titech.ac.jp</a></tt>
+<br>
+
+<p><dt>The home page of Javassist:
+<dd><tt><a href="http://www.csg.is.titech.ac.jp/~chiba/javassist">http://www.csg.is.titech.ac.jp/~chiba/javassist</a></tt>
+
+<p><dt>The Javassist mailing list:
+<dd><tt><a href="javassist@csg.is.titech.ac.jp">javassist@csg.is.titech.ac.jp</a></tt>
+</dl>
+
+<p><br>
+
+<h2>Copyright notices</h2>
+
+<p>This software is subject to the <a href="license.html">Mozilla Public
+License Version 1.1</a>.
+
+<p>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.
+
+<p>The Original Code is Javassist.
+
+<p>The Initial Developer of the Original Code is Shigeru Chiba.
+<br>Portions
+created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba.
+All Rights Reserved.
+
+<p><br>
+
+<h2>Acknowledgments</h2>
+
+<p>The development of this software is sponsored in part by the PRESTO
+program (Sakigake Kenkyu 21) of <a href="http://www.jst.go.jp/">Japan
+Science and Technology Corporation</a>.
+
+<p>I'd like to thank Michiaki Tatsubori, Johan Cloetens,
+Philip Tomlinson, Alex Villazon, Pascal Rapicault, Dan HE, Eric Tanter,
+Michael Haupt, Toshiyuki Sasaki, Renaud Pawlak, Luc Bourlier,
+Eric Bui, Lewis Stiller, Susumu Yamazaki, Rodrigo Teruo Tomita,
+Marc Segura-Devillechaise, Jan Baudisch, Julien Blass, Yoshiki Sato,
+Fabian Crabus, Bo Norregaard Jorgensen, Bob Lee, Bill Burke,
+Remy Sanlaville, Muga Nishizawa, Alexey Loubyansky, Saori Oki,
+Andreas Salathe, and Dante Torres estrada for their contributions.
+
+<p><br>
+
+<hr>
+<a href="http://www.is.titech.ac.jp/~chiba">Shigeru Chiba</a>
+(Email: <tt>chiba@is.titech.ac.jp</tt>)
+<br>Dept. of Math. and Computing Sciences,
+<a href="http://www.titech.ac.jp">Tokyo Institute of Technology</a>
diff --git a/build.xml b/build.xml new file mode 100644 index 00000000..5d57a3a3 --- /dev/null +++ b/build.xml @@ -0,0 +1,85 @@ +<?xml version="1.0"?>
+
+<!-- ======================================================================= -->
+<!-- JBoss build file -->
+<!-- ======================================================================= -->
+
+<project name="javassist" default="jar" basedir=".">
+
+ <property environment="env"/>
+ <property name="src.dir" value="${basedir}/src/main"/>
+ <property name="build.dir" value="${basedir}/build"/>
+ <property name="build.classes.dir" value="${build.dir}/classes"/>
+
+ <!-- Build classpath -->
+ <path id="classpath">
+ <pathelement location="${build.classes.dir}"/>
+ </path>
+
+ <property name="build.classpath" refid="classpath"/>
+
+ <!-- =================================================================== -->
+ <!-- Prepares the build directory -->
+ <!-- =================================================================== -->
+ <target name="prepare" >
+ <mkdir dir="${build.dir}"/>
+ <mkdir dir="${build.classes.dir}"/>
+ </target>
+
+ <!-- =================================================================== -->
+ <!-- Compiles the source code -->
+ <!-- =================================================================== -->
+ <target name="compile" depends="prepare">
+ <javac srcdir="${src.dir}"
+ destdir="${build.classes.dir}"
+ debug="on"
+ deprecation="on"
+ optimize="off"
+ includes="**">
+ <classpath refid="classpath"/>
+ </javac>
+ </target>
+
+ <target name="jar" depends="compile">
+ <jar jarfile="javassist.jar">
+ <fileset dir="${build.classes.dir}">
+ <include name="**/*.class"/>
+ </fileset>
+ </jar>
+ </target>
+
+ <target name="javadocs">
+ <javadoc packagenames="javassist.*"
+ sourcepath="src/main"
+ defaultexcludes="yes"
+ destdir="html"
+ author="true"
+ version="true"
+ use="true"
+ windowtitle="Javassist API">
+ <doctitle><![CDATA[<h1>Javassist</h1>]]></doctitle>
+ <bottom><![CDATA[<i>Javassist, a Java-bytecode translator toolkit.
+Copyright (C) 1999-2003 Shigeru Chiba. All Rights Reserved.</i>]]></bottom>
+ </javadoc>
+ </target>
+
+
+ <target name="dist" depends="jar,javadocs">
+ <delete file="javassist-dist.zip"/>
+ <zip zipfile="javassist-dist.zip">
+ <fileset dir="${basedir}">
+ <include name="**"/>
+ <exclude name="build/**"/>
+ <exclude name="javassist-dist.zip"/>
+ </fileset>
+ </zip>
+ </target>
+
+ <target name="clean">
+ <delete dir="build"/>
+ <delete dir="html"/>
+ <delete file="javassist.jar"/>
+ <delete file="javassist-dist.zip"/>
+ </target>
+</project>
+
diff --git a/sample/Test.java b/sample/Test.java new file mode 100644 index 00000000..0692943f --- /dev/null +++ b/sample/Test.java @@ -0,0 +1,44 @@ +package sample;
+
+import javassist.*;
+
+/*
+ A very simple sample program
+
+ This program overwrites sample/Test.class (the class file of this
+ class itself) for adding a method g(). If the method g() is not
+ defined in class Test, then this program adds a copy of
+ f() to the class Test with name g(). Otherwise, this program does
+ not modify sample/Test.class at all.
+
+ To see the modified class definition, execute:
+
+ % javap sample.Test
+
+ after running this program.
+*/
+public class Test {
+ public int f(int i) {
+ return i + 1;
+ }
+
+ public static void main(String[] args) throws Exception {
+ ClassPool pool = ClassPool.getDefault(null);
+
+ CtClass cc = pool.get("sample.Test");
+ try {
+ cc.getDeclaredMethod("g");
+ System.out.println("g() is already defined in sample.Test.");
+ }
+ catch (NotFoundException e) {
+ /* getDeclaredMethod() throws an exception if g()
+ * is not defined in sample.Test.
+ */
+ CtMethod fMethod = cc.getDeclaredMethod("f");
+ CtMethod gMethod = CtNewMethod.copy(fMethod, "g", cc, null);
+ cc.addMethod(gMethod);
+ pool.writeFile("sample.Test"); // update the class file
+ System.out.println("g() was added.");
+ }
+ }
+}
diff --git a/sample/duplicate/Ball.java b/sample/duplicate/Ball.java new file mode 100644 index 00000000..21d6e1cf --- /dev/null +++ b/sample/duplicate/Ball.java @@ -0,0 +1,44 @@ +package sample.duplicate;
+
+import java.awt.Graphics;
+import java.awt.Color;
+
+public class Ball {
+ private int x, y;
+ private Color color;
+ private int radius = 30;
+ private boolean isBackup = false;
+
+ public Ball(int x, int y) {
+ move(x, y);
+ changeColor(Color.orange);
+ }
+
+ // This constructor is for a backup object.
+ public Ball(Ball b) {
+ isBackup = true;
+ }
+
+ // Adjust the position so that the backup object is visible.
+ private void adjust() {
+ if (isBackup) {
+ this.x += 50;
+ this.y += 50;
+ }
+ }
+
+ public void paint(Graphics g) {
+ g.setColor(color);
+ g.fillOval(x, y, radius, radius);
+ }
+
+ public void move(int x, int y) {
+ this.x = x;
+ this.y = y;
+ adjust();
+ }
+
+ public void changeColor(Color color) {
+ this.color = color;
+ }
+}
diff --git a/sample/duplicate/DuplicatedObject.java b/sample/duplicate/DuplicatedObject.java new file mode 100644 index 00000000..5995abcc --- /dev/null +++ b/sample/duplicate/DuplicatedObject.java @@ -0,0 +1,38 @@ +package sample.duplicate;
+
+import javassist.reflect.*;
+
+public class DuplicatedObject extends Metaobject {
+ private DuplicatedObject backup;
+
+ // if a base-level object is created, this metaobject creates
+ // a copy of the base-level object.
+
+ public DuplicatedObject(Object self, Object[] args) {
+ super(self, args);
+ ClassMetaobject clazz = getClassMetaobject();
+ if (clazz.isInstance(args[0]))
+ backup = null; // self is a backup object.
+ else {
+ Object[] args2 = new Object[1];
+ args2[0] = self;
+ try {
+ Metalevel m = (Metalevel)clazz.newInstance(args2);
+ backup = (DuplicatedObject)m._getMetaobject();
+ }
+ catch (CannotCreateException e) {
+ backup = null;
+ }
+ }
+ }
+
+ public Object trapMethodcall(int identifier, Object[] args)
+ throws Throwable
+ {
+ Object obj = super.trapMethodcall(identifier, args);
+ if (backup != null)
+ backup.trapMethodcall(identifier, args);
+
+ return obj;
+ }
+}
diff --git a/sample/duplicate/Main.java b/sample/duplicate/Main.java new file mode 100644 index 00000000..e152a23e --- /dev/null +++ b/sample/duplicate/Main.java @@ -0,0 +1,44 @@ +package sample.duplicate;
+
+/*
+ Runtime metaobject (JDK 1.2 or later only).
+
+ With the javassist.reflect package, the users can attach a metaobject
+ to an object. The metaobject can control the behavior of the object.
+ For example, you can implement fault tolerancy with this ability. One
+ of the implementation techniques of fault tolernacy is to make a copy
+ of every object containing important data and maintain it as a backup.
+ If the machine running the object becomes down, the backup object on a
+ different machine is used to continue the execution.
+
+ To make the copy of the object a real backup, all the method calls to
+ the object must be also sent to that copy. The metaobject is needed
+ for this duplication of the method calls. It traps every method call
+ and invoke the same method on the copy of the object so that the
+ internal state of the copy is kept equivalent to that of the original
+ object.
+
+ First, run sample.duplicate.Viewer without a metaobject.
+
+ % java sample.duplicate.Viewer
+
+ This program shows a ball in a window.
+
+ Then, run the same program with a metaobject, which is an instance
+ of sample.duplicate.DuplicatedObject.
+
+ % java sample.duplicate.Main
+
+ You would see two balls in a window. This is because
+ sample.duplicate.Viewer is loaded by javassist.reflect.Loader so that
+ a metaobject would be attached.
+*/
+public class Main {
+ public static void main(String[] args) throws Throwable {
+ javassist.reflect.Loader cl = new javassist.reflect.Loader();
+ cl.makeReflective("sample.duplicate.Ball",
+ "sample.duplicate.DuplicatedObject",
+ "javassist.reflect.ClassMetaobject");
+ cl.run("sample.duplicate.Viewer", args);
+ }
+}
diff --git a/sample/duplicate/Viewer.java b/sample/duplicate/Viewer.java new file mode 100644 index 00000000..aec13f6b --- /dev/null +++ b/sample/duplicate/Viewer.java @@ -0,0 +1,78 @@ +package sample.duplicate;
+
+import java.applet.*;
+import java.awt.*;
+import java.awt.event.*;
+
+public class Viewer extends Applet
+ implements MouseListener, ActionListener, WindowListener
+{
+ private static final Color[] colorList = {
+ Color.orange, Color.pink, Color.green, Color.blue };
+
+ private Ball ball;
+ private int colorNo;
+
+ public void init() {
+ colorNo = 0;
+ Button b = new Button("change");
+ b.addActionListener(this);
+ add(b);
+
+ addMouseListener(this);
+ }
+
+ public void start() {
+ ball = new Ball(50, 50);
+ ball.changeColor(colorList[0]);
+ }
+
+ public void paint(Graphics g) {
+ ball.paint(g);
+ }
+
+ public void mouseClicked(MouseEvent ev) {
+ ball.move(ev.getX(), ev.getY());
+ repaint();
+ }
+
+ public void mouseEntered(MouseEvent ev) {}
+
+ public void mouseExited(MouseEvent ev) {}
+
+ public void mousePressed(MouseEvent ev) {}
+
+ public void mouseReleased(MouseEvent ev) {}
+
+ public void actionPerformed(ActionEvent e) {
+ ball.changeColor(colorList[++colorNo % colorList.length]);
+ repaint();
+ }
+
+ public void windowOpened(WindowEvent e) {}
+
+ public void windowClosing(WindowEvent e) {
+ System.exit(0);
+ }
+
+ public void windowClosed(WindowEvent e) {}
+
+ public void windowIconified(WindowEvent e) {}
+
+ public void windowDeiconified(WindowEvent e) {}
+
+ public void windowActivated(WindowEvent e) {}
+
+ public void windowDeactivated(WindowEvent e) {}
+
+ public static void main(String[] args) {
+ Frame f = new Frame("Viewer");
+ Viewer view = new Viewer();
+ f.addWindowListener(view);
+ f.add(view);
+ f.setSize(300, 300);
+ view.init();
+ view.start();
+ f.setVisible(true);
+ }
+}
diff --git a/sample/evolve/CannotCreateException.java b/sample/evolve/CannotCreateException.java new file mode 100644 index 00000000..828afb06 --- /dev/null +++ b/sample/evolve/CannotCreateException.java @@ -0,0 +1,14 @@ +package sample.evolve;
+
+/**
+ * Signals that VersionManager.newInstance() fails.
+ */
+public class CannotCreateException extends RuntimeException {
+ public CannotCreateException(String s) {
+ super(s);
+ }
+
+ public CannotCreateException(Exception e) {
+ super("by " + e.toString());
+ }
+}
diff --git a/sample/evolve/CannotUpdateException.java b/sample/evolve/CannotUpdateException.java new file mode 100644 index 00000000..bd28bba6 --- /dev/null +++ b/sample/evolve/CannotUpdateException.java @@ -0,0 +1,14 @@ +package sample.evolve;
+
+/**
+ * Signals that VersionManager.update() fails.
+ */
+public class CannotUpdateException extends Exception {
+ public CannotUpdateException(String msg) {
+ super(msg);
+ }
+
+ public CannotUpdateException(Exception e) {
+ super("by " + e.toString());
+ }
+}
diff --git a/sample/evolve/DemoLoader.java b/sample/evolve/DemoLoader.java new file mode 100644 index 00000000..5fa950da --- /dev/null +++ b/sample/evolve/DemoLoader.java @@ -0,0 +1,40 @@ +package sample.evolve;
+
+import javassist.*;
+import java.io.*;
+import java.util.Hashtable;
+
+/**
+ * DemoLoader is a class loader for running a program including
+ * an updatable class. This simple loader allows only a single
+ * class to be updatable. (Extending it for supporting multiple
+ * updatable classes is easy.)
+ *
+ * To run, type as follows:
+ *
+ * % java sample.evolve.DemoLoader <port number>
+ *
+ * Then DemoLoader launches sample.evolve.DemoServer with <port number>.
+ * It also translates sample.evolve.WebPage, which sample.evolve.DemoServer
+ * uses, so that it is an updable class.
+ *
+ * Note: JDK 1.2 or later only.
+ */
+public class DemoLoader {
+ private static final int initialVersion = 0;
+ private String updatableClassName = null;
+ private CtClass updatableClass = null;
+
+ /* Creates a <code>DemoLoader</code> for making class WebPage
+ * updatable. Then it runs main() in sample.evolve.DemoServer.
+ */
+ public static void main(String[] args) throws Throwable {
+ Evolution translator = new Evolution();
+ ClassPool cp = ClassPool.getDefault(translator);
+ Loader cl = new Loader();
+ cl.setClassPool(cp);
+
+ translator.makeUpdatable("sample.evolve.WebPage");
+ cl.run("sample.evolve.DemoServer", args);
+ }
+}
diff --git a/sample/evolve/DemoServer.java b/sample/evolve/DemoServer.java new file mode 100644 index 00000000..943c509c --- /dev/null +++ b/sample/evolve/DemoServer.java @@ -0,0 +1,102 @@ +package sample.evolve;
+
+import javassist.*;
+import javassist.web.*;
+import java.io.*;
+
+/**
+ * A web server for demonstrating class evolution. It must be
+ * run with a DemoLoader.
+ *
+ * If a html file /java.html is requested, this web server calls
+ * WebPage.show() for constructing the contents of that html file
+ * So if a DemoLoader changes the definition of WebPage, then
+ * the image of /java.html is also changed.
+ * Note that WebPage is not an applet. It is rather
+ * similar to a CGI script or a servlet. The web server never
+ * sends the class file of WebPage to web browsers.
+ *
+ * Furthermore, if a html file /update.html is requested, this web
+ * server overwrites WebPage.class (class file) and calls update()
+ * in VersionManager so that WebPage.class is loaded into the JVM
+ * again. The new contents of WebPage.class are copied from
+ * either WebPage.class.0 or WebPage.class.1.
+ */
+public class DemoServer extends Webserver {
+
+ public static void main(String[] args) throws IOException
+ {
+ if (args.length == 1) {
+ DemoServer web = new DemoServer(Integer.parseInt(args[0]));
+ web.run();
+ }
+ else
+ System.err.println(
+ "Usage: java sample.evolve.DemoServer <port number>");
+ }
+
+ public DemoServer(int port) throws IOException {
+ super(port);
+ htmlfileBase = "sample/evolve/";
+ }
+
+ private static final String ver0 = "sample/evolve/WebPage.class.0";
+ private static final String ver1 = "sample/evolve/WebPage.class.1";
+ private String currentVersion = ver0;
+
+ public void doReply(InputStream in, OutputStream out, String cmd)
+ throws IOException, BadHttpRequest
+ {
+ if (cmd.startsWith("GET /java.html ")) {
+ runJava(out);
+ return;
+ }
+ else if (cmd.startsWith("GET /update.html ")) {
+ try {
+ if (currentVersion == ver0)
+ currentVersion = ver1;
+ else
+ currentVersion = ver0;
+
+ updateClassfile(currentVersion);
+ VersionManager.update("sample.evolve.WebPage");
+ }
+ catch (CannotUpdateException e) {
+ logging(e.toString());
+ }
+ catch (FileNotFoundException e) {
+ logging(e.toString());
+ }
+ }
+
+ super.doReply(in, out, cmd);
+ }
+
+ private void runJava(OutputStream outs) throws IOException {
+ OutputStreamWriter out = new OutputStreamWriter(outs);
+ out.write("HTTP/1.0 200 OK\r\n\r\n");
+ WebPage page = new WebPage();
+ page.show(out);
+ out.close();
+ }
+
+ /* updateClassfile() copies the specified file to WebPage.class.
+ */
+ private void updateClassfile(String filename)
+ throws IOException, FileNotFoundException
+ {
+ byte[] buf = new byte[1024];
+
+ FileInputStream fin
+ = new FileInputStream(filename);
+ FileOutputStream fout
+ = new FileOutputStream("sample/evolve/WebPage.class");
+ for (;;) {
+ int len = fin.read(buf);
+ if (len >= 0)
+ fout.write(buf, 0, len);
+ else
+ break;
+ }
+ }
+}
diff --git a/sample/evolve/Evolution.java b/sample/evolve/Evolution.java new file mode 100644 index 00000000..810f1986 --- /dev/null +++ b/sample/evolve/Evolution.java @@ -0,0 +1,203 @@ +package sample.evolve;
+
+import javassist.*;
+import java.io.IOException;
+
+/**
+ * Evolution provides a set of methods for instrumenting bytecodes.
+ *
+ * For class evolution, updatable class A is renamed to B. Then an
+ * abstract class named A is produced as the super class of B. If the
+ * original class A has a public method m(), then the abstract class A
+ * has an abstract method m().
+ *
+ * abstract class A
+ * abstract m()
+ * _makeInstance()
+ * |
+ * class A --------> class B
+ * m() m()
+ *
+ * Also, all the other classes are translated so that "new A(i)"
+ * in the methods is replaced with "_makeInstance(i)". This makes
+ * it possible to change the behavior of the instantiation of
+ * the class A.
+ */
+public class Evolution implements Translator {
+ public final static String handlerMethod = "_makeInstance";
+ public final static String latestVersionField
+ = VersionManager.latestVersionField;
+ public final static String versionManagerMethod = "initialVersion";
+
+ private static CtMethod trapMethod;
+ private static final int initialVersion = 0;
+ private ClassPool pool;
+ private String updatableClassName = null;
+ private CtClass updatableClass = null;
+
+ public void start(ClassPool _pool) throws NotFoundException {
+ pool = _pool;
+
+ // Get the definition of Sample.make() and store it into trapMethod
+ // for later use.
+ trapMethod = _pool.getMethod("sample.evolve.Sample", "make");
+ }
+
+ public void onWrite(ClassPool _pool, String classname)
+ throws NotFoundException, CannotCompileException
+ {
+ onWriteUpdatable(classname);
+
+ /*
+ * Replaces all the occurrences of the new operator with a call
+ * to _makeInstance().
+ */
+ CtClass clazz = _pool.get(classname);
+ CtClass absClass = updatableClass;
+ CodeConverter converter = new CodeConverter();
+ converter.replaceNew(absClass, absClass, handlerMethod);
+ clazz.instrument(converter);
+ }
+
+ private void onWriteUpdatable(String classname)
+ throws NotFoundException, CannotCompileException
+ {
+ // if the class is a concrete class,
+ // classname is <updatableClassName>$<version>.
+
+ int i = classname.lastIndexOf('$');
+ if (i <= 0)
+ return;
+
+ String orgname = classname.substring(0, i);
+ if (!orgname.equals(updatableClassName))
+ return;
+
+ int version;
+ try {
+ version = Integer.parseInt(classname.substring(i + 1));
+ }
+ catch (NumberFormatException e) {
+ throw new NotFoundException(classname, e);
+ }
+
+
+ CtClass clazz = pool.getAndRename(orgname, classname);
+ makeConcreteClass(clazz, updatableClass, version);
+ }
+
+ /* Register an updatable class.
+ */
+ public void makeUpdatable(String classname)
+ throws NotFoundException, CannotCompileException
+ {
+ if (pool == null)
+ throw new RuntimeException(
+ "Evolution has not been linked to ClassPool.");
+
+ CtClass c = pool.get(classname);
+ updatableClassName = classname;
+ updatableClass = makeAbstractClass(c);
+ }
+
+ /**
+ * Produces an abstract class.
+ */
+ protected CtClass makeAbstractClass(CtClass clazz)
+ throws CannotCompileException, NotFoundException
+ {
+ int i;
+
+ CtClass absClass = pool.makeClass(clazz.getName());
+ absClass.setModifiers(Modifier.PUBLIC | Modifier.ABSTRACT);
+ absClass.setSuperclass(clazz.getSuperclass());
+ absClass.setInterfaces(clazz.getInterfaces());
+
+ // absClass.inheritAllConstructors();
+
+ CtField fld = new CtField(pool.get("java.lang.Class"),
+ latestVersionField, absClass);
+ fld.setModifiers(Modifier.PUBLIC | Modifier.STATIC);
+
+ CtField.Initializer finit
+ = CtField.Initializer.byCall(
+ pool.get("sample.evolve.VersionManager"),
+ versionManagerMethod,
+ new String[] { clazz.getName() });
+ absClass.addField(fld, finit);
+
+ CtField[] fs = clazz.getDeclaredFields();
+ for (i = 0; i < fs.length; ++i) {
+ CtField f = fs[i];
+ if (Modifier.isPublic(f.getModifiers()))
+ absClass.addField(new CtField(f.getType(), f.getName(),
+ absClass));
+ }
+
+ CtConstructor[] cs = clazz.getDeclaredConstructors();
+ for (i = 0; i < cs.length; ++i) {
+ CtConstructor c = cs[i];
+ int mod = c.getModifiers();
+ if (Modifier.isPublic(mod)) {
+ CtMethod wm
+ = CtNewMethod.wrapped(absClass, handlerMethod,
+ c.getParameterTypes(),
+ c.getExceptionTypes(),
+ trapMethod, null, absClass);
+ wm.setModifiers(Modifier.PUBLIC | Modifier.STATIC);
+ absClass.addMethod(wm);
+ }
+ }
+
+ CtMethod[] ms = clazz.getDeclaredMethods();
+ for (i = 0; i < ms.length; ++i) {
+ CtMethod m = ms[i];
+ int mod = m.getModifiers();
+ if (Modifier.isPublic(mod))
+ if (Modifier.isStatic(mod))
+ throw new CannotCompileException(
+ "static methods are not supported.");
+ else {
+ CtMethod m2
+ = CtNewMethod.abstractMethod(m.getReturnType(),
+ m.getName(),
+ m.getParameterTypes(),
+ m.getExceptionTypes(),
+ absClass);
+ absClass.addMethod(m2);
+ }
+ }
+
+ return absClass;
+ }
+
+ /**
+ * Modifies the given class file so that it is a subclass of the
+ * abstract class produced by makeAbstractClass().
+ *
+ * Note: the naming convention must be consistent with
+ * VersionManager.update().
+ */
+ protected void makeConcreteClass(CtClass clazz,
+ CtClass abstractClass, int version)
+ throws CannotCompileException, NotFoundException
+ {
+ int i;
+ clazz.setSuperclass(abstractClass);
+ CodeConverter converter = new CodeConverter();
+ CtField[] fs = clazz.getDeclaredFields();
+ for (i = 0; i < fs.length; ++i) {
+ CtField f = fs[i];
+ if (Modifier.isPublic(f.getModifiers()))
+ converter.redirectFieldAccess(f, abstractClass, f.getName());
+ }
+
+ CtConstructor[] cs = clazz.getDeclaredConstructors();
+ for (i = 0; i < cs.length; ++i)
+ cs[i].instrument(converter);
+
+ CtMethod[] ms = clazz.getDeclaredMethods();
+ for (i = 0; i < ms.length; ++i)
+ ms[i].instrument(converter);
+ }
+}
diff --git a/sample/evolve/Sample.java b/sample/evolve/Sample.java new file mode 100644 index 00000000..c147c965 --- /dev/null +++ b/sample/evolve/Sample.java @@ -0,0 +1,12 @@ +package sample.evolve;
+
+/**
+ * This is a sample class used by Transformer.
+ */
+public class Sample {
+ public static Class _version;
+
+ public static Object make(Object[] args) {
+ return VersionManager.make(_version, args);
+ }
+}
diff --git a/sample/evolve/VersionManager.java b/sample/evolve/VersionManager.java new file mode 100644 index 00000000..d95268b8 --- /dev/null +++ b/sample/evolve/VersionManager.java @@ -0,0 +1,90 @@ +package sample.evolve;
+
+import java.util.Hashtable;
+import java.lang.reflect.*;
+import javassist.CtClass;
+
+/**
+ * Runtime system for class evolution
+ */
+public class VersionManager {
+ private static Hashtable versionNo = new Hashtable();
+ public final static String latestVersionField = "_version";
+
+ /**
+ * For updating the definition of class my.X, say:
+ *
+ * VersionManager.update("my.X");
+ */
+ public static void update(String qualifiedClassname)
+ throws CannotUpdateException
+ {
+ try {
+ Class c = getUpdatedClass(qualifiedClassname);
+ Field f = c.getField(latestVersionField);
+ f.set(null, c);
+ }
+ catch (ClassNotFoundException e) {
+ throw new CannotUpdateException("cannot update class: "
+ + qualifiedClassname);
+ }
+ catch (Exception e) {
+ throw new CannotUpdateException(e);
+ }
+ }
+
+ private static Class getUpdatedClass(String qualifiedClassname)
+ throws ClassNotFoundException
+ {
+ int version;
+ Object found = versionNo.get(qualifiedClassname);
+ if (found == null)
+ version = 0;
+ else
+ version = ((Integer)found).intValue() + 1;
+
+ Class c = Class.forName(qualifiedClassname + '$' + version);
+ versionNo.put(qualifiedClassname, new Integer(version));
+ return c;
+ }
+
+ /* initiaVersion() is used to initialize the _version field of
+ * the updatable classes.
+ */
+ public static Class initialVersion(String[] params) {
+ try {
+ return getUpdatedClass(params[0]);
+ }
+ catch (ClassNotFoundException e) {
+ throw new RuntimeException("cannot initialize " + params[0]);
+ }
+ }
+
+ /** make() performs the object creation of the updatable classes.
+ * The expression "new <updatable class>" is replaced with a call
+ * to this method.
+ */
+ public static Object make(Class clazz, Object[] args) {
+ Constructor[] constructors = clazz.getConstructors();
+ int n = constructors.length;
+ for (int i = 0; i < n; ++i) {
+ try {
+ return constructors[i].newInstance(args);
+ }
+ catch (IllegalArgumentException e) {
+ // try again
+ }
+ catch (InstantiationException e) {
+ throw new CannotCreateException(e);
+ }
+ catch (IllegalAccessException e) {
+ throw new CannotCreateException(e);
+ }
+ catch (InvocationTargetException e) {
+ throw new CannotCreateException(e);
+ }
+ }
+
+ throw new CannotCreateException("no constructor matches");
+ }
+}
diff --git a/sample/evolve/WebPage.class.0 b/sample/evolve/WebPage.class.0 Binary files differnew file mode 100644 index 00000000..3cc1d743 --- /dev/null +++ b/sample/evolve/WebPage.class.0 diff --git a/sample/evolve/WebPage.class.1 b/sample/evolve/WebPage.class.1 Binary files differnew file mode 100644 index 00000000..fe49380e --- /dev/null +++ b/sample/evolve/WebPage.class.1 diff --git a/sample/evolve/WebPage.java b/sample/evolve/WebPage.java new file mode 100644 index 00000000..7c2b7cfb --- /dev/null +++ b/sample/evolve/WebPage.java @@ -0,0 +1,31 @@ +package sample.evolve;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Updatable class. DemoServer instantiates this class and calls
+ * show() on the created object.
+ */
+
+// WebPage.class.0
+public class WebPage {
+ public void show(OutputStreamWriter out) throws IOException {
+ Calendar c = new GregorianCalendar();
+ out.write(c.getTime().toString());
+ out.write("<P><A HREF=\"demo.html\">Return to the home page.</A>");
+ }
+}
+/*
+// WebPage.class.1
+public class WebPage {
+ public void show(OutputStreamWriter out) throws IOException {
+ out.write("<H2>Current Time:</H2>");
+ Calendar c = new GregorianCalendar();
+ out.write("<CENTER><H3><FONT color=\"blue\">");
+ out.write(c.getTime().toString());
+ out.write("</FONT></H3></CENTER><HR>");
+ out.write("<P><A HREF=\"demo.html\">Return to the home page.</A>");
+ }
+}
+*/
diff --git a/sample/evolve/demo.html b/sample/evolve/demo.html new file mode 100644 index 00000000..6be4a2c3 --- /dev/null +++ b/sample/evolve/demo.html @@ -0,0 +1,38 @@ +<H2>Class Evolution</H2>
+
+<P>This is a demonstration of the class evolution mechanism
+implemented with Javassist. This mechanism enables a Java program to
+reload an existing class file. Although the reloaded class file is
+not applied to exiting objects (the old class file is used for those
+objects), it is effective in newly created objects.
+
+<P>Since the reloading is transparently executed, no programming
+convention is needed. However, there are some limitations on possible
+changes of the class definition. For example, the new class definition
+must have the same set of methods as the old one. These limitations are
+necessary for keeping the type system consistent.
+
+
+<H3><a href="java.html">Run WebPage.show()</a></H3>
+
+<P>The web server creates a new <code>WebPage</code> object and
+calls <code>show()</code> on that object. This method works as
+if it is a CGI script or a servlet and you will see the html file
+produced by this method on your browser.
+
+<H3><a href="update.html">Change WebPage.class</a></H3>
+
+<P>The web server overwrites class file <code>WebPage.class</code>
+on the local disk. Then it signals that <code>WebPage.class</code>
+must be reloaded into the JVM. If you run <code>WebPage.show()</code>
+again, you will see a different page on your browser.
+
+<H3>Source files</H3>
+
+<P>Web server: <A HREF="DemoServer.java"><code>DemoServer.java</code></A>
+
+<P>WebPage: <A HREF="WebPage.java"><code>WebPage.java</code></A>
+
+<P>Class loader: <A HREF="DemoLoader.java"><code>DemoLoader.java</code></A>,
+ <A HREF="Evolution.java"><code>Evolution.java</code></A>, and
+ <A HREF="VersionManager.java"><code>VersionManager.java</code></A>.
diff --git a/sample/evolve/start.html b/sample/evolve/start.html new file mode 100644 index 00000000..d31d9d08 --- /dev/null +++ b/sample/evolve/start.html @@ -0,0 +1,23 @@ +<h2>Instructions</h2>
+
+<p>1. Compile <code>sample/evolve/*.java</code>.
+ Copy <code>WebPage.class</code> to <code>WebPage.class.0</code>.
+
+<p>2. Edit <code>Webpage.java</code>, compile it,
+ and copy <code>WebPage.class</code> to <code>WebPage.class.1</code>.
+<br><code>WebPage.class.0</code> and
+ <code>WebPage.class.1</code> are used
+ for changing the contents of <code>WebPage.class</code> during
+ the demo.
+
+<p>3. Run the server on the local host (where your web browser is running):
+
+<ul>
+<code>% java sample.evolve.DemoLoader 5003
+</code></ul>
+
+<p>4. Click below:
+
+<ul><h2><a href="http://localhost:5003/demo.html">
+Start!
+</a></h2></ul>
diff --git a/sample/evolve/update.html b/sample/evolve/update.html new file mode 100644 index 00000000..82551653 --- /dev/null +++ b/sample/evolve/update.html @@ -0,0 +1,3 @@ +<h2><code>WebPage.class</code> has been changed.</h2>
+
+<a href="demo.html">Return to the home page.</a>
diff --git a/sample/reflect/Main.java b/sample/reflect/Main.java new file mode 100644 index 00000000..6086d9f8 --- /dev/null +++ b/sample/reflect/Main.java @@ -0,0 +1,33 @@ +package sample.reflect;
+
+import javassist.reflect.ClassMetaobject;
+import javassist.reflect.Loader;
+
+/*
+ The "verbose metaobject" example (JDK 1.2 or later only).
+
+ Since this program registers class Person as a reflective class
+ (in a more realistic demonstration, what classes are reflective
+ would be specified by some configuration file), the class loader
+ modifies Person.class when loading into the JVM so that the class
+ Person is changed into a reflective class and a Person object is
+ controlled by a VerboseMetaobj.
+
+ To run,
+
+ % java javassist.reflect.Loader sample.reflect.Main Joe
+
+ Compare this result with that of the regular execution without reflection:
+
+ % java sample.reflect.Person Joe
+*/
+public class Main {
+ public static void main(String[] args) throws Throwable {
+ Loader cl = (Loader)Main.class.getClassLoader();
+ cl.makeReflective("sample.reflect.Person",
+ "sample.reflect.VerboseMetaobj",
+ "javassist.reflect.ClassMetaobject");
+
+ cl.run("sample.reflect.Person", args);
+ }
+}
diff --git a/sample/reflect/Person.java b/sample/reflect/Person.java new file mode 100644 index 00000000..5e0e2ad8 --- /dev/null +++ b/sample/reflect/Person.java @@ -0,0 +1,51 @@ +/*
+ A base-level class controlled by VerboseMetaobj.
+*/
+
+package sample.reflect;
+
+import javassist.reflect.Metalevel;
+import javassist.reflect.Metaobject;
+
+public class Person {
+ public String name;
+ public static int birth = 3;
+ public static final String defaultName = "John";
+
+ public Person(String name, int birthYear) {
+ if (name == null)
+ this.name = defaultName;
+ else
+ this.name = name;
+
+ this.birth = birthYear;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getAge(int year) {
+ return year - birth;
+ }
+
+ public static void main(String[] args) {
+ String name;
+ if (args.length > 0)
+ name = args[0];
+ else
+ name = "Bill";
+
+ Person p = new Person(name, 1960);
+ System.out.println("name: " + p.getName());
+ System.out.println("object: " + p.toString());
+
+ // change the metaobject of p.
+ if (p instanceof Metalevel) {
+ ((Metalevel)p)._setMetaobject(new Metaobject(p, null));
+ System.out.println("<< the metaobject was changed.>>");
+ }
+
+ System.out.println("age: " + p.getAge(1999));
+ }
+}
diff --git a/sample/reflect/VerboseMetaobj.java b/sample/reflect/VerboseMetaobj.java new file mode 100644 index 00000000..a9f75dd1 --- /dev/null +++ b/sample/reflect/VerboseMetaobj.java @@ -0,0 +1,29 @@ +package sample.reflect;
+
+import javassist.*;
+import javassist.reflect.*;
+
+public class VerboseMetaobj extends Metaobject {
+ public VerboseMetaobj(Object self, Object[] args) {
+ super(self, args);
+ System.out.println("** constructed: " + self.getClass().getName());
+ }
+
+ public Object trapFieldRead(String name) {
+ System.out.println("** field read: " + name);
+ return super.trapFieldRead(name);
+ }
+
+ public void trapFieldWrite(String name, Object value) {
+ System.out.println("** field write: " + name);
+ super.trapFieldWrite(name, value);
+ }
+
+ public Object trapMethodcall(int identifier, Object[] args)
+ throws Throwable
+ {
+ System.out.println("** trap: " + getMethodName(identifier)
+ + "() in " + getClassMetaobject().getName());
+ return super.trapMethodcall(identifier, args);
+ }
+}
diff --git a/sample/rmi/AlertDialog.java b/sample/rmi/AlertDialog.java new file mode 100644 index 00000000..99fae5c2 --- /dev/null +++ b/sample/rmi/AlertDialog.java @@ -0,0 +1,30 @@ +package sample.rmi;
+
+import java.awt.*;
+import java.awt.event.*;
+
+public class AlertDialog extends Frame implements ActionListener {
+ private Label label;
+
+ public AlertDialog() {
+ super("Alert");
+ setSize(200, 100);
+ setLocation(100, 100);
+ label = new Label();
+ Button b = new Button("OK");
+ b.addActionListener(this);
+ Panel p = new Panel();
+ p.add(b);
+ add("North", label);
+ add("South", p);
+ }
+
+ public void show(String message) {
+ label.setText(message);
+ setVisible(true);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ setVisible(false);
+ }
+}
diff --git a/sample/rmi/CountApplet.java b/sample/rmi/CountApplet.java new file mode 100644 index 00000000..0bebdaf9 --- /dev/null +++ b/sample/rmi/CountApplet.java @@ -0,0 +1,83 @@ +package sample.rmi;
+
+import java.applet.*;
+import java.awt.*;
+import java.awt.event.*;
+import javassist.rmi.ObjectImporter;
+import javassist.rmi.ObjectNotFoundException;
+import javassist.web.Viewer;
+
+public class CountApplet extends Applet implements ActionListener {
+ private Font font;
+ private ObjectImporter importer;
+ private Counter counter;
+ private AlertDialog dialog;
+ private String message;
+
+ private String paramButton;
+ private String paramName;
+
+ public void init() {
+ paramButton = getParameter("button");
+ paramName = getParameter("name");
+ importer = new ObjectImporter(this);
+ commonInit();
+ }
+
+ /* call this method instead of init() if this program is not run
+ * as an applet.
+ */
+ public void applicationInit() {
+ paramButton = "OK";
+ paramName = "counter";
+ Viewer cl = (Viewer)getClass().getClassLoader();
+ importer = new ObjectImporter(cl.getServer(), cl.getPort());
+ commonInit();
+ }
+
+ private void commonInit() {
+ font = new Font("SansSerif", Font.ITALIC, 40);
+ Button b = new Button(paramButton);
+ b.addActionListener(this);
+ add(b);
+ dialog = new AlertDialog();
+ message = "???";
+ }
+
+ public void destroy() {
+ dialog.dispose();
+ }
+
+ public void start() {
+ try {
+ counter = (Counter)importer.lookupObject(paramName);
+ message = Integer.toString(counter.get());
+ }
+ catch (ObjectNotFoundException e) {
+ dialog.show(e.toString());
+ }
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ counter.increase();
+ message = Integer.toString(counter.get());
+ repaint();
+ }
+
+ public void paint(Graphics g) {
+ g.setFont(font);
+ g.drawRect(50, 50, 100, 100);
+ g.setColor(Color.blue);
+ g.drawString(message, 60, 120);
+ }
+
+ public static void main(String[] args) {
+ Frame f = new Frame("CountApplet");
+ CountApplet ca = new CountApplet();
+ f.add(ca);
+ f.setSize(300, 300);
+ ca.applicationInit();
+ ca.start();
+ f.setVisible(true);
+ }
+}
diff --git a/sample/rmi/Counter.java b/sample/rmi/Counter.java new file mode 100644 index 00000000..f8a0fcf5 --- /dev/null +++ b/sample/rmi/Counter.java @@ -0,0 +1,32 @@ +package sample.rmi;
+
+import javassist.rmi.AppletServer;
+import java.io.IOException;
+import javassist.CannotCompileException;
+import javassist.NotFoundException;
+
+public class Counter {
+ private int count = 0;
+
+ public int get() {
+ return count;
+ }
+
+ synchronized public int increase() {
+ count += 1;
+ return count;
+ }
+
+ public static void main(String[] args)
+ throws IOException, NotFoundException, CannotCompileException
+ {
+ if (args.length == 1) {
+ AppletServer web = new AppletServer(args[0]);
+ web.exportObject("counter", new Counter());
+ web.run();
+ }
+ else
+ System.err.println(
+ "Usage: java sample.rmi.Counter <port number>");
+ }
+}
diff --git a/sample/rmi/inside.gif b/sample/rmi/inside.gif Binary files differnew file mode 100644 index 00000000..c69c8ee8 --- /dev/null +++ b/sample/rmi/inside.gif diff --git a/sample/rmi/start.html b/sample/rmi/start.html new file mode 100644 index 00000000..696b629c --- /dev/null +++ b/sample/rmi/start.html @@ -0,0 +1,15 @@ +<h2>Instructions</h2>
+
+<p>1. Run the server on the local host (where your web browser is running):
+
+<ul>% java sample.rmi.Counter 5001</ul>
+
+<p>2. Click below:
+
+<h2><a href="webdemo.html">
+Start!
+</a></h2>
+
+<p>If you don't want to use a web browser, do as follows:
+
+<ul><pre>% java javassist.web.Viewer localhost 5001 sample.rmi.CountApplet</pre></ul>
diff --git a/sample/rmi/webdemo.html b/sample/rmi/webdemo.html new file mode 100644 index 00000000..d313ec3c --- /dev/null +++ b/sample/rmi/webdemo.html @@ -0,0 +1,203 @@ +<html>
+<body>
+<h2>Remote Method Invocation</h2>
+
+<P>Javassist enables an applet to access a remote object as if it is a
+local object. The applet can communicate through a socket with the
+host that executes the web server distributing that applet. However,
+the applet cannot directly call a method on an object if the object is
+on a remote host. The <code>javassist.rmi</code> package provides
+a mechanism for the applet to transparently access the remote object.
+The rules that the applet must be subject to are simpler than the
+standard Java RMI.
+
+<h3>1. Sample applet</h3>
+
+<P>The applet showing below is a simple number counter.
+If you press the button, the number is increased by one.
+An interesting feature of this applet is that the object
+recording the current number is contained by the web server
+written in Java. The applet must access the object through a socket
+to obtain the current number.
+
+<p><center>
+<applet codebase="http://localhost:5001"
+code="sample.rmi.CountApplet" width=200 height=200>
+<param name=name value="counter">
+<param name=button value="+1">
+</applet>
+</center>
+
+<p>However, the program of the applet does not need to directly handle
+a socket. The <code>ObjectImporter</code> provided by Javassist deals
+with all the awkward programming.
+Look at the lines shown with red:
+
+<p><b>Figure 1: Applet</b>
+
+<pre>
+<font color="red">import javassist.rmi.ObjectImporter;</font>
+
+public class CountApplet extends Applet implements ActionListener {
+ private Font font;
+ <font color="red">private ObjectImporter importer;
+ private Counter counter;</font>
+ private AlertDialog dialog;
+ private String message;
+
+ public void init() {
+ font = new Font("SansSerif", Font.ITALIC, 40);
+ Button b = new Button(getParameter("button"));
+ b.addActionListener(this);
+ add(b);
+ <font color="red">importer = new ObjectImporter(this);</font>
+ dialog = new AlertDialog();
+ message = "???";
+ }
+
+ public void start() {
+ String counterName = getParameter("name");
+ <font color="red">counter = (Counter)importer.getObject(counterName);</font>
+ message = Integer.toString(<font color="red">counter.get()</font>);
+ }
+
+ /* The method called when the button is pressed.
+ */
+ public void actionPerformed(ActionEvent e) {
+ message = Integer.toString(<font color="red">counter.increase()</font>);
+ repaint();
+ }
+
+ public void paint(Graphics g) {
+ g.setFont(font);
+ g.drawRect(50, 50, 100, 100);
+ g.setColor(Color.blue);
+ g.drawString(message, 60, 120);
+ }
+}
+</pre>
+
+<p>A <code>Counter</code> object running on a remote host
+maintains the counter number. To access this object, the applet first
+calls <code>getObject()</code> on an <code>ObjectImporter</code>
+to obtain a reference to the object. The parameter is the name associated
+with the object by the web server. Once the reference is obtained, it
+is delt with as if it is a reference to a local object.
+For example, <code>counter.get()</code> and <code>counter.increase()</code>
+call methods on the remote object.
+
+<p>The definition of the <code>Counter</code> class is also
+straightforward:
+
+<p><b>Figure 2: Remote object</b>
+
+<pre>
+public class Counter {
+ private int count = 0;
+
+ public int get() {
+ return count;
+ }
+
+ public int increase() {
+ count += 1;
+ return count;
+ }
+}
+</pre>
+
+<p>Note that the <code>javassist.rmi</code> package does not require
+the <code>Counter</code> class to be an interface unlike the Java RMI,
+with which <code>Counter</code> must be an interface and it must be
+implemented by another class.
+
+<p>To make the <code>Counter</code> object available from the applet,
+it must be registered with the web server. A <code>AppletServer</code>
+object is a simple webserver that can distribute <code>.html</code> files
+and <code>.class</code> files (Java applets).
+
+<p><b>Figure 3: Server-side program</b>
+
+<pre>
+public class MyWebServer {
+ public static void main(String[] args) throws IOException, CannotCompileException
+ {
+ AppletServer web = new AppletServer(args[0]);
+ <font color="red">web.exportObject("counter", new Counter());</font>
+ web.run();
+ }
+}
+</pre>
+
+<p>The <code>exportObject()</code> method registers a remote object
+with the <code>AppletServer</code> object. In the example above,
+a <code>Counter</code> object is registered. The applet can access
+the object with the name "counter". The web server starts the service
+if the <code>run()</code> method is called.
+
+<p><br>
+
+<h3>2. Features</h3>
+
+The remote method invocation mechanism provided by Javassist has the
+following features:
+
+<ul>
+<li><b>Regular Java syntax:</b><br>
+ The applet can call a method on a remote object with regular
+ Java syntax.
+<p>
+
+<li><b>No special naming convention:</b><br>
+ The applet can use the same class name as the server-side program.
+ The reference object to a remote <code>Foo</code> object is
+ also represented by the class <code>Foo</code>.
+ Unlike other similar
+ systems, it is not represented by a different class such as
+ <code>ProxyFoo</code> or an interface implemented by
+ <code>Foo</code>.
+<p>
+
+<li><b>No extra compiler:</b><br>
+ All the programs, both the applet and the server-side program,
+ are compiled by the regular Java compiler. No external compiler
+ is needed.
+</ul>
+
+<p> With the Java RMI or Voyager, the applet programmer must define
+an interface for every remote object class and access the remote object
+through that interface.
+On the other hand, the <code>javassist.rmi</code> package does not
+require the programmer to follow that programming convention.
+It is suitable for writing simple distributed programs like applets.
+
+<p><br>
+
+<h3>3. Inside of the system</h3>
+
+<p>A key idea of the implementation is that the applet and the server-side
+program must use different versions of the class <code>Counter</code>.
+The <code>Counter</code> object in the applet must work as a proxy
+object, which transfers the method invocations to the <code>Counter</code>
+object in the server-side program.
+
+<p>With other systems like the Java RMI, the class of this proxy object is
+produced by a special compiler such as <code>rmic</code>.
+It must be manually maintained by the programmer.
+
+<center><img src="inside.gif"></center>
+
+<p>However, Javassist automatically generates the proxy class at
+runtime so that the programmer does not have to be concerned about the
+maintenance of the proxy class.
+If the web browser running the applet
+requests to load the <code>Counter</code> class, which is the class
+of an exported object,
+then the web server
+transfers the version of <code>Counter</code> that Javassist generates
+as a proxy class.
+
+<p><br>
+
+</body>
+</html>
diff --git a/sample/vector/Sample.java b/sample/vector/Sample.java new file mode 100644 index 00000000..7a47aadf --- /dev/null +++ b/sample/vector/Sample.java @@ -0,0 +1,14 @@ +package sample.vector;
+
+public class Sample extends java.util.Vector {
+ public void add(X e) {
+ super.addElement(e);
+ }
+
+ public X at(int i) {
+ return (X)super.elementAt(i);
+ }
+}
+
+class X {
+}
diff --git a/sample/vector/Sample2.java b/sample/vector/Sample2.java new file mode 100644 index 00000000..dd5c965e --- /dev/null +++ b/sample/vector/Sample2.java @@ -0,0 +1,13 @@ +package sample.vector;
+
+public class Sample2 extends java.util.Vector {
+ public Object add(Object[] args) {
+ super.addElement(args[0]);
+ return null;
+ }
+
+ public Object at(Object[] args) {
+ int i = ((Integer)args[0]).intValue();
+ return super.elementAt(i);
+ }
+}
diff --git a/sample/vector/Test.j b/sample/vector/Test.j new file mode 100644 index 00000000..6f524c93 --- /dev/null +++ b/sample/vector/Test.j @@ -0,0 +1,38 @@ +/*
+ A sample program using sample.vector.VectorAssistant
+ and the javassist.preproc package.
+
+ This automatically produces the classes representing vectors of integer
+ and vectors of java.lang.String.
+
+ To compile and run this program, do as follows:
+
+ % java javassist.tool.Compiler sample/vector/Test.j
+ % javac sample/vector/Test.java
+ % java sample.vector.Test
+
+ The first line produces one source file (sample/Test.java) and
+ two class files (sample/vector/intVector.class and
+ sample/vector/StringVector.class).
+*/
+
+package sample.vector;
+
+import java.util.Vector by sample.vector.VectorAssistant(java.lang.String);
+import java.util.Vector by sample.vector.VectorAssistant(int);
+
+public class Test {
+ public static void main(String[] args) {
+ intVector iv = new intVector();
+ iv.add(3);
+ iv.add(4);
+ for (int i = 0; i < iv.size(); ++i)
+ System.out.println(iv.at(i));
+
+ StringVector sv = new StringVector();
+ sv.add("foo");
+ sv.add("bar");
+ for (int i = 0; i < sv.size(); ++i)
+ System.out.println(sv.at(i));
+ }
+}
diff --git a/sample/vector/VectorAssistant.java b/sample/vector/VectorAssistant.java new file mode 100644 index 00000000..44fdd41c --- /dev/null +++ b/sample/vector/VectorAssistant.java @@ -0,0 +1,135 @@ +package sample.vector;
+
+import java.io.IOException;
+import javassist.*;
+import javassist.preproc.Assistant;
+
+/**
+ * This is a Javassist program which produce a new class representing
+ * vectors of a given type. For example,
+ *
+ * <ul>import java.util.Vector by sample.vector.VectorAssistant(int)</ul>
+ *
+ * <p>requests the Javassist preprocessor to substitute the following
+ * lines for the original import declaration:
+ *
+ * <ul><pre>
+ * import java.util.Vector;
+ * import sample.vector.intVector;
+ * </pre></ul>
+ *
+ * <p>The Javassist preprocessor calls <code>VectorAssistant.assist()</code>
+ * and produces class <code>intVector</code> equivalent to:
+ *
+ * <ul><pre>
+ * package sample.vector;
+ *
+ * public class intVector extends Vector {
+ * pubilc void add(int value) {
+ * addElement(new Integer(value));
+ * }
+ *
+ * public int at(int index) {
+ * return elementAt(index).intValue();
+ * }
+ * }
+ * </pre></ul>
+ *
+ * <p><code>VectorAssistant.assist()</code> uses
+ * <code>sample.vector.Sample</code> and <code>sample.vector.Sample2</code>
+ * as a template to produce the methods <code>add()</code> and
+ * <code>at()</code>.
+ */
+public class VectorAssistant implements Assistant {
+ public final String packageName = "sample.vector.";
+
+ /**
+ * Calls <code>makeSubclass()</code> and produces a new vector class.
+ * This method is called by a <code>javassist.preproc.Compiler</code>.
+ *
+ * @see javassist.preproc.Compiler
+ */
+ public CtClass[] assist(ClassPool pool, String vec, String[] args)
+ throws CannotCompileException
+ {
+ if (args.length != 1)
+ throw new CannotCompileException(
+ "VectorAssistant receives a single argument.");
+
+ try {
+ CtClass subclass;
+ CtClass elementType = pool.get(args[0]);
+ if (elementType.isPrimitive())
+ subclass = makeSubclass2(pool, elementType);
+ else
+ subclass = makeSubclass(pool, elementType);
+
+ CtClass[] results = { subclass, pool.get(vec) };
+ return results;
+ }
+ catch (NotFoundException e) {
+ throw new CannotCompileException(e);
+ }
+ catch (IOException e) {
+ throw new CannotCompileException(e);
+ }
+ }
+
+ /**
+ * Produces a new vector class. This method does not work if
+ * the element type is a primitive type.
+ *
+ * @param type the type of elements
+ */
+ public CtClass makeSubclass(ClassPool pool, CtClass type)
+ throws CannotCompileException, NotFoundException, IOException
+ {
+ CtClass vec = pool.makeClass(makeClassName(type));
+ vec.setSuperclass(pool.get("java.util.Vector"));
+
+ CtClass c = pool.get("sample.vector.Sample");
+ CtMethod addmethod = c.getDeclaredMethod("add");
+ CtMethod atmethod = c.getDeclaredMethod("at");
+
+ ClassMap map = new ClassMap();
+ map.put("sample.vector.X", type.getName());
+
+ vec.addMethod(CtNewMethod.copy(addmethod, "add", vec, map));
+ vec.addMethod(CtNewMethod.copy(atmethod, "at", vec, map));
+ pool.writeFile(vec.getName());
+ return vec;
+ }
+
+ /**
+ * Produces a new vector class. This uses wrapped methods so that
+ * the element type can be a primitive type.
+ *
+ * @param type the type of elements
+ */
+ public CtClass makeSubclass2(ClassPool pool, CtClass type)
+ throws CannotCompileException, NotFoundException, IOException
+ {
+ CtClass vec = pool.makeClass(makeClassName(type));
+ vec.setSuperclass(pool.get("java.util.Vector"));
+
+ CtClass c = pool.get("sample.vector.Sample2");
+ CtMethod addmethod = c.getDeclaredMethod("add");
+ CtMethod atmethod = c.getDeclaredMethod("at");
+
+ CtClass[] args1 = { type };
+ CtClass[] args2 = { CtClass.intType };
+ CtMethod m
+ = CtNewMethod.wrapped(CtClass.voidType, "add", args1,
+ null, addmethod, null, vec);
+ vec.addMethod(m);
+ m = CtNewMethod.wrapped(type, "at", args2,
+ null, atmethod, null, vec);
+ vec.addMethod(m);
+ pool.writeFile(vec.getName());
+ return vec;
+ }
+
+ private String makeClassName(CtClass type) {
+ return packageName + type.getSimpleName() + "Vector";
+ }
+}
diff --git a/src/main/META-INF/MANIFEST.MF b/src/main/META-INF/MANIFEST.MF new file mode 100644 index 00000000..b28ad33a --- /dev/null +++ b/src/main/META-INF/MANIFEST.MF @@ -0,0 +1,7 @@ +Manifest-Version: 1.0
+Specification-Title: Javassist
+Created-By: Shigeru Chiba, Tokyo Institute of Technology
+Specification-Vendor: Shigeru Chiba, Tokyo Institute of Technology
+Specification-Version: 2.4
+Name: javassist/
+
diff --git a/src/main/javassist/ByteArrayClassPath.java b/src/main/javassist/ByteArrayClassPath.java new file mode 100644 index 00000000..14b37d8f --- /dev/null +++ b/src/main/javassist/ByteArrayClassPath.java @@ -0,0 +1,84 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +import java.io.*; + +/** + * A <code>ByteArrayClassPath</code> contains bytes that is served as + * a class file to a <code>ClassPool</code>. It is useful to convert + * a byte array to a <code>CtClass</code> object. + * + * <p>For example, if you want to convert a byte array <code>b</code> + * into a <code>CtClass</code> object representing the class with a name + * <code>classname</code>, then do as following: + * + * <ul><pre> + * ClassPool cp = ClassPool.getDefault(); + * cp.insertClassPath(new ByteArrayClassPath(classname, b)); + * CtClass cc = cp.get(classname); + * </pre></ul> + * + * <p>The <code>ClassPool</code> object <code>cp</code> uses the created + * <code>ByteArrayClassPath</code> object as the source of the class file. + * + * <p>A <code>ByteArrayClassPath</code> must be instantiated for every + * class. It contains only a single class file. + * + * @see javassist.ClassPath + * @see ClassPool#insertClassPath(ClassPath) + * @see ClassPool#appendClassPath(ClassPath) + */ +public class ByteArrayClassPath implements ClassPath { + protected String classname; + protected byte[] classfile; + + /* + * Creates a <code>ByteArrayClassPath</code> containing the given + * bytes. + * + * @param name a fully qualified class name + * @param classfile the contents of a class file. + */ + public ByteArrayClassPath(String name, byte[] classfile) { + this.classname = name; + this.classfile = classfile; + } + + public String toString() { + return "byte[]:" + classname; + } + + /** + * Opens a class file. + */ + public InputStream openClassfile(String classname) { + if(this.classname.equals(classname)) + return new ByteArrayInputStream(classfile); + else + return null; + } +} diff --git a/src/main/javassist/CannotCompileException.java b/src/main/javassist/CannotCompileException.java new file mode 100644 index 00000000..41ec7ff5 --- /dev/null +++ b/src/main/javassist/CannotCompileException.java @@ -0,0 +1,89 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +import javassist.compiler.CompileError; + +/** + * Thrown when bytecode transformation has failed. + */ +public class CannotCompileException extends Exception { + private String message; + + public String getReason() { + if (message != null) + return message; + else + return this.toString(); + } + + /** + * Constructs a CannotCompileException with a message. + */ + public CannotCompileException(String msg) { + super(msg); + message = msg; + } + + /** + * Constructs a CannotCompileException with an <code>Exception</code>. + */ + public CannotCompileException(Exception e) { + super("by " + e.toString()); + message = null; + } + + /** + * Constructs a CannotCompileException with a + * <code>NotFoundException</code>. + */ + public CannotCompileException(NotFoundException e) { + this("cannot find " + e.getMessage()); + } + + /** + * Constructs a CannotCompileException with an <code>CompileError</code>. + */ + public CannotCompileException(CompileError e) { + super("[source error] " + e.getMessage()); + message = null; + } + + /** + * Constructs a CannotCompileException + * with a <code>ClassNotFoundException</code>. + */ + public CannotCompileException(ClassNotFoundException e, String name) { + this("cannot find " + name); + } + + /** + * Constructs a CannotCompileException with a ClassFormatError. + */ + public CannotCompileException(ClassFormatError e, String name) { + this("invalid class format: " + name); + } +} diff --git a/src/main/javassist/ClassMap.java b/src/main/javassist/ClassMap.java new file mode 100644 index 00000000..c729ff7c --- /dev/null +++ b/src/main/javassist/ClassMap.java @@ -0,0 +1,139 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +import javassist.bytecode.Descriptor; + +/** + * A hashtable associating class names with different names. + * + * <p>This hashtable is used for replacing class names in a class + * definition or a method body. Define a subclass of this class + * if a more complex mapping algorithm is needed. For example, + * + * <ul><pre>class MyClassMap extends ClassMap { + * public Object get(Object jvmClassName) { + * String name = toJavaName((String)jvmClassName); + * if (name.startsWith("java.")) + * return toJvmName("java2." + name.substring(5)); + * else + * return super.get(jvmClassName); + * } + * }</pre></ul> + * + * <p>This subclass maps <code>java.lang.String</code> to + * <code>java2.lang.String</code>. Note that <code>get()</code> + * receives and returns the internal representation of a class name. + * For example, the internal representation of <code>java.lang.String</code> + * is <code>java/lang/String</code>. + * + * @see #get(Object) + * @see CtClass#replaceClassName(ClassMap) + * @see CtNewMethod#copy(CtMethod,String,CtClass,ClassMap) + */ +public class ClassMap extends java.util.HashMap { + /** + * Maps a class name to another name in this hashtable. + * The names are obtained with calling <code>Class.getName()</code>. + * This method translates the given class names into the + * internal form used in the JVM before putting it in + * the hashtable. + * + * @param oldname the original class name + * @param newname the substituted class name. + */ + public void put(CtClass oldname, CtClass newname) { + put(oldname.getName(), newname.getName()); + } + + /** + * Maps a class name to another name in this hashtable. + * This method translates the given class names into the + * internal form used in the JVM before putting it in + * the hashtable. + * + * @param oldname the original class name + * @param newname the substituted class name. + */ + public void put(String oldname, String newname) { + if (oldname == newname) + return; + + String oldname2 = toJvmName(oldname); + String s = (String)get(oldname2); + if (s == null || !s.equals(oldname2)) + super.put(oldname2, toJvmName(newname)); + } + + protected final void put0(Object oldname, Object newname) { + super.put(oldname, newname); + } + + /** + * Returns the class name to wihch the given <code>jvmClassName</code> + * is mapped. A subclass of this class should override this method. + * + * <p>This method receives and returns the internal representation of + * class name used in the JVM. + * + * @see #toJvmName(String) + * @see #toJavaName(String) + */ + public Object get(Object jvmClassName) { + return super.get(jvmClassName); + } + + /** + * Prevents a mapping from the specified class name to another name. + */ + public void fix(CtClass clazz) { + fix(clazz.getName()); + } + + /** + * Prevents a mapping from the specified class name to another name. + */ + public void fix(String name) { + String name2 = toJvmName(name); + super.put(name2, name2); + } + + /** + * Converts a class name into the internal representation used in + * the JVM. + */ + public static String toJvmName(String classname) { + return Descriptor.toJvmName(classname); + } + + /** + * Converts a class name from the internal representation used in + * the JVM to the normal one used in Java. + */ + public static String toJavaName(String classname) { + return Descriptor.toJavaName(classname); + } +} diff --git a/src/main/javassist/ClassPath.java b/src/main/javassist/ClassPath.java new file mode 100644 index 00000000..f50c225c --- /dev/null +++ b/src/main/javassist/ClassPath.java @@ -0,0 +1,57 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +import java.io.InputStream; + +/** + * <code>ClassPath</code> is an interface implemented by objects + * representing a class search path. + * <code>ClassPool</code> uses those objects for reading class files. + * + * <code>The users can define a class implementing this interface so that + * a class file is obtained from a non-standard source. + * + * @see ClassPool#insertClassPath(ClassPath) + * @see ClassPool#appendClassPath(ClassPath) + * @see javassist.URLClassPath + */ +public interface ClassPath { + /** + * Opens a class file. + * + * <p>This method can return null if the specified class file is not + * found. If null is returned, the next search path is examined. + * However, if an error happens, this method must throw an exception + * so that the search is terminated. + * + * <p>This method should not modify the contents of the class file. + * + * @param classname a fully-qualified class name + * @return the input stream for reading a class file + */ + InputStream openClassfile(String classname) throws NotFoundException; +} diff --git a/src/main/javassist/ClassPool.java b/src/main/javassist/ClassPool.java new file mode 100644 index 00000000..33907446 --- /dev/null +++ b/src/main/javassist/ClassPool.java @@ -0,0 +1,748 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +import java.io.*; +import java.util.Hashtable; + +/** + * A driver class for controlling bytecode editing with Javassist. + * It manages where a class file is obtained and how it is modified. + * + * <p>A <code>ClassPool</code> object can be regarded as a container + * of <code>CtClass</code> objects. It reads class files on demand + * from various + * sources represented by <code>ClassPath</code> and create + * <code>CtClass</code> objects representing those class files. + * The source may be another <code>ClassPool</code>. If so, + * <code>write()</code> is called on the source <code>ClassPool</code> + * for obtaining a class file. + * + * <p>A <code>CtClass</code> + * object contained in a <code>ClassPool</code> is written to an + * output stream (or a file) if <code>write()</code> + * (or <code>writeFile()</code>) is called on the + * <code>ClassPool</code>. + * <code>write()</code> is typically called by a class loader, + * which obtains the bytecode image to be loaded. + * + * <p>The users can modify <code>CtClass</code> objects + * before those objects are written out. + * To obtain a reference + * to a <code>CtClass</code> object contained in a + * <code>ClassPool</code>, <code>get()</code> should be + * called on the <code>ClassPool</code>. If a <code>CtClass</code> + * object is modified, then the modification is reflected on the resulting + * class file returned by <code>write()</code> in <code>ClassPool</code>. + * + * <p>In summary, + * + * <ul> + * <li><code>get()</code> returns a reference to a <code>CtClass</code> + * object contained in a <code>ClassPool</code>. + * + * <li><code>write()</code> translates a <code>CtClass</code> + * object contained in a <code>ClassPool</code> into a class file + * and writes it to an output stream. + * </ul> + * + * <p>The users can add a listener object receiving an event from a + * <code>ClassPool</code>. An event occurs when a listener is + * added to a <code>ClassPool</code> and when <code>write()</code> + * is called on a <code>ClassPool</code>. The listener class + * must implement <code>Translator</code>. A typical listener object + * is used for modifying a <code>CtClass</code> object <i>on demand</i> + * when it is written to an output stream. + * + * <p>The implementation of this class is thread-safe. + * + * @see javassist.CtClass + * @see javassist.ClassPath + * @see javassist.Translator + */ +public class ClassPool { + /* If this field is null, then the object must be an instance of + * ClassPoolTail. + */ + protected ClassPool source; + + protected Translator translator; + + protected Hashtable classes; // should be synchronous + + /** + * Creates a class pool. + * + * @param src the source of class files. If it is null, + * the class search path is initially null. + * @see javassist.ClassPool#getDefault() + */ + public ClassPool(ClassPool src) { + this(src, null); + } + + /** + * Creates a class pool. + * + * @param src the source of class files. If it is null, + * the class search path is initially null. + * @param trans the translator linked to this class pool. + * It may be null. + * @see javassist.ClassPool#getDefault() + */ + public ClassPool(ClassPool src, Translator trans) + throws RuntimeException + { + classes = new Hashtable(); + CtClass[] pt = CtClass.primitiveTypes; + for (int i = 0; i < pt.length; ++i) + classes.put(pt[i].getName(), pt[i]); + + if (src != null) + source = src; + else + source = new ClassPoolTail(); + + translator = trans; + if (trans != null) + try { + trans.start(this); + } + catch (Exception e) { + throw new RuntimeException( + "Translator.start() throws an exception: " + + e.toString()); + } + } + + protected ClassPool() { + source = null; + classes = null; + translator = null; + } + + /** + * Returns the default class pool. + * The returned object is always identical. + * + * <p>The default class pool searches the system search path, + * which usually includes the platform library, extension + * libraries, and the search path specified by the + * <code>-classpath</code> option or the <code>CLASSPATH</code> + * environment variable. + * + * @param t null or the translator linked to the class pool. + */ + public static synchronized ClassPool getDefault(Translator t) { + if (defaultPool == null) { + ClassPoolTail tail = new ClassPoolTail(); + tail.appendSystemPath(); + defaultPool = new ClassPool(tail, t); + } + + return defaultPool; + } + + private static ClassPool defaultPool = null; + + /** + * Returns the default class pool. + * The returned object is always identical. + * + * <p>This returns the result of <code>getDefault(null)</code>. + * + * @see #getDefault(Translator) + */ + public static ClassPool getDefault() { + return getDefault(null); + } + + /** + * Returns the class search path. + */ + public String toString() { + return source.toString(); + } + + /** + * Returns the <code>Translator</code> object associated with + * this <code>ClassPool</code>. + */ + public Translator getTranslator() { return translator; } + + /** + * Table of registered cflow variables. + */ + private Hashtable cflow = null; // should be synchronous. + + /** + * Records the <code>$cflow</code> variable for the field specified + * by <code>cname</code> and <code>fname</code>. + * + * @param name variable name + * @param cname class name + * @param fname field name + */ + void recordCflow(String name, String cname, String fname) { + if (cflow == null) + cflow = new Hashtable(); + + cflow.put(name, new Object[] { cname, fname }); + } + + /** + * Undocumented method. Do not use; internal-use only. + * + * @param name the name of <code>$cflow</code> variable + */ + public Object[] lookupCflow(String name) { + if (cflow == null) + cflow = new Hashtable(); + + return (Object[])cflow.get(name); + } + + /** + * Writes a class file specified with <code>classname</code> + * in the current directory. + * It never calls <code>onWrite()</code> on a translator. + * It is provided for debugging. + * + * @param classname the name of the class written on a local disk. + */ + public void debugWriteFile(String classname) + throws NotFoundException, CannotCompileException, IOException + { + debugWriteFile(classname, "."); + } + + /** + * Writes a class file specified with <code>classname</code>. + * It never calls <code>onWrite()</code> on a translator. + * It is provided for debugging. + * + * @param classname the name of the class written on a local disk. + * @param directoryName it must end without a directory separator. + */ + public void debugWriteFile(String classname, String directoryName) + throws NotFoundException, CannotCompileException, IOException + { + writeFile(classname, directoryName, false); + } + + /* void writeFile(CtClass) should not be defined since writeFile() + * may be called on the class pool that does not contain the given + * CtClass object. + */ + + /** + * Writes a class file specified with <code>classname</code> + * in the current directory. + * It calls <code>onWrite()</code> on a translator. + * + * @param classname the name of the class written on a local disk. + */ + public void writeFile(String classname) + throws NotFoundException, CannotCompileException, IOException + { + writeFile(classname, "."); + } + + /** + * Writes a class file specified with <code>classname</code> + * on a local disk. + * It calls <code>onWrite()</code> on a translator. + * + * @param classname the name of the class written on a local disk. + * @param directoryName it must end without a directory separator. + */ + public void writeFile(String classname, String directoryName) + throws NotFoundException, CannotCompileException, IOException + { + writeFile(classname, directoryName, true); + } + + private void writeFile(String classname, String directoryName, + boolean callback) + throws NotFoundException, CannotCompileException, IOException + { + String filename = directoryName + File.separatorChar + + classname.replace('.', File.separatorChar) + ".class"; + int pos = filename.lastIndexOf(File.separatorChar); + if (pos > 0) { + String dir = filename.substring(0, pos); + if (!dir.equals(".")) + new File(dir).mkdirs(); + } + + DataOutputStream out + = new DataOutputStream(new BufferedOutputStream( + new DelayedFileOutputStream(filename))); + write(classname, out, callback); + out.close(); + } + + static class DelayedFileOutputStream extends OutputStream { + private FileOutputStream file; + private String filename; + + DelayedFileOutputStream(String name) { + file = null; + filename = name; + } + + private void init() throws IOException { + if (file == null) + file = new FileOutputStream(filename); + } + + public void write(int b) throws IOException { + init(); + file.write(b); + } + + public void write(byte[] b) throws IOException { + init(); + file.write(b); + } + + public void write(byte[] b, int off, int len) throws IOException { + init(); + file.write(b, off, len); + + } + + public void flush() throws IOException { + init(); + file.flush(); + } + + public void close() throws IOException { + init(); + file.close(); + } + } + + static class LocalClassLoader extends ClassLoader { + public Class loadClass(String name, byte[] classfile) + throws ClassFormatError + { + Class c = defineClass(name, classfile, 0, classfile.length); + resolveClass(c); + return c; + } + }; + + private static LocalClassLoader classLoader = new LocalClassLoader(); + + /** + * Returns a <code>java.lang.Class</code> object that has been loaded + * by <code>writeAsClass()</code>. Note that such a class cannot be + * obtained by <code>java.lang.Class.forName()</code> because it has + * been loaded by an internal class loader. + * + * @see #writeAsClass(String) + * @see javassist.CtClass#toClass() + */ + public static Class forName(String name) throws ClassNotFoundException { + return classLoader.loadClass(name); + } + + /** + * Returns a <code>java.lang.Class</code> object. + * It calls <code>write()</code> to obtain a class file and then + * loads the obtained class file into the JVM. The returned + * <code>Class</code> object represents the loaded class. + * + * <p>This method is provided for convenience. If you need more + * complex functionality, you should write your own class loader. + * + * <p>To load a class file, this method uses an internal class loader. + * Thus, that class file is not loaded by the system class loader, + * which should have loaded this <code>ClassPool</code> class. + * The internal class loader + * loads only the classes explicitly specified by this method + * <code>writeAsClass()</code>. The other classes are loaded + * by the parent class loader (the sytem class loader) by delegation. + * Thus, if a class <code>X</code> loaded by the internal class + * loader refers to a class <code>Y</code>, then the class + * <code>Y</code> is loaded by the parent class loader. + * + * @param classname a fully-qualified class name. + * + * @see #forName(String) + * @see javassist.CtClass#toClass() + * @see javassist.Loader + */ + public Class writeAsClass(String classname) + throws NotFoundException, IOException, CannotCompileException + { + try { + return classLoader.loadClass(classname, write(classname)); + } + catch (ClassFormatError e) { + throw new CannotCompileException(e, classname); + } + } + + /** + * Returns a byte array representing the class file. + * It calls <code>onWrite()</code> on a translator. + * + * @param classname a fully-qualified class name. + */ + public byte[] write(String classname) + throws NotFoundException, IOException, CannotCompileException + { + ByteArrayOutputStream barray = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(barray); + write(classname, out, true); + out.close(); + return barray.toByteArray(); + } + + /** + * Writes a class file specified by <code>classname</code> + * to a given output stream. + * It calls <code>onWrite()</code> on a translator. + * + * <p>This method does not close the output stream in the end. + * + * @param classname a fully-qualified class name. + * @param out an output stream + */ + public void write(String classname, DataOutputStream out) + throws NotFoundException, CannotCompileException, IOException + { + write(classname, out, true); + } + + private void write(String classname, DataOutputStream out, + boolean callback) + throws NotFoundException, CannotCompileException, IOException + { + CtClass clazz = (CtClass)classes.get(classname); + if (callback && translator != null + && (clazz == null || !clazz.isFrozen())) { + translator.onWrite(this, classname); + // The CtClass object might be overwritten. + clazz = (CtClass)classes.get(classname); + } + + if (clazz == null || !clazz.isModified()) + source.write(classname, out); + else + clazz.toBytecode(out); + } + + /* for CtClassType.getClassFile2() + */ + byte[] readSource(String classname) + throws NotFoundException, IOException, CannotCompileException + { + return source.write(classname); + } + + /* + * Is invoked by CtClassType.setName(). + */ + synchronized void classNameChanged(String oldname, CtClass clazz) { + CtClass c = (CtClass)classes.get(oldname); + if (c == clazz) // must check this equation + classes.remove(c); + + String newName = clazz.getName(); + checkNotFrozen(newName, "the class with the new name is frozen."); + classes.put(newName, clazz); + } + + /* + * Is invoked by CtClassType.setName() and methods in this class. + */ + void checkNotFrozen(String classname, String errmsg) + throws RuntimeException + { + CtClass c = (CtClass)classes.get(classname); + if (c != null && c.isFrozen()) + throw new RuntimeException(errmsg); + } + + /** + * Reads a class file and constructs a <code>CtClass</code> + * object with a new name. + * This method is useful if that class file has been already + * loaded and the resulting class is frozen. + * + * @param orgName the original (fully-qualified) class name + * @param newName the new class name + */ + public CtClass getAndRename(String orgName, String newName) + throws NotFoundException + { + CtClass clazz = get0(orgName); + clazz.setName(newName); // indirectly calls + // classNameChanged() in this class + return clazz; + } + + /** + * Reads a class file from the source and returns a reference + * to the <code>CtClass</code> + * object representing that class file. If that class file has been + * already read, this method returns a reference to the + * <code>CtClass</code> created when that class file was read at the + * first time. + * + * <p>If <code>classname</code> ends with "[]", then this method + * returns a <code>CtClass</code> object for that array type. + * + * @param classname a fully-qualified class name. + */ + public synchronized CtClass get(String classname) + throws NotFoundException + { + CtClass clazz = (CtClass)classes.get(classname); + if (clazz == null) { + clazz = get0(classname); + classes.put(classname, clazz); + } + + return clazz; + } + + private CtClass get0(String classname) throws NotFoundException { + if (classname.endsWith("[]")) + return new CtArray(classname, this); + else { + checkClassName(classname); + return new CtClassType(classname, this); + } + } + + /** + * Reads class files from the source and returns an array of + * <code>CtClass</code> + * objects representing those class files. + * + * <p>If an element of <code>classnames</code> ends with "[]", + * then this method + * returns a <code>CtClass</code> object for that array type. + * + * @param classnames an array of fully-qualified class name. + */ + public CtClass[] get(String[] classnames) throws NotFoundException { + if (classnames == null) + return new CtClass[0]; + + int num = classnames.length; + CtClass[] result = new CtClass[num]; + for (int i = 0; i < num; ++i) + result[i] = get(classnames[i]); + + return result; + } + + /** + * Reads a class file and obtains a compile-time method. + * + * @param classname the class name + * @param methodname the method name + * + * @see CtClass#getDeclaredMethod(String) + */ + public CtMethod getMethod(String classname, String methodname) + throws NotFoundException + { + CtClass c = get(classname); + return c.getDeclaredMethod(methodname); + } + + /** + * Creates a new public class. + * If there already exists a class with the same name, the new class + * overwrites that previous class. + * + * @param classname a fully-qualified class name. + * @exception RuntimeException if the existing class is frozen. + */ + public CtClass makeClass(String classname) throws RuntimeException { + return makeClass(classname, null); + } + + /** + * Creates a new public class. + * If there already exists a class/interface with the same name, + * the new class overwrites that previous class. + * + * @param classname a fully-qualified class name. + * @param superclass the super class. + * @exception RuntimeException if the existing class is frozen. + */ + public synchronized CtClass makeClass(String classname, CtClass superclass) + throws RuntimeException + { + checkNotFrozen(classname, + "the class with the given name is frozen."); + CtClass clazz = new CtNewClass(classname, this, false, superclass); + classes.put(classname, clazz); + return clazz; + } + + /** + * Creates a new public interface. + * If there already exists a class/interface with the same name, + * the new interface overwrites that previous one. + * + * @param name a fully-qualified interface name. + * @exception RuntimeException if the existing interface is frozen. + */ + public CtClass makeInterface(String name) throws RuntimeException { + return makeInterface(name, null); + } + + /** + * Creates a new public interface. + * If there already exists a class/interface with the same name, + * the new interface overwrites that previous one. + * + * @param name a fully-qualified interface name. + * @param superclass the super interface. + * @exception RuntimeException if the existing interface is frozen. + */ + public synchronized CtClass makeInterface(String name, CtClass superclass) + throws RuntimeException + { + checkNotFrozen(name, + "the interface with the given name is frozen."); + CtClass clazz = new CtNewClass(name, this, true, superclass); + classes.put(name, clazz); + return clazz; + } + + /** + * Throws an exception if the class with the specified name does not + * exist. + */ + void checkClassName(String classname) + throws NotFoundException + { + source.checkClassName(classname); + } + + /** + * Appends the system search path to the end of the + * search path. The system search path + * usually includes the platform library, extension + * libraries, and the search path specified by the + * <code>-classpath</code> option or the <code>CLASSPATH</code> + * environment variable. + */ + public void appendSystemPath() { + source.appendSystemPath(); + } + + /** + * Insert a <code>ClassPath</code> object at the head of the + * search path. + * + * @see javassist.ClassPath + * @see javassist.URLClassPath + * @see javassist.ByteArrayClassPath + */ + public void insertClassPath(ClassPath cp) { + source.insertClassPath(cp); + } + + /** + * Appends a <code>ClassPath</code> object to the end of the + * search path. + * + * @see javassist.ClassPath + * @see javassist.URLClassPath + * @see javassist.ByteArrayClassPath + */ + public void appendClassPath(ClassPath cp) { + source.appendClassPath(cp); + } + + /** + * Inserts a directory or a jar (or zip) file at the head of the + * search path. + * + * @param pathname the path name of the directory or jar file. + * It must not end with a path separator ("/"). + * @exception NotFoundException if the jar file is not found. + */ + public void insertClassPath(String pathname) + throws NotFoundException + { + source.insertClassPath(pathname); + } + + /** + * Appends a directory or a jar (or zip) file to the end of the + * search path. + * + * @param pathname the path name of the directory or jar file. + * It must not end with a path separator ("/"). + * @exception NotFoundException if the jar file is not found. + */ + public void appendClassPath(String pathname) + throws NotFoundException + { + source.appendClassPath(pathname); + } + + /** + * Appends directories and jar files for search. + * + * <p>The elements of the given path list must be separated by colons + * in Unix or semi-colons in Windows. + * + * @param pathlist a (semi)colon-separated list of + * the path names of directories and jar files. + * The directory name must not end with a path + * separator ("/"). + * + * @exception NotFoundException if a jar file is not found. + */ + public void appendPathList(String pathlist) throws NotFoundException { + char sep = File.pathSeparatorChar; + int i = 0; + for (;;) { + int j = pathlist.indexOf(sep, i); + if (j < 0) { + appendClassPath(pathlist.substring(i)); + break; + } + else { + appendClassPath(pathlist.substring(i, j)); + i = j + 1; + } + } + } +} + diff --git a/src/main/javassist/ClassPoolTail.java b/src/main/javassist/ClassPoolTail.java new file mode 100644 index 00000000..1c6c7058 --- /dev/null +++ b/src/main/javassist/ClassPoolTail.java @@ -0,0 +1,307 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +import java.io.*; +import java.util.zip.*; + +final class ClassPathList { + ClassPathList next; + ClassPath path; + + ClassPathList(ClassPath p, ClassPathList n) { + next = n; + path = p; + } +} + + +final class SystemClassPath implements ClassPath { + Class thisClass; + + SystemClassPath() { + /* The value of thisClass was this.getClass() in early versions: + * + * thisClass = this.getClass(); + * + * However, this made openClassfile() not search all the system + * class paths if javassist.jar is put in jre/lib/ext/ + * (with JDK1.4). + */ + thisClass = java.lang.Object.class; + } + + public InputStream openClassfile(String classname) { + String jarname = "/" + classname.replace('.', '/') + ".class"; + return thisClass.getResourceAsStream(jarname); + } + + public String toString() { + return "*system class path*"; + } +} + + +final class DirClassPath implements ClassPath { + String directory; + + DirClassPath(String dirName) { + directory = dirName; + } + + public InputStream openClassfile(String classname) { + try { + char sep = File.separatorChar; + String filename = directory + sep + + classname.replace('.', sep) + ".class"; + return new FileInputStream(filename.toString()); + } + catch (FileNotFoundException e) {} + catch (SecurityException e) {} + return null; + } + + public String toString() { + return directory; + } +} + + +final class JarClassPath implements ClassPath { + ZipFile jarfile; + + JarClassPath(String pathname) throws NotFoundException { + try { + jarfile = new ZipFile(pathname); + return; + } + catch (ZipException e) {} + catch (IOException e) {} + throw new NotFoundException(pathname); + } + + public InputStream openClassfile(String classname) + throws NotFoundException + { + try { + String jarname = classname.replace('.', '/') + ".class"; + ZipEntry ze = jarfile.getEntry(jarname); + if (ze != null) + return jarfile.getInputStream(ze); + else + return null; // not found + } + catch (ZipException e) {} + catch (IOException e) {} + throw new NotFoundException("broken jar file?: " + + jarfile.getName()); + } + + public String toString() { + return jarfile.toString(); + } +} + +final class ClassPoolTail extends ClassPool { + protected ClassPathList pathList; + private Class thisClass; + + public ClassPoolTail() { + pathList = null; + thisClass = getClass(); + } + + public String toString() { + StringBuffer buf = new StringBuffer(); + buf.append("[class path: "); + ClassPathList list = pathList; + while (list != null) { + buf.append(list.path.toString()); + buf.append(File.pathSeparatorChar); + list = list.next; + } + + buf.append(']'); + return buf.toString(); + } + + public byte[] write(String classname) + throws NotFoundException, IOException + { + return readClassfile(classname); + } + + public void write(String classname, DataOutputStream out) + throws NotFoundException, CannotCompileException, IOException + { + byte[] b = write(classname); + out.write(b, 0, b.length); + } + + public CtClass get(String classname) throws NotFoundException { + throw new RuntimeException("fatal error"); + } + + public CtClass makeClass(String classname) { + throw new RuntimeException("fatal error"); + } + + void checkClassName(String classname) + throws NotFoundException + { + InputStream fin = openClassfile(classname); + try { + fin.close(); + } + catch (IOException e) { /* ignore */ } + } + + public synchronized void insertClassPath(ClassPath cp) { + pathList = new ClassPathList(cp, pathList); + } + + public synchronized void appendClassPath(ClassPath cp) { + ClassPathList tail = new ClassPathList(cp, null); + ClassPathList list = pathList; + if (list == null) + pathList = tail; + else { + while (list.next != null) + list = list.next; + + list.next = tail; + } + } + + public void appendSystemPath() { + appendClassPath(new SystemClassPath()); + } + + public void insertClassPath(String pathname) + throws NotFoundException + { + insertClassPath(makePathObject(pathname)); + } + + public void appendClassPath(String pathname) + throws NotFoundException + { + appendClassPath(makePathObject(pathname)); + } + + private static ClassPath makePathObject(String pathname) + throws NotFoundException + { + if (pathname.endsWith(".jar") || pathname.endsWith(".zip") + || pathname.endsWith(".JAR") || pathname.endsWith(".ZIP")) + return new JarClassPath(pathname); + else + return new DirClassPath(pathname); + } + + /** + * Obtains the contents of the class file for the class + * specified by <code>classname</code>. + * + * @param classname a fully-qualified class name + */ + byte[] readClassfile(String classname) + throws NotFoundException, IOException + { + InputStream fin = openClassfile(classname); + byte[] b = readStream(fin); + fin.close(); + return b; + } + + /** + * Opens the class file for the class specified by + * <code>classname</code>. + * + * @param classname a fully-qualified class name + */ + public InputStream openClassfile(String classname) + throws NotFoundException + { + ClassPathList list = pathList; + InputStream ins = null; + NotFoundException error = null; + while (list != null) { + try { + ins = list.path.openClassfile(classname); + } + catch (NotFoundException e) { + if (error == null) + error = e; + } + + if (ins == null) + list = list.next; + else + return ins; + } + + if (error != null) + throw error; + else + throw new NotFoundException(classname); + } + + /** + * Reads an input stream until it reaches the end. + * + * @return the contents of that input stream + */ + public static byte[] readStream(InputStream fin) throws IOException { + byte[][] bufs = new byte[8][]; + int bufsize = 4096; + + for (int i = 0; i < 8; ++i) { + bufs[i] = new byte[bufsize]; + int size = 0; + int len = 0; + do { + len = fin.read(bufs[i], size, bufsize - size); + if (len >= 0) + size += len; + else { + byte[] result = new byte[bufsize - 4096 + size]; + int s = 0; + for (int j = 0; j < i; ++j) { + System.arraycopy(bufs[j], 0, result, s, s + 4096); + s = s + s + 4096; + } + + System.arraycopy(bufs[i], 0, result, s, size); + return result; + } + } while (size < bufsize); + bufsize *= 2; + } + + throw new IOException("too much data"); + } +} diff --git a/src/main/javassist/CodeConverter.java b/src/main/javassist/CodeConverter.java new file mode 100644 index 00000000..051699b4 --- /dev/null +++ b/src/main/javassist/CodeConverter.java @@ -0,0 +1,376 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +import javassist.bytecode.*; +import javassist.convert.*; + +/** + * Simple translator of method bodies + * (also see the <code>javassist.expr</code> package). + * + * <p>Instances of this class specifies how to instrument of the + * bytecodes representing a method body. They are passed to + * <code>CtClass.instrument()</code> or + * <code>CtMethod.instrument()</code> as a parameter. + * + * <p>Example: + * <ul><pre> + * ClassPool cp = ClassPool.getDefault(); + * CtClass point = cp.get("Point"); + * CtClass singleton = cp.get("Singleton"); + * CtClass client = cp.get("Client"); + * CodeConverter conv = new CodeConverter(); + * conv.replaceNew(point, singleton, "makePoint"); + * client.instrument(conv); + * </pre></ul> + * + * <p>This program substitutes "<code>Singleton.makePoint()</code>" + * for all occurrences of "<code>new Point()</code>" + * appearing in methods declared in a <code>Client</code> class. + * + * @see javassist.CtClass#instrument(CodeConverter) + * @see javassist.CtMethod#instrument(CodeConverter) + * @see javassist.expr.ExprEditor + */ +public class CodeConverter { + Transformer transformers = null; + + /** + * Modify a method body so that instantiation of the specified class + * is replaced with a call to the specified static method. For example, + * <code>replaceNew(ctPoint, ctSingleton, "createPoint")</code> + * (where <code>ctPoint</code> and <code>ctSingleton</code> are + * compile-time classes for class <code>Point</code> and class + * <code>Singleton</code>, respectively) + * replaces all occurrences of: + * + * <ul><code>new Point(x, y)</code></ul> + * + * in the method body with: + * + * <ul><code>Singleton.createPoint(x, y)</code></ul> + * + * <p>This enables to intercept instantiation of <code>Point</code> + * and change the samentics. For example, the following + * <code>createPoint()</code> implements the singleton pattern: + * + * <ul><pre>public static Point createPoint(int x, int y) { + * if (aPoint == null) + * aPoint = new Point(x, y); + * return aPoint; + * } + * </pre></ul> + * + * <p>The static method call substituted for the original <code>new</code> + * expression must be + * able to receive the same set of parameters as the original + * constructor. If there are multiple constructors with different + * parameter types, then there must be multiple static methods + * with the same name but different parameter types. + * + * <p>The return type of the substituted static method must be + * the exactly same as the type of the instantiated class specified by + * <code>newClass</code>. + * + * @param newClass the instantiated class. + * @param calledClass the class in which the static method is + * declared. + * @param calledMethod the name of the static method. + */ + public void replaceNew(CtClass newClass, + CtClass calledClass, String calledMethod) { + transformers = new TransformNew(transformers, newClass.getName(), + calledClass.getName(), calledMethod); + } + + /** + * Modify a method body so that field read/write expressions access + * a different field from the original one. + * + * <p>Note that this method changes only the filed name and the class + * declaring the field; the type of the target object does not change. + * Therefore, the substituted field must be declared in the same class + * or a superclass of the original class. + * + * <p>Also, <code>clazz</code> and <code>newClass</code> must specify + * the class directly declaring the field. They must not specify + * a subclass of that class. + * + * @param field the originally accessed field. + * @param newClass the class declaring the substituted field. + * @param newFieldname the name of the substituted field. + */ + public void redirectFieldAccess(CtField field, + CtClass newClass, String newFieldname) { + transformers = new TransformFieldAccess(transformers, field, + newClass.getName(), + newFieldname); + } + + /** + * Modify a method body so that an expression reading the specified + * field is replaced with a call to the specified <i>static</i> method. + * This static method receives the target object of the original + * read expression as a parameter. It must return a value of + * the same type as the field. + * + * <p>For example, the program below + * + * <ul><pre>Point p = new Point(); + * int newX = p.x + 3;</pre></ul> + * + * <p>can be translated into: + * + * <ul><pre>Point p = new Point(); + * int newX = Accessor.readX(p) + 3;</pre></ul> + * + * <p>where + * + * <ul><pre>public class Accessor { + * public static int readX(Object target) { ... } + * }</pre></ul> + * + * <p>The type of the parameter of <code>readX()</code> must + * be <code>java.lang.Object</code> independently of the actual + * type of <code>target</code>. The return type must be the same + * as the field type. + * + * @param field the field. + * @param calledClass the class in which the static method is + * declared. + * @param calledMethod the name of the static method. + */ + public void replaceFieldRead(CtField field, + CtClass calledClass, String calledMethod) { + transformers = new TransformReadField(transformers, field, + calledClass.getName(), + calledMethod); + } + + /** + * Modify a method body so that an expression writing the specified + * field is replaced with a call to the specified static method. + * This static method receives two parameters: the target object of + * the original + * write expression and the assigned value. The return type of the + * static method is <code>void</code>. + * + * <p>For example, the program below + * + * <ul><pre>Point p = new Point(); + * p.x = 3;</pre></ul> + * + * <p>can be translated into: + * + * <ul><pre>Point p = new Point(); + * Accessor.writeX(3);</pre></ul> + * + * <p>where + * + * <ul><pre>public class Accessor { + * public static void writeX(Object target, int value) { ... } + * }</pre></ul> + * + * <p>The type of the first parameter of <code>writeX()</code> must + * be <code>java.lang.Object</code> independently of the actual + * type of <code>target</code>. The type of the second parameter + * is the same as the field type. + * + * @param field the field. + * @param calledClass the class in which the static method is + * declared. + * @param calledMethod the name of the static method. + */ + public void replaceFieldWrite(CtField field, + CtClass calledClass, String calledMethod) { + transformers = new TransformWriteField(transformers, field, + calledClass.getName(), + calledMethod); + } + + /** + * Modify method invocations in a method body so that a different + * method is invoked. + * + * <p>Note that the target object, the parameters, or + * the type of invocation + * (static method call, interface call, or private method call) + * are not modified. Only the method name is changed. The substituted + * method must have the same signature that the original one has. + * If the original method is a static method, the substituted method + * must be static. + * + * @param origMethod original method + * @param substMethod substituted method + */ + public void redirectMethodCall(CtMethod origMethod, + CtMethod substMethod) + throws CannotCompileException + { + String d1 = origMethod.getMethodInfo2().getDescriptor(); + String d2 = substMethod.getMethodInfo2().getDescriptor(); + if (!d1.equals(d2)) + throw new CannotCompileException("signature mismatch"); + + transformers = new TransformCall(transformers, origMethod, + substMethod); + } + + /** + * Insert a call to another method before an existing method call. + * That "before" method must be static. The return type must be + * <code>void</code>. As parameters, the before method receives + * the target object and all the parameters to the originally invoked + * method. For example, if the originally invoked method is + * <code>move()</code>: + * + * <ul><pre>class Point { + * Point move(int x, int y) { ... } + * }</pre></ul> + * + * <p>Then the before method must be something like this: + * + * <ul><pre>class Verbose { + * static void print(Point target, int x, int y) { ... } + * }</pre></ul> + * + * <p>The <code>CodeConverter</code> would translate bytecode + * equivalent to: + * + * <ul><pre>Point p2 = p.move(x + y, 0);</pre></ul> + * + * <p>into the bytecode equivalent to: + * + * <ul><pre>int tmp1 = x + y; + * int tmp2 = 0; + * Verbose.print(p, tmp1, tmp2); + * Point p2 = p.move(tmp1, tmp2);</pre></ul> + * + * @param origMethod the method originally invoked. + * @param beforeMethod the method invoked before + * <code>origMethod</code>. + */ + public void insertBeforeMethod(CtMethod origMethod, + CtMethod beforeMethod) + throws CannotCompileException + { + try { + transformers = new TransformBefore(transformers, origMethod, + beforeMethod); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + } + + /** + * Inserts a call to another method after an existing method call. + * That "after" method must be static. The return type must be + * <code>void</code>. As parameters, the after method receives + * the target object and all the parameters to the originally invoked + * method. For example, if the originally invoked method is + * <code>move()</code>: + * + * <ul><pre>class Point { + * Point move(int x, int y) { ... } + * }</pre></ul> + * + * <p>Then the after method must be something like this: + * + * <ul><pre>class Verbose { + * static void print(Point target, int x, int y) { ... } + * }</pre></ul> + * + * <p>The <code>CodeConverter</code> would translate bytecode + * equivalent to: + * + * <ul><pre>Point p2 = p.move(x + y, 0);</pre></ul> + * + * <p>into the bytecode equivalent to: + * + * <ul><pre>int tmp1 = x + y; + * int tmp2 = 0; + * Point p2 = p.move(tmp1, tmp2); + * Verbose.print(p, tmp1, tmp2);</pre></ul> + * + * @param origMethod the method originally invoked. + * @param afterMethod the method invoked after + * <code>origMethod</code>. + */ + public void insertAfterMethod(CtMethod origMethod, + CtMethod afterMethod) + throws CannotCompileException + { + try { + transformers = new TransformAfter(transformers, origMethod, + afterMethod); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + } + + /** + * Performs code conversion. + */ + void doit(CtClass clazz, MethodInfo minfo, ConstPool cp) + throws CannotCompileException + { + Transformer t; + + CodeAttribute codeAttr = minfo.getCodeAttribute(); + if (codeAttr == null || transformers == null) + return; + + for (t = transformers; t != null; t = t.getNext()) + t.initialize(cp, codeAttr); + + CodeIterator iterator = codeAttr.iterator(); + while (iterator.hasNext()) { + try { + int pos = iterator.next(); + for (t = transformers; t != null; t = t.getNext()) + pos = t.transform(clazz, pos, iterator, cp); + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } + + int locals = 0; + for (t = transformers; t != null; t = t.getNext()) { + int s = t.extraLocals(); + if (s > locals) + locals = s; + } + + for (t = transformers; t != null; t = t.getNext()) + t.clean(); + + codeAttr.setMaxLocals(codeAttr.getMaxLocals() + locals); + } +} diff --git a/src/main/javassist/CtArray.java b/src/main/javassist/CtArray.java new file mode 100644 index 00000000..904f15c9 --- /dev/null +++ b/src/main/javassist/CtArray.java @@ -0,0 +1,93 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +/** + * Array types. + */ +final class CtArray extends CtClass { + protected ClassPool pool; + + // the name of array type ends with "[]". + CtArray(String name, ClassPool cp) { + super(name); + pool = cp; + } + + public ClassPool getClassPool() { + return pool; + } + + public boolean isArray() { + return true; + } + + public boolean subtypeOf(CtClass clazz) throws NotFoundException { + if (super.subtypeOf(clazz)) + return true; + + String cname = clazz.getName(); + if (cname.equals(javaLangObject) + || cname.equals("java.lang.Cloneable")) + return true; + + return clazz.isArray() + && getComponentType().subtypeOf(clazz.getComponentType()); + } + + public CtClass getComponentType() throws NotFoundException { + String name = getName(); + return pool.get(name.substring(0, name.length() - 2)); + } + + public CtClass getSuperclass() throws NotFoundException { + return pool.get(javaLangObject); + } + + public CtMethod[] getMethods() { + try { + return getSuperclass().getMethods(); + } + catch (NotFoundException e) { + return super.getMethods(); + } + } + + public CtMethod getMethod(String name, String desc) + throws NotFoundException + { + return getSuperclass().getMethod(name, desc); + } + + public CtConstructor[] getConstructors() { + try { + return getSuperclass().getConstructors(); + } + catch (NotFoundException e) { + return super.getConstructors(); + } + } +} diff --git a/src/main/javassist/CtBehavior.java b/src/main/javassist/CtBehavior.java new file mode 100644 index 00000000..220612b7 --- /dev/null +++ b/src/main/javassist/CtBehavior.java @@ -0,0 +1,582 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +import javassist.bytecode.*; +import javassist.compiler.Javac; +import javassist.compiler.CompileError; +import javassist.expr.ExprEditor; + +/** + * <code>CtBehavior</code> is the abstract super class of + * <code>CtMethod</code> and <code>CtConstructor</code>. + */ +public abstract class CtBehavior extends CtMember { + protected MethodInfo methodInfo; + + protected CtBehavior(CtClass clazz, MethodInfo minfo) { + super(clazz); + methodInfo = minfo; + } + + /** + * Returns the MethodInfo representing this member in the + * class file. + */ + public MethodInfo getMethodInfo() { + declaringClass.checkModify(); + return methodInfo; + } + + /** + * Undocumented method. Do not use; internal-use only. + */ + public MethodInfo getMethodInfo2() { return methodInfo; } + + /** + * Obtains the modifiers of the member. + * + * @return modifiers encoded with + * <code>javassist.Modifier</code>. + * @see Modifier + */ + public int getModifiers() { + return AccessFlag.toModifier(methodInfo.getAccessFlags()); + } + + /** + * Sets the encoded modifiers of the member. + * + * @see Modifier + */ + public void setModifiers(int mod) { + declaringClass.checkModify(); + methodInfo.setAccessFlags(AccessFlag.of(mod)); + } + + /** + * Obtains the name of this member. + * + * @see CtConstructor#getName() + */ + public abstract String getName(); + + /** + * Obtains parameter types of this member. + */ + public CtClass[] getParameterTypes() throws NotFoundException { + return Descriptor.getParameterTypes(methodInfo.getDescriptor(), + declaringClass.getClassPool()); + } + + /** + * Obtains the type of the returned value. + */ + CtClass getReturnType0() throws NotFoundException { + return Descriptor.getReturnType(methodInfo.getDescriptor(), + declaringClass.getClassPool()); + } + + /** + * Returns the character string representing the parameter types + * and the return type. If two members have the same parameter types + * and the return type, <code>getSignature()</code> returns the + * same string. + */ + public String getSignature() { + return methodInfo.getDescriptor(); + } + + /** + * Obtains exceptions that this member may throw. + */ + public CtClass[] getExceptionTypes() throws NotFoundException { + String[] exceptions; + ExceptionsAttribute ea = methodInfo.getExceptionsAttribute(); + if (ea == null) + exceptions = null; + else + exceptions = ea.getExceptions(); + + return declaringClass.getClassPool().get(exceptions); + } + + /** + * Sets exceptions that this member may throw. + */ + public void setExceptionTypes(CtClass[] types) throws NotFoundException { + declaringClass.checkModify(); + if (types == null) { + methodInfo.removeExceptionsAttribute(); + return; + } + + String[] names = new String[types.length]; + for (int i = 0; i < types.length; ++i) + names[i] = types[i].getName(); + + ExceptionsAttribute ea = methodInfo.getExceptionsAttribute(); + if (ea == null) { + ea = new ExceptionsAttribute(methodInfo.getConstPool()); + methodInfo.setExceptionsAttribute(ea); + } + + ea.setExceptions(names); + } + + /** + * Sets a member body. + * + * @param src the source code representing the member body. + * It must be a single statement or block. + */ + public void setBody(String src) throws CannotCompileException { + declaringClass.checkModify(); + try { + Javac jv = new Javac(declaringClass); + Bytecode b = jv.compileBody(this, src); + methodInfo.setCodeAttribute(b.toCodeAttribute()); + methodInfo.setAccessFlags(methodInfo.getAccessFlags() + & ~AccessFlag.ABSTRACT); + } + catch (CompileError e) { + throw new CannotCompileException(e); + } + } + + static void setBody0(CtClass srcClass, MethodInfo srcInfo, + CtClass destClass, MethodInfo destInfo, + ClassMap map) + throws CannotCompileException + { + destClass.checkModify(); + + if (map == null) + map = new ClassMap(); + + map.put(srcClass.getName(), destClass.getName()); + try { + CodeAttribute cattr = srcInfo.getCodeAttribute(); + if (cattr != null) { + ConstPool cp = destInfo.getConstPool(); + CodeAttribute ca = (CodeAttribute)cattr.copy(cp, map); + destInfo.setCodeAttribute(ca); + } + } + catch (CodeAttribute.RuntimeCopyException e) { + /* the exception may be thrown by copy() in CodeAttribute. + */ + throw new CannotCompileException(e); + } + + destInfo.setAccessFlags(destInfo.getAccessFlags() + & ~AccessFlag.ABSTRACT); + } + + /** + * Obtains an attribute with the given name. + * If that attribute is not found in the class file, this + * method returns null. + * + * @param name attribute name + */ + public byte[] getAttribute(String name) { + AttributeInfo ai = methodInfo.getAttribute(name); + if (ai == null) + return null; + else + return ai.get(); + } + + /** + * Adds an attribute. The attribute is saved in the class file. + * + * @param name attribute name + * @param data attribute value + */ + public void setAttribute(String name, byte[] data) { + declaringClass.checkModify(); + methodInfo.addAttribute(new AttributeInfo(methodInfo.getConstPool(), + name, data)); + } + + /** + * Declares to use <code>$cflow</code> for this member; + * If <code>$cflow</code> is used, the class files modified + * with Javassist requires a support class + * <code>javassist.runtime.Cflow</code> at runtime + * (other Javassist classes are not required at runtime). + * + * <p>Every <code>$cflow</code> variable is given a unique name. + * For example, if the given name is <code>"Point.paint"</code>, + * then the variable is indicated by <code>$cflow(Point.paint)</code>. + * + * @param name <code>$cflow</code> name. It can include + * alphabets, numbers, <code>_</code>, + * <code>$</code>, and <code>.</code> (dot). + * + * @see javassist.runtime.Cflow + */ + public void useCflow(String name) throws CannotCompileException { + CtClass cc = declaringClass; + cc.checkModify(); + ClassPool pool = cc.getClassPool(); + String fname; + int i = 0; + while (true) { + fname = "_cflow$" + i++; + try { + cc.getDeclaredField(fname); + } + catch(NotFoundException e) { + break; + } + } + + pool.recordCflow(name, declaringClass.getName(), fname); + try { + CtClass type = pool.get("javassist.runtime.Cflow"); + CtField field = new CtField(type, fname, cc); + field.setModifiers(Modifier.PUBLIC | Modifier.STATIC); + cc.addField(field, CtField.Initializer.byNew(type)); + insertBefore(fname + ".enter();"); + String src = fname + ".exit();"; + insertAfter(src, true); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + } + + /** + * Modifies the member body. + * + * @param converter specifies how to modify. + */ + public void instrument(CodeConverter converter) + throws CannotCompileException + { + declaringClass.checkModify(); + ConstPool cp = methodInfo.getConstPool(); + converter.doit(getDeclaringClass(), methodInfo, cp); + } + + /** + * Modifies the member body. + * + * @param editor specifies how to modify. + */ + public void instrument(ExprEditor editor) + throws CannotCompileException + { + // if the class is not frozen, + // does not trun the modified flag on. + if (declaringClass.isFrozen()) + declaringClass.checkModify(); + + if (editor.doit(declaringClass, methodInfo)) + declaringClass.checkModify(); + } + + /** + * Inserts bytecode at the beginning of the body. + * + * @param src the source code representing the inserted bytecode. + * It must be a single statement or block. + */ + public void insertBefore(String src) throws CannotCompileException { + declaringClass.checkModify(); + CodeAttribute ca = methodInfo.getCodeAttribute(); + CodeIterator iterator = ca.iterator(); + Javac jv = new Javac(declaringClass); + try { + jv.recordParams(getParameterTypes(), + Modifier.isStatic(getModifiers())); + jv.compileStmnt(src); + Bytecode b = jv.getBytecode(); + int stack = b.getMaxStack(); + int locals = b.getMaxLocals(); + + if (stack > ca.getMaxStack()) + ca.setMaxStack(stack); + + if (locals > ca.getMaxLocals()) + ca.setMaxLocals(locals); + + int pos = iterator.insertEx(b.get()); + iterator.insert(b.getExceptionTable(), pos); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + catch (CompileError e) { + throw new CannotCompileException(e); + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } + + /** + * Inserts bytecode at the end of the body. + * The bytecode is inserted just before every return insturction. + * It is not executed when an exception is thrown. + * + * @param src the source code representing the inserted bytecode. + * It must be a single statement or block. + */ + public void insertAfter(String src) + throws CannotCompileException + { + insertAfter(src, false); + } + + /** + * Inserts bytecode at the end of the body. + * The bytecode is inserted just before every return insturction. + * + * @param src the source code representing the inserted bytecode. + * It must be a single statement or block. + * @param asFinally true if the inserted bytecode is executed + * not only when the control normally returns + * but also when an exception is thrown. + */ + public void insertAfter(String src, boolean asFinally) + throws CannotCompileException + { + declaringClass.checkModify(); + CodeAttribute ca = methodInfo.getCodeAttribute(); + CodeIterator iterator = ca.iterator(); + int retAddr = ca.getMaxLocals(); + Bytecode b = new Bytecode(methodInfo.getConstPool(), 0, retAddr + 1); + b.setStackDepth(ca.getMaxStack() + 1); + Javac jv = new Javac(b, declaringClass); + try { + jv.recordParams(getParameterTypes(), + Modifier.isStatic(getModifiers())); + CtClass rtype = getReturnType0(); + int varNo = jv.recordReturnType(rtype, true); + boolean isVoid = rtype == CtClass.voidType; + + int handlerLen = insertAfterHandler(asFinally, b, rtype); + + b.addAstore(retAddr); + if (isVoid) { + b.addOpcode(Opcode.ACONST_NULL); + b.addAstore(varNo); + jv.compileStmnt(src); + } + else { + b.addStore(varNo, rtype); + jv.compileStmnt(src); + b.addLoad(varNo, rtype); + } + + b.addRet(retAddr); + ca.setMaxStack(b.getMaxStack()); + ca.setMaxLocals(b.getMaxLocals()); + + int gapPos = iterator.append(b.get()); + iterator.append(b.getExceptionTable(), gapPos); + + if (asFinally) + ca.getExceptionTable().add(0, gapPos, gapPos, 0); + + int gapLen = iterator.getCodeLength() - gapPos - handlerLen; + int subr = iterator.getCodeLength() - gapLen; + + while (iterator.hasNext()) { + int pos = iterator.next(); + if (pos >= subr) + break; + + int c = iterator.byteAt(pos); + if (c == Opcode.ARETURN || c == Opcode.IRETURN + || c == Opcode.FRETURN || c == Opcode.LRETURN + || c == Opcode.DRETURN || c == Opcode.RETURN) { + if (subr - pos > Short.MAX_VALUE - 5) { + iterator.insertGap(pos, 5); + iterator.writeByte(Opcode.JSR_W, pos); + iterator.write32bit(subr - pos + 5, pos + 1); + } + else { + iterator.insertGap(pos, 3); + iterator.writeByte(Opcode.JSR, pos); + iterator.write16bit(subr - pos + 3, pos + 1); + } + + subr = iterator.getCodeLength() - gapLen; + } + } + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + catch (CompileError e) { + throw new CannotCompileException(e); + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } + + private int insertAfterHandler(boolean asFinally, Bytecode b, + CtClass rtype) + { + if (!asFinally) + return 0; + + int var = b.getMaxLocals(); + b.incMaxLocals(1); + int pc = b.currentPc(); + b.addAstore(var); + if (rtype.isPrimitive()) { + char c = ((CtPrimitiveType)rtype).getDescriptor(); + if (c == 'D') + b.addDconst(0.0); + else if (c == 'F') + b.addFconst(0); + else if (c == 'J') + b.addLconst(0); + else if (c != 'V') // int, boolean, char, short, ... + b.addIconst(0); + } + else + b.addOpcode(Opcode.ACONST_NULL); + + b.addOpcode(Opcode.JSR); + int pc2 = b.currentPc(); + b.addIndex(0); // correct later + b.addAload(var); + b.addOpcode(Opcode.ATHROW); + int pc3 = b.currentPc(); + b.write16bit(pc2, pc3 - pc2 + 1); + return pc3 - pc; + } + + /* -- OLD version -- + + public void insertAfter(String src) throws CannotCompileException { + declaringClass.checkModify(); + CodeAttribute ca = methodInfo.getCodeAttribute(); + CodeIterator iterator = ca.iterator(); + Bytecode b = new Bytecode(methodInfo.getConstPool(), + ca.getMaxStack(), ca.getMaxLocals()); + b.setStackDepth(ca.getMaxStack()); + Javac jv = new Javac(b, declaringClass); + try { + jv.recordParams(getParameterTypes(), + Modifier.isStatic(getModifiers())); + CtClass rtype = getReturnType0(); + int varNo = jv.recordReturnType(rtype, true); + boolean isVoid = rtype == CtClass.voidType; + if (isVoid) { + b.addOpcode(Opcode.ACONST_NULL); + b.addAstore(varNo); + jv.compileStmnt(src); + } + else { + b.addStore(varNo, rtype); + jv.compileStmnt(src); + b.addLoad(varNo, rtype); + } + + byte[] code = b.get(); + ca.setMaxStack(b.getMaxStack()); + ca.setMaxLocals(b.getMaxLocals()); + while (iterator.hasNext()) { + int pos = iterator.next(); + int c = iterator.byteAt(pos); + if (c == Opcode.ARETURN || c == Opcode.IRETURN + || c == Opcode.FRETURN || c == Opcode.LRETURN + || c == Opcode.DRETURN || c == Opcode.RETURN) + iterator.insert(pos, code); + } + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + catch (CompileError e) { + throw new CannotCompileException(e); + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } + */ + + /** + * Adds a catch clause that handles an exception thrown in the + * body. The catch clause must end with a return or throw statement. + * + * @param src the source code representing the catch clause. + * It must be a single statement or block. + * @param exceptionType the type of the exception handled by the + * catch clause. + * @param exceptionName the name of the variable containing the + * caught exception. + */ + public void addCatch(String src, CtClass exceptionType, + String exceptionName) + throws CannotCompileException + { + declaringClass.checkModify(); + ConstPool cp = methodInfo.getConstPool(); + CodeAttribute ca = methodInfo.getCodeAttribute(); + CodeIterator iterator = ca.iterator(); + Bytecode b = new Bytecode(cp, ca.getMaxStack(), ca.getMaxLocals()); + b.setStackDepth(1); + Javac jv = new Javac(b, declaringClass); + try { + jv.recordParams(getParameterTypes(), + Modifier.isStatic(getModifiers())); + int var = jv.recordVariable(exceptionType, exceptionName); + b.addAstore(var); + jv.compileStmnt(src); + + int stack = b.getMaxStack(); + int locals = b.getMaxLocals(); + + if (stack > ca.getMaxStack()) + ca.setMaxStack(stack); + + if (locals > ca.getMaxLocals()) + ca.setMaxLocals(locals); + + int len = iterator.getCodeLength(); + int pos = iterator.append(b.get()); + ca.getExceptionTable().add(0, len, len, + cp.addClassInfo(exceptionType)); + iterator.append(b.getExceptionTable(), pos); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + catch (CompileError e) { + throw new CannotCompileException(e); + } + } +} diff --git a/src/main/javassist/CtClass.java b/src/main/javassist/CtClass.java new file mode 100644 index 00000000..1d70b8bf --- /dev/null +++ b/src/main/javassist/CtClass.java @@ -0,0 +1,819 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +import java.io.DataOutputStream; +import java.io.IOException; +import javassist.bytecode.*; +import java.util.Collection; +import javassist.expr.ExprEditor; + +// Subclasses of CtClass: CtClassType, CtPrimitiveType, and CtArray + +/** + * An instance of <code>CtClass</code> represents a class. + * It is obtained from <code>ClassPool</code>. + * + * @see ClassPool#get(String) + */ +public abstract class CtClass { + protected String qualifiedName; + + /** + * The version number of this release. + */ + public static final String version = "2.4"; + + static final String javaLangObject = "java.lang.Object"; + + /** + * The <code>CtClass</code> object representing + * the <code>boolean</code> type. + */ + public static CtClass booleanType; + + /** + * The <code>CtClass</code> object representing + * the <code>char</code> type. + */ + public static CtClass charType; + + /** + * The <code>CtClass</code> object representing + * the <code>byte</code> type. + */ + public static CtClass byteType; + + /** + * The <code>CtClass</code> object representing + * the <code>short</code> type. + */ + public static CtClass shortType; + + /** + * The <code>CtClass</code> object representing + * the <code>int</code> type. + */ + public static CtClass intType; + + /** + * The <code>CtClass</code> object representing + * the <code>long</code> type. + */ + public static CtClass longType; + + /** + * The <code>CtClass</code> object representing + * the <code>float</code> type. + */ + public static CtClass floatType; + + /** + * The <code>CtClass</code> object representing + * the <code>double</code> type. + */ + public static CtClass doubleType; + + /** + * The <code>CtClass</code> object representing + * the <code>void</code> type. + */ + public static CtClass voidType; + + static CtClass[] primitiveTypes; + + static { + primitiveTypes = new CtClass[9]; + + booleanType = new CtPrimitiveType("boolean", 'Z', "java.lang.Boolean", + "booleanValue", "()Z", Opcode.IRETURN, + Opcode.T_BOOLEAN, 1); + primitiveTypes[0] = booleanType; + + charType = new CtPrimitiveType("char", 'C', "java.lang.Character", + "charValue", "()C", Opcode.IRETURN, + Opcode.T_CHAR, 1); + primitiveTypes[1] = charType; + + byteType = new CtPrimitiveType("byte", 'B', "java.lang.Byte", + "byteValue", "()B", Opcode.IRETURN, + Opcode.T_BYTE, 1); + primitiveTypes[2] = byteType; + + shortType = new CtPrimitiveType("short", 'S', "java.lang.Short", + "shortValue", "()S", Opcode.IRETURN, + Opcode.T_SHORT, 1); + primitiveTypes[3] = shortType; + + intType = new CtPrimitiveType("int", 'I', "java.lang.Integer", + "intValue", "()I", Opcode.IRETURN, + Opcode.T_INT, 1); + primitiveTypes[4] = intType; + + longType = new CtPrimitiveType("long", 'J', "java.lang.Long", + "longValue", "()J", Opcode.LRETURN, + Opcode.T_LONG, 2); + primitiveTypes[5] = longType; + + floatType = new CtPrimitiveType("float", 'F', "java.lang.Float", + "floatValue", "()F", Opcode.FRETURN, + Opcode.T_FLOAT, 1); + primitiveTypes[6] = floatType; + + doubleType = new CtPrimitiveType("double", 'D', "java.lang.Double", + "doubleValue", "()D", Opcode.DRETURN, + Opcode.T_DOUBLE, 2); + primitiveTypes[7] = doubleType; + + voidType = new CtPrimitiveType("void", 'V', "java.lang.Void", + null, null, Opcode.RETURN, 0, 0); + primitiveTypes[8] = voidType; + } + + protected CtClass(String name) { + qualifiedName = name; + } + + /** + * Returns a <code>ClassPool</code> for this class. + */ + public ClassPool getClassPool() { return null; } + + /** + * Returns a class file for this class. + * + * <p>This method is not available if <code>isFrozen()</code> + * is true. + */ + public ClassFile getClassFile() { + checkModify(); + return getClassFile2(); + } + + /** + * Undocumented method. Do not use; internal-use only. + */ + public ClassFile getClassFile2() { return null; } + + /** + * Returns true if the definition of the class has been modified. + */ + public boolean isModified() { return false; } + + /** + * Returns true if the class has been loaded and thus it cannot be + * modified any more. + */ + public boolean isFrozen() { return true; } + + void checkModify() throws RuntimeException { + if (isFrozen()) + throw new RuntimeException("the class is frozen"); + + // isModified() must return true after this method is invoked. + } + + /** + * Defrosts the class so that the class can be modified. + * + * To avoid changes that are never reflected, + * the class is frozen to be unmodifiable if it is loaded or + * written out. This method should be called only in a case + * that the class will be reloaded or written out later again. + */ + public void defrost() { + throw new RuntimeException("cannot defrost " + getName()); + } + + /** + * Returns <code>true</code> if this object represents a primitive + * Java type: boolean, byte, char, short, int, long, float, double, + * or void. + */ + public boolean isPrimitive() { return false; } + + /** + * Returns <code>true</code> if this object represents an array type. + */ + public boolean isArray() { + return false; + } + + /** + * If this object represents an array, this method returns the component + * type of the array. Otherwise, it returns <code>null</code>. + */ + public CtClass getComponentType() throws NotFoundException { + return null; + } + + /** + * Returns <code>true</code> if this class extends or implements + * <code>clazz</code>. It also returns <code>true</code> if + * this class is the same as <code>clazz<code>. + */ + public boolean subtypeOf(CtClass clazz) throws NotFoundException { + return this == clazz || getName().equals(clazz.getName()); + } + + /** + * Obtains the fully-qualified name of the class. + */ + public String getName() { return qualifiedName; } + + /** + * Obtains the not-qualified class name. + */ + public final String getSimpleName() { + String qname = qualifiedName; + int index = qname.lastIndexOf('.'); + if (index < 0) + return qname; + else + return qname.substring(index + 1); + } + + /** + * Obtains the package name. It may be <code>null</code>. + */ + public final String getPackageName() { + String qname = qualifiedName; + int index = qname.lastIndexOf('.'); + if (index < 0) + return null; + else + return qname.substring(0, index); + } + + /** + * Sets the class name + * + * @param name fully-qualified name + */ + public void setName(String name) { + checkModify(); + if (name != null) + qualifiedName = name; + } + + /** + * Substitutes <code>newName</code> for all occurrences of a class + * name <code>oldName</code> in the class file. + * + * @param oldName replaced class name + * @param newName substituted class name + */ + public void replaceClassName(String oldname, String newname) { + checkModify(); + } + + /** + * Changes class names appearing in the class file according to the + * given <code>map</code>. + * + * <p>All the class names appearing in the class file are tested + * with <code>map</code> to determine whether each class name is + * replaced or not. Thus this method can be used for collecting + * all the class names in the class file. To do that, first define + * a subclass of <code>ClassMap</code> so that <code>get()</code> + * records all the given parameters. Then, make an instance of + * that subclass as an empty hash-table. Finally, pass that instance + * to this method. After this method finishes, that instance would + * contain all the class names appearing in the class file. + * + * @param map the hashtable associating replaced class names + * with substituted names. + */ + public void replaceClassName(ClassMap map) { + checkModify(); + } + + /** + * Returns a collection of the names of all the classes + * referenced in this class. + * That collection includes the name of this class. + * + * <p>This method may return <code>null</code>. + */ + public Collection getRefClasses() { + ClassFile cf = getClassFile2(); + if (cf != null) { + ClassMap cm = new ClassMap() { + public void put(String oldname, String newname) { + put0(oldname, newname); + } + + public Object get(Object jvmClassName) { + String n = toJavaName((String)jvmClassName); + put0(n, n); + return null; + } + + public void fix(String name) {} + }; + cf.renameClass(cm); + return cm.values(); + } + else + return null; + } + + /** + * Determines whether this object represents a class or an interface. + * It returns <code>true</code> if this object represents an interface. + */ + public boolean isInterface() { + return false; + } + + /** + * Returns the modifiers for this class, encoded in an integer. + * For decoding, use <code>javassist.Modifier</code>. + * + * @see Modifier + */ + public int getModifiers() { + return 0; + } + + /** + * Sets the modifiers. + * + * @param mod modifiers encoded by + * <code>javassist.Modifier</code> + * @see Modifier + */ + public void setModifiers(int mod) { + checkModify(); + } + + /** + * Determines whether the class directly or indirectly extends + * the given class. If this class extends a class A and + * the class A extends a class B, then subclassof(B) returns true. + * + * <p>This method returns true if the given class is identical to + * the class represented by this object. + */ + public boolean subclassOf(CtClass superclass) { + return false; + } + + /** + * Obtains the class object representing the superclass of the + * class. + * It returns null if the class is <code>java.lang.Object</code>. + */ + public CtClass getSuperclass() throws NotFoundException { + return null; + } + + /** + * Changes a super class. + */ + public void setSuperclass(CtClass clazz) throws CannotCompileException { + checkModify(); + } + + /** + * Obtains the class objects representing the interfaces of the + * class. + */ + public CtClass[] getInterfaces() throws NotFoundException { + return new CtClass[0]; + } + + /** + * Sets interfaces. + * + * @param list a list of the <code>CtClass</code> objects + * representing interfaces, or + * <code>null</code> if the class implements + * no interfaces. + */ + public void setInterfaces(CtClass[] list) { + checkModify(); + } + + /** + * Adds an interface. + * + * @param anInterface the added interface. + */ + public void addInterface(CtClass anInterface) { + checkModify(); + } + + /** + * Returns an array containing <code>CtField</code> objects + * representing all the public fields of the class. + * That array includes public fields inherited from the + * superclasses. + */ + public CtField[] getFields() { return new CtField[0]; } + + /** + * Returns the field with the specified name. The returned field + * may be a private field declared in a super class or interface. + */ + public CtField getField(String name) throws NotFoundException { + throw new NotFoundException(name); + } + + /** + * Gets all the fields declared in the class. The inherited fields + * are not included. + * + * <p>Note: the result does not include inherited fields. + */ + public CtField[] getDeclaredFields() { return new CtField[0]; } + + /** + * Retrieves the field with the specified name among the fields + * declared in the class. + * + * <p>Note: this method does not search the superclasses. + */ + public CtField getDeclaredField(String name) throws NotFoundException { + throw new NotFoundException(name); + } + + /** + * Gets all the constructors and methods declared in the class. + */ + public CtBehavior[] getDeclaredBehaviors() { + return new CtBehavior[0]; + } + + /** + * Returns an array containing <code>CtConstructor</code> objects + * representing all the public constructors of the class. + */ + public CtConstructor[] getConstructors() { + return new CtConstructor[0]; + } + + /** + * Returns the constructor with the given signature, + * which is represented by a character string + * called method descriptor. + * For details of the method descriptor, see the JVM specification + * or <code>javassist.bytecode.Descriptor</code>. + * + * @param name method name + * @param desc method descriptor + * @see javassist.bytecode.Descriptor + */ + public CtConstructor getConstructor(String desc) + throws NotFoundException + { + throw new NotFoundException("no such a constructor"); + } + + /** + * Gets all the constructors declared in the class. + * + * @see javassist.CtConstructor + */ + public CtConstructor[] getDeclaredConstructors() { + return new CtConstructor[0]; + } + + /** + * Returns a constructor receiving the specified parameters. + * + * @param params parameter types. + */ + public CtConstructor getDeclaredConstructor(CtClass[] params) + throws NotFoundException + { + String desc = Descriptor.ofConstructor(params); + return getConstructor(desc); + } + + /** + * Gets the class initializer (static constructor) + * declared in the class. + * This method returns <code>null</code> if + * no class initializer is not declared. + * + * @see javassist.CtConstructor + */ + public CtConstructor getClassInitializer() { + return null; + } + + /** + * Returns an array containing <code>CtMethod</code> objects + * representing all the public methods of the class. + * That array includes public methods inherited from the + * superclasses. + */ + public CtMethod[] getMethods() { + return new CtMethod[0]; + } + + /** + * Returns the method with the given name and signature. + * The returned method may be declared in a super class. + * The method signature is represented by a character string + * called method descriptor, + * which is defined in the JVM specification. + * + * @param name method name + * @param desc method descriptor + * @see javassist.bytecode.Descriptor + */ + public CtMethod getMethod(String name, String desc) + throws NotFoundException + { + throw new NotFoundException(name); + } + + /** + * Gets all methods declared in the class. The inherited methods + * are not included. + * + * @see javassist.CtMethod + */ + public CtMethod[] getDeclaredMethods() { + return new CtMethod[0]; + } + + /** + * Retrieves the method with the specified name and parameter types + * among the methods declared in the class. + * + * <p>Note: this method does not search the superclasses. + * + * @param name method name + * @param params parameter types + * @see javassist.CtMethod + */ + public CtMethod getDeclaredMethod(String name, CtClass[] params) + throws NotFoundException + { + throw new NotFoundException(name); + } + + /** + * Retrieves the method with the specified name among the methods + * declared in the class. If there are multiple methods with + * the specified name, then this method returns one of them. + * + * <p>Note: this method does not search the superclasses. + * + * @see javassist.CtMethod + */ + public CtMethod getDeclaredMethod(String name) throws NotFoundException { + throw new NotFoundException(name); + } + + /** + * Adds a constructor. + */ + public void addConstructor(CtConstructor c) + throws CannotCompileException + { + checkModify(); + } + + /** + * Adds a method. + */ + public void addMethod(CtMethod m) throws CannotCompileException { + checkModify(); + } + + /** + * Adds a field. + * + * <p>The <code>CtField</code> belonging to another + * <code>CtClass</code> cannot be directly added to this class. + * Only a field created for this class can be added. + * + * @see javassist.CtField#CtField(CtField,CtClass) + */ + public void addField(CtField f) throws CannotCompileException { + addField(f, (CtField.Initializer)null); + } + + /** + * Adds a field with an initial value. + * + * <p>The <code>CtField</code> belonging to another + * <code>CtClass</code> cannot be directly added to this class. + * Only a field created for this class can be added. + * + * <p>The initial value is given as an expression written in Java. + * Any regular Java expression can be used for specifying the initial + * value. The followings are examples. + * + * <ul><pre> + * cc.addField(f, "0") // the initial value is 0. + * cc.addField(f, "i + 1") // i + 1. + * cc.addField(f, "new Point()"); // a Point object. + * </pre></ul> + * + * <p>Here, the type of variable <code>cc</code> is <code>CtClass</code>. + * The type of <code>f</code> is <code>CtField</code>. + * + * @param init an expression for the initial value. + * + * @see javassist.CtField.Initializer#byExpr(String) + * @see javassist.CtField#CtField(CtField,CtClass) + */ + public void addField(CtField f, String init) + throws CannotCompileException + { + checkModify(); + } + + /** + * Adds a field with an initial value. + * + * <p>The <code>CtField</code> belonging to another + * <code>CtClass</code> cannot be directly added to this class. + * Only a field created for this class can be added. + * + * <p>For example, + * + * <ul><pre> + * CtClass cc = ...; + * addField(new CtField(CtClass.intType, "i", cc), + * CtField.Initializer.constant(1)); + * </pre></ul> + * + * <p>This code adds an <code>int</code> field named "i". The + * initial value of this field is 1. + * + * @param init specifies the initial value of the field. + * + * @see javassist.CtField#CtField(CtField,CtClass) + */ + public void addField(CtField f, CtField.Initializer init) + throws CannotCompileException + { + checkModify(); + } + + /** + * Obtains an attribute with the given name. + * If that attribute is not found in the class file, this + * method returns null. + * + * @param name attribute name + */ + public byte[] getAttribute(String name) { + return null; + } + + /** + * Adds a named attribute. + * An arbitrary data (smaller than 64Kb) can be saved in the class + * file. Some attribute name are reserved by the JVM. + * The attributes with the non-reserved names are ignored when a + * class file is loaded into the JVM. + * If there is already an attribute with + * the same name, this method substitutes the new one for it. + * + * @param name attribute name + * @param data attribute value + */ + public void setAttribute(String name, byte[] data) { + checkModify(); + } + + /** + * Applies the given converter to all methods and constructors + * declared in the class. This method calls <code>instrument()</code> + * on every <code>CtMethod</code> and <code>CtConstructor</code> object + * in the class. + * + * @param converter specifies how to modify. + */ + public void instrument(CodeConverter converter) + throws CannotCompileException + { + checkModify(); + } + + /** + * Modifies the bodies of all methods and constructors + * declared in the class. This method calls <code>instrument()</code> + * on every <code>CtMethod</code> and <code>CtConstructor</code> object + * in the class. + * + * @param editor specifies how to modify. + */ + public void instrument(ExprEditor editor) + throws CannotCompileException + { + checkModify(); + } + + /** + * Converts this class to a <code>java.lang.Class</code> object. + * Once this method is called, further modifications are not + * possible any more. + * + * <p>This method is equivalent to: + * <ul><pre>this.getClassPool().writeAsClass(this.getName())</pre></ul> + * + * <p>See the description of <code>ClassPool.writeAsClass()</code> + * before you use this method. + * + * @see javassist.ClassPool#writeAsClass(String) + * @see javassist.ClassPool#forName(String) + */ + public Class toClass() + throws NotFoundException, IOException, CannotCompileException + { + return getClassPool2().writeAsClass(getName()); + } + + /** + * Converts this class to a class file. + * Once this method is called, further modifications are not + * possible any more. + * + * <p>This method is equivalent to: + * <ul><pre>this.getClassPool().write(this.getName())</pre></ul> + * + * @see javassist.ClassPool#write(String) + */ + public byte[] toBytecode() + throws NotFoundException, IOException, CannotCompileException + { + return getClassPool2().write(getName()); + } + + /** + * Writes a class file represented by this <code>CtClass</code> + * object in the current directory. + * Once this method is called, further modifications are not + * possible any more. + * + * <p>This method is equivalent to: + * <ul><pre>this.getClassPool().writeFile(this.getName())</pre></ul> + * + * @see javassist.ClassPool#writeFile(String) + */ + public void writeFile() + throws NotFoundException, IOException, CannotCompileException + { + getClassPool2().writeFile(getName()); + } + + private ClassPool getClassPool2() throws CannotCompileException { + ClassPool cp = getClassPool(); + if (cp == null) + throw new CannotCompileException( + "no ClassPool found. not a class?"); + else + return cp; + } + + /** + * Converts this class to a class file. + * Once this method is called, further modifications are not + * possible any more. + * + * <p>If this method is used to obtain a byte array representing + * the class file, <code>Translator.onWrite()</code> is never + * called on this class. <code>ClassPool.write()</code> should + * be used. + * + * <p>This method dose not close the output stream in the end. + * + * @param out the output stream that a class file is written to. + */ + void toBytecode(DataOutputStream out) + throws CannotCompileException, IOException + { + throw new CannotCompileException("not a class"); + } +} diff --git a/src/main/javassist/CtClassType.java b/src/main/javassist/CtClassType.java new file mode 100644 index 00000000..e08ed7bd --- /dev/null +++ b/src/main/javassist/CtClassType.java @@ -0,0 +1,872 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +import javassist.bytecode.*; +import javassist.compiler.Javac; +import javassist.compiler.CompileError; +import javassist.expr.ExprEditor; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; + +/** + * Class types. + */ +class CtClassType extends CtClass { + protected ClassPool classPool; + protected boolean wasChanged; + protected boolean wasFrozen; + protected ClassFile classfile; + + private CtField fieldsCache; + private CtConstructor constructorsCache; + private CtConstructor classInitializerCache; + private CtMethod methodsCache; + + private FieldInitLink fieldInitializers; + private Hashtable hiddenMethods; // must be synchronous + private int uniqueNumberSeed; + + CtClassType(String name, ClassPool cp) { + super(name); + classPool = cp; + wasChanged = wasFrozen = false; + classfile = null; + fieldInitializers = null; + hiddenMethods = null; + uniqueNumberSeed = 0; + eraseCache(); + } + + protected void eraseCache() { + fieldsCache = null; + constructorsCache = null; + classInitializerCache = null; + methodsCache = null; + } + + public ClassFile getClassFile2() { + if (classfile != null) + return classfile; + + try { + byte[] b = classPool.readSource(getName()); + DataInputStream dis + = new DataInputStream(new ByteArrayInputStream(b)); + return (classfile = new ClassFile(dis)); + } + catch (NotFoundException e) { + throw new RuntimeException(e.toString()); + } + catch (IOException e) { + throw new RuntimeException(e.toString()); + } + catch (CannotCompileException e) { + throw new RuntimeException(e.toString()); + } + } + + public ClassPool getClassPool() { return classPool; } + + public boolean isModified() { return wasChanged; } + + public boolean isFrozen() { return wasFrozen; } + + void checkModify() throws RuntimeException { + super.checkModify(); + wasChanged = true; + } + + public void defrost() { wasFrozen = false; } + + 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; + } + + public void setName(String name) throws RuntimeException { + String oldname = getName(); + if (name.equals(oldname)) + return; + + classPool.checkNotFrozen(name, + "the class with the new name is frozen"); + ClassFile cf = getClassFile2(); + super.setName(name); + cf.setName(name); + eraseCache(); + classPool.classNameChanged(oldname, this); + } + + public void replaceClassName(ClassMap classnames) + throws RuntimeException + { + String oldClassName = getName(); + String newClassName + = (String)classnames.get(Descriptor.toJvmName(oldClassName)); + if (newClassName != null) { + newClassName = Descriptor.toJavaName(newClassName); + classPool.checkNotFrozen(newClassName, + "the class " + newClassName + " is frozen"); + } + + super.replaceClassName(classnames); + ClassFile cf = getClassFile2(); + cf.renameClass(classnames); + eraseCache(); + + if (newClassName != null) { + super.setName(newClassName); + classPool.classNameChanged(oldClassName, this); + } + } + + public void replaceClassName(String oldname, String newname) + throws RuntimeException + { + String thisname = getName(); + if (thisname.equals(oldname)) + setName(newname); + else { + super.replaceClassName(oldname, newname); + getClassFile2().renameClass(oldname, newname); + eraseCache(); + } + } + + public boolean isInterface() { + return Modifier.isInterface(getModifiers()); + } + + public int getModifiers() { + int acc = getClassFile2().getAccessFlags(); + acc = AccessFlag.clear(acc, AccessFlag.SUPER); + return AccessFlag.toModifier(acc); + } + + public void setModifiers(int mod) { + checkModify(); + int acc = AccessFlag.of(mod) | AccessFlag.SUPER; + getClassFile2().setAccessFlags(acc); + } + + public boolean subclassOf(CtClass superclass) { + CtClass curr = this; + try { + while (curr != null) { + if (curr == superclass) + return true; + + curr = curr.getSuperclass(); + } + } + catch (Exception ignored) {} + return false; + } + + public CtClass getSuperclass() throws NotFoundException { + String supername = getClassFile2().getSuperclass(); + if (supername == null) + return null; + else + return classPool.get(supername); + } + + public void setSuperclass(CtClass clazz) throws CannotCompileException { + checkModify(); + getClassFile2().setSuperclass(clazz.getName()); + } + + 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; + } + + 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); + } + + public void addInterface(CtClass anInterface) { + checkModify(); + if (anInterface != null) + getClassFile2().addInterface(anInterface.getName()); + } + + 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) {} + + CtField cf = ((CtClassType)cc).getFieldsCache(); + while (cf != null) { + if (Modifier.isPublic(cf.getModifiers())) + alist.add(cf); + + cf = cf.next; + } + } + + public CtField getField(String name) throws NotFoundException { + try { + return getDeclaredField(name); + } + catch (NotFoundException e) {} + + try { + CtClass[] ifs = getInterfaces(); + int num = ifs.length; + for (int i = 0; i < num; ++i) + try { + return ifs[i].getField(name); + } + catch (NotFoundException e) {} + } + catch (NotFoundException e) {} + + try { + CtClass s = getSuperclass(); + if (s != null) + return s.getField(name); + } + catch (NotFoundException e) {} + + throw new NotFoundException(name); + } + + public CtField[] getDeclaredFields() { + CtField cf = getFieldsCache(); + int num = CtField.count(cf); + CtField[] cfs = new CtField[num]; + int i = 0; + while (cf != null) { + cfs[i++] = cf; + cf = cf.next; + } + + return cfs; + } + + protected CtField getFieldsCache() { + if (fieldsCache == null) { + List list = getClassFile2().getFields(); + int n = list.size(); + for (int i = 0; i < n; ++i) { + FieldInfo finfo = (FieldInfo)list.get(i); + fieldsCache = CtField.append(fieldsCache, + new CtField(finfo, this)); + } + } + + return fieldsCache; + } + + public CtField getDeclaredField(String name) throws NotFoundException { + CtField cf = getFieldsCache(); + while (cf != null) { + if (cf.getName().equals(name)) + return cf; + + cf = cf.next; + } + + throw new NotFoundException(name); + } + + public CtBehavior[] getDeclaredBehaviors() { + CtConstructor cc = getConstructorsCache(); + CtMethod cm = getMethodsCache(); + int num = CtMethod.count(cm) + CtConstructor.count(cc); + CtBehavior[] cb = new CtBehavior[num]; + int i = 0; + while (cc != null) { + cb[i++] = cc; + cc = cc.next; + } + + while (cm != null) { + cb[i++] = cm; + cm = cm.next; + } + + return cb; + } + + public CtConstructor[] getConstructors() { + CtConstructor[] cons = getDeclaredConstructors(); + if (cons.length == 0) + return cons; + + int n = 0; + int i = cons.length; + while (--i >= 0) + if (Modifier.isPublic(cons[i].getModifiers())) + ++n; + + CtConstructor[] result = new CtConstructor[n]; + n = 0; + i = cons.length; + while (--i >= 0) { + CtConstructor c = cons[i]; + if (Modifier.isPublic(c.getModifiers())) + result[n++] = c; + } + + return result; + } + + public CtConstructor getConstructor(String desc) + throws NotFoundException + { + CtConstructor cc = getConstructorsCache(); + while (cc != null) { + if (cc.getMethodInfo2().getDescriptor().equals(desc)) + return cc; + + cc = cc.next; + } + + return super.getConstructor(desc); + } + + public CtConstructor[] getDeclaredConstructors() { + CtConstructor cc = getConstructorsCache(); + int num = CtConstructor.count(cc); + CtConstructor[] ccs = new CtConstructor[num]; + int i = 0; + while (cc != null) { + ccs[i++] = cc; + cc = cc.next; + } + + return ccs; + } + + protected CtConstructor getConstructorsCache() { + if (constructorsCache == null) { + List list = getClassFile2().getMethods(); + int n = list.size(); + for (int i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + if (minfo.isConstructor()) + constructorsCache + = CtConstructor.append(constructorsCache, + new CtConstructor(minfo, this)); + } + } + + return constructorsCache; + } + + public CtConstructor getClassInitializer() { + if (classInitializerCache == null) { + List list = getClassFile2().getMethods(); + int n = list.size(); + for (int i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + if (minfo.isStaticInitializer()) { + classInitializerCache = new CtConstructor(minfo, this); + break; + } + } + } + + return classInitializerCache; + } + + public CtMethod[] getMethods() { + HashMap h = new HashMap(); + getMethods0(h, this); + return (CtMethod[])h.values().toArray(new CtMethod[0]); + } + + 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) { + CtMethod cm = ((CtClassType)cc).getMethodsCache(); + while (cm != null) { + if (Modifier.isPublic(cm.getModifiers())) + h.put(cm, cm); + + cm = cm.next; + } + } + } + + 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) { + CtMethod cm = ((CtClassType)cc).getMethodsCache(); + while (cm != null) { + if (cm.getName().equals(name) + && cm.getMethodInfo2().getDescriptor().equals(desc)) + return cm; + + cm = cm.next; + } + } + + 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; + } + + public CtMethod[] getDeclaredMethods() { + CtMethod cm = getMethodsCache(); + int num = CtMethod.count(cm); + CtMethod[] cms = new CtMethod[num]; + int i = 0; + while (cm != null) { + cms[i++] = cm; + cm = cm.next; + } + + return cms; + } + + public CtMethod getDeclaredMethod(String name) throws NotFoundException { + CtMethod m = getMethodsCache(); + while (m != null) { + if (m.getName().equals(name)) + return m; + + m = m.next; + } + + throw new NotFoundException(name + "(..) is not found in " + + getName()); + } + + public CtMethod getDeclaredMethod(String name, CtClass[] params) + throws NotFoundException + { + String desc = Descriptor.ofParameters(params); + CtMethod m = getMethodsCache(); + while (m != null) { + if (m.getName().equals(name) + && m.getMethodInfo2().getDescriptor().startsWith(desc)) + return m; + + m = m.next; + } + + throw new NotFoundException(name + "(..) is not found in " + + getName()); + } + + protected CtMethod getMethodsCache() { + if (methodsCache == null) { + List list = getClassFile2().getMethods(); + int n = list.size(); + for (int i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + if (minfo.isMethod()) + methodsCache = CtMethod.append(methodsCache, + new CtMethod(minfo, this)); + } + } + + return methodsCache; + } + + public void addField(CtField f, String init) + throws CannotCompileException + { + addField(f, CtField.Initializer.byExpr(init)); + } + + public void addField(CtField f, CtField.Initializer init) + throws CannotCompileException + { + checkModify(); + if (f.getDeclaringClass() != this) + throw new CannotCompileException("cannot add"); + + if (init == null) + init = f.getInit(); + + getFieldsCache(); + fieldsCache = CtField.append(fieldsCache, f); + getClassFile2().addField(f.getFieldInfo2()); + + if (init != null) { + FieldInitLink fil = new FieldInitLink(f, init); + FieldInitLink link = fieldInitializers; + if (link == null) + fieldInitializers = fil; + else { + while (link.next != null) + link = link.next; + + link.next = fil; + } + } + } + + public void addConstructor(CtConstructor c) + throws CannotCompileException + { + checkModify(); + if (c.getDeclaringClass() != this) + throw new CannotCompileException("cannot add"); + + getConstructorsCache(); + constructorsCache = CtConstructor.append(constructorsCache, c); + getClassFile2().addMethod(c.getMethodInfo2()); + } + + public void addMethod(CtMethod m) throws CannotCompileException { + checkModify(); + if (m.getDeclaringClass() != this) + throw new CannotCompileException("cannot add"); + + getMethodsCache(); + methodsCache = CtMethod.append(methodsCache, m); + getClassFile2().addMethod(m.getMethodInfo2()); + if ((m.getModifiers() & Modifier.ABSTRACT) != 0) + setModifiers(getModifiers() | Modifier.ABSTRACT); + } + + public byte[] getAttribute(String name) { + AttributeInfo ai = getClassFile2().getAttribute(name); + if (ai == null) + return null; + else + return ai.get(); + } + + public void setAttribute(String name, byte[] data) { + checkModify(); + ClassFile cf = getClassFile2(); + cf.addAttribute(new AttributeInfo(cf.getConstPool(), name, data)); + } + + public void instrument(CodeConverter converter) + throws CannotCompileException + { + checkModify(); + ClassFile cf = getClassFile2(); + 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); + converter.doit(this, minfo, cp); + } + } + + public void instrument(ExprEditor editor) + throws CannotCompileException + { + checkModify(); + ClassFile cf = getClassFile2(); + List list = cf.getMethods(); + int n = list.size(); + for (int i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + editor.doit(this, minfo); + } + } + + void toBytecode(DataOutputStream out) + throws CannotCompileException, IOException + { + ClassFile cf = getClassFile2(); + try { + modifyClassConstructor(cf); + modifyConstructors(cf); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + + wasFrozen = true; + try { + cf.write(out); + out.flush(); + } + catch (IOException e) { + throw new CannotCompileException(e); + } + } + + protected void modifyClassConstructor(ClassFile cf) + throws CannotCompileException, NotFoundException + { + Bytecode code = new Bytecode(cf.getConstPool(), 0, 0); + Javac jv = new Javac(code, this); + int stacksize = 0; + boolean none = true; + for (FieldInitLink fi = fieldInitializers; fi != null; fi = fi.next) { + CtField f = fi.field; + if (Modifier.isStatic(f.getModifiers())) { + none = false; + int s = fi.init.compileIfStatic(f.getType(), f.getName(), + code, jv); + if (stacksize < s) + stacksize = s; + } + } + + if (none) + return; // no initializer for static fileds. + + MethodInfo m = cf.getStaticInitializer(); + if (m == null) { + code.add(Bytecode.RETURN); + code.setMaxStack(stacksize); + m = new MethodInfo(cf.getConstPool(), + "<clinit>", "()V"); + m.setAccessFlags(AccessFlag.STATIC); + m.setCodeAttribute(code.toCodeAttribute()); + cf.addMethod(m); + } + else { + CodeAttribute codeAttr = m.getCodeAttribute(); + if (codeAttr == null) + throw new CannotCompileException("empty <clinit>"); + + 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); + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } + } + + protected 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); + } + 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 (!Modifier.isStatic(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++; } +} + +class FieldInitLink { + FieldInitLink next; + CtField field; + CtField.Initializer init; + + FieldInitLink(CtField f, CtField.Initializer i) { + next = null; + field = f; + init = i; + } +} diff --git a/src/main/javassist/CtConstructor.java b/src/main/javassist/CtConstructor.java new file mode 100644 index 00000000..52024e77 --- /dev/null +++ b/src/main/javassist/CtConstructor.java @@ -0,0 +1,458 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +import javassist.bytecode.*; +import javassist.compiler.Javac; +import javassist.compiler.CompileError; +import javassist.expr.ExprEditor; + +/* Some methods do nothing except calling the super's method. + * They might seem redundant but they are necessary so that javadoc + * includes the description of those methods in the page of this class. + */ + +/** + * An instance of CtConstructor represents a constructor. + * It may represent a static constructor + * (class initializer). To distinguish a constructor and a class + * initializer, call <code>isClassInitializer()</code>. + * + * @see CtClass#getDeclaredConstructors() + * @see CtClass#getClassInitializer() + * @see CtNewConstructor + */ +public final class CtConstructor extends CtBehavior { + protected CtConstructor next; + + protected CtConstructor(MethodInfo minfo, CtClass declaring) { + super(declaring, minfo); + next = null; + } + + /** + * Creates a constructor with no constructor body. + * The created constructor + * must be added to a class with <code>CtClass.addConstructor()</code>. + * + * <p>The created constructor does not include a constructor body, + * must be specified with <code>setBody()</code>. + * + * @param declaring the class to which the created method is added. + * @param parameters a list of the parameter types + * + * @see CtClass#addConstructor(CtConstructor) + * @see CtConstructor#setBody(String) + * @see CtConstructor#setBody(CtConstructor,ClassMap) + */ + public CtConstructor(CtClass[] parameters, CtClass declaring) { + this((MethodInfo)null, declaring); + ConstPool cp = declaring.getClassFile2().getConstPool(); + String desc = Descriptor.ofConstructor(parameters); + methodInfo = new MethodInfo(cp, "<init>", desc); + setModifiers(Modifier.PUBLIC); + } + + /** + * Creates a copy of a <code>CtConstructor</code> object. + * The created constructor must be + * added to a class with <code>CtClass.addConstructor()</code>. + * + * <p>All occurrences of class names in the created constructor + * are replaced with names specified by + * <code>map</code> if <code>map</code> is not <code>null</code>. + * + * <p>By default, all the occurrences of the names of the class + * declaring <code>src</code> and the superclass are replaced + * with the name of the class and the superclass that + * the created constructor is added to. + * This is done whichever <code>map</code> is null or not. + * To prevent this replacement, call <code>ClassMap.fix()</code>. + * + * <p><b>Note:</b> if the <code>.class</code> notation (for example, + * <code>String.class</code>) is included in an expression, the + * Javac compiler may produce a helper method. + * Since this constructor never + * copies this helper method, the programmers have the responsiblity of + * copying it. Otherwise, use <code>Class.forName()</code> in the + * expression. + * + * @param src the source method. + * @param declaring the class to which the created method is added. + * @param map the hashtable associating original class names + * with substituted names. + * It can be <code>null</code>. + * + * @see CtClass#addConstructor(CtConstructor) + * @see ClassMap#fix(String) + */ + public CtConstructor(CtConstructor src, CtClass declaring, ClassMap map) + throws CannotCompileException + { + this((MethodInfo)null, declaring); + MethodInfo srcInfo = src.methodInfo; + CtClass srcClass = src.getDeclaringClass(); + ConstPool cp = declaring.getClassFile2().getConstPool(); + if (map == null) + map = new ClassMap(); + + map.put(srcClass.getName(), declaring.getName()); + try { + boolean patch = false; + CtClass srcSuper = srcClass.getSuperclass(); + String destSuperName = declaring.getSuperclass().getName(); + if (srcSuper != null) { + String srcSuperName = srcSuper.getName(); + if (!srcSuperName.equals(destSuperName)) + if (srcSuperName.equals(CtClass.javaLangObject)) + patch = true; + else + map.put(srcSuperName, destSuperName); + } + + methodInfo = new MethodInfo(cp, srcInfo.getName(), srcInfo, map); + if (patch) + methodInfo.setSuperclass(destSuperName); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } + + static CtConstructor append(CtConstructor list, CtConstructor tail) { + tail.next = null; + if (list == null) + return tail; + else { + CtConstructor lst = list; + while (lst.next != null) + lst = lst.next; + + lst.next = tail; + return list; + } + } + + static int count(CtConstructor m) { + int n = 0; + while (m != null) { + ++n; + m = m.next; + } + + return n; + } + + /** + * Returns the MethodInfo representing the constructor in the + * class file. + */ + public MethodInfo getMethodInfo() { + return super.getMethodInfo(); + } + + /** + * Returns true if this object represents a constructor. + */ + public boolean isConstructor() { + return methodInfo.isConstructor(); + } + + /** + * Returns true if this object represents a static initializer. + */ + public boolean isClassInitializer() { + return methodInfo.isStaticInitializer(); + } + + /** + * Obtains the encoded modifiers of the constructor. + * + * @return modifiers encoded with + * <code>javassist.Modifier</code>. + * @see Modifier + */ + public int getModifiers() { + return super.getModifiers(); + } + + /** + * Sets the encoded modifiers of the constructor. + * + * @see Modifier + */ + public void setModifiers(int mod) { + super.setModifiers(mod); + } + + /** + * Obtains the name of this constructor. + * It is the same as the simple name of the class declaring this + * constructor. If this object represents a class initializer, + * then this method returns <code>"<clinit>"</code>. + */ + public String getName() { + if (methodInfo.isStaticInitializer()) + return MethodInfo.nameClinit; + else + return declaringClass.getName(); + } + + /** + * Returns the class that declares this constructor. + */ + public CtClass getDeclaringClass() { + return super.getDeclaringClass(); + } + + /** + * Obtains parameter types of this constructor. + */ + public CtClass[] getParameterTypes() throws NotFoundException { + return super.getParameterTypes(); + } + + /** + * Returns the character string representing the parameter types. + * If two constructors have the same parameter types, + * <code>getSignature()</code> returns the same string. + */ + public String getSignature() { + return super.getSignature(); + } + + /** + * Obtains exceptions that this constructor may throw. + */ + public CtClass[] getExceptionTypes() throws NotFoundException { + return super.getExceptionTypes(); + } + + /** + * Sets exceptions that this constructor may throw. + */ + public void setExceptionTypes(CtClass[] types) + throws NotFoundException + { + super.setExceptionTypes(types); + } + + /** + * Sets a constructor body. + * + * @param src the source code representing the constructor body. + * It must be a single statement or block. + */ + public void setBody(String src) throws CannotCompileException { + super.setBody(src); + } + + /** + * Copies a constructor body from another constructor. + * + * <p>All occurrences of the class names in the copied body + * are replaced with the names specified by + * <code>map</code> if <code>map</code> is not <code>null</code>. + * + * @param src the method that the body is copied from. + * @param map the hashtable associating original class names + * with substituted names. + * It can be <code>null</code>. + */ + public void setBody(CtConstructor src, ClassMap map) + throws CannotCompileException + { + setBody0(src.declaringClass, src.methodInfo, + declaringClass, methodInfo, map); + } + + /** + * Obtains an attribute with the given name. + * If that attribute is not found in the class file, this + * method returns null. + * + * @param name attribute name + */ + public byte[] getAttribute(String name) { + return super.getAttribute(name); + } + + /** + * Adds an attribute. The attribute is saved in the class file. + * + * @param name attribute name + * @param data attribute value + */ + public void setAttribute(String name, byte[] data) { + super.setAttribute(name, data); + } + + /** + * Declares to use <code>$cflow</code> for this constructor. + * If <code>$cflow</code> is used, the class files modified + * with Javassist requires a support class + * <code>javassist.runtime.Cflow</code> at runtime + * (other Javassist classes are not required at runtime). + * + * <p>Every <code>$cflow</code> variable is given a unique name. + * For example, if the given name is <code>"Point.paint"</code>, + * then the variable is indicated by <code>$cflow(Point.paint)</code>. + * + * @param name <code>$cflow</code> name. It can include + * alphabets, numbers, <code>_</code>, + * <code>$</code>, and <code>.</code> (dot). + * + * @see javassist.runtime.Cflow + */ + public void useCflow(String name) throws CannotCompileException { + super.useCflow(name); + } + + /** + * Modifies the constructor body. + * + * @param converter specifies how to modify. + */ + public void instrument(CodeConverter converter) + throws CannotCompileException + { + super.instrument(converter); + } + + /** + * Modifies the constructor body. + * + * @param editor specifies how to modify. + */ + public void instrument(ExprEditor editor) + throws CannotCompileException + { + super.instrument(editor); + } + + /** + * Inserts bytecode at the beginning of the constructor body. + * Since the bytecode is inserted before a constructor in the super + * class or this class is called, the bytecode is subject + * to constraints described + * in Section 4.8.2 of The Java Virtual Machine Specification (2nd ed). + * For example, it cannot access instance members although it can access + * static members. + * + * @param src the source code representing the inserted bytecode. + * It must be a single statement or block. + */ + public void insertBefore(String src) throws CannotCompileException { + super.insertBefore(src); + } + + /** + * Inserts bytecode just after another constructor in the super class + * or this class is called. + * + * @param src the source code representing the inserted bytecode. + * It must be a single statement or block. + */ + public void insertBeforeBody(String src) throws CannotCompileException { + declaringClass.checkModify(); + CodeAttribute ca = methodInfo.getCodeAttribute(); + CodeIterator iterator = ca.iterator(); + Bytecode b = new Bytecode(methodInfo.getConstPool(), + ca.getMaxStack(), ca.getMaxLocals()); + b.setStackDepth(ca.getMaxStack()); + Javac jv = new Javac(b, declaringClass); + try { + jv.recordParams(getParameterTypes(), false); + jv.compileStmnt(src); + ca.setMaxStack(b.getMaxStack()); + ca.setMaxLocals(b.getMaxLocals()); + iterator.skipConstructor(); + int pos = iterator.insertEx(b.get()); + iterator.insert(b.getExceptionTable(), pos); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + catch (CompileError e) { + throw new CannotCompileException(e); + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } + + /** + * Inserts bytecode at the end of the constructor body. + * The bytecode is inserted just before every return insturction. + * It is not executed when an exception is thrown. + * + * @param src the source code representing the inserted bytecode. + * It must be a single statement or block. + */ + public void insertAfter(String src) + throws CannotCompileException + { + super.insertAfter(src); + } + + /** + * Inserts bytecode at the end of the constructor body. + * The bytecode is inserted just before every return insturction. + * + * @param src the source code representing the inserted bytecode. + * It must be a single statement or block. + * @param asFinally true if the inserted bytecode is executed + * not only when the transfer normally returns + * but also when an exception is thrown. + */ + public void insertAfter(String src, boolean asFinally) + throws CannotCompileException + { + super.insertAfter(src, asFinally); + } + + /** + * Adds a catch clause that handles an exception thrown in the + * constructor body. + * The catch clause must end with a return or throw statement. + * + * @param src the source code representing the catch clause. + * It must be a single statement or block. + * @param exceptionType the type of the exception handled by the + * catch clause. + * @param exceptionName the name of the variable containing the + * caught exception. + */ + public void addCatch(String src, CtClass exceptionType, + String exceptionName) + throws CannotCompileException + { + super.addCatch(src, exceptionType, exceptionName); + } +} diff --git a/src/main/javassist/CtField.java b/src/main/javassist/CtField.java new file mode 100644 index 00000000..6bb45e9c --- /dev/null +++ b/src/main/javassist/CtField.java @@ -0,0 +1,1107 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +import javassist.bytecode.*; +import javassist.compiler.Javac; +import javassist.compiler.CompileError; +import javassist.compiler.ast.ASTree; + +/** + * An instance of CtField represents a field. + * + * @see CtClass#getDeclaredFields() + */ +public class CtField extends CtMember { + protected FieldInfo fieldInfo; + CtField next; + + /** + * Creates a <code>CtField</code> object. + * The created field must be added to a class + * with <code>CtClass.addField()</code>. + * An initial value of the field is specified + * by a <code>CtField.Initializer</code> object. + * + * <p>If getter and setter methods are needed, + * call <code>CtNewMethod.getter()</code> and + * <code>CtNewMethod.setter()</code>. + * + * @param type field type + * @param name field name + * @param declaring the class to which the field will be added. + * + * @see CtClass#addField(CtField) + * @see CtNewMethod#getter(String,CtField) + * @see CtNewMethod#setter(String,CtField) + * @see CtField.Initializer + */ + public CtField(CtClass type, String name, CtClass declaring) + throws CannotCompileException + { + this(Descriptor.of(type), name, declaring); + } + + /** + * Creates a copy of the given field. + * The created field must be added to a class + * with <code>CtClass.addField()</code>. + * An initial value of the field is specified + * by a <code>CtField.Initializer</code> object. + * + * <p>If getter and setter methods are needed, + * call <code>CtNewMethod.getter()</code> and + * <code>CtNewMethod.setter()</code>. + * + * @param src the original field + * @param declaring the class to which the field will be added. + * @see CtNewMethod#getter(String,CtField) + * @see CtNewMethod#setter(String,CtField) + * @see CtField.Initializer + */ + public CtField(CtField src, CtClass declaring) + throws CannotCompileException + { + this(src.fieldInfo.getDescriptor(), src.fieldInfo.getName(), + declaring); + } + + private CtField(String typeDesc, String name, CtClass clazz) + throws CannotCompileException + { + super(clazz); + next = null; + ClassFile cf = clazz.getClassFile2(); + if (cf == null) + throw new CannotCompileException("bad declaring class: " + + clazz.getName()); + + fieldInfo = new FieldInfo(cf.getConstPool(), name, typeDesc); + } + + CtField(FieldInfo fi, CtClass clazz) { + super(clazz); + fieldInfo = fi; + next = null; + } + + /* Javac.CtFieldWithInit overrides. + */ + protected ASTree getInitAST() { return null; } + + /* Called by CtClassType.addField(). + */ + Initializer getInit() { + ASTree tree = getInitAST(); + if (tree == null) + return null; + else + return Initializer.byExpr(tree); + } + + /** + * Compiles the given source code and creates a field. + * Examples of the source code are: + * + * <ul><pre> + * "public String name;" + * "public int k = 3;"</pre></ul> + * + * <p>Note that the source code ends with <code>';'</code> + * (semicolon). + * + * @param src the source text. + * @param declaring the class to which the created field is added. + */ + public static CtField make(String src, CtClass declaring) + throws CannotCompileException + { + Javac compiler = new Javac(declaring); + try { + CtMember obj = compiler.compile(src); + if (obj instanceof CtField) + return (CtField)obj; // an instance of Javac.CtFieldWithInit + } + catch (CompileError e) { + throw new CannotCompileException(e); + } + + throw new CannotCompileException("not a field"); + } + + static CtField append(CtField list, CtField tail) { + tail.next = null; + if (list == null) + return tail; + else { + CtField lst = list; + while (lst.next != null) + lst = lst.next; + + lst.next = tail; + return list; + } + } + + static int count(CtField f) { + int n = 0; + while (f != null) { + ++n; + f = f.next; + } + + return n; + } + + /** + * Returns the FieldInfo representing the field in the class file. + */ + public FieldInfo getFieldInfo() { + declaringClass.checkModify(); + return fieldInfo; + } + + /** + * Undocumented method. Do not use; internal-use only. + */ + public FieldInfo getFieldInfo2() { return fieldInfo; } + + /** + * Returns the class declaring the field. + */ + public CtClass getDeclaringClass() { + // this is redundant but for javadoc. + return super.getDeclaringClass(); + } + + /** + * Returns the name of the field. + */ + public String getName() { + return fieldInfo.getName(); + } + + /** + * Changes the name of the field. + */ + public void setName(String newName) { + fieldInfo.setName(newName); + } + + /** + * Returns the encoded modifiers of the field. + * + * @see Modifier + */ + public int getModifiers() { + return AccessFlag.toModifier(fieldInfo.getAccessFlags()); + } + + /** + * Sets the encoded modifiers of the field. + * + * @see Modifier + */ + public void setModifiers(int mod) { + fieldInfo.setAccessFlags(AccessFlag.of(mod)); + } + + /** + * Returns the type of the field. + */ + public CtClass getType() throws NotFoundException { + return Descriptor.toCtClass(fieldInfo.getDescriptor(), + declaringClass.getClassPool()); + } + + /** + * Sets the type of the field. + */ + public void setType(CtClass clazz) { + declaringClass.checkModify(); + fieldInfo.setDescriptor(Descriptor.of(clazz)); + } + + /** + * Obtains an attribute with the given name. + * If that attribute is not found in the class file, this + * method returns null. + * + * @param name attribute name + */ + public byte[] getAttribute(String name) { + AttributeInfo ai = fieldInfo.getAttribute(name); + if (ai == null) + return null; + else + return ai.get(); + } + + /** + * Adds an attribute. The attribute is saved in the class file. + * + * @param name attribute name + * @param data attribute value + */ + public void setAttribute(String name, byte[] data) { + declaringClass.checkModify(); + fieldInfo.addAttribute(new AttributeInfo(fieldInfo.getConstPool(), + name, data)); + } + + // inner classes + + /** + * Instances of this class specify how to initialize a field. + * <code>Initializer</code> is passed to + * <code>CtClass.addField()</code> with a <code>CtField</code>. + * + * <p>This class cannot be instantiated with the <code>new</code> operator. + * Factory methods such as <code>byParameter()</code> and + * <code>byNew</code> + * must be used for the instantiation. They create a new instance with + * the given parameters and return it. + * + * @see CtClass#addField(CtField,CtField.Initializer) + */ + public static abstract class Initializer { + /** + * Makes an initializer that assigns a constant integer value. + * The field must be integer type. + */ + public static Initializer constant(int i) { + return new IntInitializer(i); + } + + /** + * Makes an initializer that assigns a constant long value. + * The field must be long type. + */ + public static Initializer constant(long l) { + return new LongInitializer(l); + } + + /** + * Makes an initializer that assigns a constant double value. + * The field must be double type. + */ + public static Initializer constant(double d) { + return new DoubleInitializer(d); + } + + /** + * Makes an initializer that assigns a constant string value. + * The field must be <code>java.lang.String</code> type. + */ + public static Initializer constant(String s) { + return new StringInitializer(s); + } + + /** + * Makes an initializer using a constructor parameter. + * + * <p>The initial value is the + * N-th parameter given to the constructor of the object including + * the field. If the constructor takes less than N parameters, + * the field is not initialized. + * If the field is static, it is never initialized. + * + * @param nth the n-th (>= 0) parameter is used as + * the initial value. + * If nth is 0, then the first parameter is + * used. + */ + public static Initializer byParameter(int nth) { + ParamInitializer i = new ParamInitializer(); + i.nthParam = nth; + return i; + } + + /** + * Makes an initializer creating a new object. + * + * <p>This initializer creates a new object and uses it as the initial + * value of the field. The constructor of the created object receives + * the parameter: + * + * <ul><code>Object obj</code> - the object including the field.<br> + * </ul> + * + * <p>If the initialized field is static, then the constructor does + * not receive any parameters. + * + * @param objectType the class instantiated for the initial value. + */ + public static Initializer byNew(CtClass objectType) { + NewInitializer i = new NewInitializer(); + i.objectType = objectType; + i.stringParams = null; + i.withConstructorParams = false; + return i; + } + + /** + * Makes an initializer creating a new object. + * + * <p>This initializer creates a new object and uses it as the initial + * value of the field. The constructor of the created object receives + * the parameters: + * + * <ul><code>Object obj</code> - the object including the field.<br> + * <code>String[] strs</code> - the character strings specified + * by <code>stringParams</code><br> + * </ul> + * + * <p>If the initialized field is static, then the constructor + * receives only <code>strs</code>. + * + * @param objectType the class instantiated for the initial value. + * @param stringParams the array of strings passed to the + * constructor. + */ + public static Initializer byNew(CtClass objectType, + String[] stringParams) { + NewInitializer i = new NewInitializer(); + i.objectType = objectType; + i.stringParams = stringParams; + i.withConstructorParams = false; + return i; + } + + /** + * Makes an initializer creating a new object. + * + * <p>This initializer creates a new object and uses it as the initial + * value of the field. The constructor of the created object receives + * the parameters: + * + * <ul><code>Object obj</code> - the object including the field.<br> + * <code>Object[] args</code> - the parameters passed to the + * constructor of the object including the + * filed. + * </ul> + * + * <p>If the initialized field is static, then the constructor does + * not receive any parameters. + * + * @param objectType the class instantiated for the initial value. + * + * @see javassist.CtField.Initializer#byNewArray(CtClass,int) + * @see javassist.CtField.Initializer#byNewArray(CtClass,int[]) + */ + public static Initializer byNewWithParams(CtClass objectType) { + NewInitializer i = new NewInitializer(); + i.objectType = objectType; + i.stringParams = null; + i.withConstructorParams = true; + return i; + } + + /** + * Makes an initializer creating a new object. + * + * <p>This initializer creates a new object and uses it as the initial + * value of the field. The constructor of the created object receives + * the parameters: + * + * <ul><code>Object obj</code> - the object including the field.<br> + * <code>String[] strs</code> - the character strings specified + * by <code>stringParams</code><br> + * <code>Object[] args</code> - the parameters passed to the + * constructor of the object including the + * filed. + * </ul> + * + * <p>If the initialized field is static, then the constructor receives + * only <code>strs</code>. + * + * @param objectType the class instantiated for the initial value. + * @param stringParams the array of strings passed to the + * constructor. + */ + public static Initializer byNewWithParams(CtClass objectType, + String[] stringParams) { + NewInitializer i = new NewInitializer(); + i.objectType = objectType; + i.stringParams = stringParams; + i.withConstructorParams = true; + return i; + } + + /** + * Makes an initializer calling a static method. + * + * <p>This initializer calls a static method and uses the returned + * value as the initial value of the field. + * The called method receives the parameters: + * + * <ul><code>Object obj</code> - the object including the field.<br> + * </ul> + * + * <p>If the initialized field is static, then the method does + * not receive any parameters. + * + * <p>The type of the returned value must be the same as the field + * type. + * + * @param methodClass the class that the static method is + * declared in. + * @param methodName the name of the satic method. + */ + public static Initializer byCall(CtClass methodClass, + String methodName) { + MethodInitializer i = new MethodInitializer(); + i.objectType = methodClass; + i.methodName = methodName; + i.stringParams = null; + i.withConstructorParams = false; + return i; + } + + /** + * Makes an initializer calling a static method. + * + * <p>This initializer calls a static method and uses the returned + * value as the initial value of the field. The called method + * receives the parameters: + * + * <ul><code>Object obj</code> - the object including the field.<br> + * <code>String[] strs</code> - the character strings specified + * by <code>stringParams</code><br> + * </ul> + * + * <p>If the initialized field is static, then the method + * receive only <code>strs</code>. + * + * <p>The type of the returned value must be the same as the field + * type. + * + * @param methodClass the class that the static method is + * declared in. + * @param methodName the name of the satic method. + * @param stringParams the array of strings passed to the + * static method. + */ + public static Initializer byCall(CtClass methodClass, + String methodName, + String[] stringParams) { + MethodInitializer i = new MethodInitializer(); + i.objectType = methodClass; + i.methodName = methodName; + i.stringParams = stringParams; + i.withConstructorParams = false; + return i; + } + + /** + * Makes an initializer calling a static method. + * + * <p>This initializer calls a static method and uses the returned + * value as the initial value of the field. The called method + * receives the parameters: + * + * <ul><code>Object obj</code> - the object including the field.<br> + * <code>Object[] args</code> - the parameters passed to the + * constructor of the object including the + * filed. + * </ul> + * + * <p>If the initialized field is static, then the method does + * not receive any parameters. + * + * <p>The type of the returned value must be the same as the field + * type. + * + * @param methodClass the class that the static method is + * declared in. + * @param methodName the name of the satic method. + */ + public static Initializer byCallWithParams(CtClass methodClass, + String methodName) { + MethodInitializer i = new MethodInitializer(); + i.objectType = methodClass; + i.methodName = methodName; + i.stringParams = null; + i.withConstructorParams = true; + return i; + } + + /** + * Makes an initializer calling a static method. + * + * <p>This initializer calls a static method and uses the returned + * value as the initial value of the field. The called method + * receives the parameters: + * + * <ul><code>Object obj</code> - the object including the field.<br> + * <code>String[] strs</code> - the character strings specified + * by <code>stringParams</code><br> + * <code>Object[] args</code> - the parameters passed to the + * constructor of the object including the + * filed. + * </ul> + * + * <p>If the initialized field is static, then the method + * receive only <code>strs</code>. + * + * <p>The type of the returned value must be the same as the field + * type. + * + * @param methodClass the class that the static method is + * declared in. + * @param methodName the name of the satic method. + * @param stringParams the array of strings passed to the + * static method. + */ + public static Initializer byCallWithParams(CtClass methodClass, + String methodName, String[] stringParams) { + MethodInitializer i = new MethodInitializer(); + i.objectType = methodClass; + i.methodName = methodName; + i.stringParams = stringParams; + i.withConstructorParams = true; + return i; + } + + /** + * Makes an initializer creating a new array. + * + * @param type the type of the array. + * @param size the size of the array. + * @throws NotFoundException if the type of the array components + * is not found. + */ + public static Initializer byNewArray(CtClass type, int size) + throws NotFoundException + { + return new ArrayInitializer(type.getComponentType(), size); + } + + /** + * Makes an initializer creating a new multi-dimensional array. + * + * @param type the type of the array. + * @param sizes an <code>int</code> array of the size in every + * dimension. + * The first element is the size in the first + * dimension. The second is in the second, etc. + */ + public static Initializer byNewArray(CtClass type, int[] sizes) { + return new MultiArrayInitializer(type, sizes); + } + + /** + * Makes an initializer. + * + * @param source initializer expression. + */ + public static Initializer byExpr(String source) { + return new CodeInitializer(source); + } + + static Initializer byExpr(ASTree source) { + return new PtreeInitializer(source); + } + + // Check whether this initializer is valid for the field type. + // If it is invaild, this method throws an exception. + void check(CtClass type) throws CannotCompileException {} + + // produce codes for initialization + abstract int compile(CtClass type, String name, Bytecode code, + CtClass[] parameters, Javac drv) + throws CannotCompileException; + + // produce codes for initialization + abstract int compileIfStatic(CtClass type, String name, + Bytecode code, Javac drv) throws CannotCompileException; + } + + static abstract class CodeInitializer0 extends Initializer { + abstract void compileExpr(Javac drv) throws CompileError; + + int compile(CtClass type, String name, Bytecode code, + CtClass[] parameters, Javac drv) + throws CannotCompileException + { + try { + code.addAload(0); + compileExpr(drv); + code.addPutfield(Bytecode.THIS, name, Descriptor.of(type)); + return code.getMaxStack(); + } + catch (CompileError e) { + throw new CannotCompileException(e); + } + } + + int compileIfStatic(CtClass type, String name, Bytecode code, + Javac drv) throws CannotCompileException + { + try { + compileExpr(drv); + code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type)); + return code.getMaxStack(); + } + catch (CompileError e) { + throw new CannotCompileException(e); + } + } + } + + static class CodeInitializer extends CodeInitializer0 { + private String expression; + + CodeInitializer(String expr) { expression = expr; } + + void compileExpr(Javac drv) throws CompileError { + drv.compileExpr(expression); + } + } + + static class PtreeInitializer extends CodeInitializer0 { + private ASTree expression; + + PtreeInitializer(ASTree expr) { expression = expr; } + + void compileExpr(Javac drv) throws CompileError { + drv.compileExpr(expression); + } + } + + /** + * A field initialized with a parameter passed to the constructor + * of the class containing that field. + */ + static class ParamInitializer extends Initializer { + int nthParam; + + ParamInitializer() {} + + int compile(CtClass type, String name, Bytecode code, + CtClass[] parameters, Javac drv) + throws CannotCompileException + { + if (parameters != null && nthParam < parameters.length) { + code.addAload(0); + int nth = nthParamToLocal(nthParam, parameters, false); + int s = code.addLoad(nth, type) + 1; + code.addPutfield(Bytecode.THIS, name, Descriptor.of(type)); + return s; // stack size + } + else + return 0; // do not initialize + } + + /** + * Computes the index of the local variable that the n-th parameter + * is assigned to. + * + * @param nth n-th parameter + * @param params list of parameter types + * @param isStatic true if the method is static. + */ + static int nthParamToLocal(int nth, CtClass[] params, + boolean isStatic) { + CtClass longType = CtClass.longType; + CtClass doubleType = CtClass.doubleType; + int k; + if (isStatic) + k = 0; + else + k = 1; // 0 is THIS. + + for (int i = 0; i < nth; ++i) { + CtClass type = params[i]; + if (type == longType || type == doubleType) + k += 2; + else + ++k; + } + + return k; + } + + int compileIfStatic(CtClass type, String name, Bytecode code, + Javac drv) throws CannotCompileException + { + return 0; + } + } + + /** + * A field initialized with an object created by the new operator. + */ + static class NewInitializer extends Initializer { + CtClass objectType; + String[] stringParams; + boolean withConstructorParams; + + NewInitializer() {} + + /** + * Produces codes in which a new object is created and assigned to + * the field as the initial value. + */ + int compile(CtClass type, String name, Bytecode code, + CtClass[] parameters, Javac drv) + throws CannotCompileException + { + int stacksize; + + code.addAload(0); + code.addNew(objectType); + code.add(Bytecode.DUP); + code.addAload(0); + + if (stringParams == null) + stacksize = 4; + else + stacksize = compileStringParameter(code) + 4; + + if (withConstructorParams) + stacksize += CtNewWrappedMethod.compileParameterList(code, + parameters, 1); + + code.addInvokespecial(objectType, "<init>", getDescriptor()); + code.addPutfield(Bytecode.THIS, name, Descriptor.of(type)); + return stacksize; + } + + private String getDescriptor() { + final String desc3 + = "(Ljava/lang/Object;[Ljava/lang/String;[Ljava/lang/Object;)V"; + + if (stringParams == null) + if (withConstructorParams) + return "(Ljava/lang/Object;[Ljava/lang/Object;)V"; + else + return "(Ljava/lang/Object;)V"; + else + if (withConstructorParams) + return desc3; + else + return "(Ljava/lang/Object;[Ljava/lang/String;)V"; + } + + /** + * Produces codes for a static field. + */ + int compileIfStatic(CtClass type, String name, Bytecode code, + Javac drv) throws CannotCompileException + { + String desc; + + code.addNew(objectType); + code.add(Bytecode.DUP); + + int stacksize = 2; + if (stringParams == null) + desc = "()V"; + else { + desc = "([Ljava/lang/String;)V"; + stacksize += compileStringParameter(code); + } + + code.addInvokespecial(objectType, "<init>", desc); + code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type)); + return stacksize; + } + + protected final int compileStringParameter(Bytecode code) + throws CannotCompileException + { + int nparam = stringParams.length; + code.addIconst(nparam); + code.addAnewarray("java.lang.String"); + for (int j = 0; j < nparam; ++j) { + code.add(Bytecode.DUP); // dup + code.addIconst(j); // iconst_<j> + code.addLdc(stringParams[j]); // ldc ... + code.add(Bytecode.AASTORE); // aastore + } + + return 4; + } + + } + + /** + * A field initialized with the result of a static method call. + */ + static class MethodInitializer extends NewInitializer { + String methodName; + // the method class is specified by objectType. + + MethodInitializer() {} + + /** + * Produces codes in which a new object is created and assigned to + * the field as the initial value. + */ + int compile(CtClass type, String name, Bytecode code, + CtClass[] parameters, Javac drv) + throws CannotCompileException + { + int stacksize; + + code.addAload(0); + code.addAload(0); + + if (stringParams == null) + stacksize = 2; + else + stacksize = compileStringParameter(code) + 2; + + if (withConstructorParams) + stacksize += CtNewWrappedMethod.compileParameterList(code, + parameters, 1); + + String typeDesc = Descriptor.of(type); + String mDesc = getDescriptor() + typeDesc; + code.addInvokestatic(objectType, methodName, mDesc); + code.addPutfield(Bytecode.THIS, name, typeDesc); + return stacksize; + } + + private String getDescriptor() { + final String desc3 + = "(Ljava/lang/Object;[Ljava/lang/String;[Ljava/lang/Object;)"; + + if (stringParams == null) + if (withConstructorParams) + return "(Ljava/lang/Object;[Ljava/lang/Object;)"; + else + return "(Ljava/lang/Object;)"; + else + if (withConstructorParams) + return desc3; + else + return "(Ljava/lang/Object;[Ljava/lang/String;)"; + } + + /** + * Produces codes for a static field. + */ + int compileIfStatic(CtClass type, String name, Bytecode code, + Javac drv) throws CannotCompileException + { + String desc; + + int stacksize = 1; + if (stringParams == null) + desc = "()"; + else { + desc = "([Ljava/lang/String;)"; + stacksize += compileStringParameter(code); + } + + String typeDesc = Descriptor.of(type); + code.addInvokestatic(objectType, methodName, desc + typeDesc); + code.addPutstatic(Bytecode.THIS, name, typeDesc); + return stacksize; + } + } + + static class IntInitializer extends Initializer { + int value; + + IntInitializer(int v) { value = v; } + + void check(CtClass type) throws CannotCompileException { + if (type != CtClass.intType) + throw new CannotCompileException("type mismatch"); + } + + int compile(CtClass type, String name, Bytecode code, + CtClass[] parameters, Javac drv) + throws CannotCompileException + { + code.addAload(0); + code.addIconst(value); + code.addPutfield(Bytecode.THIS, name, Descriptor.of(type)); + return 2; // stack size + } + + int compileIfStatic(CtClass type, String name, Bytecode code, + Javac drv) throws CannotCompileException + { + code.addIconst(value); + code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type)); + return 1; // stack size + } + } + + static class LongInitializer extends Initializer { + long value; + + LongInitializer(long v) { value = v; } + + void check(CtClass type) throws CannotCompileException { + if (type != CtClass.longType) + throw new CannotCompileException("type mismatch"); + } + + int compile(CtClass type, String name, Bytecode code, + CtClass[] parameters, Javac drv) + throws CannotCompileException + { + code.addAload(0); + code.addLdc2w(value); + code.addPutfield(Bytecode.THIS, name, Descriptor.of(type)); + return 3; // stack size + } + + int compileIfStatic(CtClass type, String name, Bytecode code, + Javac drv) throws CannotCompileException + { + code.addLdc2w(value); + code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type)); + return 2; // stack size + } + } + + static class DoubleInitializer extends Initializer { + double value; + + DoubleInitializer(double v) { value = v; } + + void check(CtClass type) throws CannotCompileException { + if (type != CtClass.doubleType) + throw new CannotCompileException("type mismatch"); + } + + int compile(CtClass type, String name, Bytecode code, + CtClass[] parameters, Javac drv) + throws CannotCompileException + { + code.addAload(0); + code.addLdc2w(value); + code.addPutfield(Bytecode.THIS, name, Descriptor.of(type)); + return 3; // stack size + } + + int compileIfStatic(CtClass type, String name, Bytecode code, + Javac drv) throws CannotCompileException + { + code.addLdc2w(value); + code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type)); + return 2; // stack size + } + } + + static class StringInitializer extends Initializer { + String value; + + StringInitializer(String v) { value = v; } + + void check(CtClass type) throws CannotCompileException { + if (!type.getName().equals("java.lang.String")) + throw new CannotCompileException("type mismatch"); + } + + int compile(CtClass type, String name, Bytecode code, + CtClass[] parameters, Javac drv) + throws CannotCompileException + { + code.addAload(0); + code.addLdc(value); + code.addPutfield(Bytecode.THIS, name, Descriptor.of(type)); + return 2; // stack size + } + + int compileIfStatic(CtClass type, String name, Bytecode code, + Javac drv) throws CannotCompileException + { + code.addLdc(value); + code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type)); + return 1; // stack size + } + } + + static class ArrayInitializer extends Initializer { + CtClass type; + int size; + + ArrayInitializer(CtClass t, int s) { type = t; size = s; } + + private void addNewarray(Bytecode code) { + if (type.isPrimitive()) + code.addNewarray(((CtPrimitiveType)type).getArrayType(), + size); + else + code.addAnewarray(type, size); + } + + int compile(CtClass type, String name, Bytecode code, + CtClass[] parameters, Javac drv) + throws CannotCompileException + { + code.addAload(0); + addNewarray(code); + code.addPutfield(Bytecode.THIS, name, Descriptor.of(type)); + return 2; // stack size + } + + int compileIfStatic(CtClass type, String name, Bytecode code, + Javac drv) throws CannotCompileException + { + addNewarray(code); + code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type)); + return 1; // stack size + } + } + + static class MultiArrayInitializer extends Initializer { + CtClass type; + int[] dim; + + MultiArrayInitializer(CtClass t, int[] d) { type = t; dim = d; } + + void check(CtClass type) throws CannotCompileException { + if (!type.isArray()) + throw new CannotCompileException("type mismatch"); + } + + int compile(CtClass type, String name, Bytecode code, + CtClass[] parameters, Javac drv) + throws CannotCompileException + { + code.addAload(0); + int s = code.addMultiNewarray(type, dim); + code.addPutfield(Bytecode.THIS, name, Descriptor.of(type)); + return s + 1; // stack size + } + + int compileIfStatic(CtClass type, String name, Bytecode code, + Javac drv) throws CannotCompileException + { + int s = code.addMultiNewarray(type, dim); + code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type)); + return s; // stack size + } + } +} diff --git a/src/main/javassist/CtMember.java b/src/main/javassist/CtMember.java new file mode 100644 index 00000000..8621a18b --- /dev/null +++ b/src/main/javassist/CtMember.java @@ -0,0 +1,79 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +/** + * An instance of <code>CtMember</code> represents a field, a constructor, + * or a method. + */ +public abstract class CtMember { + protected CtClass declaringClass; + + protected CtMember(CtClass clazz) { declaringClass = clazz; } + + /** + * Returns the class that declares this member. + */ + public CtClass getDeclaringClass() { return declaringClass; } + + /** + * Obtains the modifiers of the member. + * + * @return modifiers encoded with + * <code>javassist.Modifier</code>. + * @see Modifier + */ + public abstract int getModifiers(); + + /** + * Sets the encoded modifiers of the member. + * + * @see Modifier + */ + public abstract void setModifiers(int mod); + + /** + * Obtains the name of the member. + */ + public abstract String getName(); + + /** + * Obtains an attribute with the given name. + * If that attribute is not found in the class file, this + * method returns null. + * + * @param name attribute name + */ + public abstract byte[] getAttribute(String name); + + /** + * Adds an attribute. The attribute is saved in the class file. + * + * @param name attribute name + * @param data attribute value + */ + public abstract void setAttribute(String name, byte[] data); +} diff --git a/src/main/javassist/CtMethod.java b/src/main/javassist/CtMethod.java new file mode 100644 index 00000000..dbe1931c --- /dev/null +++ b/src/main/javassist/CtMethod.java @@ -0,0 +1,579 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +import javassist.bytecode.*; +import javassist.expr.ExprEditor; + +/* Some methods do nothing except calling the super's method. + * They might seem redundant but they are necessary so that javadoc + * includes the description of those methods in the page of this class. + */ + +/** + * An instance of <code>CtMethod</code> represents a method. + * + * @see CtClass#getDeclaredMethods() + * @see CtNewMethod + */ +public final class CtMethod extends CtBehavior { + protected CtMethod next; + protected int cachedHashCode; + + CtMethod(MethodInfo minfo, CtClass declaring) { + super(declaring, minfo); + next = null; + cachedHashCode = 0; + } + + /** + * Creates a public abstract method. The created method must be + * added to a class with <code>CtClass.addMethod()</code>. + * + * @param declaring the class to which the created method is added. + * @param returnType the type of the returned value + * @param mname the method name + * @param parameters a list of the parameter types + * + * @see CtClass#addMethod(CtMethod) + */ + public CtMethod(CtClass returnType, String mname, + CtClass[] parameters, CtClass declaring) { + this(null, declaring); + ConstPool cp = declaring.getClassFile2().getConstPool(); + String desc = Descriptor.ofMethod(returnType, parameters); + methodInfo = new MethodInfo(cp, mname, desc); + setModifiers(Modifier.PUBLIC | Modifier.ABSTRACT); + } + + /** + * Creates a copy of a <code>CtMethod</code> object. + * The created method must be + * added to a class with <code>CtClass.addMethod()</code>. + * + * <p>All occurrences of class names in the created method + * are replaced with names specified by + * <code>map</code> if <code>map</code> is not <code>null</code>. + * + * <p>For example, suppose that a method <code>at()</code> is as + * follows: + * + * <ul><pre>public X at(int i) { + * return (X)super.elementAt(i); + * }</pre></ul> + * + * <p>(<code>X</code> is a class name.) If <code>map</code> substitutes + * <code>String</code> for <code>X</code>, then the created method is: + * + * <ul><pre>public String at(int i) { + * return (String)super.elementAt(i); + * }</pre></ul> + * + * <p>By default, all the occurrences of the names of the class + * declaring <code>at()</code> and the superclass are replaced + * with the name of the class and the superclass that the + * created method is added to. + * This is done whichever <code>map</code> is null or not. + * To prevent this replacement, call <code>ClassMap.fix()</code>. + * + * <p><b>Note:</b> if the <code>.class</code> notation (for example, + * <code>String.class</code>) is included in an expression, the + * Javac compiler may produce a helper method. + * Since this constructor never + * copies this helper method, the programmers have the responsiblity of + * copying it. Otherwise, use <code>Class.forName()</code> in the + * expression. + * + * @param src the source method. + * @param declaring the class to which the created method is added. + * @param map the hashtable associating original class names + * with substituted names. + * It can be <code>null</code>. + * + * @see CtClass#addMethod(CtMethod) + * @see ClassMap#fix(String) + */ + public CtMethod(CtMethod src, CtClass declaring, ClassMap map) + throws CannotCompileException + { + this(null, declaring); + MethodInfo srcInfo = src.methodInfo; + CtClass srcClass = src.getDeclaringClass(); + ConstPool cp = declaring.getClassFile2().getConstPool(); + if (map == null) + map = new ClassMap(); + + map.put(srcClass.getName(), declaring.getName()); + try { + CtClass srcSuper = srcClass.getSuperclass(); + if (srcSuper != null) { + String srcSuperName = srcSuper.getName(); + if (!srcSuperName.equals(CtClass.javaLangObject)) + map.put(srcSuperName, + declaring.getSuperclass().getName()); + } + + methodInfo = new MethodInfo(cp, srcInfo.getName(), srcInfo, map); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } + + static CtMethod append(CtMethod list, CtMethod tail) { + tail.next = null; + if (list == null) + return tail; + else { + CtMethod lst = list; + while (lst.next != null) + lst = lst.next; + + lst.next = tail; + return list; + } + } + + static int count(CtMethod m) { + int n = 0; + while (m != null) { + ++n; + m = m.next; + } + + return n; + } + + /** + * Returns a hash code value for the method. + * If two methods have the same name and signature, then + * the hash codes for the two methods are equal. + */ + public int hashCode() { + /* This method is overridden in ExistingMethod for optimization. + */ + if (cachedHashCode == 0) { + String signature + = methodInfo.getName() + ':' + methodInfo.getDescriptor(); + + // System.identityHashCode() returns 0 only for null. + cachedHashCode = System.identityHashCode(signature.intern()); + } + + return cachedHashCode; + } + + /** + * Indicates whether <code>obj</code> has the same name and the + * same signature as this method. + */ + public boolean equals(Object obj) { + return obj != null && obj instanceof CtMethod + && obj.hashCode() == hashCode(); + } + + /** + * Returns the MethodInfo representing the method in the class file. + */ + public MethodInfo getMethodInfo() { + return super.getMethodInfo(); + } + + /** + * Obtains the modifiers of the method. + * + * @return modifiers encoded with + * <code>javassist.Modifier</code>. + * @see Modifier + */ + public int getModifiers() { + return super.getModifiers(); + } + + /** + * Sets the encoded modifiers of the method. + * + * @see Modifier + */ + public void setModifiers(int mod) { + super.setModifiers(mod); + } + + /** + * Obtains the name of this method. + */ + public String getName() { + return methodInfo.getName(); + } + + /** + * Changes the name of this method. + */ + public void setName(String newname) { + declaringClass.checkModify(); + methodInfo.setName(newname); + } + + /** + * Returns the class that declares this method. + */ + public CtClass getDeclaringClass() { + return super.getDeclaringClass(); + } + + /** + * Obtains parameter types of this method. + */ + public CtClass[] getParameterTypes() throws NotFoundException { + return super.getParameterTypes(); + } + + /** + * Obtains the type of the returned value. + */ + public CtClass getReturnType() throws NotFoundException { + return getReturnType0(); + } + + /** + * Returns the character string representing the parameter types + * and the return type. If two methods have the same parameter types + * and the return type, <code>getSignature()</code> returns the + * same string. + */ + public String getSignature() { + return super.getSignature(); + } + + /** + * Obtains exceptions that this method may throw. + */ + public CtClass[] getExceptionTypes() throws NotFoundException { + return super.getExceptionTypes(); + } + + /** + * Sets exceptions that this method may throw. + * + * @param types exception types (or null) + */ + public void setExceptionTypes(CtClass[] types) throws NotFoundException { + super.setExceptionTypes(types); + } + + /** + * Sets a method body. + * + * @param src the source code representing the method body. + * It must be a single statement or block. + */ + public void setBody(String src) throws CannotCompileException { + super.setBody(src); + } + + /** + * Copies a method body from another method. + * If this method is abstract, the abstract modifier is removed + * after the method body is copied. + * + * <p>All occurrences of the class names in the copied method body + * are replaced with the names specified by + * <code>map</code> if <code>map</code> is not <code>null</code>. + * + * @param src the method that the body is copied from. + * @param map the hashtable associating original class names + * with substituted names. + * It can be <code>null</code>. + */ + public void setBody(CtMethod src, ClassMap map) + throws CannotCompileException + { + setBody0(src.declaringClass, src.methodInfo, + declaringClass, methodInfo, map); + } + + /** + * Replace a method body with a new method body wrapping the + * given method. + * + * @param mbody the wrapped method + * @param constParam the constant parameter given to + * the wrapped method + * (maybe <code>null</code>). + * + * @see CtNewMethod#wrapped(CtClass,String,CtClass[],CtClass[],CtMethod,CtMethod.ConstParameter,CtClass) + */ + public void setWrappedBody(CtMethod mbody, ConstParameter constParam) + throws CannotCompileException + { + declaringClass.checkModify(); + + CtClass clazz = getDeclaringClass(); + CtClass[] params; + CtClass retType; + try { + params = getParameterTypes(); + retType = getReturnType(); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + + Bytecode code = CtNewWrappedMethod.makeBody(clazz, + clazz.getClassFile2(), + mbody, + params, retType, + constParam); + CodeAttribute cattr = code.toCodeAttribute(); + methodInfo.setCodeAttribute(cattr); + methodInfo.setAccessFlags(methodInfo.getAccessFlags() + & ~AccessFlag.ABSTRACT); + } + + /** + * Obtains an attribute with the given name. + * If that attribute is not found in the class file, this + * method returns null. + * + * @param name attribute name + */ + public byte[] getAttribute(String name) { + return super.getAttribute(name); + } + + /** + * Adds an attribute. The attribute is saved in the class file. + * + * @param name attribute name + * @param data attribute value + */ + public void setAttribute(String name, byte[] data) { + super.setAttribute(name, data); + } + + /** + * Declares to use <code>$cflow</code> for this method. + * If <code>$cflow</code> is used, the class files modified + * with Javassist requires a support class + * <code>javassist.runtime.Cflow</code> at runtime + * (other Javassist classes are not required at runtime). + * + * <p>Every <code>$cflow</code> variable is given a unique name. + * For example, if the given name is <code>"Point.paint"</code>, + * then the variable is indicated by <code>$cflow(Point.paint)</code>. + * + * @param name <code>$cflow</code> name. It can include + * alphabets, numbers, <code>_</code>, + * <code>$</code>, and <code>.</code> (dot). + * + * @see javassist.runtime.Cflow + */ + public void useCflow(String name) throws CannotCompileException { + super.useCflow(name); + } + + /** + * Modifies the method body. + * + * @param converter specifies how to modify. + */ + public void instrument(CodeConverter converter) + throws CannotCompileException + { + super.instrument(converter); + } + + /** + * Modifies the method body. + * + * @param editor specifies how to modify. + */ + public void instrument(ExprEditor editor) + throws CannotCompileException + { + super.instrument(editor); + } + + /** + * Inserts bytecode at the beginning of the method body. + * + * @param src the source code representing the inserted bytecode. + * It must be a single statement or block. + */ + public void insertBefore(String src) throws CannotCompileException { + super.insertBefore(src); + } + + /** + * Inserts bytecode at the end of the method body. + * The bytecode is inserted just before every return insturction. + * It is not executed when an exception is thrown. + * + * @param src the source code representing the inserted bytecode. + * It must be a single statement or block. + */ + public void insertAfter(String src) + throws CannotCompileException + { + super.insertAfter(src); + } + + /** + * Inserts bytecode at the end of the method body. + * The bytecode is inserted just before every return insturction. + * + * @param src the source code representing the inserted bytecode. + * It must be a single statement or block. + * @param asFinally true if the inserted bytecode is executed + * not only when the transfer normally returns + * but also when an exception is thrown. + */ + public void insertAfter(String src, boolean asFinally) + throws CannotCompileException + { + super.insertAfter(src, asFinally); + } + + /** + * Adds a catch clause that handles an exception thrown in the + * method body. + * The catch clause must end with a return or throw statement. + * + * @param src the source code representing the catch clause. + * It must be a single statement or block. + * @param exceptionType the type of the exception handled by the + * catch clause. + * @param exceptionName the name of the variable containing the + * caught exception. + */ + public void addCatch(String src, CtClass exceptionType, + String exceptionName) + throws CannotCompileException + { + super.addCatch(src, exceptionType, exceptionName); + } + + // inner classes + + /** + * Instances of this class represent a constant parameter. + * They are used to specify the parameter given to the methods + * created by <code>CtNewMethod.wrapped()</code>. + * + * @see CtMethod#setWrappedBody(CtMethod,CtMethod.ConstParameter) + * @see CtNewMethod#wrapped(CtClass,String,CtClass[],CtClass[],CtMethod,CtMethod.ConstParameter,CtClass) + * @see CtNewConstructor#make(CtClass[],CtClass[],int,CtMethod,CtMethod.ConstParameter,CtClass) + */ + public static class ConstParameter { + /** + * Makes an integer constant. + * + * @param i the constant value. + */ + public static ConstParameter integer(int i) { + return new IntConstParameter(i); + } + + /** + * Makes an <code>String</code> constant. + * + * @param s the constant value. + */ + public static ConstParameter string(String s) { + return new StringConstParameter(s); + } + + ConstParameter() {} + + int compile(Bytecode code) throws CannotCompileException { + return 0; + } + + String descriptor() { + return defaultDescriptor(); + } + + static String defaultDescriptor() { + return "([Ljava/lang/Object;)Ljava/lang/Object;"; + } + + String constDescriptor() { + return defaultConstDescriptor(); + } + + /** + * Returns the descriptor for constructors. + */ + static String defaultConstDescriptor() { + return "([Ljava/lang/Object;)V"; + } + } + + static class IntConstParameter extends ConstParameter { + int param; + + IntConstParameter(int i) { + param = i; + } + + int compile(Bytecode code) throws CannotCompileException { + code.addIconst(param); + return 1; + } + + String descriptor() { + return "([Ljava/lang/Object;I)Ljava/lang/Object;"; + } + + String constDescriptor() { + return "([Ljava/lang/Object;I)V"; + } + } + + static class StringConstParameter extends ConstParameter { + String param; + + StringConstParameter(String s) { + param = s; + } + + int compile(Bytecode code) throws CannotCompileException { + code.addLdc(param); + return 1; + } + + String descriptor() { + return "([Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;"; + } + + String constDescriptor() { + return "([Ljava/lang/Object;Ljava/lang/String;)V"; + } + } +} diff --git a/src/main/javassist/CtNewClass.java b/src/main/javassist/CtNewClass.java new file mode 100644 index 00000000..09bf345d --- /dev/null +++ b/src/main/javassist/CtNewClass.java @@ -0,0 +1,108 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +import java.io.DataOutputStream; +import java.io.IOException; +import javassist.bytecode.ClassFile; + +class CtNewClass extends CtClassType { + /* true if the class is an interface. + */ + protected boolean hasConstructor; + + CtNewClass(String name, ClassPool cp, + boolean isInterface, CtClass superclass) { + super(name, cp); + wasChanged = true; + eraseCache(); + String superName; + if (superclass == null) + superName = null; + else + superName = superclass.getName(); + + classfile = new ClassFile(isInterface, name, superName); + + setModifiers(Modifier.setPublic(getModifiers())); + hasConstructor = isInterface; + } + + public void addConstructor(CtConstructor c) + throws CannotCompileException + { + hasConstructor = true; + super.addConstructor(c); + } + + void toBytecode(DataOutputStream out) + throws CannotCompileException, IOException + { + if (!hasConstructor) + try { + inheritAllConstructors(); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + + super.toBytecode(out); + } + + /** + * Adds constructors inhrited from the super class. + * + * <p>After this method is called, the class inherits all the + * constructors from the super class. The added constructor + * calls the super's constructor with the same signature. + */ + public void inheritAllConstructors() + throws CannotCompileException, NotFoundException + { + CtClass superclazz; + CtConstructor[] cs; + + superclazz = getSuperclass(); + cs = superclazz.getDeclaredConstructors(); + + int n = 0; + for (int i = 0; i < cs.length; ++i) { + CtConstructor c = cs[i]; + if (Modifier.isPublic(c.getModifiers())) { + CtConstructor cons + = CtNewConstructor.make(c.getParameterTypes(), + c.getExceptionTypes(), this); + addConstructor(cons); + ++n; + } + } + + if (n < 1) + throw new CannotCompileException( + "no public constructor in " + superclazz.getName()); + + } +} diff --git a/src/main/javassist/CtNewConstructor.java b/src/main/javassist/CtNewConstructor.java new file mode 100644 index 00000000..13bae8a6 --- /dev/null +++ b/src/main/javassist/CtNewConstructor.java @@ -0,0 +1,313 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +import javassist.bytecode.*; +import javassist.compiler.Javac; +import javassist.compiler.CompileError; +import javassist.CtMethod.ConstParameter; + +/** + * A collection of static methods for creating a <code>CtConstructor</code>. + * An instance of this class does not make any sense. + * + * @see CtClass#addConstructor(CtConstructor) + */ +public class CtNewConstructor { + /** + * Specifies that no parameters are passed to a super-class' + * constructor. That is, the default constructor is invoked. + */ + public static final int PASS_NONE = 0; // call super() + + /** + * Specifies that parameters are converted into an array of + * <code>Object</code> and passed to a super-class' + * constructor. + */ + public static final int PASS_ARRAY = 1; // an array of parameters + + /** + * Specifies that parameters are passed <i>as is</i> + * to a super-class' constructor. The signature of that + * constructor must be the same as that of the created constructor. + */ + public static final int PASS_PARAMS = 2; + + /** + * Compiles the given source code and creates a constructor. + * The source code must include not only the constructor body + * but the whole declaration. + * + * @param src the source text. + * @param declaring the class to which the created constructor is added. + */ + public static CtConstructor make(String src, CtClass declaring) + throws CannotCompileException + { + Javac compiler = new Javac(declaring); + try { + CtMember obj = compiler.compile(src); + if (obj instanceof CtConstructor) + return (CtConstructor)obj; + } + catch (CompileError e) { + throw new CannotCompileException(e); + } + + throw new CannotCompileException("not a constructor"); + } + + /** + * Creates a public constructor. + * + * @param returnType the type of the returned value + * @param mname the method name + * @param parameters a list of the parameter types + * @param exceptions a list of the exception types + * @param src the source text of the method body. + * It must be a block surrounded by <code>{}</code>. + * @param declaring the class to which the created method is added. + */ + public static CtConstructor make(CtClass[] parameters, + CtClass[] exceptions, + String body, CtClass declaring) + throws CannotCompileException + { + try { + CtConstructor cc = new CtConstructor(parameters, declaring); + cc.setExceptionTypes(exceptions); + cc.setBody(body); + return cc; + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + } + + /** + * Creats a copy of a constructor. + * + * @param c the copied constructor. + * @param declaring the class to which the created method is added. + * @param map the hashtable associating original class names + * with substituted names. + * It can be <code>null</code>. + * + * @see CtConstructor#CtConstructor(CtConstructor,CtClass,ClassMap) + */ + public static CtConstructor copy(CtConstructor c, CtClass declaring, + ClassMap map) throws CannotCompileException { + return new CtConstructor(c, declaring, map); + } + + /** + * Creates a default (public) constructor. + * + * <p>The created constructor takes no parameter. It calls + * <code>super()</code>. + */ + public static CtConstructor defaultConstructor(CtClass declaring) + throws CannotCompileException + { + CtConstructor cons = new CtConstructor((CtClass[])null, declaring); + + ConstPool cp = declaring.getClassFile2().getConstPool(); + Bytecode code = new Bytecode(cp, 1, 1); + code.addAload(0); + try { + code.addInvokespecial(declaring.getSuperclass(), + "<init>", "()V"); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + + code.add(Bytecode.RETURN); + + cons.getMethodInfo2().setCodeAttribute(code.toCodeAttribute()); + return cons; + } + + /** + * Creates a public constructor that only calls a constructor + * in the super class. The created constructor receives parameters + * specified by <code>parameters</code> but calls the super's + * constructor without those parameters (that is, it calls the default + * constructor). + * + * <p>The parameters passed to the created constructor should be + * used for field initialization. <code>CtField.Initializer</code> + * objects implicitly insert initialization code in constructor + * bodies. + * + * @param parameters parameter types + * @param exceptions exception types + * @param declaring the class to which the created constructor + * is added. + * @see CtField.Initializer#byParameter(int) + */ + public static CtConstructor skeleton(CtClass[] parameters, + CtClass[] exceptions, CtClass declaring) + throws CannotCompileException + { + return make(parameters, exceptions, PASS_NONE, + null, null, declaring); + } + + /** + * Creates a public constructor that only calls a constructor + * in the super class. The created constructor receives parameters + * specified by <code>parameters</code> and calls the super's + * constructor with those parameters. + * + * @param parameters parameter types + * @param exceptions exception types + * @param declaring the class to which the created constructor + * is added. + */ + public static CtConstructor make(CtClass[] parameters, + CtClass[] exceptions, CtClass declaring) + throws CannotCompileException + { + return make(parameters, exceptions, PASS_PARAMS, + null, null, declaring); + } + + /** + * Creates a public constructor. + * + * <p>If <code>howto</code> is <code>PASS_PARAMS</code>, + * the created constructor calls the super's constructor with the + * same signature. The superclass must contain + * a constructor taking the same set of parameters as the created one. + * + * <p>If <code>howto</code> is <code>PASS_NONE</code>, + * the created constructor calls the super's default constructor. + * The superclass must contain a constructor taking no parameters. + * + * <p>If <code>howto</code> is <code>PASS_ARRAY</code>, + * the created constructor calls the super's constructor + * with the given parameters in the form of an array of + * <code>Object</code>. The signature of the super's constructor + * must be: + * + * <ul><code>constructor(Object[] params, <type> cvalue) + * </code></ul> + * + * <p>Here, <code>cvalue</code> is the constant value specified + * by <code>cparam</code>. + * + * <p>If <code>cparam</code> is <code>null</code>, the signature + * must be: + * + * <ul><code>constructor(Object[] params)</code></ul> + * + * <p>If <code>body</code> is not null, a copy of that method is + * embedded in the body of the created constructor. + * The embedded method is executed after + * the super's constructor is called and the values of fields are + * initialized. Note that <code>body</code> must not + * be a constructor but a method. + * + * <p>Since the embedded method is wrapped + * in parameter-conversion code + * as in <code>CtNewMethod.wrapped()</code>, + * the constructor parameters are + * passed in the form of an array of <code>Object</code>. + * The method specified by <code>body</code> must have the + * signature shown below: + * + * <ul><code>Object method(Object[] params, <type> cvalue) + * </code></ul> + * + * <p>If <code>cparam</code> is <code>null</code>, the signature + * must be: + * + * <ul><code>Object method(Object[] params)</code></ul> + * + * <p>Although the type of the returned value is <code>Object</code>, + * the value must be always <code>null</code>. + * + * <p><i>Example:</i> + * + * <ul><pre>ClassPool pool = ... ; + * CtClass xclass = pool.makeClass("X"); + * CtMethod method = pool.getMethod("Sample", "m"); + * xclass.setSuperclass(pool.get("Y")); + * CtClass[] argTypes = { CtClass.intType }; + * ConstParameter cparam = ConstParameter.string("test"); + * CtConstructor c = CtNewConstructor.make(argTypes, null, + * PASS_PARAMS, method, cparam, xclass); + * xclass.addConstructor(c);</pre></ul> + * + * <p>where the class <code>Sample</code> is as follows: + * + * <ul><pre>public class Sample { + * public Object m(Object[] args, String msg) { + * System.out.println(msg); + * return null; + * } + * }</pre></ul> + * + * <p>This program produces the following class: + * + * <ul><pre>public class X extends Y { + * public X(int p0) { + * super(p0); + * String msg = "test"; + * Object[] args = new Object[] { p0 }; + * // begin of copied body + * System.out.println(msg); + * Object result = null; + * // end + * } + * }</pre></ul> + * + * @param parameters a list of the parameter types + * @param exceptions a list of the exceptions + * @param howto how to pass parameters to the super-class' + * constructor (<code>PASS_NONE</code>, + * <code>PASS_ARRAY</code>, + * or <code>PASS_PARAMS</code>) + * @param body appended body (may be <code>null</code>). + * It must be not a constructor but a method. + * @param cparam constant parameter (may be <code>null</code>.) + * @param declaring the class to which the created constructor + * is added. + * + * @see CtNewMethod#wrapped(CtClass,String,CtClass[],CtClass[],CtMethod,CtMethod.ConstParameter,CtClass) + */ + public static CtConstructor make(CtClass[] parameters, + CtClass[] exceptions, int howto, + CtMethod body, ConstParameter cparam, + CtClass declaring) + throws CannotCompileException + { + return CtNewWrappedConstructor.wrapped(parameters, exceptions, + howto, body, cparam, declaring); + } +} diff --git a/src/main/javassist/CtNewMethod.java b/src/main/javassist/CtNewMethod.java new file mode 100644 index 00000000..05532902 --- /dev/null +++ b/src/main/javassist/CtNewMethod.java @@ -0,0 +1,439 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +import javassist.bytecode.*; +import javassist.compiler.Javac; +import javassist.compiler.CompileError; +import javassist.CtMethod.ConstParameter; + +/** + * A collection of static methods for creating a <code>CtMethod</code>. + * An instance of this class does not make any sense. + * + * @see CtClass#addMethod(CtMethod) + */ +public class CtNewMethod { + + /** + * Compiles the given source code and creates a method. + * The source code must include not only the method body + * but the whole declaration, for example, + * + * <ul><pre>"public Object id(Object obj) { return obj; }"</pre></ul> + * + * @param src the source text. + * @param declaring the class to which the created method is added. + */ + public static CtMethod make(String src, CtClass declaring) + throws CannotCompileException + { + return make(src, declaring, null, null); + } + + /** + * Compiles the given source code and creates a method. + * The source code must include not only the method body + * but the whole declaration, for example, + * + * <ul><pre>"public Object id(Object obj) { return obj; }"</pre></ul> + * + * <p>If the source code includes <code>$proceed()</code>, then + * it is compiled into a method call on the specified object. + * + * @param src the source text. + * @param declaring the class to which the created method is added. + * @param delegateObj the source text specifying the object + * that is called on by <code>$proceed()</code>. + * @param delegateMethod the name of the method + * that is called by <code>$proceed()</code>. + */ + public static CtMethod make(String src, CtClass declaring, + String delegateObj, String delegateMethod) + throws CannotCompileException + { + Javac compiler = new Javac(declaring); + try { + if (delegateMethod != null) + compiler.recordProceed(delegateObj, delegateMethod); + + CtMember obj = compiler.compile(src); + if (obj instanceof CtMethod) + return (CtMethod)obj; + } + catch (CompileError e) { + throw new CannotCompileException(e); + } + + throw new CannotCompileException("not a method"); + } + + /** + * Creates a public method. + * + * @param returnType the type of the returned value + * @param mname the method name + * @param parameters a list of the parameter types + * @param exceptions a list of the exception types + * @param src the source text of the method body. + * It must be a block surrounded by <code>{}</code>. + * @param declaring the class to which the created method is added. + */ + public static CtMethod make(CtClass returnType, String mname, + CtClass[] parameters, CtClass[] exceptions, + String body, CtClass declaring) + throws CannotCompileException + { + try { + CtMethod cm + = new CtMethod(returnType, mname, parameters, declaring); + cm.setExceptionTypes(exceptions); + cm.setBody(body); + return cm; + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + } + + /** + * Creates a copy of a method. This method is provided for creating + * a new method based on an existing method. + * + * @param src the source method. + * @param declaring the class to which the created method is added. + * @param map the hashtable associating original class names + * with substituted names. + * It can be <code>null</code>. + * + * @see CtMethod#CtMethod(CtMethod,CtClass,ClassMap) + */ + public static CtMethod copy(CtMethod src, CtClass declaring, + ClassMap map) throws CannotCompileException { + return new CtMethod(src, declaring, map); + } + + /** + * Creates a copy of a method with a new name. + * This method is provided for creating + * a new method based on an existing method. + * + * @param src the source method. + * @param name the name of the created method. + * @param declaring the class to which the created method is added. + * @param map the hashtable associating original class names + * with substituted names. + * It can be <code>null</code>. + * + * @see CtMethod#CtMethod(CtMethod,CtClass,ClassMap) + */ + public static CtMethod copy(CtMethod src, String name, CtClass declaring, + ClassMap map) throws CannotCompileException { + CtMethod cm = new CtMethod(src, declaring, map); + cm.setName(name); + return cm; + } + + /** + * Creates a public abstract method. + * + * @param returnType the type of the returned value + * @param mname the method name + * @param parameters a list of the parameter types + * @param exceptions a list of the exception types + * @param declaring the class to which the created method is added. + * + * @see CtMethod#CtMethod(CtClass,String,CtClass[],CtClass) + */ + public static CtMethod abstractMethod(CtClass returnType, + String mname, + CtClass[] parameters, + CtClass[] exceptions, + CtClass declaring) + throws NotFoundException + { + CtMethod cm = new CtMethod(returnType, mname, parameters, declaring); + cm.setExceptionTypes(exceptions); + return cm; + } + + /** + * Creates a public getter method. The getter method returns the value + * of the specified field in the class to which this method is added. + * The created method is initially not static even if the field is + * static. Change the modifiers if the method should be static. + * + * @param methodName the name of the getter + * @param field the field accessed. + */ + public static CtMethod getter(String methodName, CtField field) + throws CannotCompileException + { + FieldInfo finfo = field.getFieldInfo2(); + String fieldType = finfo.getDescriptor(); + String desc = "()" + fieldType; + ConstPool cp = finfo.getConstPool(); + MethodInfo minfo = new MethodInfo(cp, methodName, desc); + minfo.setAccessFlags(AccessFlag.PUBLIC); + + Bytecode code = new Bytecode(cp, 2, 1); + try { + String fieldName = finfo.getName(); + if ((finfo.getAccessFlags() & AccessFlag.STATIC) == 0) { + code.addAload(0); + code.addGetfield(Bytecode.THIS, fieldName, fieldType); + } + else + code.addGetstatic(Bytecode.THIS, fieldName, fieldType); + + code.addReturn(field.getType()); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + + minfo.setCodeAttribute(code.toCodeAttribute()); + return new CtMethod(minfo, field.getDeclaringClass()); + } + + /** + * Creates a public setter method. The setter method assigns the + * value of the first parameter to the specified field + * in the class to which this method is added. + * The created method is initially not static even if the field is + * static. Change the modifiers if the method should be static. + * + * @param methodName the name of the setter + * @param field the field accessed. + */ + public static CtMethod setter(String methodName, CtField field) + throws CannotCompileException + { + FieldInfo finfo = field.getFieldInfo2(); + String fieldType = finfo.getDescriptor(); + String desc = "(" + fieldType + ")V"; + ConstPool cp = finfo.getConstPool(); + MethodInfo minfo = new MethodInfo(cp, methodName, desc); + minfo.setAccessFlags(AccessFlag.PUBLIC); + + Bytecode code = new Bytecode(cp, 3, 3); + try { + String fieldName = finfo.getName(); + if ((finfo.getAccessFlags() & AccessFlag.STATIC) == 0) { + code.addAload(0); + code.addLoad(1, field.getType()); + code.addPutfield(Bytecode.THIS, fieldName, fieldType); + } + else { + code.addLoad(0, field.getType()); + code.addPutstatic(Bytecode.THIS, fieldName, fieldType); + } + + code.addReturn(null); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + + minfo.setCodeAttribute(code.toCodeAttribute()); + return new CtMethod(minfo, field.getDeclaringClass()); + } + + /** + * Creates a method forwarding to a delegate in + * a super class. The created method calls a method specified + * by <code>delegate</code> with all the parameters passed to the + * created method. If the delegate method returns a value, + * the created method returns that value to the caller. + * The delegate method must be declared in a super class. + * + * <p>The following method is an example of the created method. + * + * <ul><pre>int f(int p, int q) { + * return super.f(p, q); + * }</pre></ul> + * + * <p>The name of the created method can be changed by + * <code>setName()</code>. + * + * @param delegate the method that the created method forwards to. + * @param declaring the class to which the created method is + * added. + */ + public static CtMethod delegator(CtMethod delegate, CtClass declaring) + throws CannotCompileException + { + try { + return delegator0(delegate, declaring); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + } + + private static CtMethod delegator0(CtMethod delegate, CtClass declaring) + throws CannotCompileException, NotFoundException + { + MethodInfo deleInfo = delegate.getMethodInfo2(); + String methodName = deleInfo.getName(); + String desc = deleInfo.getDescriptor(); + ConstPool cp = declaring.getClassFile2().getConstPool(); + MethodInfo minfo = new MethodInfo(cp, methodName, desc); + minfo.setAccessFlags(deleInfo.getAccessFlags()); + + ExceptionsAttribute eattr = deleInfo.getExceptionsAttribute(); + if (eattr != null) + minfo.setExceptionsAttribute( + (ExceptionsAttribute)eattr.copy(cp, null)); + + Bytecode code = new Bytecode(cp, 0, 0); + boolean isStatic = Modifier.isStatic(delegate.getModifiers()); + CtClass deleClass = delegate.getDeclaringClass(); + CtClass[] params = delegate.getParameterTypes(); + int s; + if (isStatic) { + s = code.addLoadParameters(params); + code.addInvokestatic(deleClass, methodName, desc); + } + else { + code.addLoad(0, deleClass); + s = code.addLoadParameters(params); + code.addInvokespecial(deleClass, methodName, desc); + } + + code.addReturn(delegate.getReturnType()); + code.setMaxLocals(++s); + code.setMaxStack(s < 2 ? 2 : s); // for a 2-word return value + minfo.setCodeAttribute(code.toCodeAttribute()); + return new CtMethod(minfo, declaring); + } + + /** + * Creates a wrapped method. The wrapped method receives parameters + * in the form of an array of <code>Object</code>. + * + * <p>The body of the created method is a copy of the body of a method + * specified by <code>body</code>. However, it is wrapped in + * parameter-conversion code. + * + * <p>The method specified by <code>body</code> must have this singature: + * + * <ul><code>Object method(Object[] params, <type> cvalue) + * </code></ul> + * + * <p>The type of the <code>cvalue</code> depends on + * <code>constParam</code>. + * If <code>constParam</code> is <code>null</code>, the signature + * must be: + * + * <ul><code>Object method(Object[] params)</code></ul> + * + * <p>The method body copied from <code>body</code> is wrapped in + * parameter-conversion code, which converts parameters specified by + * <code>parameterTypes</code> into an array of <code>Object</code>. + * The returned value is also converted from the <code>Object</code> + * type to the type specified by <code>returnType</code>. Thus, + * the resulting method body is as follows: + * + * <ul><pre>Object[] params = new Object[] { p0, p1, ... }; + * <<i>type</i>> cvalue = <<i>constant-value</i>>; + * <i>... copied method body ...</i> + * Object result = <<i>returned value</i>> + * return (<i><returnType></i>)result; + * </pre></ul> + * + * <p>The variables <code>p0</code>, <code>p2</code>, ... represent + * formal parameters of the created method. + * The value of <code>cvalue</code> is specified by + * <code>constParam</code>. + * + * <p>If the type of a parameter or a returned value is a primitive + * type, then the value is converted into a wrapper object such as + * <code>java.lang.Integer</code>. If the type of the returned value + * is <code>void</code>, the returned value is discarded. + * + * <p><i>Example:</i> + * + * <ul><pre>ClassPool pool = ... ; + * CtClass vec = pool.makeClass("intVector"); + * vec.setSuperclass(pool.get("java.util.Vector")); + * CtMethod addMethod = pool.getMethod("Sample", "add0"); + * + * CtClass[] argTypes = { CtClass.intType }; + * CtMethod m = CtNewMethod.wrapped(CtClass.voidType, "add", argTypes, + * null, addMethod, null, vec); + * vec.addMethod(m);</pre></ul> + * + * <p>where the class <code>Sample</code> is as follows: + * + * <ul><pre>public class Sample extends java.util.Vector { + * public Object add0(Object[] args) { + * super.addElement(args[0]); + * return null; + * } + * }</pre></ul> + * + * <p>This program produces a class <code>intVector</code>: + * + * <ul><pre>public class intVector extends java.util.Vector { + * public void add(int p0) { + * Object[] args = new Object[] { p0 }; + * // begin of copied body + * super.addElement(args[0]); + * Object result = null; + * // end + * } + * }</pre></ul> + * + * <p>Note that the type of the parameter to <code>add()</code> depends + * only on the value of <code>argTypes</code> passed to + * <code>CtNewMethod.wrapped()</code>. Thus, it is easy to + * modify this program to produce a + * <code>StringVector</code> class, which is a vector containing + * only <code>String</code> objects, and other vector classes. + * + * @param returnType the type of the returned value. + * @param mname the method name. + * @param parameters a list of the parameter types. + * @param exceptions a list of the exception types. + * @param body the method body + * @param constParam the constant parameter + * (maybe <code>null</code>). + * @param declaring the class to which the created method is + * added. + */ + public static CtMethod wrapped(CtClass returnType, + String mname, + CtClass[] parameterTypes, + CtClass[] exceptionTypes, + CtMethod body, ConstParameter constParam, + CtClass declaring) + throws CannotCompileException + { + return CtNewWrappedMethod.wrapped(returnType, mname, parameterTypes, + exceptionTypes, body, constParam, declaring); + } +} diff --git a/src/main/javassist/CtNewWrappedConstructor.java b/src/main/javassist/CtNewWrappedConstructor.java new file mode 100644 index 00000000..fa4786e9 --- /dev/null +++ b/src/main/javassist/CtNewWrappedConstructor.java @@ -0,0 +1,111 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +import javassist.bytecode.*; +import javassist.CtMethod.ConstParameter; + +class CtNewWrappedConstructor extends CtNewWrappedMethod { + private static final int PASS_NONE = CtNewConstructor.PASS_NONE; + private static final int PASS_ARRAY = CtNewConstructor.PASS_ARRAY; + private static final int PASS_PARAMS = CtNewConstructor.PASS_PARAMS; + + public static CtConstructor wrapped(CtClass[] parameterTypes, + CtClass[] exceptionTypes, + int howToCallSuper, + CtMethod body, + ConstParameter constParam, + CtClass declaring) + throws CannotCompileException + { + try { + CtConstructor cons = new CtConstructor(parameterTypes, declaring); + cons.setExceptionTypes(exceptionTypes); + Bytecode code = makeBody(declaring, declaring.getClassFile2(), + howToCallSuper, body, + parameterTypes, constParam); + cons.getMethodInfo2().setCodeAttribute(code.toCodeAttribute()); + return cons; + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + } + + protected static Bytecode makeBody(CtClass declaring, ClassFile classfile, + int howToCallSuper, + CtMethod wrappedBody, + CtClass[] parameters, + ConstParameter cparam) + throws CannotCompileException + { + int stacksize, stacksize2; + + int superclazz = classfile.getSuperclassId(); + Bytecode code = new Bytecode(classfile.getConstPool(), 0, 0); + code.setMaxLocals(false, parameters, 0); + code.addAload(0); + if (howToCallSuper == PASS_NONE) { + stacksize = 1; + code.addInvokespecial(superclazz, "<init>", "()V"); + } + else if (howToCallSuper == PASS_PARAMS) { + stacksize = code.addLoadParameters(parameters) + 1; + code.addInvokespecial(superclazz, "<init>", + Descriptor.ofConstructor(parameters)); + } + else { + stacksize = compileParameterList(code, parameters, 1); + String desc; + if (cparam == null) { + stacksize2 = 2; + desc = ConstParameter.defaultConstDescriptor(); + } + else { + stacksize2 = cparam.compile(code) + 2; + desc = cparam.constDescriptor(); + } + + if (stacksize < stacksize2) + stacksize = stacksize2; + + code.addInvokespecial(superclazz, "<init>", desc); + } + + if (wrappedBody == null) + code.add(Bytecode.RETURN); + else { + stacksize2 = makeBody0(declaring, classfile, wrappedBody, + false, parameters, CtClass.voidType, + cparam, code); + if (stacksize < stacksize2) + stacksize = stacksize2; + } + + code.setMaxStack(stacksize); + return code; + } +} diff --git a/src/main/javassist/CtNewWrappedMethod.java b/src/main/javassist/CtNewWrappedMethod.java new file mode 100644 index 00000000..718e3c4f --- /dev/null +++ b/src/main/javassist/CtNewWrappedMethod.java @@ -0,0 +1,199 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +import javassist.bytecode.*; +import javassist.compiler.JvstCodeGen; +import java.util.Hashtable; +import javassist.CtMethod.ConstParameter; + +class CtNewWrappedMethod { + + private static final String addedWrappedMethod = "_added_m$"; + + public static CtMethod wrapped(CtClass returnType, String mname, + CtClass[] parameterTypes, + CtClass[] exceptionTypes, + CtMethod body, ConstParameter constParam, + CtClass declaring) + throws CannotCompileException + { + CtMethod mt = new CtMethod(returnType, mname, parameterTypes, + declaring); + mt.setModifiers(body.getModifiers()); + try { + mt.setExceptionTypes(exceptionTypes); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + + Bytecode code = makeBody(declaring, declaring.getClassFile2(), body, + parameterTypes, returnType, constParam); + mt.getMethodInfo2().setCodeAttribute(code.toCodeAttribute()); + return mt; + } + + static Bytecode makeBody(CtClass clazz, ClassFile classfile, + CtMethod wrappedBody, + CtClass[] parameters, + CtClass returnType, + ConstParameter cparam) + throws CannotCompileException + { + boolean isStatic = Modifier.isStatic(wrappedBody.getModifiers()); + Bytecode code = new Bytecode(classfile.getConstPool(), 0, 0); + int stacksize = makeBody0(clazz, classfile, wrappedBody, isStatic, + parameters, returnType, cparam, code); + code.setMaxStack(stacksize); + code.setMaxLocals(isStatic, parameters, 0); + return code; + } + + protected static int makeBody0(CtClass clazz, ClassFile classfile, + CtMethod wrappedBody, + boolean isStatic, CtClass[] parameters, + CtClass returnType, ConstParameter cparam, + Bytecode code) + throws CannotCompileException + { + if (!(clazz instanceof CtClassType)) + throw new CannotCompileException("bad declaring class" + + clazz.getName()); + + if (!isStatic) + code.addAload(0); + + int stacksize = compileParameterList(code, parameters, + (isStatic ? 0 : 1)); + int stacksize2; + String desc; + if (cparam == null) { + stacksize2 = 0; + desc = ConstParameter.defaultDescriptor(); + } + else { + stacksize2 = cparam.compile(code); + desc = cparam.descriptor(); + } + + checkSignature(wrappedBody, desc); + + String bodyname; + try { + bodyname = addBodyMethod((CtClassType)clazz, classfile, + wrappedBody); + /* if an exception is thrown below, the method added above + * should be removed. (future work :<) + */ + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + + if (isStatic) + code.addInvokestatic(Bytecode.THIS, bodyname, desc); + else + code.addInvokespecial(Bytecode.THIS, bodyname, desc); + + compileReturn(code, returnType); // consumes 2 stack entries + + if (stacksize < stacksize2 + 2) + stacksize = stacksize2 + 2; + + return stacksize; + } + + private static void checkSignature(CtMethod wrappedBody, + String descriptor) + throws CannotCompileException + { + if (!descriptor.equals(wrappedBody.getMethodInfo2().getDescriptor())) + throw new CannotCompileException( + "wrapped method with a bad signature: " + + wrappedBody.getDeclaringClass().getName() + + '.' + wrappedBody.getName()); + } + + private static String addBodyMethod(CtClassType clazz, + ClassFile classfile, + CtMethod src) + throws BadBytecode + { + Hashtable bodies = clazz.getHiddenMethods(); + String bodyname = (String)bodies.get(src); + if (bodyname == null) { + do { + bodyname = addedWrappedMethod + clazz.getUniqueNumber(); + } while (classfile.getMethod(bodyname) != null); + + ClassMap map = new ClassMap(); + map.put(src.getDeclaringClass().getName(), clazz.getName()); + MethodInfo body = new MethodInfo(classfile.getConstPool(), + bodyname, src.getMethodInfo2(), + map); + int acc = body.getAccessFlags(); + body.setAccessFlags(AccessFlag.setPrivate(acc)); + classfile.addMethod(body); + bodies.put(src, bodyname); + } + + return bodyname; + } + + /* compileParameterList() returns the stack size used + * by the produced code. + * + * @param regno the index of the local variable in which + * the first argument is received. + * (0: static method, 1: regular method.) + */ + static int compileParameterList(Bytecode code, + CtClass[] params, int regno) { + return JvstCodeGen.compileParameterList(code, params, regno); + } + + /* + * The produced codes cosume 1 or 2 stack entries. + */ + private static void compileReturn(Bytecode code, CtClass type) { + if (type.isPrimitive()) { + CtPrimitiveType pt = (CtPrimitiveType)type; + if (pt != CtClass.voidType) { + String wrapper = pt.getWrapperName(); + code.addCheckcast(wrapper); + code.addInvokevirtual(wrapper, pt.getGetMethodName(), + pt.getGetMethodDescriptor()); + } + + code.addOpcode(pt.getReturnOp()); + } + else { + code.addCheckcast(type); + code.addOpcode(Bytecode.ARETURN); + } + } +} diff --git a/src/main/javassist/CtPrimitiveType.java b/src/main/javassist/CtPrimitiveType.java new file mode 100644 index 00000000..8e73ca8e --- /dev/null +++ b/src/main/javassist/CtPrimitiveType.java @@ -0,0 +1,111 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +/** + * An instance of <code>CtPrimitiveType</code> represents a primitive type. + * It is obtained from <code>CtClass</code>. + */ +public final class CtPrimitiveType extends CtClass { + private char descriptor; + private String wrapperName; + private String getMethodName; + private String mDescriptor; + private int returnOp; + private int arrayType; + private int dataSize; + + CtPrimitiveType(String name, char desc, String wrapper, + String methodName, String mDesc, int opcode, int atype, + int size) { + super(name); + descriptor = desc; + wrapperName = wrapper; + getMethodName = methodName; + mDescriptor = mDesc; + returnOp = opcode; + arrayType = atype; + dataSize = size; + } + + /** + * Returns <code>true</code> if this object represents a primitive + * Java type: boolean, byte, char, short, int, long, float, double, + * or void. + */ + public boolean isPrimitive() { return true; } + + /** + * Returns the descriptor representing this type. + * For example, if the type is int, then the descriptor is I. + */ + public char getDescriptor() { return descriptor; } + + /** + * Returns the name of the wrapper class. + * For example, if the type is int, then the wrapper class is + * <code>java.lang.Integer</code>. + */ + public String getWrapperName() { return wrapperName; } + + /** + * Returns the name of the method for retrieving the value + * from the wrapper object. + * For example, if the type is int, then the method name is + * <code>intValue</code>. + */ + public String getGetMethodName() { return getMethodName; } + + /** + * Returns the descriptor of the method for retrieving the value + * from the wrapper object. + * For example, if the type is int, then the method descriptor is + * <code>()I</code>. + */ + public String getGetMethodDescriptor() { return mDescriptor; } + + /** + * Returns the opcode for returning a value of the type. + * For example, if the type is int, then the returned opcode is + * <code>javassit.bytecode.Opcode.IRETURN</code>. + */ + public int getReturnOp() { return returnOp; } + + /** + * Returns the array-type code representing the type. + * It is used for the newarray instruction. + * For example, if the type is int, then this method returns + * <code>javassit.bytecode.Opcode.T_INT</code>. + */ + public int getArrayType() { return arrayType; } + + /** + * Returns the data size of the primitive type. + * If the type is long or double, this method returns 2. + * Otherwise, it returns 1. + */ + public int getDataSize() { return dataSize; } +} diff --git a/src/main/javassist/Dump.java b/src/main/javassist/Dump.java new file mode 100644 index 00000000..afc91066 --- /dev/null +++ b/src/main/javassist/Dump.java @@ -0,0 +1,67 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +import java.io.*; +import javassist.bytecode.ClassFile; +import javassist.bytecode.ClassFileWriter; + +/** + * Dump is a tool for viewing the class definition in the given + * class file. Unlike the JDK javap tool, Dump works even if + * the class file is broken. + * + * <p>For example, + * <ul><pre>% java javassist.Dump foo.class</pre></ul> + * + * <p>prints the contents of the constant pool and the list of methods + * and fields. + */ +public class Dump { + private Dump() {} + + /** + * Main method. + * + * @param args[0] class file name. + */ + public static void main(String[] args) throws Exception { + if (args.length != 1) { + System.err.println("Usage: java Dump <class file name>"); + return; + } + + DataInputStream in = new DataInputStream( + new FileInputStream(args[0])); + ClassFile w = new ClassFile(in); + PrintWriter out = new PrintWriter(System.out, true); + out.println("*** constant pool ***"); + w.getConstPool().print(out); + out.println(); + out.println("*** members ***"); + ClassFileWriter.print(w, out); + } +} diff --git a/src/main/javassist/Loader.java b/src/main/javassist/Loader.java new file mode 100644 index 00000000..948d3de9 --- /dev/null +++ b/src/main/javassist/Loader.java @@ -0,0 +1,340 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +import java.io.*; +import java.util.Hashtable; +import java.util.Vector; + +/** + * The class loader for Javassist (JDK 1.2 or later only). + * + * <p>This is a sample class loader using <code>ClassPool</code>. + * Unlike a regular class loader, this class loader obtains bytecode + * from a <code>ClassPool</code>. + * + * <p>Note that Javassist can be used without this class loader; programmers + * can define their own versions of class loader. They can run + * a program even without any user-defined class loader if that program + * is statically translated with Javassist. + * This class loader is just provided as a utility class. + * + * <p>Suppose that an instance of <code>MyTranslator</code> implementing + * the interface <code>Translator</code> is responsible for modifying + * class files. + * The startup program of an application using <code>MyTranslator</code> + * should be something like this: + * + * <ul><pre> + * import javassist.*; + * + * public class Main { + * public static void main(String[] args) throws Throwable { + * MyTranslator myTrans = new MyTranslator(); + * ClassPool cp = ClassPool.getDefault(myTrans); + * Loader cl = new Loader(cp); + * cl.run("MyApp", args); + * } + * } + * </pre></ul> + * + * <p>Class <code>MyApp</code> is the main program of the application. + * + * <p>This program should be executed as follows: + * + * <ul><pre> + * % java Main <i>arg1</i> <i>arg2</i>... + * </pre></ul> + * + * <p>It modifies the class <code>MyApp</code> with a <code>MyTranslator</code> + * object before loading it into the JVM. + * Then it calls <code>main()</code> in <code>MyApp</code> with arguments + * <i>arg1</i>, <i>arg2</i>, ... + * + * <p>This program execution is equivalent to: + * + * <ul><pre> + * % java MyApp <i>arg1</i> <i>arg2</i>... + * </pre></ul> + * + * <p>except that classes are translated by <code>MyTranslator</code> + * at load time. + * + * <p>If only a particular class is modified when it is loaded, + * a program like the following can be used: + * + * <ul><pre>ClassPool cp = ClassPool.getDefault(); + * Loader cl = new Loader(cp); + * + * CtClass ct = cp.get("test.Rectangle"); + * ct.setSuperclass(loader.get("test.Point")); + * + * Class c = cl.loadClass("test.Rectangle"); + * Object rect = c.newInstance();</pre></ul> + * + * <p>This program modifies the super class of the <code>Rectangle</code> + * class and loads it into the JVM with a class loader <code>cl</code>. + * + * <p><b>Note 1:</b> + * + * <p>This class loader does not allow the users to intercept the loading + * of <code>java.*</code> and <code>javax.*</code> classes unless + * <code>Loader.doDelegation</code> is <code>false</code>. Also see + * Note 2. + * + * <p><b>Note 2:</b> + * + * <p>If classes are loaded with different class loaders, they belong to + * separate name spaces. If class <code>C</code> is loaded by a class + * loader <code>CL</code>, all classes that the class <code>C</code> + * refers to are also loaded by <code>CL</code>. However, if <code>CL</code> + * delegates the loading of the class <code>C</code> to <code>CL'</code>, + * then those classes that the class <code>C</code> refers to + * are loaded by a parent class loader <code>CL'</code> + * instead of <code>CL</code>. + * + * <p>If an object of class <code>C</code> is assigned + * to a variable of class <code>C</code> belonging to a different name + * space, then a <code>ClassCastException</code> is thrown. + * + * <p>Because of the fact above, this loader delegates only the loading of + * <code>javassist.Loader</code> + * and classes included in package <code>java.*</code> and + * <code>javax.*</code> to the parent class + * loader. Other classes are directly loaded by this loader. + * + * <p>For example, suppose that <code>java.lang.String</code> would be loaded + * by this loader while <code>java.io.File</code> is loaded by the parent + * class loader. If the constructor of <code>java.io.File</code> is called + * with an instance of <code>java.lang.String</code>, then it may throw + * an exception since it accepts an instance of only the + * <code>java.lang.String</code> loaded by the parent class loader. + * + * @see javassist.ClassPool + * @see javassist.Translator + */ +public class Loader extends ClassLoader { + private Hashtable notDefinedHere; // must be atomic. + private Vector notDefinedPackages; // must be atomic. + private ClassPool source; + + /** + * Specifies the algorithm of class loading. + * + * <p>This class loader uses the parent class loader for + * <code>java.*</code> and <code>javax.*</code> classes. + * If this variable <code>doDelegation</code> + * is <code>false</code>, this class loader does not delegate those + * classes to the parent class loader. + * + * <p>The default value is <code>true</code>. + */ + public boolean doDelegation = true; + + /** + * Creates a new class loader. + */ + public Loader() { + this(null); + } + + /** + * Creates a new class loader. + * + * @param cp the source of class files. + */ + public Loader(ClassPool cp) { + notDefinedHere = new Hashtable(); + notDefinedPackages = new Vector(); + source = cp; + delegateLoadingOf("javassist.Loader"); + } + + /** + * Records a class so that the loading of that class is delegated + * to the parent class loader. + * + * <p>If the given class name ends with <code>.</code> (dot), then + * that name is interpreted as a package name. All the classes + * in that package and the sub packages are delegated. + */ + public void delegateLoadingOf(String classname) { + if (classname.endsWith(".")) + notDefinedPackages.addElement(classname); + else + notDefinedHere.put(classname, this); + } + + /** + * Sets the soruce <code>ClassPool</code>. + */ + public void setClassPool(ClassPool cp) { + source = cp; + } + + /** + * Loads a class with an instance of <code>Loader</code> + * and calls <code>main()</code> of that class. + * + * <p>This method calls <code>run()</code>. + * + * @param args[0] class name to be loaded. + * @param args[1-n] parameters passed to <code>main()</code>. + * + * @see javassist.Loader#run(String[]) + */ + public static void main(String[] args) throws Throwable { + Loader cl = new Loader(); + cl.run(args); + } + + /** + * Loads a class and calls <code>main()</code> in that class. + * + * @param args[0] the name of the loaded class. + * @param args[1-n] parameters passed to <code>main()</code>. + */ + public void run(String[] args) throws Throwable { + int n = args.length - 1; + if (n >= 0) { + String[] args2 = new String[n]; + for (int i = 0; i < n; ++i) + args2[i] = args[i + 1]; + + run(args[0], args2); + } + } + + /** + * Loads a class and calls <code>main()</code> in that class. + * + * @param classname the loaded class. + * @param args parameters passed to <code>main()</code>. + */ + public void run(String classname, String[] args) throws Throwable { + Class c = loadClass(classname); + try { + c.getDeclaredMethod("main", new Class[] { String[].class }) + .invoke(null, new Object[] { args }); + } + catch (java.lang.reflect.InvocationTargetException e) { + throw e.getTargetException(); + } + } + + /** + * Requests the class loader to load a class. + */ + protected Class loadClass(String name, boolean resolve) + throws ClassFormatError, ClassNotFoundException + { + Class c = findLoadedClass(name); + if (c == null) + c = loadClassByDelegation(name); + + if (c == null) + c = findClass(name); + + if (c == null) + c = delegateToParent(name); + + if (resolve) + resolveClass(c); + + return c; + } + + /** + * Finds the specified class using <code>ClassPath</code>. + * If the source throws an exception, this returns null. + * + * <p>This method can be overridden by a subclass of + * <code>Loader</code>. + */ + protected Class findClass(String name) { + byte[] classfile; + try { + if (source != null) + classfile = source.write(name); + else { + String jarname = "/" + name.replace('.', '/') + ".class"; + InputStream in = this.getClass().getResourceAsStream(jarname); + + classfile = ClassPoolTail.readStream(in); + } + } + catch (Exception e) { + return null; + } + + return defineClass(name, classfile, 0, classfile.length); + } + + private Class loadClassByDelegation(String name) + throws ClassNotFoundException + { + /* The swing components must be loaded by a system + * class loader. + * javax.swing.UIManager loads a (concrete) subclass + * of LookAndFeel by a system class loader and cast + * an instance of the class to LookAndFeel for + * (maybe) a security reason. To avoid failure of + * type conversion, LookAndFeel must not be loaded + * by this class loader. + */ + + Class c = null; + if (doDelegation) + if (name.startsWith("java.") || name.startsWith("javax.") + || name.startsWith("sun.") || name.startsWith("com.sun.") + || notDelegated(name)) + c = delegateToParent(name); + + return c; + } + + private boolean notDelegated(String name) { + if (notDefinedHere.get(name) != null) + return true; + + int n = notDefinedPackages.size(); + for (int i = 0; i < n; ++i) + if (name.startsWith((String)notDefinedPackages.elementAt(i))) + return true; + + return false; + } + + private Class delegateToParent(String classname) + throws ClassNotFoundException + { + ClassLoader cl = getParent(); + if (cl != null) + return cl.loadClass(classname); + else + return findSystemClass(classname); + } +} diff --git a/src/main/javassist/Modifier.java b/src/main/javassist/Modifier.java new file mode 100644 index 00000000..e6c7d2c5 --- /dev/null +++ b/src/main/javassist/Modifier.java @@ -0,0 +1,191 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +import javassist.bytecode.AccessFlag; + +/** + * The Modifier class provides static methods and constants to decode + * class and member access modifiers. The constant values are equivalent + * to the corresponding values in <code>javassist.bytecode.AccessFlag</code>. + * + * <p>All the methods/constants in this class are compatible with + * ones in <code>java.lang.reflect.Modifier</code>. + * + * @see CtClass#getModifiers() + */ +public class Modifier { + public static final int PUBLIC = AccessFlag.PUBLIC; + public static final int PRIVATE = AccessFlag.PRIVATE; + public static final int PROTECTED = AccessFlag.PROTECTED; + public static final int STATIC = AccessFlag.STATIC; + public static final int FINAL = AccessFlag.FINAL; + public static final int SYNCHRONIZED = AccessFlag.SYNCHRONIZED; + public static final int VOLATILE = AccessFlag.VOLATILE; + public static final int TRANSIENT = AccessFlag.TRANSIENT; + public static final int NATIVE = AccessFlag.NATIVE; + public static final int INTERFACE = AccessFlag.INTERFACE; + public static final int ABSTRACT = AccessFlag.ABSTRACT; + public static final int STRICT = AccessFlag.STRICT; + + /** + * Returns true if the modifiers include the <tt>public</tt> + * modifier. + */ + public static boolean isPublic(int mod) { + return (mod & PUBLIC) != 0; + } + + /** + * Returns true if the modifiers include the <tt>private</tt> + * modifier. + */ + public static boolean isPrivate(int mod) { + return (mod & PRIVATE) != 0; + } + + /** + * Returns true if the modifiers include the <tt>protected</tt> + * modifier. + */ + public static boolean isProtected(int mod) { + return (mod & PROTECTED) != 0; + } + + /** + * Returns true if the modifiers include the <tt>static</tt> + * modifier. + */ + public static boolean isStatic(int mod) { + return (mod & STATIC) != 0; + } + + /** + * Returns true if the modifiers include the <tt>final</tt> + * modifier. + */ + public static boolean isFinal(int mod) { + return (mod & FINAL) != 0; + } + + /** + * Returns true if the modifiers include the <tt>synchronized</tt> + * modifier. + */ + public static boolean isSynchronized(int mod) { + return (mod & SYNCHRONIZED) != 0; + } + + /** + * Returns true if the modifiers include the <tt>volatile</tt> + * modifier. + */ + public static boolean isVolatile(int mod) { + return (mod & VOLATILE) != 0; + } + + /** + * Returns true if the modifiers include the <tt>transient</tt> + * modifier. + */ + public static boolean isTransient(int mod) { + return (mod & TRANSIENT) != 0; + } + + /** + * Returns true if the modifiers include the <tt>native</tt> + * modifier. + */ + public static boolean isNative(int mod) { + return (mod & NATIVE) != 0; + } + + /** + * Returns true if the modifiers include the <tt>interface</tt> + * modifier. + */ + public static boolean isInterface(int mod) { + return (mod & INTERFACE) != 0; + } + + /** + * Returns true if the modifiers include the <tt>abstract</tt> + * modifier. + */ + public static boolean isAbstract(int mod) { + return (mod & ABSTRACT) != 0; + } + + /** + * Returns true if the modifiers include the <tt>strictfp</tt> + * modifier. + */ + public static boolean isStrict(int mod) { + return (mod & STRICT) != 0; + } + + /** + * Truns the public bit on. The protected and private bits are + * cleared. + */ + public static int setPublic(int mod) { + return (mod & ~(PRIVATE | PROTECTED)) | PUBLIC; + } + + /** + * Truns the protected bit on. The protected and public bits are + * cleared. + */ + public static int setProtected(int mod) { + return (mod & ~(PRIVATE | PUBLIC)) | PROTECTED; + } + + /** + * Truns the private bit on. The protected and private bits are + * cleared. + */ + public static int setPrivate(int mod) { + return (mod & ~(PROTECTED | PUBLIC)) | PRIVATE; + } + + /** + * Clears the public, protected, and private bits. + */ + public static int setPackage(int mod) { + return (mod & ~(PROTECTED | PUBLIC | PRIVATE)); + } + + /** + * Clears a specified bit in <code>mod</code>. + */ + public static int clear(int mod, int clearBit) { + return mod & ~clearBit; + } + + public static String toString(int mod) { + return java.lang.reflect.Modifier.toString(mod); + } +} diff --git a/src/main/javassist/NotFoundException.java b/src/main/javassist/NotFoundException.java new file mode 100644 index 00000000..30b4b882 --- /dev/null +++ b/src/main/javassist/NotFoundException.java @@ -0,0 +1,39 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +/** + * Signals that something could not be found. + */ +public class NotFoundException extends Exception { + public NotFoundException(String msg) { + super(msg); + } + + public NotFoundException(String msg, Exception e) { + super(msg + " because of " + e.toString()); + } +} diff --git a/src/main/javassist/SerialVersionUID.java b/src/main/javassist/SerialVersionUID.java new file mode 100644 index 00000000..a16009ca --- /dev/null +++ b/src/main/javassist/SerialVersionUID.java @@ -0,0 +1,207 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +import java.io.*; +import javassist.bytecode.*; +import java.util.*; +import java.security.*; + +/** + * Utility for calculating serialVersionUIDs for Serializable classes. + * + * @author Bob Lee (crazybob@crazybob.org) + * @author modified by Shigeru Chiba + */ +public class SerialVersionUID { + + /** + * Adds serialVersionUID if one does not already exist. Call this before + * modifying a class to maintain serialization compatability. + */ + public static void setSerialVersionUID(CtClass clazz) + throws CannotCompileException, NotFoundException + { + // check for pre-existing field. + try { + clazz.getDeclaredField("serialVersionUID"); + return; + } + catch (NotFoundException e) {} + + // check if the class is serializable. + if (!isSerializable(clazz)) + return; + + // add field with default value. + CtField field = new CtField(CtClass.longType, "serialVersionUID", + clazz); + field.setModifiers(Modifier.PRIVATE | Modifier.STATIC | + Modifier.FINAL); + clazz.addField(field, calculateDefault(clazz) + "L"); + } + + /** + * Does the class implement Serializable? + */ + private static boolean isSerializable(CtClass clazz) + throws NotFoundException + { + ClassPool pool = clazz.getClassPool(); + return clazz.subtypeOf(pool.get("java.io.Serializable")); + } + + /** + * Calculate default value. See Java Serialization Specification, Stream + * Unique Identifiers. + */ + static long calculateDefault(CtClass clazz) + throws CannotCompileException + { + try { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(bout); + ClassFile classFile = clazz.getClassFile(); + + // class name. + String javaName = javaName(clazz); + out.writeUTF(javaName); + + // class modifiers. + out.writeInt(clazz.getModifiers() & (Modifier.PUBLIC | + Modifier.FINAL | Modifier.INTERFACE | Modifier.ABSTRACT)); + + // interfaces. + String[] interfaces = classFile.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) + interfaces[i] = javaName(interfaces[i]); + + Arrays.sort(interfaces); + for (int i = 0; i < interfaces.length; i++) + out.writeUTF(interfaces[i]); + + // fields. + CtField[] fields = clazz.getDeclaredFields(); + Arrays.sort(fields, new Comparator() { + public int compare(Object o1, Object o2) { + CtField field1 = (CtField)o1; + CtField field2 = (CtField)o2; + return field1.getName().compareTo(field2.getName()); + } + }); + + for (int i = 0; i < fields.length; i++) { + CtField field = (CtField) fields[i]; + int mods = field.getModifiers(); + if (((mods & Modifier.PRIVATE) == 0) || + ((mods & (Modifier.STATIC | Modifier.TRANSIENT)) == 0)) { + out.writeUTF(field.getName()); + out.writeInt(mods); + out.writeUTF(field.getFieldInfo2().getDescriptor()); + } + } + + // static initializer. + if (classFile.getStaticInitializer() != null) { + out.writeUTF("<clinit>"); + out.writeInt(Modifier.STATIC); + out.writeUTF("()V"); + } + + // constructors. + CtConstructor[] constructors = clazz.getDeclaredConstructors(); + Arrays.sort(constructors, new Comparator() { + public int compare(Object o1, Object o2) { + CtConstructor c1 = (CtConstructor)o1; + CtConstructor c2 = (CtConstructor)o2; + return c1.getMethodInfo2().getDescriptor().compareTo( + c2.getMethodInfo2().getDescriptor()); + } + }); + + for (int i = 0; i < constructors.length; i++) { + CtConstructor constructor = constructors[i]; + int mods = constructor.getModifiers(); + if ((mods & Modifier.PRIVATE) == 0) { + out.writeUTF("<init>"); + out.writeInt(mods); + out.writeUTF(constructor.getMethodInfo2() + .getDescriptor().replace('/', '.')); + } + } + + // methods. + CtMethod[] methods = clazz.getDeclaredMethods(); + Arrays.sort(methods, new Comparator() { + public int compare(Object o1, Object o2) { + CtMethod m1 = (CtMethod)o1; + CtMethod m2 = (CtMethod)o2; + int value = m1.getName().compareTo(m2.getName()); + if (value == 0) + value = m1.getMethodInfo2().getDescriptor() + .compareTo(m2.getMethodInfo2().getDescriptor()); + + return value; + } + }); + + for (int i = 0; i < methods.length; i++) { + CtMethod method = methods[i]; + int mods = method.getModifiers(); + if ((mods & Modifier.PRIVATE) == 0) { + out.writeUTF(method.getName()); + out.writeInt(mods); + out.writeUTF(method.getMethodInfo2() + .getDescriptor().replace('/', '.')); + } + } + + // calculate hash. + out.flush(); + MessageDigest digest = MessageDigest.getInstance("SHA"); + byte[] digested = digest.digest(bout.toByteArray()); + long hash = 0; + for (int i = Math.min(digested.length, 8) - 1; i >= 0; i--) + hash = (hash << 8) | (digested[i] & 0xFF); + + return hash; + } + catch (IOException e) { + throw new CannotCompileException(e); + } + catch (NoSuchAlgorithmException e) { + throw new CannotCompileException(e); + } + } + + private static String javaName(CtClass clazz) { + return Descriptor.toJavaName(Descriptor.toJvmName(clazz)); + } + + private static String javaName(String name) { + return Descriptor.toJavaName(Descriptor.toJvmName(name)); + } +} diff --git a/src/main/javassist/Translator.java b/src/main/javassist/Translator.java new file mode 100644 index 00000000..42599c9d --- /dev/null +++ b/src/main/javassist/Translator.java @@ -0,0 +1,70 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +/** + * An observer of <code>ClassPool</code>. + * The users can define a class implementing this + * interface and attach an instance of that class to a + * <code>ClassPool</code> object so that it can translate a class file + * when the class file is loaded into the JVM, for example. + * + * @see ClassPool#ClassPool(ClassPool,Translator) + * @see ClassPool#getDefault(Translator) + */ +public interface Translator { + /** + * Is invoked by a <code>ClassPool</code> for initialization + * when the object is attached to a <code>ClassPool</code> object. + * + * @param pool the <code>ClassPool</code> that this translator + * is attached to. + * + * @see ClassPool#ClassPool(ClassPool,Translator) + * @see ClassPool#getDefault(Translator) + */ + void start(ClassPool pool) + throws NotFoundException, CannotCompileException; + + /** + * Is invoked by a <code>ClassPool</code> for notifying that + * a class is written out to an output stream. + * + * <p>If CtClass.frozen() is true, that is, if the class has been + * already modified and written, then onWrite() is not invoked. + * + * @param pool the <code>ClassPool</code> that this translator + * is attached to. + * @param classname a fully-qualified class name + * + * @see ClassPool#writeFile(String) + * @see ClassPool#writeFile(String, String) + * @see ClassPool#write(String) + * @see ClassPool#write(String,DataOutputStream) + */ + void onWrite(ClassPool pool, String classname) + throws NotFoundException, CannotCompileException; +} diff --git a/src/main/javassist/URLClassPath.java b/src/main/javassist/URLClassPath.java new file mode 100644 index 00000000..c359c778 --- /dev/null +++ b/src/main/javassist/URLClassPath.java @@ -0,0 +1,143 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist; + +import java.io.*; +import java.net.*; + +/** + * A class search-path specified with URL (http). + * + * @see javassist.ClassPath + * @see ClassPool#insertClassPath(ClassPath) + * @see ClassPool#appendClassPath(ClassPath) + */ +public class URLClassPath implements ClassPath { + protected String hostname; + protected int port; + protected String directory; + protected String packageName; + + /* + * Creates a search path specified with URL (http). + * + * <p>This search path is used only if a requested + * class name starts with the name specified by <code>packageName</code>. + * If <code>packageName</code> is "mypack" and a requested class is + * "mypack.sub.Test", then the given URL is used for loading that class. + * If <code>packageName</code> is <code>null</code>, the URL is used + * for loading any class. + * + * @param host host name + * @param port port number + * @param directory directory name ending with "/". + * It can be "/" (root directory). + * @param packageName package name. + */ + public URLClassPath(String host, int port, + String directory, String packageName) { + hostname = host; + this.port = port; + this.directory = directory; + this.packageName = packageName; + } + + public String toString() { + return hostname + ":" + port + directory; + } + + /** + * Opens a class file with http. + */ + public InputStream openClassfile(String classname) { + try { + if (packageName == null || classname.startsWith(packageName)) { + String jarname + = directory + classname.replace('.', '/') + ".class"; + URLConnection con = fetchClass0(hostname, port, jarname); + return con.getInputStream(); + } + } + catch (IOException e) {} + return null; // not found + } + + /** + * Reads a class file on an http server. + * + * @param host host name + * @param port port number + * @param directory directory name ending with "/". + * It can be "/" (root directory). + * @param classname fully-qualified class name + */ + public static byte[] fetchClass(String host, int port, + String directory, String classname) + throws IOException + { + byte[] b; + URLConnection con = fetchClass0(host, port, + directory + classname.replace('.', '/') + ".class"); + int size = con.getContentLength(); + InputStream s = con.getInputStream(); + if (size <= 0) + b = ClassPoolTail.readStream(s); + else { + b = new byte[size]; + int len = 0; + do { + int n = s.read(b, len, size - len); + if (n < 0) { + s.close(); + throw new IOException("the stream was closed: " + + classname); + } + len += n; + } while (len < size); + } + + s.close(); + return b; + } + + private static URLConnection fetchClass0(String host, int port, + String filename) + throws IOException + { + URL url; + try { + url = new URL("http", host, port, filename); + } + catch (MalformedURLException e) { + // should never reache here. + throw new IOException("invalid URL?"); + } + + URLConnection con = url.openConnection(); + con.connect(); + return con; + } +} diff --git a/src/main/javassist/bytecode/AccessFlag.java b/src/main/javassist/bytecode/AccessFlag.java new file mode 100644 index 00000000..c964ca7c --- /dev/null +++ b/src/main/javassist/bytecode/AccessFlag.java @@ -0,0 +1,107 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.bytecode; + +/** + * A support class providing static methods and constants + * for access modifiers such as public, rivate, ... + */ +public class AccessFlag { + public static final int PUBLIC = 0x0001; + public static final int PRIVATE = 0x0002; + public static final int PROTECTED = 0x0004; + public static final int STATIC = 0x0008; + public static final int FINAL = 0x0010; + public static final int SYNCHRONIZED = 0x0020; + public static final int VOLATILE = 0x0040; + public static final int TRANSIENT = 0x0080; + public static final int NATIVE = 0x0100; + public static final int INTERFACE = 0x0200; + public static final int ABSTRACT = 0x0400; + public static final int STRICT = 0x0800; + public static final int SUPER = 0x0020; + + // Note: 0x0020 is assigned to both ACC_SUPER and ACC_SYNCHRONIZED + // although java.lang.reflect.Modifier does not recognize ACC_SUPER. + + /** + * Truns the public bit on. The protected and private bits are + * cleared. + */ + public static int setPublic(int accflags) { + return (accflags & ~(PRIVATE | PROTECTED)) | PUBLIC; + } + + /** + * Truns the protected bit on. The protected and public bits are + * cleared. + */ + public static int setProtected(int accflags) { + return (accflags & ~(PRIVATE | PUBLIC)) | PROTECTED; + } + + /** + * Truns the private bit on. The protected and private bits are + * cleared. + */ + public static int setPrivate(int accflags) { + return (accflags & ~(PROTECTED | PUBLIC)) | PRIVATE; + } + + /** + * Clears the public, protected, and private bits. + */ + public static int setPackage(int accflags) { + return (accflags & ~(PROTECTED | PUBLIC | PRIVATE)); + } + + /** + * Clears a specified bit in <code>accflags</code>. + */ + public static int clear(int accflags, int clearBit) { + return accflags & ~clearBit; + } + + /** + * Converts a javassist.Modifier into + * a javassist.bytecode.AccessFlag. + * + * @param modifier javassist.Modifier + */ + public static int of(int modifier) { + return modifier; + } + + /** + * Converts a javassist.bytecode.AccessFlag + * into a javassist.Modifier. + * + * @param accflags javassist.bytecode.Accessflag + */ + public static int toModifier(int accflags) { + return accflags; + } +} diff --git a/src/main/javassist/bytecode/AttributeInfo.java b/src/main/javassist/bytecode/AttributeInfo.java new file mode 100644 index 00000000..29d7904b --- /dev/null +++ b/src/main/javassist/bytecode/AttributeInfo.java @@ -0,0 +1,240 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Map; +import java.util.LinkedList; +import java.util.ListIterator; + +// Note: if you define a new subclass of AttributeInfo, then +// update AttributeInfo.read(). + +/** + * <code>attribute_info</code> structure. + */ +public class AttributeInfo { + protected ConstPool constPool; + int name; + byte[] info; + + protected AttributeInfo(ConstPool cp, int attrname, byte[] attrinfo) { + constPool = cp; + name = attrname; + info = attrinfo; + } + + protected AttributeInfo(ConstPool cp, String attrname) { + this(cp, attrname, (byte[])null); + } + + /** + * Constructs an <code>attribute_info</code> structure. + * + * @param cp constant pool table + * @param attrname attribute name + * @param attrinfo <code>info</code> field + * of <code>attribute_info</code> structure. + */ + public AttributeInfo(ConstPool cp, String attrname, byte[] attrinfo) { + this(cp, cp.addUtf8Info(attrname), attrinfo); + } + + protected AttributeInfo(ConstPool cp, int n, DataInputStream in) + throws IOException + { + constPool = cp; + name = n; + int len = in.readInt(); + info = new byte[len]; + if (len > 0) + in.readFully(info); + } + + static AttributeInfo read(ConstPool cp, DataInputStream in) + throws IOException + { + int name = in.readUnsignedShort(); + String nameStr = cp.getUtf8Info(name); + if (nameStr.equals(CodeAttribute.tag)) + return new CodeAttribute(cp, name, in); + else if (nameStr.equals(ExceptionsAttribute.tag)) + return new ExceptionsAttribute(cp, name, in); + else if (nameStr.equals(ConstantAttribute.tag)) + return new ConstantAttribute(cp, name, in); + else if (nameStr.equals(SourceFileAttribute.tag)) + return new SourceFileAttribute(cp, name, in); + else if (nameStr.equals(LineNumberAttribute.tag)) + return new LineNumberAttribute(cp, name, in); + else if (nameStr.equals(SyntheticAttribute.tag)) + return new SyntheticAttribute(cp, name, in); + else if (nameStr.equals(InnerClassesAttribute.tag)) + return new InnerClassesAttribute(cp, name, in); + else + return new AttributeInfo(cp, name, in); + } + + /** + * Returns an attribute name. + */ + public String getName() { + return constPool.getUtf8Info(name); + } + + /** + * Returns a constant pool table. + */ + public ConstPool getConstPool() { return constPool; } + + /** + * Returns the length of this <code>attribute_info</code> + * structure. + * The returned value is <code>attribute_length + 6</code>. + */ + public int length() { + return info.length + 6; + } + + /** + * Returns the <code>info</code> field + * of this <code>attribute_info</code> structure. + * + * <p>This method is not available if the object is an instance + * of <code>CodeAttribute</code>. + */ + public byte[] get() { return info; } + + /** + * Sets the <code>info</code> field + * of this <code>attribute_info</code> structure. + * + * <p>This method is not available if the object is an instance + * of <code>CodeAttribute</code>. + */ + public void set(byte[] newinfo) { info = newinfo; } + + /** + * Makes a copy. Class names are replaced according to the + * given <code>Map</code> object. + * + * @param newCp the constant pool table used by the new copy. + * @param classnames pairs of replaced and substituted + * class names. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) { + int s = info.length; + byte[] newInfo = new byte[s]; + for (int i = 0; i < s; ++i) + newInfo[i] = info[i]; + + return new AttributeInfo(newCp, getName(), newInfo); + } + + void write(DataOutputStream out) throws IOException { + out.writeShort(name); + out.writeInt(info.length); + if (info.length > 0) + out.write(info); + } + + static int getLength(LinkedList list) { + int size = 0; + int n = list.size(); + for (int i = 0; i < n; ++i) { + AttributeInfo attr = (AttributeInfo)list.get(i); + size += attr.length(); + } + + return size; + } + + static AttributeInfo lookup(LinkedList list, String name) { + if (list == null) + return null; + + ListIterator iterator = list.listIterator(); + while (iterator.hasNext()) { + AttributeInfo ai = (AttributeInfo)iterator.next(); + if (ai.getName().equals(name)) + return ai; + } + + return null; // no such attribute + } + + static AttributeInfo lookup(LinkedList list, Class type) { + if (list == null) + return null; + + ListIterator iterator = list.listIterator(); + while (iterator.hasNext()) { + Object obj = iterator.next(); + if (type.isInstance(obj)) + return (AttributeInfo)obj; + } + + return null; // no such attribute + } + + static synchronized void remove(LinkedList list, String name) { + if (list == null) + return; + + ListIterator iterator = list.listIterator(); + while (iterator.hasNext()) { + AttributeInfo ai = (AttributeInfo)iterator.next(); + if (ai.getName().equals(name)) + iterator.remove(); + } + } + + static synchronized void remove(LinkedList list, Class type) { + if (list == null) + return; + + ListIterator iterator = list.listIterator(); + while (iterator.hasNext()) { + Object obj = iterator.next(); + if (type.isInstance(obj)) + iterator.remove(); + } + } + + static void writeAll(LinkedList list, DataOutputStream out) + throws IOException + { + if (list == null) + return; + + int n = list.size(); + for (int i = 0; i < n; ++i) { + AttributeInfo attr = (AttributeInfo)list.get(i); + attr.write(out); + } + } +} diff --git a/src/main/javassist/bytecode/BadBytecode.java b/src/main/javassist/bytecode/BadBytecode.java new file mode 100644 index 00000000..51d287d7 --- /dev/null +++ b/src/main/javassist/bytecode/BadBytecode.java @@ -0,0 +1,39 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.bytecode; + +/** + * Signals that a bad bytecode sequence has been found. + */ +public class BadBytecode extends Exception { + public BadBytecode(int opcode) { + super("bytecode " + opcode); + } + + public BadBytecode(String msg) { + super(msg); + } +} diff --git a/src/main/javassist/bytecode/ByteArray.java b/src/main/javassist/bytecode/ByteArray.java new file mode 100644 index 00000000..e10231e8 --- /dev/null +++ b/src/main/javassist/bytecode/ByteArray.java @@ -0,0 +1,86 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.bytecode; + +/** + * A collection of static methods for reading and writing a byte array. + */ +public class ByteArray { + /** + * Reads an unsigned 16bit integer at the index. + */ + public static int readU16bit(byte[] code, int index) { + return ((code[index] & 0xff) << 8) | (code[index + 1] & 0xff); + } + + /** + * Reads a signed 16bit integer at the index. + */ + public static int readS16bit(byte[] code, int index) { + return (code[index] << 8) | (code[index + 1] & 0xff); + } + + /** + * Writes a 16bit integer at the index. + */ + public static void write16bit(int value, byte[] code, int index) { + code[index] = (byte)(value >>> 8); + code[index + 1] = (byte)value; + } + + /** + * Reads a 32bit integer at the index. + */ + public static int read32bit(byte[] code, int index) { + return (code[index] << 24) | ((code[index + 1] & 0xff) << 16) + | ((code[index + 2] & 0xff) << 8) | (code[index + 3] & 0xff); + } + + /** + * Writes a 32bit integer at the index. + */ + public static void write32bit(int value, byte[] code, int index) { + code[index] = (byte)(value >>> 24); + code[index + 1] = (byte)(value >>> 16); + code[index + 2] = (byte)(value >>> 8); + code[index + 3] = (byte)value; + } + + /** + * Copies a 32bit integer. + * + * @param src the source byte array. + * @param isrc the index into the source byte array. + * @param dest the destination byte array. + * @param idest the index into the destination byte array. + */ + static void copy32bit(byte[] src, int isrc, byte[] dest, int idest) { + dest[idest] = src[isrc]; + dest[idest + 1] = src[isrc + 1]; + dest[idest + 2] = src[isrc + 2]; + dest[idest + 3] = src[isrc + 3]; + } +} diff --git a/src/main/javassist/bytecode/Bytecode.java b/src/main/javassist/bytecode/Bytecode.java new file mode 100644 index 00000000..f903e4be --- /dev/null +++ b/src/main/javassist/bytecode/Bytecode.java @@ -0,0 +1,1262 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.bytecode; + +import java.io.DataOutputStream; +import java.io.IOException; +import javassist.CannotCompileException; +import javassist.CtClass; +import javassist.CtPrimitiveType; + +/** + * A utility class for producing a bytecode sequence. + * + * <p>A <code>Bytecode</code> object is an unbounded array + * containing bytecode. For example, + * + * <ul><pre>ConstPool cp = ...; // constant pool table + * Bytecode b = new Bytecode(cp, 1, 0); + * b.addIconst(3); + * b.addReturn(CtClass.intType); + * CodeAttribute ca = b.toCodeAttribute();</ul></pre> + * + * <p>This program produces a Code attribute including a bytecode + * sequence: + * + * <ul><pre>iconst_3 + * ireturn</pre></ul> + * + * @see ConstPool + * @see CodeAttribute + */ +public class Bytecode implements Opcode { + /** + * Represents the <code>CtClass</code> file using the + * constant pool table given to this <code>Bytecode</code> object. + */ + public static final CtClass THIS = ConstPool.THIS; + + static final int bufsize = 64; + ConstPool constPool; + int maxStack, maxLocals; + ExceptionTable tryblocks; + Bytecode next; + byte[] buffer; + int num; + + private int stackDepth; + + /** + * Constructs a <code>Bytecode</code> object with an empty bytecode + * sequence. + * + * <p>The parameters <code>stacksize</code> and <code>localvars</code> + * specify initial values + * of <code>max_stack</code> and <code>max_locals</code>. + * They can be changed later. + * + * @param cp constant pool table. + * @param stacksize <code>max_stack</code>. + * @param localvars <code>max_locals</code>. + */ + public Bytecode(ConstPool cp, int stacksize, int localvars) { + this(); + constPool = cp; + maxStack = stacksize; + maxLocals = localvars; + tryblocks = new ExceptionTable(cp); + stackDepth = 0; + } + + /* used in add(). + */ + private Bytecode() { + buffer = new byte[bufsize]; + num = 0; + next = null; + } + + /** + * Gets a constant pool table. + */ + public ConstPool getConstPool() { return constPool; } + + /** + * Returns <code>exception_table</code>. + */ + public ExceptionTable getExceptionTable() { return tryblocks; } + + /** + * Converts to a <code>CodeAttribute</code>. + */ + public CodeAttribute toCodeAttribute() { + return new CodeAttribute(constPool, maxStack, maxLocals, + get(), tryblocks); + } + + /** + * Returns the length of the bytecode sequence. + */ + public int length() { + int len = 0; + Bytecode b = this; + while (b != null) { + len += b.num; + b = b.next; + } + + return len; + } + + private void copy(byte[] dest, int index) { + Bytecode b = this; + while (b != null) { + System.arraycopy(b.buffer, 0, dest, index, b.num); + index += b.num; + b = b.next; + } + } + + /** + * Returns the produced bytecode sequence. + */ + public byte[] get() { + byte[] b = new byte[length()]; + copy(b, 0); + return b; + } + + /** + * Gets <code>max_stack</code>. + */ + public int getMaxStack() { return maxStack; } + + /** + * Sets <code>max_stack</code>. + * + * <p>This value may be automatically updated when an instruction + * is appended. A <code>Bytecode</code> object maintains the current + * stack depth whenever an instruction is added + * by <code>addOpcode()</code>. For example, if DUP is appended, + * the current stack depth is increased by one. If the new stack + * depth is more than <code>max_stack</code>, then it is assigned + * to <code>max_stack</code>. However, if branch instructions are + * appended, the current stack depth may not be correctly maintained. + * + * @see #addOpcode(int) + */ + public void setMaxStack(int size) { + maxStack = size; + } + + /** + * Gets <code>max_locals</code>. + */ + public int getMaxLocals() { return maxLocals; } + + /** + * Sets <code>max_locals</code>. + */ + public void setMaxLocals(int size) { + maxLocals = size; + } + + /** + * Sets <code>max_locals</code>. + * + * <p>This computes the number of local variables + * used to pass method parameters and sets <code>max_locals</code> + * to that number plus <code>locals</code>. + * + * @param isStatic true if <code>params</code> must be + * interpreted as parameters to a static method. + * @param params parameter types. + * @param locals the number of local variables excluding + * ones used to pass parameters. + */ + public void setMaxLocals(boolean isStatic, CtClass[] params, + int locals) { + if (!isStatic) + ++locals; + + if (params != null) { + CtClass doubleType = CtClass.doubleType; + CtClass longType = CtClass.longType; + int n = params.length; + for (int i = 0; i < n; ++i) { + CtClass type = params[i]; + if (type == doubleType || type == longType) + locals += 2; + else + ++locals; + } + } + + maxLocals = locals; + } + + /** + * Increments <code>max_locals</code>. + */ + public void incMaxLocals(int diff) { + maxLocals += diff; + } + + /** + * Adds a new entry of <code>exception_table</code>. + */ + public void addExceptionHandler(int start, int end, + int handler, CtClass type) { + addExceptionHandler(start, end, handler, + constPool.addClassInfo(type)); + } + + /** + * Adds a new entry of <code>exception_table</code>. + */ + public void addExceptionHandler(int start, int end, + int handler, int type) { + tryblocks.add(start, end, handler, type); + } + + /** + * Returns the length of bytecode sequence + * that have been added so far. + */ + public int currentPc() { + int n = 0; + Bytecode b = this; + while (b != null) { + n += b.num; + b = b.next; + } + + return n; + } + + /** + * Reads a signed 8bit value at the offset from the beginning of the + * bytecode sequence. + * + * @throws ArrayIndexOutOfBoundsException if offset is invalid. + */ + public int read(int offset) { + if (offset < 0) + return Opcode.NOP; + else if (offset < num) + return buffer[offset]; + else + try { + return next.read(offset - num); + } + catch (NullPointerException e) { + throw new ArrayIndexOutOfBoundsException(offset); + } + } + + /** + * Reads a signed 16bit value at the offset from the beginning of the + * bytecode sequence. + */ + public int read16bit(int offset) { + int v1 = read(offset); + int v2 = read(offset + 1); + return (v1 << 8) + (v2 & 0xff); + } + + /** + * Writes an 8bit value at the offset from the beginning of the + * bytecode sequence. + * + * @throws ArrayIndexOutOfBoundsException if offset is invalid. + */ + public void write(int offset, int value) { + if (offset < num) + buffer[offset] = (byte)value; + else + try { + next.write(offset - num, value); + } + catch (NullPointerException e) { + throw new ArrayIndexOutOfBoundsException(offset); + } + } + + /** + * Writes an 16bit value at the offset from the beginning of the + * bytecode sequence. + */ + public void write16bit(int offset, int value) { + write(offset, value >>> 8); + write(offset + 1, value); + } + + /** + * Appends an 8bit value to the end of the bytecode sequence. + */ + public void add(int code) { + if (num < bufsize) + buffer[num++] = (byte)code; + else { + if (next == null) + next = new Bytecode(); + + next.add(code); + } + } + + /** + * Appends an 8bit opcode to the end of the bytecode sequence. + * The current stack depth is updated. + * <code>max_stack</code> is updated if the current stack depth + * is the deepest so far. + * + * <p>Note: some instructions such as INVOKEVIRTUAL does not + * update the current stack depth since the increment depends + * on the method signature. + * <code>growStack()</code> must be explicitly called. + */ + public void addOpcode(int code) { + add(code); + growStack(STACK_GROW[code]); + } + + /** + * Increases the current stack depth. + * It also updates <code>max_stack</code> if the current stack depth + * is the deepest so far. + * + * @param diff the number added to the current stack depth. + */ + public void growStack(int diff) { + setStackDepth(stackDepth + diff); + } + + /** + * Returns the current stack depth. + */ + public int getStackDepth() { return stackDepth; } + + /** + * Sets the current stack depth. + * It also updates <code>max_stack</code> if the current stack depth + * is the deepest so far. + * + * @param depth new value. + */ + public void setStackDepth(int depth) { + stackDepth = depth; + if (stackDepth > maxStack) + maxStack = stackDepth; + } + + /** + * Appends a 16bit value to the end of the bytecode sequence. + * It never changes the current stack depth. + */ + public void addIndex(int index) { + add(index >> 8); + add(index); + } + + /** + * Appends ALOAD or (WIDE) ALOAD_<n> + * + * @param n an index into the local variable array. + */ + public void addAload(int n) { + if (n < 4) + addOpcode(42 + n); // aload_<n> + else if (n < 0x100) { + addOpcode(ALOAD); // aload + add(n); + } + else { + addOpcode(WIDE); + addOpcode(ALOAD); + addIndex(n); + } + } + + /** + * Appends ASTORE or (WIDE) ASTORE_<n> + * + * @param n an index into the local variable array. + */ + public void addAstore(int n) { + if (n < 4) + addOpcode(75 + n); // astore_<n> + else if (n < 0x100) { + addOpcode(ASTORE); // astore + add(n); + } + else { + addOpcode(WIDE); + addOpcode(ASTORE); + addIndex(n); + } + } + + /** + * Appends ICONST or ICONST_<n> + * + * @param n the pushed integer constant. + */ + public void addIconst(int n) { + if (n < 6 && -2 < n) + addOpcode(3 + n); // iconst_<i> -1..5 + else if (n <= 127 && -128 <= n) { + addOpcode(16); // bipush + add(n); + } + else if (n <= 32767 && -32768 <= n) { + addOpcode(17); // sipush + add(n >> 8); + add(n); + } + else + addLdc(constPool.addIntegerInfo(n)); + } + + /** + * Appends ILOAD or (WIDE) ILOAD_<n> + * + * @param n an index into the local variable array. + */ + public void addIload(int n) { + if (n < 4) + addOpcode(26 + n); // iload_<n> + else if (n < 0x100) { + addOpcode(ILOAD); // iload + add(n); + } + else { + addOpcode(WIDE); + addOpcode(ILOAD); + addIndex(n); + } + } + + /** + * Appends ISTORE or (WIDE) ISTORE_<n> + * + * @param n an index into the local variable array. + */ + public void addIstore(int n) { + if (n < 4) + addOpcode(59 + n); // istore_<n> + else if (n < 0x100) { + addOpcode(ISTORE); // istore + add(n); + } + else { + addOpcode(WIDE); + addOpcode(ISTORE); + addIndex(n); + } + } + + /** + * Appends LCONST or LCONST_<n> + * + * @param n the pushed long integer constant. + */ + public void addLconst(long n) { + if (n == 0 || n == 1) + addOpcode(9 + (int)n); // lconst_<n> + else + addLdc2w(n); + } + + /** + * Appends LLOAD or (WIDE) LLOAD_<n> + * + * @param n an index into the local variable array. + */ + public void addLload(int n) { + if (n < 4) + addOpcode(30 + n); // lload_<n> + else if (n < 0x100) { + addOpcode(LLOAD); // lload + add(n); + } + else { + addOpcode(WIDE); + addOpcode(LLOAD); + addIndex(n); + } + } + + /** + * Appends LSTORE or LSTORE_<n> + * + * @param n an index into the local variable array. + */ + public void addLstore(int n) { + if (n < 4) + addOpcode(63 + n); // lstore_<n> + else if (n < 0x100) { + addOpcode(LSTORE); // lstore + add(n); + } + else { + addOpcode(WIDE); + addOpcode(LSTORE); + addIndex(n); + } + } + + /** + * Appends DCONST or DCONST_<n> + * + * @param d the pushed double constant. + */ + public void addDconst(double d) { + if (d == 0.0 || d == 1.0) + addOpcode(14 + (int)d); // dconst_<n> + else + addLdc2w(d); + } + + /** + * Appends DLOAD or (WIDE) DLOAD_<n> + * + * @param n an index into the local variable array. + */ + public void addDload(int n) { + if (n < 4) + addOpcode(38 + n); // dload_<n> + else if (n < 0x100) { + addOpcode(DLOAD); // dload + add(n); + } + else { + addOpcode(WIDE); + addOpcode(DLOAD); + addIndex(n); + } + } + + /** + * Appends DSTORE or (WIDE) DSTORE_<n> + * + * @param n an index into the local variable array. + */ + public void addDstore(int n) { + if (n < 4) + addOpcode(71 + n); // dstore_<n> + else if (n < 0x100) { + addOpcode(DSTORE); // dstore + add(n); + } + else { + addOpcode(WIDE); + addOpcode(DSTORE); + addIndex(n); + } + } + + /** + * Appends FCONST or FCONST_<n> + * + * @param f the pushed float constant. + */ + public void addFconst(float f) { + if (f == 0.0f || f == 1.0f || f == 2.0f) + addOpcode(11 + (int)f); // fconst_<n> + else + addLdc(constPool.addFloatInfo(f)); + } + + /** + * Appends FLOAD or (WIDE) FLOAD_<n> + * + * @param n an index into the local variable array. + */ + public void addFload(int n) { + if (n < 4) + addOpcode(34 + n); // fload_<n> + else if (n < 0x100) { + addOpcode(FLOAD); // fload + add(n); + } + else { + addOpcode(WIDE); + addOpcode(FLOAD); + addIndex(n); + } + } + + /** + * Appends FSTORE or FSTORE_<n> + * + * @param n an index into the local variable array. + */ + public void addFstore(int n) { + if (n < 4) + addOpcode(67 + n); // fstore_<n> + else if (n < 0x100) { + addOpcode(FSTORE); // fstore + add(n); + } + else { + addOpcode(WIDE); + addOpcode(FSTORE); + addIndex(n); + } + } + + /** + * Appends an instruction for loading a value from the + * local variable at the index <code>n</code>. + * + * @param n the index. + * @param type the type of the loaded value. + * @return the size of the value (1 or 2 word). + */ + public int addLoad(int n, CtClass type) { + if (type.isPrimitive()) { + if (type == CtClass.booleanType || type == CtClass.charType + || type == CtClass.byteType || type == CtClass.shortType + || type == CtClass.intType) + addIload(n); + else if (type == CtClass.longType) { + addLload(n); + return 2; + } + else if(type == CtClass.floatType) + addFload(n); + else if(type == CtClass.doubleType) { + addDload(n); + return 2; + } + else + throw new RuntimeException("void type?"); + } + else + addAload(n); + + return 1; + } + + /** + * Appends an instruction for storing a value into the + * local variable at the index <code>n</code>. + * + * @param n the index. + * @param type the type of the stored value. + * @return 2 if the type is long or double. Otherwise 1. + */ + public int addStore(int n, CtClass type) { + if (type.isPrimitive()) { + if (type == CtClass.booleanType || type == CtClass.charType + || type == CtClass.byteType || type == CtClass.shortType + || type == CtClass.intType) + addIstore(n); + else if (type == CtClass.longType) { + addLstore(n); + return 2; + } + else if(type == CtClass.floatType) + addFstore(n); + else if(type == CtClass.doubleType) { + addDstore(n); + return 2; + } + else + throw new RuntimeException("void type?"); + } + else + addAstore(n); + + return 1; + } + + /** + * Appends instructions for loading all the parameters onto the + * operand stack. + */ + public int addLoadParameters(CtClass[] params) { + int stacksize = 0; + if (params != null) { + int n = params.length; + for (int i = 0; i < n; ++i) + stacksize += addLoad(stacksize + 1, params[i]); + } + + return stacksize; + } + + /** + * Appends CHECKCAST. + * + * @param c the type. + */ + public void addCheckcast(CtClass c) { + addOpcode(CHECKCAST); + addIndex(constPool.addClassInfo(c)); + } + + /** + * Appends CHECKCAST. + * + * @param classname a fully-qualified class name. + */ + public void addCheckcast(String classname) { + addOpcode(CHECKCAST); + addIndex(constPool.addClassInfo(classname)); + } + + /** + * Appends INSTANCEOF. + * + * @param classname the class name. + */ + public void addInstanceof(String classname) { + addOpcode(INSTANCEOF); + addIndex(constPool.addClassInfo(classname)); + } + + /** + * Appends GETFIELD. + * + * @param c the class + * @param name the field name + * @param type the descriptor of the field type. + * + * @see Descriptor#of(CtClass) + */ + public void addGetfield(CtClass c, String name, String type) { + add(GETFIELD); + int ci = constPool.addClassInfo(c); + addIndex(constPool.addFieldrefInfo(ci, name, type)); + growStack(Descriptor.dataSize(type) - 1); + } + + /** + * Appends GETSTATIC. + * + * @param c the class + * @param name the field name + * @param type the descriptor of the field type. + * + * @see Descriptor#of(CtClass) + */ + public void addGetstatic(CtClass c, String name, String type) { + add(GETSTATIC); + int ci = constPool.addClassInfo(c); + addIndex(constPool.addFieldrefInfo(ci, name, type)); + growStack(Descriptor.dataSize(type)); + } + + /** + * Appends GETSTATIC. + * + * @param c the fully-qualified class name + * @param name the field name + * @param type the descriptor of the field type. + * + * @see Descriptor#of(CtClass) + */ + public void addGetstatic(String c, String name, String type) { + add(GETSTATIC); + int ci = constPool.addClassInfo(c); + addIndex(constPool.addFieldrefInfo(ci, name, type)); + growStack(Descriptor.dataSize(type)); + } + + /** + * Appends INVOKESPECIAL. + * + * @param clazz the target class. + * @param name the method name. + * @param returnType the return type. + * @param paramTypes the parameter types. + */ + public void addInvokespecial(CtClass clazz, String name, + CtClass returnType, CtClass[] paramTypes) { + String desc = Descriptor.ofMethod(returnType, paramTypes); + addInvokespecial(clazz, name, desc); + } + + /** + * Appends INVOKESPECIAL. + * + * @param clazz the target class. + * @param name the method name + * @param desc the descriptor of the method signature. + * + * @see Descriptor#ofMethod(CtClass,CtClass[]) + * @see Descriptor#ofConstructor(CtClass[]) + */ + public void addInvokespecial(CtClass clazz, String name, String desc) { + addInvokespecial(constPool.addClassInfo(clazz), name, desc); + } + + /** + * Appends INVOKESPECIAL. + * + * @param clazz the fully-qualified class name. + * @param name the method name + * @param desc the descriptor of the method signature. + * + * @see Descriptor#ofMethod(CtClass,CtClass[]) + * @see Descriptor#ofConstructor(CtClass[]) + */ + public void addInvokespecial(String clazz, String name, String desc) { + addInvokespecial(constPool.addClassInfo(clazz), name, desc); + } + + /** + * Appends INVOKESPECIAL. + * + * @param clazz the index of <code>CONSTANT_Class_info</code> + * structure. + * @param name the method name + * @param desc the descriptor of the method signature. + * + * @see Descriptor#ofMethod(CtClass,CtClass[]) + * @see Descriptor#ofConstructor(CtClass[]) + */ + public void addInvokespecial(int clazz, String name, String desc) { + add(INVOKESPECIAL); + addIndex(constPool.addMethodrefInfo(clazz, name, desc)); + growStack(Descriptor.dataSize(desc) - 1); + } + + /** + * Appends INVOKESTATIC. + * + * @param clazz the target class. + * @param name the method name + * @param returnType the return type. + * @param paramTypes the parameter types. + */ + public void addInvokestatic(CtClass clazz, String name, + CtClass returnType, CtClass[] paramTypes) { + String desc = Descriptor.ofMethod(returnType, paramTypes); + addInvokestatic(clazz, name, desc); + } + + /** + * Appends INVOKESTATIC. + * + * @param clazz the target class. + * @param name the method name + * @param desc the descriptor of the method signature. + * + * @see Descriptor#ofMethod(CtClass,CtClass[]) + */ + public void addInvokestatic(CtClass clazz, String name, String desc) { + addInvokestatic(constPool.addClassInfo(clazz), name, desc); + } + + /** + * Appends INVOKESTATIC. + * + * @param classname the fully-qualified class name. + * @param name the method name + * @param desc the descriptor of the method signature. + * + * @see Descriptor#ofMethod(CtClass,CtClass[]) + */ + public void addInvokestatic(String classname, String name, String desc) { + addInvokestatic(constPool.addClassInfo(classname), name, desc); + } + + /** + * Appends INVOKESTATIC. + * + * @param clazz the index of <code>CONSTANT_Class_info</code> + * structure. + * @param name the method name + * @param desc the descriptor of the method signature. + * + * @see Descriptor#ofMethod(CtClass,CtClass[]) + */ + public void addInvokestatic(int clazz, String name, String desc) { + add(INVOKESTATIC); + addIndex(constPool.addMethodrefInfo(clazz, name, desc)); + growStack(Descriptor.dataSize(desc)); + } + + /** + * Appends INVOKEVIRTUAL. + * + * <p>The specified method must not be an inherited method. + * It must be directly declared in the class specified + * in <code>clazz</code>. + * + * @param clazz the target class. + * @param name the method name + * @param returnType the return type. + * @param paramTypes the parameter types. + */ + public void addInvokevirtual(CtClass clazz, String name, + CtClass returnType, CtClass[] paramTypes) { + String desc = Descriptor.ofMethod(returnType, paramTypes); + addInvokevirtual(clazz, name, desc); + } + + /** + * Appends INVOKEVIRTUAL. + * + * <p>The specified method must not be an inherited method. + * It must be directly declared in the class specified + * in <code>clazz</code>. + * + * @param clazz the target class. + * @param name the method name + * @param desc the descriptor of the method signature. + * + * @see Descriptor#ofMethod(CtClass,CtClass[]) + */ + public void addInvokevirtual(CtClass clazz, String name, String desc) { + addInvokevirtual(constPool.addClassInfo(clazz), name, desc); + } + + /** + * Appends INVOKEVIRTUAL. + * + * <p>The specified method must not be an inherited method. + * It must be directly declared in the class specified + * in <code>classname</code>. + * + * @param classname the fully-qualified class name. + * @param name the method name + * @param desc the descriptor of the method signature. + * + * @see Descriptor#ofMethod(CtClass,CtClass[]) + */ + public void addInvokevirtual(String classname, String name, String desc) { + addInvokevirtual(constPool.addClassInfo(classname), name, desc); + } + + /** + * Appends INVOKEVIRTUAL. + * + * <p>The specified method must not be an inherited method. + * It must be directly declared in the class specified + * by <code>clazz</code>. + * + * @param clazz the index of <code>CONSTANT_Class_info</code> + * structure. + * @param name the method name + * @param desc the descriptor of the method signature. + * + * @see Descriptor#ofMethod(CtClass,CtClass[]) + */ + public void addInvokevirtual(int clazz, String name, String desc) { + add(INVOKEVIRTUAL); + addIndex(constPool.addMethodrefInfo(clazz, name, desc)); + growStack(Descriptor.dataSize(desc) - 1); + } + + /** + * Appends INVOKEINTERFACE. + * + * @param clazz the target class. + * @param name the method name + * @param returnType the return type. + * @param paramTypes the parameter types. + * @param count the count operand of the instruction. + */ + public void addInvokeinterface(CtClass clazz, String name, + CtClass returnType, CtClass[] paramTypes, + int count) { + String desc = Descriptor.ofMethod(returnType, paramTypes); + addInvokeinterface(clazz, name, desc, count); + } + + /** + * Appends INVOKEINTERFACE. + * + * @param clazz the target class. + * @param name the method name + * @param desc the descriptor of the method signature. + * @param count the count operand of the instruction. + * + * @see Descriptor#ofMethod(CtClass,CtClass[]) + */ + public void addInvokeinterface(CtClass clazz, String name, + String desc, int count) { + addInvokeinterface(constPool.addClassInfo(clazz), name, desc, + count); + } + + /** + * Appends INVOKEINTERFACE. + * + * @param classname the fully-qualified class name. + * @param name the method name + * @param desc the descriptor of the method signature. + * @param count the count operand of the instruction. + * + * @see Descriptor#ofMethod(CtClass,CtClass[]) + */ + public void addInvokeinterface(String classname, String name, + String desc, int count) { + addInvokeinterface(constPool.addClassInfo(classname), name, desc, + count); + } + + /** + * Appends INVOKEINTERFACE. + * + * @param clazz the index of <code>CONSTANT_Class_info</code> + * structure. + * @param name the method name + * @param desc the descriptor of the method signature. + * @param count the count operand of the instruction. + * + * @see Descriptor#ofMethod(CtClass,CtClass[]) + */ + public void addInvokeinterface(int clazz, String name, + String desc, int count) { + add(INVOKEINTERFACE); + addIndex(constPool.addInterfaceMethodrefInfo(clazz, name, desc)); + add(count); + add(0); + growStack(Descriptor.dataSize(desc) - 1); + } + + /** + * Appends LDC or LDC_W. The pushed item is a <code>String</code> + * object. + * + * @param s the character string pushed by LDC or LDC_W. + */ + public void addLdc(String s) { + addLdc(constPool.addStringInfo(s)); + } + + /** + * Appends LDC or LDC_W. + * + * @param i index into the constant pool. + */ + public void addLdc(int i) { + if (i > 0xFF) { + addOpcode(LDC_W); + addIndex(i); + } + else { + addOpcode(LDC); + add(i); + } + } + + /** + * Appends LDC2_W. The pushed item is a long value. + */ + public void addLdc2w(long l) { + addOpcode(LDC2_W); + addIndex(constPool.addLongInfo(l)); + } + + /** + * Appends LDC2_W. The pushed item is a double value. + */ + public void addLdc2w(double d) { + addOpcode(LDC2_W); + addIndex(constPool.addDoubleInfo(d)); + } + + /** + * Appends NEW. + * + * @param clazz the class of the created instance. + */ + public void addNew(CtClass clazz) { + addOpcode(NEW); + addIndex(constPool.addClassInfo(clazz)); + } + + /** + * Appends NEW. + * + * @param classname the fully-qualified class name. + */ + public void addNew(String classname) { + addOpcode(NEW); + addIndex(constPool.addClassInfo(classname)); + } + + /** + * Appends ANEWARRAY. + * + * @param classname the qualified class name of the element type. + */ + public void addAnewarray(String classname) { + addOpcode(ANEWARRAY); + addIndex(constPool.addClassInfo(classname)); + } + + /** + * Appends ICONST and ANEWARRAY. + * + * @param clazz the elememnt type. + * @param length the array length. + */ + public void addAnewarray(CtClass clazz, int length) { + addIconst(length); + addOpcode(ANEWARRAY); + addIndex(constPool.addClassInfo(clazz)); + } + + /** + * Appends NEWARRAY for primitive types. + * + * @param atype <code>T_BOOLEAN</code>, <code>T_CHAR</code>, ... + * @see Opcode + */ + public void addNewarray(int atype, int length) { + addIconst(length); + addOpcode(NEWARRAY); + add(atype); + } + + /** + * Appends MULTINEWARRAY. + * + * @param clazz the array type. + * @param dimensions the sizes of all dimensions. + * @return the length of <code>dimensions</code>. + */ + public int addMultiNewarray(CtClass clazz, int[] dimensions) { + int len = dimensions.length; + for (int i = 0; i < len; ++i) + addIconst(dimensions[i]); + + growStack(len); + return addMultiNewarray(clazz, len); + } + + /** + * Appends MULTINEWARRAY. The size of every dimension must have been + * already pushed on the stack. + * + * @param clazz the array type. + * @param dim the number of the dimensions. + * @return the value of <code>dim</code>. + */ + public int addMultiNewarray(CtClass clazz, int dim) { + add(MULTIANEWARRAY); + addIndex(constPool.addClassInfo(clazz)); + add(dim); + growStack(1 - dim); + return dim; + } + + /** + * Appends MULTINEWARRAY. + * + * @param desc the type descriptor of the created array. + * @param dim dimensions. + * @return the value of <code>dim</code>. + */ + public int addMultiNewarray(String desc, int dim) { + add(MULTIANEWARRAY); + addIndex(constPool.addClassInfo(desc)); + add(dim); + growStack(1 - dim); + return dim; + } + + /** + * Appends PUTFIELD. + * + * @param c the target class. + * @param name the field name. + * @param desc the descriptor of the field type. + */ + public void addPutfield(CtClass c, String name, String desc) { + add(PUTFIELD); + int ci = constPool.addClassInfo(c); + addIndex(constPool.addFieldrefInfo(ci, name, desc)); + growStack(-1 - Descriptor.dataSize(desc)); + } + + /** + * Appends PUTSTATIC. + * + * @param c the target class. + * @param name the field name. + * @param desc the descriptor of the field type. + */ + public void addPutstatic(CtClass c, String name, String desc) { + add(PUTSTATIC); + int ci = constPool.addClassInfo(c); + addIndex(constPool.addFieldrefInfo(ci, name, desc)); + growStack(-Descriptor.dataSize(desc)); + } + + /** + * Appends ARETURN, IRETURN, .., or RETURN. + * + * @param type the return type. + */ + public void addReturn(CtClass type) { + if (type == null) + addOpcode(RETURN); + else if (type.isPrimitive()) { + CtPrimitiveType ptype = (CtPrimitiveType)type; + addOpcode(ptype.getReturnOp()); + } + else + addOpcode(ARETURN); + } + + /** + * Appends RET. + * + * @param var local variable + */ + public void addRet(int var) { + if (var < 0x100) { + addOpcode(RET); + add(var); + } + else { + addOpcode(WIDE); + addOpcode(RET); + addIndex(var); + } + } + + /** + * Appends instructions for executing + * <code>java.lang.System.println(<i>message</i>)</code>. + * + * @param message printed message. + */ + public void addPrintln(String message) { + addGetstatic("java.lang.System", "err", "Ljava/io/PrintStream;"); + addLdc(message); + addInvokevirtual("java.io.PrintStream", + "println", "(Ljava/lang/String;)V"); + } +} diff --git a/src/main/javassist/bytecode/ClassFile.java b/src/main/javassist/bytecode/ClassFile.java new file mode 100644 index 00000000..063caa59 --- /dev/null +++ b/src/main/javassist/bytecode/ClassFile.java @@ -0,0 +1,543 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Map; +import java.util.LinkedList; +import java.util.List; +import javassist.CannotCompileException; + +/** + * <code>ClassFile</code> represents a Java <code>.class</code> file, + * which consists of a constant pool, methods, fields, and attributes. + * + * @see javassist.CtClass#getClassFile() + */ +public final class ClassFile { + ConstPool constPool; + int thisClass; + int accessFlags; + int superClass; + int[] interfaces; + LinkedList fields; + LinkedList methods; + LinkedList attributes; + + String thisclassname; // not JVM-internal name + + /** + * Constructs a class file from a byte stream. + */ + public ClassFile(DataInputStream in) throws IOException { + read(in); + } + + /** + * Constructs a class file including no members. + * + * @param isInterface true if this is an interface. + * false if this is a class. + * @param classname a fully-qualified class name + * @param superclass a fully-qualified super class name + */ + public ClassFile(boolean isInterface, + String classname, String superclass) { + constPool = new ConstPool(classname); + thisClass = constPool.getThisClassInfo(); + if (isInterface) + accessFlags = AccessFlag.SUPER | AccessFlag.INTERFACE + | AccessFlag.ABSTRACT; + else + accessFlags = AccessFlag.SUPER; + + initSuperclass(superclass); + interfaces = null; + fields = new LinkedList(); + methods = new LinkedList(); + thisclassname = classname; + + attributes = new LinkedList(); + attributes.add(new SourceFileAttribute(constPool, + getSourcefileName(thisclassname))); + } + + private void initSuperclass(String superclass) { + if (superclass != null) + superClass = constPool.addClassInfo(superclass); + else + superClass = constPool.addClassInfo("java.lang.Object"); + } + + private static String getSourcefileName(String qname) { + int index = qname.lastIndexOf('.'); + if (index >= 0) + qname = qname.substring(index + 1); + + return qname + ".java"; + } + + /** + * Returns a constant pool table. + */ + public ConstPool getConstPool() { + return constPool; + } + + /** + * Returns true if this is an interface. + */ + public boolean isInterface() { + return (accessFlags & AccessFlag.INTERFACE) != 0; + } + + /** + * Returns true if this is a final class or interface. + */ + public boolean isFinal() { + return (accessFlags & AccessFlag.FINAL) != 0; + } + + /** + * Returns true if this is an abstract class or an interface. + */ + public boolean isAbstract() { + return (accessFlags & AccessFlag.ABSTRACT) != 0; + } + + /** + * Returns access flags. + * + * @see javassist.bytecode.AccessFlag + */ + public int getAccessFlags() { + return accessFlags; + } + + /** + * Changes access flags. + * + * @see javassist.bytecode.AccessFlag + */ + public void setAccessFlags(int acc) { + accessFlags = acc | AccessFlag.SUPER; + } + + /** + * Returns the class name. + */ + public String getName() { + return thisclassname; + } + + /** + * Sets the class name. This method substitutes the new name + * for all occurrences of the old class name in the class file. + */ + public void setName(String name) { + renameClass(thisclassname, name); + } + + /** + * Returns the super class name. + */ + public String getSuperclass() { + return constPool.getClassInfo(superClass); + } + + /** + * Returns the index of the constant pool entry representing + * the super class. + */ + public int getSuperclassId() { + return superClass; + } + + /** + * Sets the super class. + * + * <p>Unless the old super class is + * <code>java.lang.Object</code>, this method substitutes the new name + * for all occurrences of the old class name in the class file. + * If the old super class is <code>java.lang.Object</code>, + * only the calls to a super constructor are modified. + */ + public void setSuperclass(String superclass) + throws CannotCompileException + { + if (constPool.getClassInfo(superClass).equals("java.lang.Object")) { + if (superclass != null) + try { + superClass = constPool.addClassInfo(superclass); + setSuperclass2(superclass); + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } + else { + if (superclass == null) + superclass = "java.lang.Object"; + + renameClass(constPool.getClassInfo(superClass), superclass); + } + } + + /* If the original super class is java.lang.Object, a special + * treatment is needed. Some occurrences of java.lang.Object + * in the class file should not be changed into the new super + * class name. For example, the call of Vector.addElement(Object) + * should not be changed into the call of Vector.addElement(X), + * where X is the new super class. + */ + private void setSuperclass2(String superclass) throws BadBytecode { + LinkedList list = methods; + int n = list.size(); + for (int i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + minfo.setSuperclass(superclass); + } + } + + /** + * Replaces all occurrences of a class name in the class file. + * + * <p>If class X is substituted for class Y in the class file, + * X and Y must have the same signature. If Y provides a method + * m(), X must provide it even if X inherits m() from the super class. + * If this fact is not guaranteed, the bytecode verifier may cause + * an error. + * + * @param oldname the replaced class name + * @param newname the substituted class name + */ + public final void renameClass(String oldname, String newname) { + LinkedList list; + int n; + + if (oldname.equals(newname)) + return; + + if (oldname.equals(thisclassname)) + thisclassname = newname; + + oldname = Descriptor.toJvmName(oldname); + newname = Descriptor.toJvmName(newname); + constPool.renameClass(oldname, newname); + + list = methods; + n = list.size(); + for (int i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + String desc = minfo.getDescriptor(); + minfo.setDescriptor(Descriptor.rename(desc, oldname, newname)); + } + + list = fields; + n = list.size(); + for (int i = 0; i < n; ++i) { + FieldInfo finfo = (FieldInfo)list.get(i); + String desc = finfo.getDescriptor(); + finfo.setDescriptor(Descriptor.rename(desc, oldname, newname)); + } + } + + /** + * Replaces all occurrences of several class names in the class file. + * + * @param classnames specifies which class name is replaced + * with which new name. Class names must + * be described with the JVM-internal + * representation like + * <code>java/lang/Object</code>. + * + * @see #renameClass(String,String) + */ + public final void renameClass(Map classnames) { + String jvmNewThisName + = (String)classnames.get(Descriptor.toJvmName(thisclassname)); + if (jvmNewThisName != null) + thisclassname = Descriptor.toJavaName(jvmNewThisName); + + constPool.renameClass(classnames); + + LinkedList list = methods; + int n = list.size(); + for (int i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + String desc = minfo.getDescriptor(); + minfo.setDescriptor(Descriptor.rename(desc, classnames)); + } + + list = fields; + n = list.size(); + for (int i = 0; i < n; ++i) { + FieldInfo finfo = (FieldInfo)list.get(i); + String desc = finfo.getDescriptor(); + finfo.setDescriptor(Descriptor.rename(desc, classnames)); + } + } + + /** + * Returns the names of the interfaces implemented by the class. + */ + public String[] getInterfaces() { + if (interfaces == null) + return new String[0]; + else { + int n = interfaces.length; + String[] list = new String[n]; + for (int i = 0; i < n; ++i) + list[i] = constPool.getClassInfo(interfaces[i]); + + return list; + } + } + + /** + * Sets the interfaces. + * + * @param nameList the names of the interfaces. + */ + public void setInterfaces(String[] nameList) { + if (nameList != null) { + int n = nameList.length; + interfaces = new int[n]; + for (int i = 0; i < n; ++i) + interfaces[i] = constPool.addClassInfo(nameList[i]); + } + } + + /** + * Appends an interface to the + * interfaces implemented by the class. + */ + public void addInterface(String name) { + int info = constPool.addClassInfo(name); + if (interfaces == null) { + interfaces = new int[1]; + interfaces[0] = info; + } + else { + int n = interfaces.length; + int[] newarray = new int[n + 1]; + System.arraycopy(interfaces, 0, newarray, 0, n); + newarray[n] = info; + interfaces = newarray; + } + } + + /** + * Returns all the fields declared in the class. + * + * @return a list of <code>FieldInfo</code>. + * @see FieldInfo + */ + public List getFields() { return fields; } + + /** + * Appends a field to the class. + */ + public void addField(FieldInfo finfo) { + fields.add(finfo); + } + + /** + * Returns all the methods declared in the class. + * + * @return a list of <code>MethodInfo</code>. + * @see MethodInfo + */ + public List getMethods() { return methods; } + + /** + * Returns the method with the specified name. If there are multiple + * methods with that name, this method returns one of them. + * + * @return null if no such a method is found. + */ + public MethodInfo getMethod(String name) { + LinkedList list = methods; + int n = list.size(); + for (int i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + if (minfo.getName().equals(name)) + return minfo; + } + + return null; + } + + /** + * Returns a static initializer (class initializer), or null if + * it does not exist. + */ + public MethodInfo getStaticInitializer() { + return getMethod(MethodInfo.nameClinit); + } + + /** + * Appends a method to the class. + */ + public void addMethod(MethodInfo minfo) { + methods.add(minfo); + } + + /** + * Returns all the attributes. + * + * @return a list of <code>AttributeInfo</code> objects. + * @see AttributeInfo + */ + public List getAttributes() { return attributes; } + + /** + * Returns the attribute with the specified name. + * + * @param name attribute name + */ + public AttributeInfo getAttribute(String name) { + LinkedList list = attributes; + int n = list.size(); + for (int i = 0; i < n; ++i) { + AttributeInfo ai = (AttributeInfo)list.get(i); + if (ai.getName().equals(name)) + return ai; + } + + return null; + } + + /** + * Appends an attribute. If there is already an attribute with + * the same name, the new one substitutes for it. + */ + public void addAttribute(AttributeInfo info) { + AttributeInfo.remove(attributes, info.getName()); + attributes.add(info); + } + + /** + * Returns the source file containing this class. + * + * @return null if this information is not available. + */ + public String getSourceFile() { + SourceFileAttribute sf + = (SourceFileAttribute)getAttribute(SourceFileAttribute.tag); + if (sf == null) + return null; + else + return sf.getFileName(); + } + + private void read(DataInputStream in) throws IOException { + int i, n; + int magic = in.readInt(); + if (magic != 0xCAFEBABE) + throw new IOException("non class file"); + + int major = in.readUnsignedShort(); + int minor = in.readUnsignedShort(); + constPool = new ConstPool(in); + accessFlags = in.readUnsignedShort(); + thisClass = in.readUnsignedShort(); + constPool.setThisClassInfo(thisClass); + superClass = in.readUnsignedShort(); + n = in.readUnsignedShort(); + if (n == 0) + interfaces = null; + else { + interfaces = new int[n]; + for (i = 0; i < n; ++i) + interfaces[i] = in.readUnsignedShort(); + } + + ConstPool cp = constPool; + n = in.readUnsignedShort(); + fields = new LinkedList(); + for (i = 0; i < n; ++i) + addField(new FieldInfo(cp, in)); + + n = in.readUnsignedShort(); + methods = new LinkedList(); + for (i = 0; i < n; ++i) + addMethod(new MethodInfo(cp, in)); + + attributes = new LinkedList(); + n = in.readUnsignedShort(); + for (i = 0; i < n; ++i) + addAttribute(AttributeInfo.read(cp, in)); + + thisclassname = constPool.getClassInfo(thisClass); + } + + /** + * Writes a class file represened by this object + * into an output stream. + */ + public void write(DataOutputStream out) throws IOException { + int i, n; + + out.writeInt(0xCAFEBABE); // magic + out.writeShort(3); // major version + out.writeShort(45); // minor version + constPool.write(out); // constant pool + out.writeShort(accessFlags); + out.writeShort(thisClass); + out.writeShort(superClass); + + if (interfaces == null) + n = 0; + else + n = interfaces.length; + + out.writeShort(n); + for (i = 0; i < n; ++i) + out.writeShort(interfaces[i]); + + LinkedList list = fields; + n = list.size(); + out.writeShort(n); + for (i = 0; i < n; ++i) { + FieldInfo finfo = (FieldInfo)list.get(i); + finfo.write(out); + } + + list = methods; + n = list.size(); + out.writeShort(n); + for (i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + minfo.write(out); + } + + out.writeShort(attributes.size()); + AttributeInfo.writeAll(attributes, out); + } +} diff --git a/src/main/javassist/bytecode/ClassFileWriter.java b/src/main/javassist/bytecode/ClassFileWriter.java new file mode 100644 index 00000000..5b83a5aa --- /dev/null +++ b/src/main/javassist/bytecode/ClassFileWriter.java @@ -0,0 +1,116 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.bytecode; + +import java.io.PrintWriter; +import javassist.Modifier; +import java.util.List; + +/** + * A utility class for priting the contents of a class file. + * It prints a constant pool table, fields, and methods in a + * human readable representation. + */ +public class ClassFileWriter { + /** + * Prints the contents of a class file to the standard output stream. + */ + public static void print(ClassFile cf) { + print(cf, new PrintWriter(System.out, true)); + } + + /** + * Prints the contents of a class file. + */ + public static void print(ClassFile cf, PrintWriter out) { + List list; + int n; + + /* 0x0020 (SYNCHRONIZED) means ACC_SUPER if the modifiers + * are of a class. + */ + int mod + = AccessFlag.toModifier(cf.getAccessFlags() + & ~AccessFlag.SYNCHRONIZED); + out.println(Modifier.toString(mod) + " class " + + cf.getName() + " extends " + cf.getSuperclass()); + out.println(); + + ConstPool cp = cf.getConstPool(); + list = cf.getFields(); + n = list.size(); + for (int i = 0; i < n; ++i) { + FieldInfo finfo = (FieldInfo)list.get(i); + int acc = finfo.getAccessFlags(); + out.println(Modifier.toString(AccessFlag.toModifier(acc)) + + " " + finfo.getName() + "\t" + + finfo.getDescriptor()); + printAttributes(finfo.getAttributes(), out); + } + + out.println(); + list = cf.getMethods(); + n = list.size(); + for (int i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + int acc = minfo.getAccessFlags(); + out.println(Modifier.toString(AccessFlag.toModifier(acc)) + + " " + minfo.getName() + "\t" + + minfo.getDescriptor()); + printAttributes(minfo.getAttributes(), out); + out.println(); + } + + out.println(); + printAttributes(cf.getAttributes(), out); + } + + static void printAttributes(List list, PrintWriter out) { + if (list == null) + return; + + int n = list.size(); + for (int i = 0; i < n; ++i) { + AttributeInfo ai = (AttributeInfo)list.get(i); + if (ai instanceof CodeAttribute) { + CodeAttribute ca = (CodeAttribute)ai; + out.println("attribute: " + ai.getName() + ": " + + ai.getClass().getName()); + out.println("max stack " + ca.getMaxStack() + + ", max locals " + ca.getMaxLocals() + + ", " + ca.getExceptionTable().size() + + " catch blocks"); + out.println("<code attribute begin>"); + printAttributes(ca.getAttributes(), out); + out.println("<code attribute end>"); + } + else + out.println("attribute: " + ai.getName() + + " (" + ai.get().length + " byte): " + + ai.getClass().getName()); + } + } +} diff --git a/src/main/javassist/bytecode/CodeAttribute.java b/src/main/javassist/bytecode/CodeAttribute.java new file mode 100644 index 00000000..6f45c03e --- /dev/null +++ b/src/main/javassist/bytecode/CodeAttribute.java @@ -0,0 +1,404 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.List; +import java.util.LinkedList; +import java.util.Map; +import javassist.CtClass; + +/** + * <code>Code_attribute</code>. + * + * <p>To browse the <code>code</code> field of + * a <code>Code_attribute</code> structure, + * use <code>CodeIterator</code>. + * + * @see CodeIterator + */ +public class CodeAttribute extends AttributeInfo implements Opcode { + /** + * The name of this attribute <code>"Code"</code>. + */ + public static final String tag = "Code"; + + // code[] is stored in AttributeInfo.info. + + private int maxStack; + private int maxLocals; + private ExceptionTable exceptions; + private LinkedList attributes; + + /** + * Constructs a <code>Code_attribute</code>. + * + * @param cp constant pool table + * @param stack <code>max_stack</code> + * @param locals <code>max_locals</code> + * @param code <code>code[]</code> + * @param etable <code>exception_table[]</code> + */ + public CodeAttribute(ConstPool cp, int stack, int locals, byte[] code, + ExceptionTable etable) + { + super(cp, tag); + maxStack = stack; + maxLocals = locals; + info = code; + exceptions = etable; + attributes = new LinkedList(); + } + + /** + * Constructs a copy of <code>Code_attribute</code>. + * Specified class names are replaced during the copy. + * + * @param cp constant pool table. + * @param src source Code attribute. + * @param classnames pairs of replaced and substituted + * class names. + */ + private CodeAttribute(ConstPool cp, CodeAttribute src, Map classnames) + throws BadBytecode + { + super(cp, tag); + + maxStack = src.getMaxStack(); + maxLocals = src.getMaxLocals(); + exceptions = src.getExceptionTable().copy(cp, classnames); + info = src.copyCode(cp, classnames, exceptions); + attributes = new LinkedList(); + + /* Since an index into the source constant pool table may not + be translated, we don't copy the attributes. + */ + /* + List src_attr = src.getAttributes(); + int num = src_attr.size(); + for (int i = 0; i < num; ++i) { + AttributeInfo ai = (AttributeInfo)src_attr.get(i); + attributes.add(ai.copy(cp, classnames)); + } + */ + } + + CodeAttribute(ConstPool cp, int name_id, DataInputStream in) + throws IOException + { + super(cp, name_id, (byte[])null); + int attr_len = in.readInt(); + + maxStack = in.readUnsignedShort(); + maxLocals = in.readUnsignedShort(); + + int code_len = in.readInt(); + info = new byte[code_len]; + in.readFully(info); + + exceptions = new ExceptionTable(cp, in); + + attributes = new LinkedList(); + int num = in.readUnsignedShort(); + for (int i = 0; i < num; ++i) + attributes.add(AttributeInfo.read(cp, in)); + } + + /** + * Makes a copy. Class names are replaced according to the + * given <code>Map</code> object. + * + * @param newCp the constant pool table used by the new copy. + * @param classnames pairs of replaced and substituted + * class names. + * @exception RuntimeCopyException if a <code>BadBytecode</code> + * exception is thrown, it is + * converted into + * <code>RuntimeCopyException</code>. + * + * @return <code>CodeAttribute</code> object. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) + throws RuntimeCopyException + { + try { + return new CodeAttribute(newCp, this, classnames); + } + catch (BadBytecode e) { + throw new RuntimeCopyException("bad bytecode. fatal?"); + } + } + + /** + * An exception that may be thrown by <code>copy()</code> + * in <code>CodeAttribute</code>. + */ + public static class RuntimeCopyException extends RuntimeException { + /** + * Constructs an exception. + */ + public RuntimeCopyException(String s) { + super(s); + } + } + + /** + * Returns the length of this <code>attribute_info</code> + * structure. + * The returned value is <code>attribute_length + 6</code>. + */ + public int length() { + return 18 + info.length + exceptions.size() * 8 + + AttributeInfo.getLength(attributes); + } + + void write(DataOutputStream out) throws IOException { + out.writeShort(name); // attribute_name_index + out.writeInt(length() - 6); // attribute_length + out.writeShort(maxStack); // max_stack + out.writeShort(maxLocals); // max_locals + out.writeInt(info.length); // code_length + out.write(info); // code + exceptions.write(out); + out.writeShort(attributes.size()); // attributes_count + AttributeInfo.writeAll(attributes, out); // attributes + } + + /** + * This method is not available. + * + * @throws java.lang.UnsupportedOperationException always thrown. + */ + public byte[] get() { + throw new UnsupportedOperationException("CodeAttribute.get()"); + } + + /** + * This method is not available. + * + * @throws java.lang.UnsupportedOperationException always thrown. + */ + public void set(byte[] newinfo) { + throw new UnsupportedOperationException("CodeAttribute.set()"); + } + + /** + * Returns the name of the class declaring the method including + * this code attribute. + */ + public String getDeclaringClass() { + ConstPool cp = getConstPool(); + return cp.getClassName(); + } + + /** + * Returns <code>max_stack</code>. + */ + public int getMaxStack() { + return maxStack; + } + + /** + * Sets <code>max_stack</code>. + */ + public void setMaxStack(int value) { + maxStack = value; + } + + /** + * Returns <code>max_locals</code>. + */ + public int getMaxLocals() { + return maxLocals; + } + + /** + * Sets <code>max_locals</code>. + */ + public void setMaxLocals(int value) { + maxLocals = value; + } + + /** + * Returns <code>code_length</code>. + */ + public int getCodeLength() { + return info.length; + } + + /** + * Returns <code>code[]</code>. + */ + public byte[] getCode() { + return info; + } + + /** + * Sets <code>code[]</code>. + */ + void setCode(byte[] newinfo) { super.set(newinfo); } + + /** + * Makes a new iterator for reading this code attribute. + */ + public CodeIterator iterator() { + return new CodeIterator(this); + } + + /** + * Returns <code>exception_table[]</code>. + */ + public ExceptionTable getExceptionTable() { return exceptions; } + + /** + * Returns <code>attributes[]</code>. + * It returns a list of <code>AttributeInfo</code>. + * + * @see AttributeInfo + */ + public List getAttributes() { return attributes; } + + /** + * Returns the attribute with the specified name. + * If it is not found, this method returns null. + * + * @param name attribute name + * @return an <code>AttributeInfo</code> object or null. + */ + public AttributeInfo getAttribute(String name) { + return AttributeInfo.lookup(attributes, name); + } + + /** + * Copies code. + */ + private byte[] copyCode(ConstPool destCp, Map classnames, + ExceptionTable etable) + throws BadBytecode + { + int len = getCodeLength(); + byte[] newCode = new byte[len]; + + LdcEntry ldc = copyCode(this.info, 0, len, this.getConstPool(), + newCode, destCp, classnames); + return LdcEntry.doit(newCode, ldc, etable); + } + + private static LdcEntry copyCode(byte[] code, int beginPos, int endPos, + ConstPool srcCp, byte[] newcode, + ConstPool destCp, Map classnameMap) + throws BadBytecode + { + int i2, index; + LdcEntry ldcEntry = null; + + for (int i = beginPos; i < endPos; i = i2) { + i2 = CodeIterator.nextOpcode(code, i); + byte c = code[i]; + newcode[i] = c; + switch (c & 0xff) { + case LDC_W : + case LDC2_W : + case GETSTATIC : + case PUTSTATIC : + case GETFIELD : + case PUTFIELD : + case INVOKEVIRTUAL : + case INVOKESPECIAL : + case INVOKESTATIC : + case NEW : + case ANEWARRAY : + case CHECKCAST : + case INSTANCEOF : + copyConstPoolInfo(i + 1, code, srcCp, newcode, destCp, + classnameMap); + break; + case LDC : + index = code[i + 1] & 0xff; + index = srcCp.copy(index, destCp, classnameMap); + if (index < 0x100) + newcode[i + 1] = (byte)index; + else { + LdcEntry ldc = new LdcEntry(); + ldc.where = i; + ldc.index = index; + ldc.next = ldcEntry; + ldcEntry = ldc; + } + break; + case INVOKEINTERFACE : + copyConstPoolInfo(i + 1, code, srcCp, newcode, destCp, + classnameMap); + newcode[i + 3] = code[i + 3]; + newcode[i + 4] = code[i + 4]; + break; + case MULTIANEWARRAY : + copyConstPoolInfo(i + 1, code, srcCp, newcode, destCp, + classnameMap); + newcode[i + 3] = code[i + 3]; + break; + default : + while (++i < i2) + newcode[i] = code[i]; + + break; + } + } + + return ldcEntry; + } + + private static void copyConstPoolInfo(int i, byte[] code, ConstPool srcCp, + byte[] newcode, ConstPool destCp, + Map classnameMap) { + int index = ((code[i] & 0xff) << 8) | (code[i + 1] & 0xff); + index = srcCp.copy(index, destCp, classnameMap); + newcode[i] = (byte)(index >> 8); + newcode[i + 1] = (byte)index; + } +} + +final class LdcEntry { + LdcEntry next; + int where; + int index; + + static byte[] doit(byte[] code, LdcEntry ldc, ExceptionTable etable) + throws BadBytecode + { + while (ldc != null) { + int where = ldc.where; + code = CodeIterator.insertGap(code, where, 1, false, etable); + code[where] = (byte)Opcode.LDC_W; + ByteArray.write16bit(ldc.index, code, where + 1); + ldc = ldc.next; + } + + return code; + } +} diff --git a/src/main/javassist/bytecode/CodeIterator.java b/src/main/javassist/bytecode/CodeIterator.java new file mode 100644 index 00000000..09735e27 --- /dev/null +++ b/src/main/javassist/bytecode/CodeIterator.java @@ -0,0 +1,743 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.bytecode; + +/** + * An iterator for editing a code attribute. + * + * <p>This iterator does not provide <code>remove()</code>. + * If a piece of code in a <code>Code_attribute</code> is unnecessary, + * it should be overwritten with <code>NOP</code>. + * + * @see CodeAttribute#iterator() + */ +public class CodeIterator implements Opcode { + protected CodeAttribute codeAttr; + protected byte[] bytecode; + protected int endPos; + protected int currentPos; + + CodeIterator(CodeAttribute ca) { + codeAttr = ca; + bytecode = ca.getCode(); + begin(); + } + + /** + * Moves to the first instruction. + */ + public void begin() { + currentPos = 0; + endPos = getCodeLength(); + } + + /** + * Moves to the given index. + * + * <p>The index of the next instruction is set to the given index. + * The successive call to <code>next()</code> + * returns the index that has been given to <code>move()</code>. + * + * <p>Note that the index is into the byte array returned by + * <code>get().getCode()</code>. + * + * @see CodeAttribute#getCode() + */ + public void move(int index) { + currentPos = index; + } + + /** + * Returns a Code attribute read with this iterator. + */ + public CodeAttribute get() { + return codeAttr; + } + + /** + * Returns <code>code_length</code> of <code>Code_attribute</code>. + */ + public int getCodeLength() { + return bytecode.length; + } + + /** + * Returns the unsigned 8bit value at the given index. + */ + public int byteAt(int index) { return bytecode[index] & 0xff; } + + /** + * Writes an 8bit value at the given index. + */ + public void writeByte(int value, int index) { + bytecode[index] = (byte)value; + } + + /** + * Returns the unsigned 16bit value at the given index. + */ + public int u16bitAt(int index) { + return ByteArray.readU16bit(bytecode, index); + } + + /** + * Returns the signed 16bit value at the given index. + */ + public int s16bitAt(int index) { + return ByteArray.readS16bit(bytecode, index); + } + + /** + * Writes a 16 bit integer at the index. + */ + public void write16bit(int value, int index) { + ByteArray.write16bit(value, bytecode, index); + } + + /** + * Returns the signed 32bit value at the given index. + */ + public int s32bitAt(int index) { + return ByteArray.read32bit(bytecode, index); + } + + /** + * Writes a 32bit integer at the index. + */ + public void write32bit(int value, int index) { + ByteArray.write32bit(value, bytecode, index); + } + + /** + * Writes a byte array at the index. + */ + public void write(byte[] code, int index) { + int len = code.length; + for (int j = 0; j < len; ++j) + bytecode[index++] = code[j]; + } + + /** + * Returns true if there is more instructions. + */ + public boolean hasNext() { return currentPos < endPos; } + + /** + * Returns the index of the next instruction + * (not the next opcode). + * + * <p>Note that the index is into the byte array returned by + * <code>get().getCode()</code>. + * + * @see CodeAttribute#getCode() + * @see CodeIterator#byteAt(int) + */ + public int next() throws BadBytecode { + int pos = currentPos; + currentPos = nextOpcode(bytecode, pos); + return pos; + } + + /** + * Moves to the first instruction following + * constructor invocation <code>super()</code> or <code>this()</code>. + * + * <p>This method skips all the instructions for executing + * <code>super()</code> or <code>this()</code>, which should be + * placed at the beginning of a constructor body. + * + * <p>This method returns the index of INVOKESPECIAL instruction + * executing <code>super()</code> or <code>this()</code>. + * A successive call to <code>next()</code> returns the + * index of the next instruction following that INVOKESPECIAL. + * + * <p>This method works only for a constructor. + * + * @return the index of the INVOKESPECIAL instruction, or -1 + * if a constructor invocation is not found. + */ + public int skipConstructor() throws BadBytecode { + return skipSuperConstructor0(-1); + } + + /** + * Moves to the first instruction following super + * constructor invocation <code>super()</code>. + * + * <p>This method skips all the instructions for executing + * <code>super()</code>, which should be + * placed at the beginning of a constructor body. + * + * <p>This method returns the index of INVOKESPECIAL instruction + * executing <code>super()</code>. + * A successive call to <code>next()</code> returns the + * index of the next instruction following that INVOKESPECIAL. + * + * <p>This method works only for a constructor. + * + * @return the index of the INVOKESPECIAL instruction, or -1 + * if a super constructor invocation is not found + * but <code>this()</code> is found. + */ + public int skipSuperConstructor() throws BadBytecode { + return skipSuperConstructor0(0); + } + + /** + * Moves to the first instruction following explicit + * constructor invocation <code>this()</code>. + * + * <p>This method skips all the instructions for executing + * <code>this()</code>, which should be + * placed at the beginning of a constructor body. + * + * <p>This method returns the index of INVOKESPECIAL instruction + * executing <code>this()</code>. + * A successive call to <code>next()</code> returns the + * index of the next instruction following that INVOKESPECIAL. + * + * <p>This method works only for a constructor. + * + * @return the index of the INVOKESPECIAL instruction, or -1 + * if a explicit constructor invocation is not found + * but <code>super()</code> is found. + */ + public int skipThisConstructor() throws BadBytecode { + return skipSuperConstructor0(1); + } + + /* skipSuper 1: this(), 0: super(), -1: both. + */ + private int skipSuperConstructor0(int skipThis) throws BadBytecode { + begin(); + ConstPool cp = codeAttr.getConstPool(); + String thisClassName = codeAttr.getDeclaringClass(); + int nested = 0; + while (hasNext()) { + int index = next(); + int c = byteAt(index); + if (c == NEW) + ++nested; + else if (c == INVOKESPECIAL) { + int mref = ByteArray.readU16bit(bytecode, index + 1); + if (cp.getMethodrefName(mref).equals(MethodInfo.nameInit)) + if (--nested < 0) { + if (skipThis < 0) + return index; + + String cname = cp.getMethodrefClassName(mref); + if (cname.equals(thisClassName) == (skipThis > 0)) + return index; + else + break; + } + } + } + + begin(); + return -1; + } + + /** + * Inserts the given bytecode sequence + * before the next instruction that would be returned by + * <code>next()</code> (not before the instruction returned + * by tha last call to <code>next()</code>). + * Branch offsets and the exception table are also updated. + * + * <p>If the next instruction is at the beginning of a block statement, + * then the bytecode is inserted within that block. + * + * <p>An extra gap may be inserted at the end of the inserted + * bytecode sequence for adjusting alignment if the code attribute + * includes <code>LOOKUPSWITCH</code> or <code>TABLESWITCH</code>. + * + * @param code inserted bytecode sequence. + * @return the index indicating the first byte of the + * inserted byte sequence. + */ + public int insert(byte[] code) + throws BadBytecode + { + int pos = currentPos; + insert0(currentPos, code, false); + return pos; + } + + /** + * Inserts the given bytecode sequence + * before the instruction at the given index <code>pos</code>. + * Branch offsets and the exception table are also updated. + * + * <p>If the instruction at the given index is at the beginning + * of a block statement, + * then the bytecode is inserted within that block. + * + * <p>An extra gap may be inserted at the end of the inserted + * bytecode sequence for adjusting alignment if the code attribute + * includes <code>LOOKUPSWITCH</code> or <code>TABLESWITCH</code>. + * + * @param pos the index at which a byte sequence is inserted. + * @param code inserted bytecode sequence. + */ + public void insert(int pos, byte[] code) throws BadBytecode { + insert0(pos, code, false); + } + + /** + * Inserts the given bytecode sequence exclusively + * before the next instruction that would be returned by + * <code>next()</code> (not before the instruction returned + * by tha last call to <code>next()</code>). + * Branch offsets and the exception table are also updated. + * + * <p>If the next instruction is at the beginning of a block statement, + * then the bytecode is excluded from that block. + * + * <p>An extra gap may be inserted at the end of the inserted + * bytecode sequence for adjusting alignment if the code attribute + * includes <code>LOOKUPSWITCH</code> or <code>TABLESWITCH</code>. + * + * @param code inserted bytecode sequence. + * @return the index indicating the first byte of the + * inserted byte sequence. + */ + public int insertEx(byte[] code) + throws BadBytecode + { + int pos = currentPos; + insert0(currentPos, code, true); + return pos; + } + + /** + * Inserts the given bytecode sequence exclusively + * before the instruction at the given index <code>pos</code>. + * Branch offsets and the exception table are also updated. + * + * <p>If the instruction at the given index is at the beginning + * of a block statement, + * then the bytecode is excluded from that block. + * + * <p>An extra gap may be inserted at the end of the inserted + * bytecode sequence for adjusting alignment if the code attribute + * includes <code>LOOKUPSWITCH</code> or <code>TABLESWITCH</code>. + * + * @param pos the index at which a byte sequence is inserted. + * @param code inserted bytecode sequence. + */ + public void insertEx(int pos, byte[] code) throws BadBytecode { + insert0(pos, code, true); + } + + private void insert0(int pos, byte[] code, boolean exclusive) + throws BadBytecode + { + int len = code.length; + if (len <= 0) + return; + + insertGapCore(pos, len, exclusive); // currentPos will change. + for (int j = 0; j < len; ++j) + bytecode[pos++] = code[j]; + } + + /** + * Inserts a gap + * before the next instruction that would be returned by + * <code>next()</code> (not before the instruction returned + * by tha last call to <code>next()</code>). + * Branch offsets and the exception table are also updated. + * The inserted gap is filled with NOP. The gap length may be + * extended to a multiple of 4. + * + * <p>If the next instruction is at the beginning of a block statement, + * then the gap is inserted within that block. + * + * @param length gap length + * @return the index indicating the first byte of the inserted gap. + */ + public int insertGap(int length) throws BadBytecode { + int pos = currentPos; + insertGapCore(currentPos, length, false); + return pos; + } + + /** + * Inserts a gap in front of the instruction at the given + * index <code>pos</code>. + * Branch offsets and the exception table are also updated. + * The inserted gap is filled with NOP. The gap length may be + * extended to a multiple of 4. + * + * <p>If the instruction at the given index is at the beginning + * of a block statement, + * then the gap is inserted within that block. + * + * @param pos the index at which a gap is inserted. + * @param length gap length. + */ + public void insertGap(int pos, int length) throws BadBytecode { + insertGapCore(pos, length, false); + } + + /** + * Inserts an exclusive gap + * before the next instruction that would be returned by + * <code>next()</code> (not before the instruction returned + * by tha last call to <code>next()</code>). + * Branch offsets and the exception table are also updated. + * The inserted gap is filled with NOP. The gap length may be + * extended to a multiple of 4. + * + * <p>If the next instruction is at the beginning of a block statement, + * then the gap is excluded from that block. + * + * @param length gap length + * @return the index indicating the first byte of the inserted gap. + */ + public int insertExGap(int length) throws BadBytecode { + int pos = currentPos; + insertGapCore(currentPos, length, true); + return pos; + } + + /** + * Inserts an exclusive gap in front of the instruction at the given + * index <code>pos</code>. + * Branch offsets and the exception table are also updated. + * The inserted gap is filled with NOP. The gap length may be + * extended to a multiple of 4. + * + * <p>If the instruction at the given index is at the beginning + * of a block statement, + * then the gap is excluded from that block. + * + * @param pos the index at which a gap is inserted. + * @param length gap length. + */ + public void insertExGap(int pos, int length) throws BadBytecode { + insertGapCore(pos, length, true); + } + + private void insertGapCore(int pos, int length, boolean exclusive) + throws BadBytecode + { + if (length <= 0) + return; + + int cur = currentPos; + byte[] c = insertGap(bytecode, pos, length, exclusive, + get().getExceptionTable()); + if (cur >= pos) + currentPos = cur + (c.length - bytecode.length); + + codeAttr.setCode(c); + bytecode = c; + endPos = getCodeLength(); + } + + /** + * Copies and inserts the entries in the given exception table + * at the beginning of the exception table in the code attribute + * edited by this object. + * + * @param offset the value added to the code positions included + * in the entries. + */ + public void insert(ExceptionTable et, int offset) { + codeAttr.getExceptionTable().add(0, et, offset); + } + + /** + * Appends the given bytecode sequence at the end. + * + * @param code the bytecode appended. + * @return the position of the first byte of the appended bytecode. + */ + public int append(byte[] code) { + int size = getCodeLength(); + int len = code.length; + if (len <= 0) + return size; + + appendGap(len); + byte[] dest = bytecode; + for (int i = 0; i < len; ++i) + dest[i + size] = code[i]; + + return size; + } + + /** + * Appends a gap at the end of the bytecode sequence. + * + * @param length gap length + */ + public void appendGap(int gapLength) { + byte[] code = bytecode; + int codeLength = code.length; + byte[] newcode = new byte[codeLength + gapLength]; + + int i; + for (i = 0; i < codeLength; ++i) + newcode[i] = code[i]; + + for (i = codeLength; i < codeLength + gapLength; ++i) + newcode[i] = NOP; + + codeAttr.setCode(newcode); + bytecode = newcode; + endPos = getCodeLength(); + } + + /** + * Copies and appends the entries in the given exception table + * at the end of the exception table in the code attribute + * edited by this object. + * + * @param offset the value added to the code positions included + * in the entries. + */ + public void append(ExceptionTable et, int offset) { + ExceptionTable table = codeAttr.getExceptionTable(); + table.add(table.size(), et, offset); + } + + /* opcodeLegth is used for implementing nextOpcode(). + */ + private static final int opcodeLength[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 2, 3, + 3, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 0, 0, 1, 1, 1, 1, 1, 1, 3, 3, + 3, 3, 3, 3, 3, 5, 0, 3, 2, 3, 1, 1, 3, 3, 1, 1, 0, 4, 3, 3, + 5, 5 + }; + // 0 .. UNUSED (186), LOOKUPSWITCH, TABLESWITCH, WIDE + + /** + * Calculates the index of the next opcode. + */ + static int nextOpcode(byte[] code, int index) + throws BadBytecode + { + int opcode; + try { + opcode = code[index] & 0xff; + } + catch (IndexOutOfBoundsException e) { + throw new BadBytecode("invalid opcode address"); + } + + try { + int len = opcodeLength[opcode]; + if (len > 0) + return index + len; + else if (opcode == WIDE) + if (code[index + 1] == (byte)IINC) // WIDE IINC + return index + 6; + else + return index + 4; // WIDE ... + else { + int index2 = (index & ~3) + 8; + if (opcode == LOOKUPSWITCH) { + int npairs = ByteArray.read32bit(code, index2); + return index2 + npairs * 8 + 4; + } + else if (opcode == TABLESWITCH) { + int low = ByteArray.read32bit(code, index2); + int high = ByteArray.read32bit(code, index2 + 4); + return index2 + (high - low + 1) * 4 + 8; + } + // else + // throw new BadBytecode(opcode); + } + } + catch (IndexOutOfBoundsException e) { + } + + // opcode is UNUSED or an IndexOutOfBoundsException was thrown. + throw new BadBytecode(opcode); + } + + // methods for implementing insertGap(). + + /* If "where" is the beginning of a block statement, then the inserted + * gap is also included in the block statement. + * "where" must indicate the first byte of an opcode. + * The inserted gap is filled with NOP. gapLength may be extended to + * a multiple of 4. + */ + static byte[] insertGap(byte[] code, int where, int gapLength, + boolean exclusive, ExceptionTable etable) + throws BadBytecode + { + if (gapLength <= 0) + return code; + + try { + return insertGap0(code, where, gapLength, exclusive, etable); + } + catch (AlignmentException e) { + try { + return insertGap0(code, where, (gapLength + 3) & ~3, + exclusive, etable); + } + catch (AlignmentException e2) { + throw new RuntimeException("fatal error?"); + } + } + } + + private static byte[] insertGap0(byte[] code, int where, int gapLength, + boolean exclusive, ExceptionTable etable) + throws BadBytecode, AlignmentException + { + int codeLength = code.length; + byte[] newcode = new byte[codeLength + gapLength]; + insertGap2(code, where, gapLength, codeLength, newcode, exclusive); + etable.shiftPc(where, gapLength, exclusive); + return newcode; + } + + private static void insertGap2(byte[] code, int where, int gapLength, + int endPos, byte[] newcode, boolean exclusive) + throws BadBytecode, AlignmentException + { + int nextPos; + int i = 0; + int j = 0; + for (; i < endPos; i = nextPos) { + if (i == where) { + int j2 = j + gapLength; + while (j < j2) + newcode[j++] = NOP; + } + + nextPos = nextOpcode(code, i); + int inst = code[i] & 0xff; + // if<cond>, if_icmp<cond>, if_acmp<cond>, goto, jsr + if ((153 <= inst && inst <= 168) + || inst == IFNULL || inst == IFNONNULL) { + /* 2bytes *signed* offset */ + int offset = (code[i + 1] << 8) | (code[i + 2] & 0xff); + offset = newOffset(i, offset, where, gapLength, exclusive); + newcode[j] = code[i]; + ByteArray.write16bit(offset, newcode, j + 1); + j += 3; + } + else if (inst == GOTO_W || inst == JSR_W) { + /* 4bytes offset */ + int offset = ByteArray.read32bit(code, i + 1); + offset = newOffset(i, offset, where, gapLength, exclusive); + newcode[j++] = code[i]; + ByteArray.write32bit(offset, newcode, j); + j += 4; + } + else if (inst == TABLESWITCH) { + if ((gapLength & 3) != 0) + throw new AlignmentException(); + + int i0 = i; + int i2 = (i & ~3) + 4; // 0-3 byte padding + while (i0 < i2) + newcode[j++] = code[i0++]; + + int defaultbyte = newOffset(i, ByteArray.read32bit(code, i2), + where, gapLength, exclusive); + ByteArray.write32bit(defaultbyte, newcode, j); + int lowbyte = ByteArray.read32bit(code, i2 + 4); + ByteArray.write32bit(lowbyte, newcode, j + 4); + int highbyte = ByteArray.read32bit(code, i2 + 8); + ByteArray.write32bit(highbyte, newcode, j + 8); + j += 12; + i0 = i2 + 12; + i2 = i0 + (highbyte - lowbyte + 1) * 4; + while (i0 < i2) { + int offset = newOffset(i, ByteArray.read32bit(code, i0), + where, gapLength, exclusive); + ByteArray.write32bit(offset, newcode, j); + j += 4; + i0 += 4; + } + } + else if (inst == LOOKUPSWITCH) { + if ((gapLength & 3) != 0) + throw new AlignmentException(); + + int i0 = i; + int i2 = (i & ~3) + 4; // 0-3 byte padding + while (i0 < i2) + newcode[j++] = code[i0++]; + + int defaultbyte = newOffset(i, ByteArray.read32bit(code, i2), + where, gapLength, exclusive); + ByteArray.write32bit(defaultbyte, newcode, j); + int npairs = ByteArray.read32bit(code, i2 + 4); + ByteArray.write32bit(npairs, newcode, j + 4); + j += 8; + i0 = i2 + 8; + i2 = i0 + npairs * 8; + while (i0 < i2) { + ByteArray.copy32bit(code, i0, newcode, j); + int offset = newOffset(i, + ByteArray.read32bit(code, i0 + 4), + where, gapLength, exclusive); + ByteArray.write32bit(offset, newcode, j + 4); + j += 8; + i0 += 8; + } + } + else + while (i < nextPos) + newcode[j++] = code[i++]; + } + } + + private static int newOffset(int i, int offset, int where, + int gapLength, boolean exclusive) { + int target = i + offset; + if (i < where) { + if (where < target || (exclusive && where == target)) + offset += gapLength; + } + else + if (target < where || (!exclusive && where == target)) + offset -= gapLength; + + return offset; + } +} + + +class AlignmentException extends Exception { +} diff --git a/src/main/javassist/bytecode/ConstPool.java b/src/main/javassist/bytecode/ConstPool.java new file mode 100644 index 00000000..5d079e3a --- /dev/null +++ b/src/main/javassist/bytecode/ConstPool.java @@ -0,0 +1,1374 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; +import java.io.IOException; +import java.util.Map; +import java.util.Hashtable; +import javassist.CtClass; + +/** + * Constant pool table. + */ +public final class ConstPool { + LongVector items; + int numOfItems; + Hashtable classes; + Hashtable strings; + int thisClassInfo; + private static final int SIZE = 128; + + /** + * <code>CONSTANT_Class</code> + */ + public static final int CONST_Class = ClassInfo.tag; + + /** + * <code>CONSTANT_Fieldref</code> + */ + public static final int CONST_Fieldref = FieldrefInfo.tag; + + /** + * <code>CONSTANT_Methodref</code> + */ + public static final int CONST_Methodref = MethodrefInfo.tag; + + /** + * <code>CONSTANT_InterfaceMethodref</code> + */ + public static final int CONST_InterfaceMethodref + = InterfaceMethodrefInfo.tag; + + /** + * <code>CONSTANT_String</code> + */ + public static final int CONST_String = StringInfo.tag; + + /** + * <code>CONSTANT_Integer</code> + */ + public static final int CONST_Integer = IntegerInfo.tag; + + /** + * <code>CONSTANT_Float</code> + */ + public static final int CONST_Float = IntegerInfo.tag; + + /** + * <code>CONSTANT_Long</code> + */ + public static final int CONST_Long = LongInfo.tag; + + /** + * <code>CONSTANT_Double</code> + */ + public static final int CONST_Double = DoubleInfo.tag; + + /** + * <code>CONSTANT_NameAndType</code> + */ + public static final int CONST_NameAndType = NameAndTypeInfo.tag; + + /** + * <code>CONSTANT_Utf8</code> + */ + public static final int CONST_Utf8 = Utf8Info.tag; + + /** + * Represents the class using this constant pool table. + */ + public static final CtClass THIS = null; + + /** + * Constructs a constant pool table. + * + * @param thisclass the name of the class using this constant + * pool table + */ + public ConstPool(String thisclass) { + this(); + thisClassInfo = addClassInfo(thisclass); + } + + /** + * Constructs a constant pool table from the given byte stream. + * + * @param in byte stream. + */ + public ConstPool(DataInputStream in) throws IOException { + this(); + read(in); + } + + private ConstPool() { + items = new LongVector(SIZE); + numOfItems = 0; + addItem(null); // index 0 is reserved by the JVM. + classes = new Hashtable(); + strings = new Hashtable(); + thisClassInfo = 0; + } + + /** + * Returns the name of the class using this constant pool table. + */ + public String getClassName() { + return getClassInfo(thisClassInfo); + } + + /** + * Returns the index of <code>CONSTANT_Class_info</code> structure + * specifying the class using this constant pool table. + */ + public int getThisClassInfo() { + return thisClassInfo; + } + + void setThisClassInfo(int i) { + thisClassInfo = i; + } + + ConstInfo getItem(int n) { + return (ConstInfo)items.elementAt(n); + } + + /** + * Returns the <code>tag</code> field of the constant pool table + * entry at the given index. + */ + public int getTag(int index) { + return getItem(index).getTag(); + } + + /** + * Reads <code>CONSTANT_Class_info</code> structure + * at the given index. + * + * @return a fully-qualified class or interface name specified + * by <code>name_index</code>. + */ + public String getClassInfo(int index) { + ClassInfo c = (ClassInfo)getItem(index); + if (c == null) + return null; + else + return Descriptor.toJavaName(getUtf8Info(c.name)); + } + + /** + * Reads the <code>name_index</code> field of the + * <code>CONSTANT_NameAndType_info</code> structure + * at the given index. + */ + public int getNameAndTypeName(int index) { + NameAndTypeInfo ntinfo = (NameAndTypeInfo)getItem(index); + return ntinfo.memberName; + } + + /** + * Reads the <code>descriptor_index</code> field of the + * <code>CONSTANT_NameAndType_info</code> structure + * at the given index. + */ + public int getNameAndTypeDescriptor(int index) { + NameAndTypeInfo ntinfo = (NameAndTypeInfo)getItem(index); + return ntinfo.typeDescriptor; + } + + /** + * Reads the <code>class_index</code> field of the + * <code>CONSTANT_Fieldref_info</code> structure + * at the given index. + */ + public int getFieldrefClass(int index) { + FieldrefInfo finfo = (FieldrefInfo)getItem(index); + return finfo.classIndex; + } + + /** + * Reads the <code>class_index</code> field of the + * <code>CONSTANT_Fieldref_info</code> structure + * at the given index. + * + * @return the name of the class at that <code>class_index</code>. + */ + public String getFieldrefClassName(int index) { + FieldrefInfo f = (FieldrefInfo)getItem(index); + if (f == null) + return null; + else + return getClassInfo(f.classIndex); + } + + /** + * Reads the <code>name_and_type_index</code> field of the + * <code>CONSTANT_Fieldref_info</code> structure + * at the given index. + */ + public int getFieldrefNameAndType(int index) { + FieldrefInfo finfo = (FieldrefInfo)getItem(index); + return finfo.nameAndTypeIndex; + } + + /** + * Reads the <code>name_index</code> field of the + * <code>CONSTANT_NameAndType_info</code> structure + * indirectly specified by the given index. + * + * @param index an index to a <code>CONSTANT_Fieldref_info</code>. + * @return the name of the field. + */ + public String getFieldrefName(int index) { + FieldrefInfo f = (FieldrefInfo)getItem(index); + if (f == null) + return null; + else { + NameAndTypeInfo n = (NameAndTypeInfo)getItem(f.nameAndTypeIndex); + if(n == null) + return null; + else + return getUtf8Info(n.memberName); + } + } + + /** + * Reads the <code>descriptor_index</code> field of the + * <code>CONSTANT_NameAndType_info</code> structure + * indirectly specified by the given index. + * + * @param index an index to a <code>CONSTANT_Fieldref_info</code>. + * @return the type descriptor of the field. + */ + public String getFieldrefType(int index) { + FieldrefInfo f = (FieldrefInfo)getItem(index); + if (f == null) + return null; + else { + NameAndTypeInfo n = (NameAndTypeInfo) getItem(f.nameAndTypeIndex); + if(n == null) + return null; + else + return getUtf8Info(n.typeDescriptor); + } + } + + /** + * Reads the <code>class_index</code> field of the + * <code>CONSTANT_Methodref_info</code> structure + * at the given index. + */ + public int getMethodrefClass(int index) { + MethodrefInfo minfo = (MethodrefInfo)getItem(index); + return minfo.classIndex; + } + + /** + * Reads the <code>class_index</code> field of the + * <code>CONSTANT_Methodref_info</code> structure + * at the given index. + * + * @return the name of the class at that <code>class_index</code>. + */ + public String getMethodrefClassName(int index) { + MethodrefInfo minfo = (MethodrefInfo)getItem(index); + if (minfo == null) + return null; + else + return getClassInfo(minfo.classIndex); + } + + /** + * Reads the <code>name_and_type_index</code> field of the + * <code>CONSTANT_Methodref_info</code> structure + * at the given index. + */ + public int getMethodrefNameAndType(int index) { + MethodrefInfo minfo = (MethodrefInfo)getItem(index); + return minfo.nameAndTypeIndex; + } + + /** + * Reads the <code>name_index</code> field of the + * <code>CONSTANT_NameAndType_info</code> structure + * indirectly specified by the given index. + * + * @param index an index to a <code>CONSTANT_Methodref_info</code>. + * @return the name of the method. + */ + public String getMethodrefName(int index) { + MethodrefInfo minfo = (MethodrefInfo)getItem(index); + if (minfo == null) + return null; + else { + NameAndTypeInfo n + = (NameAndTypeInfo)getItem(minfo.nameAndTypeIndex); + if(n == null) + return null; + else + return getUtf8Info(n.memberName); + } + } + + /** + * Reads the <code>descriptor_index</code> field of the + * <code>CONSTANT_NameAndType_info</code> structure + * indirectly specified by the given index. + * + * @param index an index to a <code>CONSTANT_Methodref_info</code>. + * @return the descriptor of the method. + */ + public String getMethodrefType(int index) { + MethodrefInfo minfo = (MethodrefInfo)getItem(index); + if (minfo == null) + return null; + else { + NameAndTypeInfo n + = (NameAndTypeInfo)getItem(minfo.nameAndTypeIndex); + if(n == null) + return null; + else + return getUtf8Info(n.typeDescriptor); + } + } + + /** + * Reads the <code>class_index</code> field of the + * <code>CONSTANT_InterfaceMethodref_info</code> structure + * at the given index. + */ + public int getInterfaceMethodrefClass(int index) { + InterfaceMethodrefInfo minfo + = (InterfaceMethodrefInfo)getItem(index); + return minfo.classIndex; + } + + /** + * Reads the <code>class_index</code> field of the + * <code>CONSTANT_InterfaceMethodref_info</code> structure + * at the given index. + * + * @return the name of the class at that <code>class_index</code>. + */ + public String getInterfaceMethodrefClassName(int index) { + InterfaceMethodrefInfo minfo + = (InterfaceMethodrefInfo)getItem(index); + return getClassInfo(minfo.classIndex); + } + + /** + * Reads the <code>name_and_type_index</code> field of the + * <code>CONSTANT_InterfaceMethodref_info</code> structure + * at the given index. + */ + public int getInterfaceMethodrefNameAndType(int index) { + InterfaceMethodrefInfo minfo + = (InterfaceMethodrefInfo)getItem(index); + return minfo.nameAndTypeIndex; + } + + /** + * Reads the <code>name_index</code> field of the + * <code>CONSTANT_NameAndType_info</code> structure + * indirectly specified by the given index. + * + * @param index an index to + * a <code>CONSTANT_InterfaceMethodref_info</code>. + * @return the name of the method. + */ + public String getInterfaceMethodrefName(int index) { + InterfaceMethodrefInfo minfo + = (InterfaceMethodrefInfo)getItem(index); + if (minfo == null) + return null; + else { + NameAndTypeInfo n + = (NameAndTypeInfo)getItem(minfo.nameAndTypeIndex); + if(n == null) + return null; + else + return getUtf8Info(n.memberName); + } + } + + /** + * Reads the <code>descriptor_index</code> field of the + * <code>CONSTANT_NameAndType_info</code> structure + * indirectly specified by the given index. + * + * @param index an index to + * a <code>CONSTANT_InterfaceMethodref_info</code>. + * @return the descriptor of the method. + */ + public String getInterfaceMethodrefType(int index) { + InterfaceMethodrefInfo minfo + = (InterfaceMethodrefInfo)getItem(index); + if (minfo == null) + return null; + else { + NameAndTypeInfo n + = (NameAndTypeInfo)getItem(minfo.nameAndTypeIndex); + if(n == null) + return null; + else + return getUtf8Info(n.typeDescriptor); + } + } + /** + * Reads <code>CONSTANT_Integer_info</code>, <code>_Float_info</code>, + * <code>_Long_info</code>, <code>_Double_info</code>, or + * <code>_String_info</code> structure. + * These are used with the LDC instruction. + * + * @return a <code>String</code> value or a wrapped primitive-type + * value. + */ + public Object getLdcValue(int index) { + ConstInfo constInfo = this.getItem(index); + Object value = null; + if (constInfo instanceof StringInfo) + value = this.getStringInfo(index); + else if (constInfo instanceof FloatInfo) + value = new Float(getFloatInfo(index)); + else if (constInfo instanceof IntegerInfo) + value = new Integer(getIntegerInfo(index)); + else if (constInfo instanceof LongInfo) + value = new Long(getLongInfo(index)); + else if (constInfo instanceof DoubleInfo) + value = new Double(getDoubleInfo(index)); + else + value = null; + + return value; + } + + /** + * Reads <code>CONSTANT_Integer_info</code> structure + * at the given index. + * + * @return the value specified by this entry. + */ + public int getIntegerInfo(int index) { + IntegerInfo i = (IntegerInfo)getItem(index); + return i.value; + } + + /** + * Reads <code>CONSTANT_Float_info</code> structure + * at the given index. + * + * @return the value specified by this entry. + */ + public float getFloatInfo(int index) { + FloatInfo i = (FloatInfo)getItem(index); + return i.value; + } + + /** + * Reads <code>CONSTANT_Long_info</code> structure + * at the given index. + * + * @return the value specified by this entry. + */ + public long getLongInfo(int index) { + LongInfo i = (LongInfo)getItem(index); + return i.value; + } + + /** + * Reads <code>CONSTANT_Double_info</code> structure + * at the given index. + * + * @return the value specified by this entry. + */ + public double getDoubleInfo(int index) { + DoubleInfo i = (DoubleInfo)getItem(index); + return i.value; + } + + /** + * Reads <code>CONSTANT_String_info</code> structure + * at the given index. + * + * @return the string specified by <code>string_index</code>. + */ + public String getStringInfo(int index) { + StringInfo si = (StringInfo)getItem(index); + return getUtf8Info(si.string); + } + + /** + * Reads <code>CONSTANT_utf8_info</code> structure + * at the given index. + * + * @return the string specified by this entry. + */ + public String getUtf8Info(int index) { + Utf8Info utf = (Utf8Info)getItem(index); + return utf.string; + } + + /** + * Determines whether <code>CONSTANT_Methodref_info</code> + * structure at the given index represents the constructor + * of the given class. + * + * @return the <code>descriptor_index</code> specifying + * the type descriptor of the that constructor. + * If it is not that constructor, + * <code>isConstructor()</code> returns 0. + */ + public int isConstructor(String classname, int index) { + return isMember(classname, MethodInfo.nameInit, index); + } + + /** + * Determines whether <code>CONSTANT_Methodref_info</code>, + * <code>CONSTANT_Fieldref_info</code>, or + * <code>CONSTANT_InterfaceMethodref_info</code> structure + * at the given index represents the member with the specified + * name and declaring class. + * + * @param classname the class declaring the member + * @param membername the member name + * @param index the index into the constant pool table + * + * @return the <code>descriptor_index</code> specifying + * the type descriptor of that member. + * If it is not that member, + * <code>isMember()</code> returns 0. + */ + public int isMember(String classname, String membername, int index) { + MemberrefInfo minfo = (MemberrefInfo)getItem(index); + if (getClassInfo(minfo.classIndex).equals(classname)) { + NameAndTypeInfo ntinfo + = (NameAndTypeInfo)getItem(minfo.nameAndTypeIndex); + if (getUtf8Info(ntinfo.memberName).equals(membername)) + return ntinfo.typeDescriptor; + } + + return 0; // false + } + + private int addItem(ConstInfo info) { + items.addElement(info); + return numOfItems++; + } + + /** + * Copies the n-th item in this ConstPool object into the destination + * ConstPool object. + * The class names that the item refers to are renamed according + * to the given map. + * + * @param n the <i>n</i>-th item + * @param dest destination constant pool table + * @param classnames the map or null. + */ + public int copy(int n, ConstPool dest, Map classnames) { + if (n == 0) + return 0; + + ConstInfo info = getItem(n); + return info.copy(this, dest, classnames); + } + + int addConstInfoPadding() { + return addItem(new ConstInfoPadding()); + } + + /** + * Adds a new <code>CONSTANT_Class_info</code> structure. + * + * <p>This also adds a <code>CONSTANT_Utf8_info</code> structure + * for storing the class name. + * + * @return the index of the added entry. + */ + public int addClassInfo(CtClass c) { + if (c == THIS) + return thisClassInfo; + else if (!c.isArray()) + return addClassInfo(c.getName()); + else { + // an array type is recorded in the hashtable with + // the key "[L<classname>;" instead of "<classname>". + // + // note: toJvmName(toJvmName(c)) is equal to toJvmName(c). + + return addClassInfo(Descriptor.toJvmName(c)); + } + } + + /** + * Adds a new <code>CONSTANT_Class_info</code> structure. + * + * <p>This also adds a <code>CONSTANT_Utf8_info</code> structure + * for storing the class name. + * + * @param qname a fully-qualified class name + * (or the JVM-internal representation of that name). + * @return the index of the added entry. + */ + public int addClassInfo(String qname) { + ClassInfo info = (ClassInfo)classes.get(qname); + if (info != null) + return info.index; + else { + int utf8 = addUtf8Info(Descriptor.toJvmName(qname)); + info = new ClassInfo(utf8, numOfItems); + classes.put(qname, info); + return addItem(info); + } + } + + /** + * Adds a new <code>CONSTANT_NameAndType_info</code> structure. + * + * <p>This also adds <code>CONSTANT_Utf8_info</code> structures. + * + * @param name <code>name_index</code> + * @param type <code>descriptor_index</code> + * @return the index of the added entry. + */ + public int addNameAndTypeInfo(String name, String type) { + return addNameAndTypeInfo(addUtf8Info(name), addUtf8Info(type)); + } + + /** + * Adds a new <code>CONSTANT_NameAndType_info</code> structure. + * + * @param name <code>name_index</code> + * @param type <code>descriptor_index</code> + * @return the index of the added entry. + */ + public int addNameAndTypeInfo(int name, int type) { + return addItem(new NameAndTypeInfo(name, type)); + } + + /** + * Adds a new <code>CONSTANT_Fieldref_info</code> structure. + * + * <p>This also adds a new <code>CONSTANT_NameAndType_info</code> + * structure. + * + * @param classInfo <code>class_index</code> + * @param name <code>name_index</code> + * of <code>CONSTANT_NameAndType_info</code>. + * @param type <code>descriptor_index</code> + * of <code>CONSTANT_NameAndType_info</code>. + * @return the index of the added entry. + */ + public int addFieldrefInfo(int classInfo, String name, String type) { + int nt = addNameAndTypeInfo(name, type); + return addFieldrefInfo(classInfo, nt); + } + + /** + * Adds a new <code>CONSTANT_Fieldref_info</code> structure. + * + * @param classInfo <code>class_index</code> + * @param nameandtypeinfo <code>name_and_type_index</code>. + * @return the index of the added entry. + */ + public int addFieldrefInfo(int classInfo, int nameAndTypeInfo) { + return addItem(new FieldrefInfo(classInfo, nameAndTypeInfo)); + } + + /** + * Adds a new <code>CONSTANT_Methodref_info</code> structure. + * + * <p>This also adds a new <code>CONSTANT_NameAndType_info</code> + * structure. + * + * @param classInfo <code>class_index</code> + * @param name <code>name_index</code> + * of <code>CONSTANT_NameAndType_info</code>. + * @param type <code>descriptor_index</code> + * of <code>CONSTANT_NameAndType_info</code>. + * @return the index of the added entry. + */ + public int addMethodrefInfo(int classInfo, String name, String type) { + int nt = addNameAndTypeInfo(name, type); + return addMethodrefInfo(classInfo, nt); + } + + /** + * Adds a new <code>CONSTANT_Methodref_info</code> structure. + * + * @param classInfo <code>class_index</code> + * @param nameandtypeinfo <code>name_and_type_index</code>. + * @return the index of the added entry. + */ + public int addMethodrefInfo(int classInfo, int nameAndTypeInfo) { + return addItem(new MethodrefInfo(classInfo, nameAndTypeInfo)); + } + + /** + * Adds a new <code>CONSTANT_InterfaceMethodref_info</code> + * structure. + * + * <p>This also adds a new <code>CONSTANT_NameAndType_info</code> + * structure. + * + * @param classInfo <code>class_index</code> + * @param name <code>name_index</code> + * of <code>CONSTANT_NameAndType_info</code>. + * @param type <code>descriptor_index</code> + * of <code>CONSTANT_NameAndType_info</code>. + * @return the index of the added entry. + */ + public int addInterfaceMethodrefInfo(int classInfo, String name, + String type) { + int nt = addNameAndTypeInfo(name, type); + return addInterfaceMethodrefInfo(classInfo, nt); + } + + /** + * Adds a new <code>CONSTANT_InterfaceMethodref_info</code> + * structure. + * + * @param classInfo <code>class_index</code> + * @param nameandtypeinfo <code>name_and_type_index</code>. + * @return the index of the added entry. + */ + public int addInterfaceMethodrefInfo(int classInfo, + int nameAndTypeInfo) { + return addItem(new InterfaceMethodrefInfo(classInfo, + nameAndTypeInfo)); + } + + /** + * Adds a new <code>CONSTANT_String_info</code> + * structure. + * + * <p>This also adds a new <code>CONSTANT_Utf8_info</code> + * structure. + * + * @return the index of the added entry. + */ + public int addStringInfo(String str) { + return addItem(new StringInfo(addUtf8Info(str))); + } + + /** + * Adds a new <code>CONSTANT_Integer_info</code> + * structure. + * + * @return the index of the added entry. + */ + public int addIntegerInfo(int i) { + return addItem(new IntegerInfo(i)); + } + + /** + * Adds a new <code>CONSTANT_Float_info</code> + * structure. + * + * @return the index of the added entry. + */ + public int addFloatInfo(float f) { + return addItem(new FloatInfo(f)); + } + + /** + * Adds a new <code>CONSTANT_Long_info</code> + * structure. + * + * @return the index of the added entry. + */ + public int addLongInfo(long l) { + int i = addItem(new LongInfo(l)); + addItem(new ConstInfoPadding()); + return i; + } + + /** + * Adds a new <code>CONSTANT_Double_info</code> + * structure. + * + * @return the index of the added entry. + */ + public int addDoubleInfo(double d) { + int i = addItem(new DoubleInfo(d)); + addItem(new ConstInfoPadding()); + return i; + } + + /** + * Adds a new <code>CONSTANT_Utf8_info</code> + * structure. + * + * <p>If the given utf8 string has been already recorded in the + * table, then this method does not add a new entry to avoid adding + * a duplicated entry. + * Instead, it returns the index of the entry already recorded. + * + * @return the index of the added entry. + */ + public int addUtf8Info(String utf8) { + Utf8Info info = (Utf8Info)strings.get(utf8); + if (info != null) + return info.index; + else { + info = new Utf8Info(utf8, numOfItems); + strings.put(utf8, info); + return addItem(info); + } + } + + /** + * Replaces all occurrences of a class name. + * + * @param oldName the replaced name + * @param newName the substituted name. + */ + public void renameClass(String oldName, String newName) { + LongVector v = items; + int size = numOfItems; + for (int i = 1; i < size; ++i) + ((ConstInfo)v.elementAt(i)).renameClass(this, oldName, newName); + } + + /** + * Replaces all occurrences of class names. + * + * @param classnames specifies pairs of replaced and substituted + * name. + */ + public void renameClass(Map classnames) { + LongVector v = items; + int size = numOfItems; + for (int i = 1; i < size; ++i) + ((ConstInfo)v.elementAt(i)).renameClass(this, classnames); + } + + private void read(DataInputStream in) throws IOException { + int n = in.readUnsignedShort(); + while (--n > 0) { // index 0 is reserved by JVM + int tag = readOne(in); + if ((tag == LongInfo.tag) || (tag == DoubleInfo.tag)) { + addItem(new ConstInfoPadding()); + --n; + } + } + } + + private int readOne(DataInputStream in) throws IOException { + ConstInfo info; + int tag = in.readUnsignedByte(); + switch (tag) { + case Utf8Info.tag : // 1 + info = new Utf8Info(in, numOfItems); + strings.put(((Utf8Info)info).string, info); + break; + case IntegerInfo.tag : // 3 + info = new IntegerInfo(in); + break; + case FloatInfo.tag : // 4 + info = new FloatInfo(in); + break; + case LongInfo.tag : // 5 + info = new LongInfo(in); + break; + case DoubleInfo.tag : // 6 + info = new DoubleInfo(in); + break; + case ClassInfo.tag : // 7 + info = new ClassInfo(in, numOfItems); + // classes.put(<classname>, info); + break; + case StringInfo.tag : // 8 + info = new StringInfo(in); + break; + case FieldrefInfo.tag : // 9 + info = new FieldrefInfo(in); + break; + case MethodrefInfo.tag : // 10 + info = new MethodrefInfo(in); + break; + case InterfaceMethodrefInfo.tag : // 11 + info = new InterfaceMethodrefInfo(in); + break; + case NameAndTypeInfo.tag : // 12 + info = new NameAndTypeInfo(in); + break; + default : + throw new IOException("invalid constant type: " + tag); + } + + addItem(info); + return tag; + } + + /** + * Writes the contents of the constant pool table. + */ + public void write(DataOutputStream out) throws IOException { + out.writeShort(numOfItems); + LongVector v = items; + int size = numOfItems; + for (int i = 1; i < size; ++i) + ((ConstInfo)v.elementAt(i)).write(out); + } + + /** + * Prints the contents of the constant pool table. + */ + public void print() { + print(new PrintWriter(System.out, true)); + } + + /** + * Prints the contents of the constant pool table. + */ + public void print(PrintWriter out) { + int size = numOfItems; + for (int i = 1; i < size; ++i) { + out.print(i); + out.print(" "); + ((ConstInfo)items.elementAt(i)).print(out); + } + } +} + +abstract class ConstInfo { + public abstract int getTag(); + + public void renameClass(ConstPool cp, String oldName, String newName) {} + public void renameClass(ConstPool cp, Map classnames) {} + public abstract int copy(ConstPool src, ConstPool dest, Map classnames); + // ** classnames is a mapping between JVM names. + + public abstract void write(DataOutputStream out) throws IOException; + public abstract void print(PrintWriter out); + + public String toString() { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + PrintWriter out = new PrintWriter(bout); + print(out); + return bout.toString(); + } +} + +/* padding following DoubleInfo or LongInfo. + */ +class ConstInfoPadding extends ConstInfo { + public int getTag() { return 0; } + + public int copy(ConstPool src, ConstPool dest, Map map) { + return dest.addConstInfoPadding(); + } + + public void write(DataOutputStream out) throws IOException {} + + public void print(PrintWriter out) { + out.println("padding"); + } +} + +class ClassInfo extends ConstInfo { + static final int tag = 7; + int name; + int index; + + public ClassInfo(int className, int i) { + name = className; + index = i; + } + + public ClassInfo(DataInputStream in, int i) throws IOException { + name = in.readUnsignedShort(); + index = i; + } + + public int getTag() { return tag; } + + public void renameClass(ConstPool cp, String oldName, String newName) { + if (cp.getUtf8Info(name).equals(oldName)) + name = cp.addUtf8Info(newName); + } + + public void renameClass(ConstPool cp, Map map) { + String oldName = cp.getUtf8Info(name); + String newName = (String)map.get(oldName); + if (newName != null && !newName.equals(oldName)) + name = cp.addUtf8Info(newName); + } + + public int copy(ConstPool src, ConstPool dest, Map map) { + String classname = src.getUtf8Info(name); + if (map != null) { + String newname = (String)map.get(classname); + if (newname != null) + classname = newname; + } + + return dest.addClassInfo(classname); + } + + public void write(DataOutputStream out) throws IOException { + out.writeByte(tag); + out.writeShort(name); + } + + public void print(PrintWriter out) { + out.print("Class #"); + out.println(name); + } +} + +class NameAndTypeInfo extends ConstInfo { + static final int tag = 12; + int memberName; + int typeDescriptor; + + public NameAndTypeInfo(int name, int type) { + memberName = name; + typeDescriptor = type; + } + + public NameAndTypeInfo(DataInputStream in) throws IOException { + memberName = in.readUnsignedShort(); + typeDescriptor = in.readUnsignedShort(); + } + + public int getTag() { return tag; } + + public void renameClass(ConstPool cp, String oldName, String newName) { + String type = cp.getUtf8Info(typeDescriptor); + String type2 = Descriptor.rename(type, oldName, newName); + if (type != type2) + typeDescriptor = cp.addUtf8Info(type2); + } + + public void renameClass(ConstPool cp, Map map) { + String type = cp.getUtf8Info(typeDescriptor); + String type2 = Descriptor.rename(type, map); + if (type != type2) + typeDescriptor = cp.addUtf8Info(type2); + } + + public int copy(ConstPool src, ConstPool dest, Map map) { + String mname = src.getUtf8Info(memberName); + String tdesc = src.getUtf8Info(typeDescriptor); + tdesc = Descriptor.rename(tdesc, map); + return dest.addNameAndTypeInfo(dest.addUtf8Info(mname), + dest.addUtf8Info(tdesc)); + } + + public void write(DataOutputStream out) throws IOException { + out.writeByte(tag); + out.writeShort(memberName); + out.writeShort(typeDescriptor); + } + + public void print(PrintWriter out) { + out.print("NameAndType #"); + out.print(memberName); + out.print(", type #"); + out.println(typeDescriptor); + } +} + +abstract class MemberrefInfo extends ConstInfo { + int classIndex; + int nameAndTypeIndex; + + public MemberrefInfo(int cindex, int ntindex) { + classIndex = cindex; + nameAndTypeIndex = ntindex; + } + + public MemberrefInfo(DataInputStream in) throws IOException { + classIndex = in.readUnsignedShort(); + nameAndTypeIndex = in.readUnsignedShort(); + } + + public int copy(ConstPool src, ConstPool dest, Map map) { + int classIndex2 = src.getItem(classIndex).copy(src, dest, map); + int ntIndex2 = src.getItem(nameAndTypeIndex).copy(src, dest, map); + return copy2(dest, classIndex2, ntIndex2); + } + + abstract protected int copy2(ConstPool dest, int cindex, int ntindex); + + public void write(DataOutputStream out) throws IOException { + out.writeByte(getTag()); + out.writeShort(classIndex); + out.writeShort(nameAndTypeIndex); + } + + public void print(PrintWriter out) { + out.print(getTagName() + " #"); + out.print(classIndex); + out.print(", name&type #"); + out.println(nameAndTypeIndex); + } + + public abstract String getTagName(); +} + +class FieldrefInfo extends MemberrefInfo { + static final int tag = 9; + + public FieldrefInfo(int cindex, int ntindex) { + super(cindex, ntindex); + } + + public FieldrefInfo(DataInputStream in) throws IOException { + super(in); + } + + public int getTag() { return tag; } + + public String getTagName() { return "Field"; } + + protected int copy2(ConstPool dest, int cindex, int ntindex) { + return dest.addFieldrefInfo(cindex, ntindex); + } +} + +class MethodrefInfo extends MemberrefInfo { + static final int tag = 10; + + public MethodrefInfo(int cindex, int ntindex) { + super(cindex, ntindex); + } + + public MethodrefInfo(DataInputStream in) throws IOException { + super(in); + } + + public int getTag() { return tag; } + + public String getTagName() { return "Method"; } + + protected int copy2(ConstPool dest, int cindex, int ntindex) { + return dest.addMethodrefInfo(cindex, ntindex); + } +} + +class InterfaceMethodrefInfo extends MemberrefInfo { + static final int tag = 11; + + public InterfaceMethodrefInfo(int cindex, int ntindex) { + super(cindex, ntindex); + } + + public InterfaceMethodrefInfo(DataInputStream in) throws IOException { + super(in); + } + + public int getTag() { return tag; } + + public String getTagName() { return "Interface"; } + + protected int copy2(ConstPool dest, int cindex, int ntindex) { + return dest.addInterfaceMethodrefInfo(cindex, ntindex); + } +} + +class StringInfo extends ConstInfo { + static final int tag = 8; + int string; + + public StringInfo(int str) { + string = str; + } + + public StringInfo(DataInputStream in) throws IOException { + string = in.readUnsignedShort(); + } + + public int getTag() { return tag; } + + public int copy(ConstPool src, ConstPool dest, Map map) { + return dest.addStringInfo(src.getUtf8Info(string)); + } + + public void write(DataOutputStream out) throws IOException { + out.writeByte(tag); + out.writeShort(string); + } + + public void print(PrintWriter out) { + out.print("String #"); + out.println(string); + } +} + +class IntegerInfo extends ConstInfo { + static final int tag = 3; + int value; + + public IntegerInfo(int i) { + value = i; + } + + public IntegerInfo(DataInputStream in) throws IOException { + value = in.readInt(); + } + + public int getTag() { return tag; } + + public int copy(ConstPool src, ConstPool dest, Map map) { + return dest.addIntegerInfo(value); + } + + public void write(DataOutputStream out) throws IOException { + out.writeByte(tag); + out.writeInt(value); + } + + public void print(PrintWriter out) { + out.print("Integer "); + out.println(value); + } +} + +class FloatInfo extends ConstInfo { + static final int tag = 4; + float value; + + public FloatInfo(float f) { + value = f; + } + + public FloatInfo(DataInputStream in) throws IOException { + value = in.readFloat(); + } + + public int getTag() { return tag; } + + public int copy(ConstPool src, ConstPool dest, Map map) { + return dest.addFloatInfo(value); + } + + public void write(DataOutputStream out) throws IOException { + out.writeByte(tag); + out.writeFloat(value); + } + + public void print(PrintWriter out) { + out.print("Float "); + out.println(value); + } +} + +class LongInfo extends ConstInfo { + static final int tag = 5; + long value; + + public LongInfo(long l) { + value = l; + } + + public LongInfo(DataInputStream in) throws IOException { + value = in.readLong(); + } + + public int getTag() { return tag; } + + public int copy(ConstPool src, ConstPool dest, Map map) { + return dest.addLongInfo(value); + } + + public void write(DataOutputStream out) throws IOException { + out.writeByte(tag); + out.writeLong(value); + } + + public void print(PrintWriter out) { + out.print("Long "); + out.println(value); + } +} + +class DoubleInfo extends ConstInfo { + static final int tag = 6; + double value; + + public DoubleInfo(double d) { + value = d; + } + + public DoubleInfo(DataInputStream in) throws IOException { + value = in.readDouble(); + } + + public int getTag() { return tag; } + + public int copy(ConstPool src, ConstPool dest, Map map) { + return dest.addDoubleInfo(value); + } + + public void write(DataOutputStream out) throws IOException { + out.writeByte(tag); + out.writeDouble(value); + } + + public void print(PrintWriter out) { + out.print("Double "); + out.println(value); + } +} + +class Utf8Info extends ConstInfo { + static final int tag = 1; + String string; + int index; + + public Utf8Info(String utf8, int i) { + string = utf8; + index = i; + } + + public Utf8Info(DataInputStream in, int i) throws IOException { + string = in.readUTF(); + index = i; + } + + public int getTag() { return tag; } + + public int copy(ConstPool src, ConstPool dest, Map map) { + return dest.addUtf8Info(string); + } + + public void write(DataOutputStream out) throws IOException { + out.writeByte(tag); + out.writeUTF(string); + } + + public void print(PrintWriter out) { + out.print("UTF8 \""); + out.print(string); + out.println("\""); + } +} diff --git a/src/main/javassist/bytecode/ConstantAttribute.java b/src/main/javassist/bytecode/ConstantAttribute.java new file mode 100644 index 00000000..4d913e15 --- /dev/null +++ b/src/main/javassist/bytecode/ConstantAttribute.java @@ -0,0 +1,82 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.util.Map; +import java.io.IOException; + +/** + * <code>ConstantValue_attribute</code>. + */ +public class ConstantAttribute extends AttributeInfo { + /** + * The name of this attribute <code>"ConstantValue"</code>. + */ + public static final String tag = "ConstantValue"; + + ConstantAttribute(ConstPool cp, int n, DataInputStream in) + throws IOException + { + super(cp, n, in); + } + + /** + * Constructs a ConstantValue attribute. + * + * @param cp a constant pool table. + * @param index <code>constantvalue_index</code> + * of <code>ConstantValue_attribute</code>. + */ + public ConstantAttribute(ConstPool cp, int index) { + super(cp, tag); + byte[] bvalue = new byte[2]; + bvalue[0] = (byte)(index >>> 8); + bvalue[1] = (byte)index; + set(bvalue); + } + + /** + * Returns <code>constantvalue_index</code>. + */ + public int getConstantValue() { + return ByteArray.readU16bit(get(), 0); + } + + /** + * Makes a copy. Class names are replaced according to the + * given <code>Map</code> object. + * + * @param newCp the constant pool table used by the new copy. + * @param classnames pairs of replaced and substituted + * class names. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) { + int index = getConstPool().copy(getConstantValue(), newCp, + classnames); + return new ConstantAttribute(newCp, index); + } +} diff --git a/src/main/javassist/bytecode/Descriptor.java b/src/main/javassist/bytecode/Descriptor.java new file mode 100644 index 00000000..065fbccf --- /dev/null +++ b/src/main/javassist/bytecode/Descriptor.java @@ -0,0 +1,543 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.bytecode; + +import java.util.Map; +import javassist.CtClass; +import javassist.CtPrimitiveType; +import javassist.ClassPool; +import javassist.NotFoundException; + +/** + * A support class for dealing with descriptors. + * + * <p>See chapter 4.3 in "The Java Virtual Machine Specification (2nd ed.)" + */ +public class Descriptor { + /** + * Converts a class name into the internal representation used in + * the JVM. + * + * <p>Note that <code>toJvmName(toJvmName(s))</code> is equivalent + * to <code>toJvmName(s)</code>. + */ + public static String toJvmName(String classname) { + return classname.replace('.', '/'); + } + + /** + * Converts a class name from the internal representation used in + * the JVM to the normal one used in Java. + */ + public static String toJavaName(String classname) { + return classname.replace('/', '.'); + } + + /** + * Returns the internal representation of the class name in the + * JVM. + */ + public static String toJvmName(CtClass clazz) { + if (clazz.isArray()) + return of(clazz); + else + return toJvmName(clazz.getName()); + } + + /** + * Substitutes a class name + * in the given descriptor string. + * + * @param desc descriptor string + * @param oldname replaced JVM class name + * @param newname substituted JVM class name + * + * @see Descriptor#toJvmName(String) + */ + public static String rename(String desc, + String oldname, String newname) { + if (desc.indexOf(oldname) < 0) + return desc; + + StringBuffer newdesc = new StringBuffer(); + int head = 0; + int i = 0; + for (;;) { + int j = desc.indexOf('L', i); + if (j < 0) + break; + else if (desc.startsWith(oldname, j + 1) + && desc.charAt(j + oldname.length() + 1) == ';') { + newdesc.append(desc.substring(head, j)); + newdesc.append('L'); + newdesc.append(newname); + newdesc.append(';'); + head = i = j + oldname.length() + 2; + } + else { + i = desc.indexOf(';', j) + 1; + if (i < 1) + break; // ';' was not found. + } + } + + if (head == 0) + return desc; + else { + int len = desc.length(); + if (head < len) + newdesc.append(desc.substring(head, len)); + + return newdesc.toString(); + } + } + + /** + * Substitutes class names in the given descriptor string + * according to the given <code>map</code>. + * + * @param map a map between replaced and substituted + * JVM class names. + * + * @see Descriptor#toJvmName(String) + */ + public static String rename(String desc, Map map) { + if (map == null) + return desc; + + StringBuffer newdesc = new StringBuffer(); + int head = 0; + int i = 0; + for (;;) { + int j = desc.indexOf('L', i); + if (j < 0) + break; + + int k = desc.indexOf(';', j); + if (k < 0) + break; + + i = k + 1; + String name = desc.substring(j + 1, k); + String name2 = (String)map.get(name); + if (name2 != null) { + newdesc.append(desc.substring(head, j)); + newdesc.append('L'); + newdesc.append(name2); + newdesc.append(';'); + head = i; + } + } + + if (head == 0) + return desc; + else { + int len = desc.length(); + if (head < len) + newdesc.append(desc.substring(head, len)); + + return newdesc.toString(); + } + } + + /** + * Returns the descriptor representing the given type. + */ + public static String of(CtClass type) { + StringBuffer sbuf = new StringBuffer(); + toDescriptor(sbuf, type); + return sbuf.toString(); + } + + private static void toDescriptor(StringBuffer desc, CtClass type) { + if (type.isArray()) { + desc.append('['); + try { + toDescriptor(desc, type.getComponentType()); + } + catch (NotFoundException e) { + desc.append('L'); + String name = type.getName(); + desc.append(toJvmName(name.substring(0, name.length() - 2))); + desc.append(';'); + } + } + else if (type.isPrimitive()) { + CtPrimitiveType pt = (CtPrimitiveType)type; + desc.append(pt.getDescriptor()); + } + else { // class type + desc.append('L'); + desc.append(type.getName().replace('.', '/')); + desc.append(';'); + } + } + + /** + * Returns the descriptor representing a constructor receiving + * the given parameter types. + * + * @param paramTypes parameter types + */ + public static String ofConstructor(CtClass[] paramTypes) { + return ofMethod(CtClass.voidType, paramTypes); + } + + /** + * Returns the descriptor representing a method that receives + * the given parameter types and returns the given type. + * + * @param returnType return type + * @param paramTypes parameter types + */ + public static String ofMethod(CtClass returnType, CtClass[] paramTypes) { + StringBuffer desc = new StringBuffer(); + desc.append('('); + if (paramTypes != null) { + int n = paramTypes.length; + for (int i = 0; i < n; ++i) + toDescriptor(desc, paramTypes[i]); + } + + desc.append(')'); + if (returnType != null) + toDescriptor(desc, returnType); + + return desc.toString(); + } + + /** + * Returns the descriptor representing a list of parameter types. + * For example, if the given parameter types are two <code>int</code>, + * then this method returns <code>"(II)"</code>. + * + * @param paramTypes parameter types + */ + public static String ofParameters(CtClass[] paramTypes) { + return ofMethod(null, paramTypes); + } + + /** + * Appends a parameter type to the parameter list represented + * by the given descriptor. + * + * <p><code>classname</code> must not be an array type. + * + * @param classname parameter type (not primitive type) + * @param desc descriptor + */ + public static String appendParameter(String classname, + String desc) { + int i = desc.indexOf(')'); + if (i < 0) + return desc; + else { + StringBuffer newdesc = new StringBuffer(); + newdesc.append(desc.substring(0, i)); + newdesc.append('L'); + newdesc.append(classname.replace('.', '/')); + newdesc.append(';'); + newdesc.append(desc.substring(i)); + return newdesc.toString(); + } + } + + /** + * Inserts a parameter type at the beginning of the parameter + * list represented + * by the given descriptor. + * + * <p><code>classname</code> must not be an array type. + * + * @param classname parameter type (not primitive type) + * @param desc descriptor + */ + public static String insertParameter(String classname, + String desc) { + if (desc.charAt(0) != '(') + return desc; + else + return "(L" + classname.replace('.', '/') + ';' + + desc.substring(1); + } + + /** + * Changes the return type included in the given descriptor. + * + * <p><code>classname</code> must not be an array type. + * + * @param classname return type + * @param desc descriptor + */ + public static String changeReturnType(String classname, String desc) { + int i = desc.indexOf(')'); + if (i < 0) + return desc; + else { + StringBuffer newdesc = new StringBuffer(); + newdesc.append(desc.substring(0, i + 1)); + newdesc.append('L'); + newdesc.append(classname.replace('.', '/')); + newdesc.append(';'); + return newdesc.toString(); + } + } + + /** + * Returns the <code>CtClass</code> objects representing the parameter + * types specified by the given descriptor. + * + * @param desc descriptor + * @param cp the class pool used for obtaining + * a <code>CtClass</code> object. + */ + public static CtClass[] getParameterTypes(String desc, ClassPool cp) + throws NotFoundException + { + if (desc.charAt(0) != '(') + return null; + else { + int num = numOfParameters(desc); + CtClass[] args = new CtClass[num]; + int n = 0; + int i = 1; + do { + i = toCtClass(cp, desc, i, args, n++); + } while(i > 0); + return args; + } + } + + /** + * Returns the <code>CtClass</code> object representing the return + * type specified by the given descriptor. + * + * @param desc descriptor + * @param cp the class pool used for obtaining + * a <code>CtClass</code> object. + */ + public static CtClass getReturnType(String desc, ClassPool cp) + throws NotFoundException + { + int i = desc.indexOf(')'); + if (i < 0) + return null; + else { + CtClass[] type = new CtClass[1]; + toCtClass(cp, desc, i + 1, type, 0); + return type[0]; + } + } + + /** + * Returns the number of the prameters included in the given + * descriptor. + * + * @param desc descriptor + */ + public static int numOfParameters(String desc) { + int n = 0; + int i = 1; + for (;;) { + char c = desc.charAt(i); + if (c == ')') + break; + + while (c == '[') + c = desc.charAt(++i); + + if (c == 'L') { + i = desc.indexOf(';', i) + 1; + if (i <= 0) + throw new IndexOutOfBoundsException("bad descriptor"); + } + else + ++i; + + ++n; + } + + return n; + } + + /** + * Returns a <code>CtClass</code> object representing the type + * specified by the given descriptor. + * + * <p>This method works even if the package-class separator is + * not <code>/</code> but <code>.</code> (period). For example, + * it accepts <code>Ljava.lang.Object;</code> + * as well as <code>Ljava/lang/Object;</code>. + * + * @param desc descriptor + * @param cp the class pool used for obtaining + * a <code>CtClass</code> object. + */ + public static CtClass toCtClass(String desc, ClassPool cp) + throws NotFoundException + { + CtClass[] clazz = new CtClass[1]; + int res = toCtClass(cp, desc, 0, clazz, 0); + if (res >= 0) + return clazz[0]; + else { + // maybe, you forgot to surround the class name with + // L and ;. It violates the protocol, but I'm tolerant... + return cp.get(desc.replace('/', '.')); + } + } + + private static int toCtClass(ClassPool cp, String desc, int i, + CtClass[] args, int n) + throws NotFoundException + { + int i2; + String name; + + int arrayDim = 0; + char c = desc.charAt(i); + while (c == '[') { + ++arrayDim; + c = desc.charAt(++i); + } + + if (c == 'L') { + i2 = desc.indexOf(';', ++i); + name = desc.substring(i, i2++).replace('/', '.'); + } + else { + CtClass type = toPrimitiveClass(c); + if (type == null) + return -1; // error + + i2 = i + 1; + if (arrayDim == 0) { + args[n] = type; + return i2; // neither an array type or a class type + } + else + name = type.getName(); + } + + if (arrayDim > 0) { + StringBuffer sbuf = new StringBuffer(name); + while (arrayDim-- > 0) + sbuf.append("[]"); + + name = sbuf.toString(); + } + + args[n] = cp.get(name); + return i2; + } + + private static CtClass toPrimitiveClass(char c) { + CtClass type = null; + switch (c) { + case 'Z' : + type = CtClass.booleanType; + break; + case 'C' : + type = CtClass.charType; + break; + case 'B' : + type = CtClass.byteType; + break; + case 'S' : + type = CtClass.shortType; + break; + case 'I' : + type = CtClass.intType; + break; + case 'J' : + type = CtClass.longType; + break; + case 'F' : + type = CtClass.floatType; + break; + case 'D' : + type = CtClass.doubleType; + break; + case 'V' : + type = CtClass.voidType; + break; + } + + return type; + } + + /** + * Computes the data size specified by the given descriptor. + * For example, if the descriptor is "D", this method returns 2. + * + * <p>If the descriptor represents a method type, this method returns + * (the size of the returned value) - (the sum of the data sizes + * of all the parameters). For example, if the descriptor is + * "(I)D", then this method returns 1 (= 2 - 1). + * + * @param desc descriptor + */ + public static int dataSize(String desc) { + int n = 0; + char c = desc.charAt(0); + if (c == '(') { + int i = 1; + for (;;) { + c = desc.charAt(i); + if (c == ')') { + c = desc.charAt(i + 1); + break; + } + + boolean array = false; + while (c == '[') { + array = true; + c = desc.charAt(++i); + } + + if (c == 'L') { + i = desc.indexOf(';', i) + 1; + if (i <= 0) + throw new IndexOutOfBoundsException("bad descriptor"); + } + else + ++i; + + if (!array && (c == 'J' || c == 'D')) + n -= 2; + else + --n; + } + } + + if (c == 'J' || c == 'D') + n += 2; + else if (c != 'V') + ++n; + + return n; + } +} diff --git a/src/main/javassist/bytecode/ExceptionTable.java b/src/main/javassist/bytecode/ExceptionTable.java new file mode 100644 index 00000000..71baa3b5 --- /dev/null +++ b/src/main/javassist/bytecode/ExceptionTable.java @@ -0,0 +1,275 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Map; + +class ExceptionTableEntry { + int startPc; + int endPc; + int handlerPc; + int catchType; + + ExceptionTableEntry(int start, int end, int handle, int type) { + startPc = start; + endPc = end; + handlerPc = handle; + catchType = type; + } +} + +/** + * <code>exception_table[]</code> of <code>Code_attribute</code>. + */ +public class ExceptionTable { + private ConstPool constPool; + private ArrayList entries; + + /** + * Constructs an <code>exception_table[]</code>. + * + * @param cp constant pool table. + */ + public ExceptionTable(ConstPool cp) { + constPool = cp; + entries = new ArrayList(); + } + + ExceptionTable(ConstPool cp, DataInputStream in) throws IOException { + constPool = cp; + int length = in.readUnsignedShort(); + ArrayList list = new ArrayList(length); + for (int i = 0; i < length; ++i) { + int start = in.readUnsignedShort(); + int end = in.readUnsignedShort(); + int handle = in.readUnsignedShort(); + int type = in.readUnsignedShort(); + list.add(new ExceptionTableEntry(start, end, handle, type)); + } + + entries = list; + } + + /** + * Returns <code>exception_table_length</code>, which is the number + * of entries in the <code>exception_table[]</code>. + */ + public int size() { + return entries.size(); + } + + /** + * Returns <code>startPc</code> of the <i>n</i>-th entry. + * + * @param nth the <i>n</i>-th (>= 0). + */ + public int startPc(int nth) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth); + return e.startPc; + } + + /** + * Sets <code>startPc</code> of the <i>n</i>-th entry. + * + * @param nth the <i>n</i>-th (>= 0). + * @param value new value. + */ + public void setStartPc(int nth, int value) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth); + e.startPc = value; + } + + /** + * Returns <code>endPc</code> of the <i>n</i>-th entry. + * + * @param nth the <i>n</i>-th (>= 0). + */ + public int endPc(int nth) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth); + return e.endPc; + } + + /** + * Sets <code>endPc</code> of the <i>n</i>-th entry. + * + * @param nth the <i>n</i>-th (>= 0). + * @param value new value. + */ + public void setEndPc(int nth, int value) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth); + e.endPc = value; + } + + /** + * Returns <code>handlerPc</code> of the <i>n</i>-th entry. + * + * @param nth the <i>n</i>-th (>= 0). + */ + public int handlerPc(int nth) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth); + return e.handlerPc; + } + + /** + * Sets <code>handlerPc</code> of the <i>n</i>-th entry. + * + * @param nth the <i>n</i>-th (>= 0). + * @param value new value. + */ + public void setHandlerPc(int nth, int value) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth); + e.handlerPc = value; + } + + /** + * Returns <code>catchType</code> of the <i>n</i>-th entry. + * + * @param nth the <i>n</i>-th (>= 0). + */ + public int catchType(int nth) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth); + return e.catchType; + } + + /** + * Sets <code>catchType</code> of the <i>n</i>-th entry. + * + * @param nth the <i>n</i>-th (>= 0). + * @param value new value. + */ + public void setCatchType(int nth, int value) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth); + e.catchType = value; + } + + /** + * Copies the given exception table at the specified position + * in the table. + * + * @param index index (>= 0) at which the entry is to be inserted. + * @param offset the offset added to the code position. + */ + public void add(int index, ExceptionTable table, int offset) { + int len = table.size(); + while (--len >= 0) { + ExceptionTableEntry e + = (ExceptionTableEntry)table.entries.get(len); + add(index, e.startPc + offset, e.endPc + offset, + e.handlerPc + offset, e.catchType); + } + } + + /** + * Adds a new entry at the specified position in the table. + * + * @param index index (>= 0) at which the entry is to be inserted. + * @param start <code>startPc</code> + * @param end <code>endPc</code> + * @param handler <code>handlerPc</code> + * @param type <code>catchType</code> + */ + public void add(int index, int start, int end, int handler, int type) { + entries.add(index, + new ExceptionTableEntry(start, end, handler, type)); + } + + /** + * Appends a new entry at the end of the table. + * + * @param start <code>startPc</code> + * @param end <code>endPc</code> + * @param handler <code>handlerPc</code> + * @param type <code>catchType</code> + */ + public void add(int start, int end, int handler, int type) { + entries.add(new ExceptionTableEntry(start, end, handler, type)); + } + + /** + * Removes the entry at the specified position in the table. + * + * @param index the index of the removed entry. + */ + public void remove(int index) { + entries.remove(index); + } + + /** + * Makes a copy of this <code>exception_table[]</code>. + * Class names are replaced according to the + * given <code>Map</code> object. + * + * @param newCp the constant pool table used by the new copy. + * @param classnames pairs of replaced and substituted + * class names. + */ + public ExceptionTable copy(ConstPool newCp, Map classnames) { + ExceptionTable et = new ExceptionTable(newCp); + ConstPool srcCp = constPool; + int len = size(); + for (int i = 0; i < len; ++i) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(i); + int type = srcCp.copy(e.catchType, newCp, classnames); + et.add(e.startPc, e.endPc, e.handlerPc, type); + } + + return et; + } + + void shiftPc(int where, int gapLength, boolean exclusive) { + int len = size(); + for (int i = 0; i < len; ++i) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(i); + e.startPc = shiftPc(e.startPc, where, gapLength, exclusive); + e.endPc = shiftPc(e.endPc, where, gapLength, exclusive); + e.handlerPc = shiftPc(e.handlerPc, where, gapLength, exclusive); + } + } + + private static int shiftPc(int pc, int where, int gapLength, + boolean exclusive) { + if (pc > where || (exclusive && pc == where)) + pc += gapLength; + + return pc; + } + + void write(DataOutputStream out) throws IOException { + int len = size(); + out.writeShort(len); // exception_table_length + for (int i = 0; i < len; ++i) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(i); + out.writeShort(e.startPc); + out.writeShort(e.endPc); + out.writeShort(e.handlerPc); + out.writeShort(e.catchType); + } + } +} diff --git a/src/main/javassist/bytecode/ExceptionsAttribute.java b/src/main/javassist/bytecode/ExceptionsAttribute.java new file mode 100644 index 00000000..db304e71 --- /dev/null +++ b/src/main/javassist/bytecode/ExceptionsAttribute.java @@ -0,0 +1,183 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Map; + +/** + * <code>Exceptions_attribute</code>. + */ +public class ExceptionsAttribute extends AttributeInfo { + /** + * The name of this attribute <code>"Exceptions"</code>. + */ + public static final String tag = "Exceptions"; + + ExceptionsAttribute(ConstPool cp, int n, DataInputStream in) + throws IOException + { + super(cp, n, in); + } + + /** + * Constructs a copy of an exceptions attribute. + * + * @param cp constant pool table. + * @param src source attribute. + */ + private ExceptionsAttribute(ConstPool cp, ExceptionsAttribute src, + Map classnames) { + super(cp, tag); + copyFrom(src, classnames); + } + + /** + * Constructs a new exceptions attribute. + * + * @param cp constant pool table. + */ + public ExceptionsAttribute(ConstPool cp) { + super(cp, tag); + byte[] data = new byte[2]; + data[0] = data[1] = 0; // empty + this.info = data; + } + + /** + * Makes a copy. Class names are replaced according to the + * given <code>Map</code> object. + * + * @param newCp the constant pool table used by the new copy. + * @param classnames pairs of replaced and substituted + * class names. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) { + return new ExceptionsAttribute(newCp, this, classnames); + } + + /** + * Copies the contents from a source attribute. + * Specified class names are replaced during the copy. + * + * @param srcAttr source Exceptions attribute + * @param classnames pairs of replaced and substituted + * class names. + */ + private void copyFrom(ExceptionsAttribute srcAttr, Map classnames) { + ConstPool srcCp = srcAttr.constPool; + ConstPool destCp = this.constPool; + byte[] src = srcAttr.info; + int num = src.length; + byte[] dest = new byte[num]; + dest[0] = src[0]; + dest[1] = src[1]; // the number of elements. + for (int i = 2; i < num; i += 2) { + int index = ByteArray.readU16bit(src, i); + ByteArray.write16bit(srcCp.copy(index, destCp, classnames), + dest, i); + } + + this.info = dest; + } + + /** + * Returns <code>exception_index_table[]</code>. + */ + public int[] getExceptionIndexes() { + byte[] blist = info; + int n = blist.length; + if (n <= 2) + return null; + + int[] elist = new int[n / 2 - 1]; + int k = 0; + for (int j = 2; j < n; j += 2) + elist[k++] = ((blist[j] & 0xff) << 8) | (blist[j + 1] & 0xff); + + return elist; + } + + /** + * Returns the names of exceptions that the method may throw. + */ + public String[] getExceptions() { + byte[] blist = info; + int n = blist.length; + if (n <= 2) + return null; + + String[] elist = new String[n / 2 - 1]; + int k = 0; + for (int j = 2; j < n; j += 2) { + int index = ((blist[j] & 0xff) << 8) | (blist[j + 1] & 0xff); + elist[k++] = constPool.getClassInfo(index); + } + + return elist; + } + + /** + * Sets <code>exception_index_table[]</code>. + */ + public void setExceptionIndexes(int[] elist) { + int n = elist.length; + byte[] blist = new byte[n * 2 + 2]; + ByteArray.write16bit(n, blist, 0); + for (int i = 0; i < n; ++i) + ByteArray.write16bit(elist[i], blist, i * 2 + 2); + + info = blist; + } + + /** + * Sets the names of exceptions that the method may throw. + */ + public void setExceptions(String[] elist) { + int n = elist.length; + byte[] blist = new byte[n * 2 + 2]; + ByteArray.write16bit(n, blist, 0); + for (int i = 0; i < n; ++i) + ByteArray.write16bit(constPool.addClassInfo(elist[i]), + blist, i * 2 + 2); + + info = blist; + } + + /** + * Returns <code>number_of_exceptions</code>. + */ + public int length() { return info.length / 2 - 1; } + + /** + * Returns the value of <code>exception_index_table[nth]</code>. + */ + public int getException(int nth) { + int index = nth * 2 + 2; // nth >= 0 + return ((info[index] & 0xff) << 8) | (info[index + 1] & 0xff); + } +} diff --git a/src/main/javassist/bytecode/FieldInfo.java b/src/main/javassist/bytecode/FieldInfo.java new file mode 100644 index 00000000..d5e3591c --- /dev/null +++ b/src/main/javassist/bytecode/FieldInfo.java @@ -0,0 +1,185 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Hashtable; +import java.util.List; +import java.util.LinkedList; + +/** + * <code>field_info</code> structure. + * + * @see javassist.CtField#getFieldInfo() + */ +public final class FieldInfo { + ConstPool constPool; + int accessFlags; + int name; + int descriptor; + LinkedList attribute; // may be null. + + private FieldInfo(ConstPool cp) { + constPool = cp; + accessFlags = 0; + attribute = null; + } + + /** + * Constructs a <code>field_info</code> structure. + * + * @param cp a constant pool table + * @param fieldName field name + * @param desc field descriptor + * + * @see Descriptor + */ + public FieldInfo(ConstPool cp, String fieldName, String desc) { + this(cp); + name = cp.addUtf8Info(fieldName); + descriptor = cp.addUtf8Info(desc); + } + + FieldInfo(ConstPool cp, DataInputStream in) throws IOException { + this(cp); + read(in); + } + + /** + * Returns the constant pool table used + * by this <code>field_info</code>. + */ + public ConstPool getConstPool() { return constPool; } + + /** + * Returns the field name. + */ + public String getName() { + return constPool.getUtf8Info(name); + } + + /** + * Sets the field name. + */ + public void setName(String newName) { + name = constPool.addUtf8Info(newName); + } + + /** + * Returns the access flags. + * + * @see AccessFlag + */ + public int getAccessFlags() { + return accessFlags; + } + + /** + * Sets the access flags. + * + * @see AccessFlag + */ + public void setAccessFlags(int acc) { + accessFlags = acc; + } + + /** + * Returns the field descriptor. + * + * @see Descriptor + */ + public String getDescriptor() { + return constPool.getUtf8Info(descriptor); + } + + /** + * Sets the field descriptor. + * + * @see Descriptor + */ + public void setDescriptor(String desc) { + if (!desc.equals(getDescriptor())) + descriptor = constPool.addUtf8Info(desc); + } + + /** + * Returns all the attributes. + * + * @return a list of <code>AttributeInfo</code> objects. + * @see AttributeInfo + */ + public List getAttributes() { + if (attribute == null) + attribute = new LinkedList(); + + return attribute; + } + + /** + * Returns the attribute with the specified name. + * + * @param name attribute name + */ + public AttributeInfo getAttribute(String name) { + return AttributeInfo.lookup(attribute, name); + } + + /** + * Appends an attribute. If there is already an attribute with + * the same name, the new one substitutes for it. + */ + public void addAttribute(AttributeInfo info) { + if (attribute == null) + attribute = new LinkedList(); + + AttributeInfo.remove(attribute, info.getName()); + attribute.add(info); + } + + private void read(DataInputStream in) throws IOException { + accessFlags = in.readUnsignedShort(); + name = in.readUnsignedShort(); + descriptor = in.readUnsignedShort(); + int n = in.readUnsignedShort(); + attribute = new LinkedList(); + for (int i = 0; i < n; ++i) + attribute.add(AttributeInfo.read(constPool, in)); + } + + void write(DataOutputStream out) throws IOException { + out.writeShort(accessFlags); + out.writeShort(name); + out.writeShort(descriptor); + if (attribute == null) + out.writeShort(0); + else { + out.writeShort(attribute.size()); + AttributeInfo.writeAll(attribute, out); + } + } +} diff --git a/src/main/javassist/bytecode/InnerClassesAttribute.java b/src/main/javassist/bytecode/InnerClassesAttribute.java new file mode 100644 index 00000000..954a166b --- /dev/null +++ b/src/main/javassist/bytecode/InnerClassesAttribute.java @@ -0,0 +1,126 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.util.Map; +import java.io.IOException; + +/** + * <code>InnerClasses_attribute</code>. + */ +public class InnerClassesAttribute extends AttributeInfo { + /** + * The name of this attribute <code>"InnerClasses"</code>. + */ + public static final String tag = "InnerClasses"; + + InnerClassesAttribute(ConstPool cp, int n, DataInputStream in) + throws IOException + { + super(cp, n, in); + } + + private InnerClassesAttribute(ConstPool cp, byte[] info) { + super(cp, tag, info); + } + + /** + * Returns <code>number_of_classes</code>. + */ + public int length() { return ByteArray.readU16bit(get(), 0); } + + /** + * Returns <code>classes[nth].inner_class_info_index</code>. + */ + public int innerClass(int nth) { + return ByteArray.readU16bit(get(), nth * 8 + 2); + } + + /** + * Returns <code>classes[nth].outer_class_info_index</code>. + */ + public int outerClass(int nth) { + return ByteArray.readU16bit(get(), nth * 8 + 4); + } + + /** + * Returns <code>classes[nth].inner_name_index</code>. + */ + public int innerName(int nth) { + return ByteArray.readU16bit(get(), nth * 8 + 6); + } + + /** + * Returns <code>classes[nth].inner_class_access_flags</code>. + */ + public int accessFlags(int nth) { + return ByteArray.readU16bit(get(), nth * 8 + 8); + } + + /** + * Makes a copy. Class names are replaced according to the + * given <code>Map</code> object. + * + * @param newCp the constant pool table used by the new copy. + * @param classnames pairs of replaced and substituted + * class names. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) { + byte[] src = get(); + byte[] dest = new byte[src.length]; + ConstPool cp = getConstPool(); + InnerClassesAttribute attr = new InnerClassesAttribute(newCp, dest); + int n = ByteArray.readU16bit(src, 0); + ByteArray.write16bit(n, dest, 0); + int j = 2; + for (int i = 0; i < n; ++i) { + int innerClass = ByteArray.readU16bit(src, j); + int outerClass = ByteArray.readU16bit(src, j + 2); + int innerName = ByteArray.readU16bit(src, j + 4); + int innerAccess = ByteArray.readU16bit(src, j + 6); + + if (innerClass != 0) + innerClass = cp.copy(innerClass, newCp, classnames); + + ByteArray.write16bit(innerClass, dest, j); + + if (outerClass != 0) + outerClass = cp.copy(outerClass, newCp, classnames); + + ByteArray.write16bit(outerClass, dest, j + 2); + + if (innerName != 0) + innerName = cp.copy(innerName, newCp, classnames); + + ByteArray.write16bit(innerName, dest, j + 4); + ByteArray.write16bit(innerAccess, dest, j + 6); + j += 8; + } + + return attr; + } +} diff --git a/src/main/javassist/bytecode/LineNumberAttribute.java b/src/main/javassist/bytecode/LineNumberAttribute.java new file mode 100644 index 00000000..35f1a020 --- /dev/null +++ b/src/main/javassist/bytecode/LineNumberAttribute.java @@ -0,0 +1,131 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Map; + +/** + * <code>LineNumberTablec_attribute</code>. + */ +public class LineNumberAttribute extends AttributeInfo { + /** + * The name of this attribute <code>"LineNumberTable"</code>. + */ + public static final String tag = "LineNumberTable"; + + LineNumberAttribute(ConstPool cp, int n, DataInputStream in) + throws IOException + { + super(cp, n, in); + } + + private LineNumberAttribute(ConstPool cp, byte[] i) { + super(cp, tag, i); + } + + /** + * Returns <code>line_number_table_length</code>. + * This represents the number of entries in the table. + */ + public int tableLength() { + return ByteArray.readU16bit(info, 0); + } + + /** + * Returns <code>line_number_table[i].start_pc</code>. + * This represents the index into the code array at which the code + * for a new line in the original source file begins. + * + * @param i the i-th entry. + */ + public int startPc(int i) { + return ByteArray.readU16bit(info, i * 4 + 2); + } + + /** + * Returns <code>line_number_table[i].line_number</code>. + * This represents the corresponding line number in the original + * source file. + * + * @param i the i-th entry. + */ + public int lineNumber(int i) { + return ByteArray.readU16bit(info, i * 4 + 4); + } + + /** + * Returns the line number corresponding to the specified bytecode. + * + * @param pc the index into the code array. + */ + public int toLineNumber(int pc) { + int n = tableLength(); + int i = 0; + for (; i < n; ++i) + if (pc < startPc(i)) + if (i == 0) + return lineNumber(0); + else + break; + + return lineNumber(i - 1); + } + + /** + * Returns the index into the code array at which the code for + * the specified line begins. + * + * @param line the line number. + * @return -1 if the specified line is not found. + */ + public int toStartPc(int line) { + int n = tableLength(); + for (int i = 0; i < n; ++i) + if (line == lineNumber(i)) + return startPc(i); + + return -1; + } + + /** + * Makes a copy. + * + * @param newCp the constant pool table used by the new copy. + * @param classnames should be null. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) { + byte[] src = info; + int num = src.length; + byte[] dest = new byte[num]; + for (int i = 0; i < num; ++i) + dest[i] = src[i]; + + LineNumberAttribute attr = new LineNumberAttribute(newCp, dest); + return attr; + } +} diff --git a/src/main/javassist/bytecode/LongVector.java b/src/main/javassist/bytecode/LongVector.java new file mode 100644 index 00000000..86ba7938 --- /dev/null +++ b/src/main/javassist/bytecode/LongVector.java @@ -0,0 +1,90 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.bytecode; + +final class LongVector { + private int num; + private Object[] objects; + private LongVector next; + + public LongVector(int initialSize) { + num = 0; + objects = new Object[initialSize]; + next = null; + } + + public void addElement(Object obj) { + LongVector p = this; + while (p.next != null) + p = p.next; + + if (p.num < p.objects.length) + p.objects[p.num++] = obj; + else { + LongVector q = p.next = new LongVector(p.objects.length); + q.objects[q.num++] = obj; + } + } + + public int size() { + LongVector p = this; + int s = 0; + while (p != null) { + s += p.num; + p = p.next; + } + + return s; + } + + public Object elementAt(int i) { + LongVector p = this; + while (p != null) + if (i < p.num) + return p.objects[i]; + else { + i -= p.num; + p = p.next; + } + + return null; + } + +/* + public static void main(String [] args) { + LongVector v = new LongVector(4); + int i; + for (i = 0; i < 128; ++i) + v.addElement(new Integer(i)); + + System.out.println(v.size()); + for (i = 0; i < v.size(); ++i) { + System.out.print(v.elementAt(i)); + System.out.print(", "); + } + } +*/ +} diff --git a/src/main/javassist/bytecode/MethodInfo.java b/src/main/javassist/bytecode/MethodInfo.java new file mode 100644 index 00000000..567529a3 --- /dev/null +++ b/src/main/javassist/bytecode/MethodInfo.java @@ -0,0 +1,389 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Map; +import java.util.List; +import java.util.LinkedList; +import javassist.CannotCompileException; + +/** + * <code>method_info</code> structure. + * + * @see javassist.CtMethod#getMethodInfo() + * @see javassist.CtConstructor#getMethodInfo() + */ +public final class MethodInfo { + ConstPool constPool; + int accessFlags; + int name; + int descriptor; + LinkedList attribute; // may be null + + /** + * The name of constructors: <code><init></code>. + */ + public static final String nameInit = "<init>"; + + /** + * The name of class initializer (static initializer): + * <code><clinit></code>. + */ + public static final String nameClinit = "<clinit>"; + + private MethodInfo(ConstPool cp) { + constPool = cp; + attribute = null; + } + + /** + * Constructs a <code>method_info</code> structure. + * + * @param cp a constant pool table + * @param methodname method name + * @param desc method descriptor + * + * @see Descriptor + */ + public MethodInfo(ConstPool cp, String methodname, String desc) { + this(cp); + accessFlags = 0; + name = cp.addUtf8Info(methodname); + descriptor = constPool.addUtf8Info(desc); + } + + MethodInfo(ConstPool cp, DataInputStream in) throws IOException { + this(cp); + read(in); + } + + /** + * Constructs a copy of <code>method_info</code> structure. + * Class names appearing in the source <code>method_info</code> + * are renamed according to <code>classnameMap</code>. + * + * <p>Note: only <code>Code</code> and <code>Exceptions</code> + * attributes are copied from the source. The other attributes + * are ignored. + * + * @param cp a constant pool table + * @param methodname a method name + * @param src a source <code>method_info</code> + * @param classnameMap specifies pairs of replaced and substituted + * name. + * @see Descriptor + */ + public MethodInfo(ConstPool cp, String methodname, MethodInfo src, + Map classnameMap) throws BadBytecode + { + this(cp); + read(src, methodname, classnameMap); + } + + /** + * Returns a method name. + */ + public String getName() { + return constPool.getUtf8Info(name); + } + + /** + * Sets a method name. + */ + public void setName(String newName) { + name = constPool.addUtf8Info(newName); + } + + /** + * Returns true if this is not a constructor or a class initializer + * (static initializer). + */ + public boolean isMethod() { + String n = getName(); + return !n.equals(nameInit) && !n.equals(nameClinit); + } + + /** + * Returns a constant pool table used by this method. + */ + public ConstPool getConstPool() { return constPool; } + + /** + * Returns true if this is a constructor. + */ + public boolean isConstructor() { return getName().equals(nameInit); } + + /** + * Returns true if this is a class initializer (static initializer). + */ + public boolean isStaticInitializer() { + return getName().equals(nameClinit); + } + + /** + * Returns access flags. + * + * @see AccessFlag + */ + public int getAccessFlags() { + return accessFlags; + } + + /** + * Sets access flags. + * + * @see AccessFlag + */ + public void setAccessFlags(int acc) { + accessFlags = acc; + } + + /** + * Returns a method descriptor. + * + * @see Descriptor + */ + public String getDescriptor() { + return constPool.getUtf8Info(descriptor); + } + + /** + * Sets a method descriptor. + * + * @see Descriptor + */ + public void setDescriptor(String desc) { + if (!desc.equals(getDescriptor())) + descriptor = constPool.addUtf8Info(desc); + } + + /** + * Returns all the attributes. + * + * @return a list of <code>AttributeInfo</code> objects. + * @see AttributeInfo + */ + public List getAttributes() { + if (attribute == null) + attribute = new LinkedList(); + + return attribute; + } + + /** + * Returns the attribute with the specified name. + * If it is not found, this method returns null. + * + * @param name attribute name + * @return an <code>AttributeInfo</code> object or null. + */ + public AttributeInfo getAttribute(String name) { + return AttributeInfo.lookup(attribute, name); + } + + /** + * Appends an attribute. If there is already an attribute with + * the same name, the new one substitutes for it. + */ + public void addAttribute(AttributeInfo info) { + if (attribute == null) + attribute = new LinkedList(); + + AttributeInfo.remove(attribute, info.getName()); + attribute.add(info); + } + + /** + * Returns an Exceptions attribute. + * + * @return an Exceptions attribute + * or null if it is not specified. + */ + public ExceptionsAttribute getExceptionsAttribute() { + AttributeInfo info + = AttributeInfo.lookup(attribute, ExceptionsAttribute.class); + return (ExceptionsAttribute)info; + } + + /** + * Returns a Code attribute. + * + * @return a Code attribute + * or null if it is not specified. + */ + public CodeAttribute getCodeAttribute() { + AttributeInfo info + = AttributeInfo.lookup(attribute, CodeAttribute.class); + return (CodeAttribute)info; + } + + /** + * Removes an Exception attribute. + */ + public void removeExceptionsAttribute() { + AttributeInfo.remove(attribute, ExceptionsAttribute.class); + } + + /** + * Adds an Exception attribute. + * + * <p>The added attribute must share the same constant pool table + * as this <code>method_info</code> structure. + */ + public void setExceptionsAttribute(ExceptionsAttribute cattr) { + removeExceptionsAttribute(); + if (attribute == null) + attribute = new LinkedList(); + + attribute.add(cattr); + } + + /** + * Removes a Code attribute. + */ + public void removeCodeAttribute() { + AttributeInfo.remove(attribute, CodeAttribute.class); + } + + /** + * Adds a Code attribute. + * + * <p>The added attribute must share the same constant pool table + * as this <code>method_info</code> structure. + */ + public void setCodeAttribute(CodeAttribute cattr) { + removeCodeAttribute(); + if (attribute == null) + attribute = new LinkedList(); + + attribute.add(cattr); + } + + /** + * Returns the line number of the source line corresponding to the + * specified bytecode contained in this method. + * + * @param pos the position of the bytecode (>= 0). + * an index into the code array. + * @return -1 if this information is not available. + */ + public int getLineNumber(int pos) { + CodeAttribute ca = getCodeAttribute(); + if (ca == null) + return -1; + + LineNumberAttribute ainfo + = (LineNumberAttribute)ca.getAttribute(LineNumberAttribute.tag); + if (ainfo == null) + return -1; + + return ainfo.toLineNumber(pos); + } + + /** + * Changes a super constructor called by this constructor. + * + * <p>This method modifies a call to <code>super()</code>, + * which should be at the + * head of a constructor body, so that a constructor in a different + * super class is called. This method does not change actural + * parameters. Hence the new super class must have a constructor + * with the same signature as the original one. + * + * <p>This method should be called when the super class + * of the class declaring this method is changed. + * + * <p>This method does not perform anything unless this + * <code>MethodInfo</code> represents a constructor. + * + * @param superclass the new super class + */ + public void setSuperclass(String superclass) throws BadBytecode { + if (!isConstructor()) + return; + + CodeAttribute ca = getCodeAttribute(); + byte[] code = ca.getCode(); + CodeIterator iterator = ca.iterator(); + int pos = iterator.skipSuperConstructor(); + if (pos >= 0) { // not this() + ConstPool cp = constPool; + int mref = ByteArray.readU16bit(code, pos + 1); + int nt = cp.getMethodrefNameAndType(mref); + int sc = cp.addClassInfo(superclass); + int mref2 = cp.addMethodrefInfo(sc, nt); + ByteArray.write16bit(mref2, code, pos + 1); + } + } + + private void read(MethodInfo src, String methodname, Map classnames) + throws BadBytecode + { + ConstPool destCp = constPool; + accessFlags = src.accessFlags; + name = destCp.addUtf8Info(methodname); + + ConstPool srcCp = src.constPool; + String desc = srcCp.getUtf8Info(src.descriptor); + String desc2 = Descriptor.rename(desc, classnames); + descriptor = destCp.addUtf8Info(desc2); + + attribute = new LinkedList(); + ExceptionsAttribute eattr = src.getExceptionsAttribute(); + if (eattr != null) + attribute.add(eattr.copy(destCp, classnames)); + + CodeAttribute cattr = src.getCodeAttribute(); + if (cattr != null) + attribute.add(cattr.copy(destCp, classnames)); + } + + private void read(DataInputStream in) throws IOException { + accessFlags = in.readUnsignedShort(); + name = in.readUnsignedShort(); + descriptor = in.readUnsignedShort(); + int n = in.readUnsignedShort(); + attribute = new LinkedList(); + for (int i = 0; i < n; ++i) + attribute.add(AttributeInfo.read(constPool, in)); + } + + void write(DataOutputStream out) throws IOException { + out.writeShort(accessFlags); + out.writeShort(name); + out.writeShort(descriptor); + + if (attribute == null) + out.writeShort(0); + else { + out.writeShort(attribute.size()); + AttributeInfo.writeAll(attribute, out); + } + } +} diff --git a/src/main/javassist/bytecode/Mnemonic.java b/src/main/javassist/bytecode/Mnemonic.java new file mode 100644 index 00000000..5fa4fd5c --- /dev/null +++ b/src/main/javassist/bytecode/Mnemonic.java @@ -0,0 +1,251 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.bytecode; + +/** + * JVM Instruction Names. + * + * <p>This interface has been separated from javassist.bytecode.Opcode + * because typical bytecode translators do not use mnemonics. If this + * interface were merged with Opcode, extra memory would be unnecessary + * consumed. + * + * @see Opcode + */ +public interface Mnemonic { + + /** + * The instruction names (mnemonics) sorted by the opcode. + * The length of this array is 202 (jsr_w=201). + * + * <p>The value at index 186 is null since no instruction is + * assigned to 186. + */ + String[] OPCODE = { + "nop", /* 0*/ + "aconst_null", /* 1*/ + "iconst_m1", /* 2*/ + "iconst_0", /* 3*/ + "iconst_1", /* 4*/ + "iconst_2", /* 5*/ + "iconst_3", /* 6*/ + "iconst_4", /* 7*/ + "iconst_5", /* 8*/ + "lconst_0", /* 9*/ + "lconst_1", /* 10*/ + "fconst_0", /* 11*/ + "fconst_1", /* 12*/ + "fconst_2", /* 13*/ + "dconst_0", /* 14*/ + "dconst_1", /* 15*/ + "bipush", /* 16*/ + "sipush", /* 17*/ + "ldc", /* 18*/ + "ldc_w", /* 19*/ + "ldc2_w", /* 20*/ + "iload", /* 21*/ + "lload", /* 22*/ + "fload", /* 23*/ + "dload", /* 24*/ + "aload", /* 25*/ + "iload_0", /* 26*/ + "iload_1", /* 27*/ + "iload_2", /* 28*/ + "iload_3", /* 29*/ + "lload_0", /* 30*/ + "lload_1", /* 31*/ + "lload_2", /* 32*/ + "lload_3", /* 33*/ + "fload_0", /* 34*/ + "fload_1", /* 35*/ + "fload_2", /* 36*/ + "fload_3", /* 37*/ + "dload_0", /* 38*/ + "dload_1", /* 39*/ + "dload_2", /* 40*/ + "dload_3", /* 41*/ + "aload_0", /* 42*/ + "aload_1", /* 43*/ + "aload_2", /* 44*/ + "aload_3", /* 45*/ + "iaload", /* 46*/ + "laload", /* 47*/ + "faload", /* 48*/ + "daload", /* 49*/ + "aaload", /* 50*/ + "baload", /* 51*/ + "caload", /* 52*/ + "saload", /* 53*/ + "istore", /* 54*/ + "lstore", /* 55*/ + "fstore", /* 56*/ + "dstore", /* 57*/ + "astore", /* 58*/ + "istore_0", /* 59*/ + "istore_1", /* 60*/ + "istore_2", /* 61*/ + "istore_3", /* 62*/ + "lstore_0", /* 63*/ + "lstore_1", /* 64*/ + "lstore_2", /* 65*/ + "lstore_3", /* 66*/ + "fstore_0", /* 67*/ + "fstore_1", /* 68*/ + "fstore_2", /* 69*/ + "fstore_3", /* 70*/ + "dstore_0", /* 71*/ + "dstore_1", /* 72*/ + "dstore_2", /* 73*/ + "dstore_3", /* 74*/ + "astore_0", /* 75*/ + "astore_1", /* 76*/ + "astore_2", /* 77*/ + "astore_3", /* 78*/ + "iastore", /* 79*/ + "lastore", /* 80*/ + "fastore", /* 81*/ + "dastore", /* 82*/ + "aastore", /* 83*/ + "bastore", /* 84*/ + "castore", /* 85*/ + "sastore", /* 86*/ + "pop", /* 87*/ + "pop2", /* 88*/ + "dup", /* 89*/ + "dup_x1", /* 90*/ + "dup_x2", /* 91*/ + "dup2", /* 92*/ + "dup2_x1", /* 93*/ + "dup2_x2", /* 94*/ + "swap", /* 95*/ + "iadd", /* 96*/ + "ladd", /* 97*/ + "fadd", /* 98*/ + "dadd", /* 99*/ + "isub", /* 100*/ + "lsub", /* 101*/ + "fsub", /* 102*/ + "dsub", /* 103*/ + "imul", /* 104*/ + "lmul", /* 105*/ + "fmul", /* 106*/ + "dmul", /* 107*/ + "idiv", /* 108*/ + "ldiv", /* 109*/ + "fdiv", /* 110*/ + "ddiv", /* 111*/ + "irem", /* 112*/ + "lrem", /* 113*/ + "frem", /* 114*/ + "drem", /* 115*/ + "ineg", /* 116*/ + "lneg", /* 117*/ + "fneg", /* 118*/ + "dneg", /* 119*/ + "ishl", /* 120*/ + "lshl", /* 121*/ + "ishr", /* 122*/ + "lshr", /* 123*/ + "iushr", /* 124*/ + "lushr", /* 125*/ + "iand", /* 126*/ + "land", /* 127*/ + "ior", /* 128*/ + "lor", /* 129*/ + "ixor", /* 130*/ + "lxor", /* 131*/ + "iinc", /* 132*/ + "i2l", /* 133*/ + "i2f", /* 134*/ + "i2d", /* 135*/ + "l2i", /* 136*/ + "l2f", /* 137*/ + "l2d", /* 138*/ + "f2i", /* 139*/ + "f2l", /* 140*/ + "f2d", /* 141*/ + "d2i", /* 142*/ + "d2l", /* 143*/ + "d2f", /* 144*/ + "i2b", /* 145*/ + "i2c", /* 146*/ + "i2s", /* 147*/ + "lcmp", /* 148*/ + "fcmpl", /* 149*/ + "fcmpg", /* 150*/ + "dcmpl", /* 151*/ + "dcmpg", /* 152*/ + "ifeq", /* 153*/ + "ifne", /* 154*/ + "iflt", /* 155*/ + "ifge", /* 156*/ + "ifgt", /* 157*/ + "ifle", /* 158*/ + "if_icmpeq", /* 159*/ + "if_icmpne", /* 160*/ + "if_icmplt", /* 161*/ + "if_icmpge", /* 162*/ + "if_icmpgt", /* 163*/ + "if_icmple", /* 164*/ + "if_acmpeq", /* 165*/ + "if_acmpne", /* 166*/ + "goto", /* 167*/ + "jsr", /* 168*/ + "ret", /* 169*/ + "tableswitch", /* 170*/ + "lookupswitch", /* 171*/ + "ireturn", /* 172*/ + "lreturn", /* 173*/ + "freturn", /* 174*/ + "dreturn", /* 175*/ + "areturn", /* 176*/ + "return", /* 177*/ + "getstatic", /* 178*/ + "putstatic", /* 179*/ + "getfield", /* 180*/ + "putfield", /* 181*/ + "invokevirtual", /* 182*/ + "invokespecial", /* 183*/ + "invokestatic", /* 184*/ + "invokeinterface", /* 185*/ + null, + "new", /* 187*/ + "newarray", /* 188*/ + "anewarray", /* 189*/ + "arraylength", /* 190*/ + "athrow", /* 191*/ + "checkcast", /* 192*/ + "instanceof", /* 193*/ + "monitorenter", /* 194*/ + "monitorexit", /* 195*/ + "wide", /* 196*/ + "multianewarray", /* 197*/ + "ifnull", /* 198*/ + "ifnonnull", /* 199*/ + "goto_w", /* 200*/ + "jsr_w" /* 201*/ + }; +} diff --git a/src/main/javassist/bytecode/Opcode.java b/src/main/javassist/bytecode/Opcode.java new file mode 100644 index 00000000..e0c21b3c --- /dev/null +++ b/src/main/javassist/bytecode/Opcode.java @@ -0,0 +1,457 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.bytecode; + +/** + * JVM Instruction Set. + * + * <p>This interface defines opcodes and + * array types for the NEWARRAY instruction. + * + * @see Mnemonic + */ +public interface Opcode { + /* Opcodes */ + + int AALOAD = 50; + int AASTORE = 83; + int ACONST_NULL = 1; + int ALOAD = 25; + int ALOAD_0 = 42; + int ALOAD_1 = 43; + int ALOAD_2 = 44; + int ALOAD_3 = 45; + int ANEWARRAY = 189; + int ARETURN = 176; + int ARRAYLENGTH = 190; + int ASTORE = 58; + int ASTORE_0 = 75; + int ASTORE_1 = 76; + int ASTORE_2 = 77; + int ASTORE_3 = 78; + int ATHROW = 191; + int BALOAD = 51; + int BASTORE = 84; + int BIPUSH = 16; + int CALOAD = 52; + int CASTORE = 85; + int CHECKCAST = 192; + int D2F = 144; + int D2I = 142; + int D2L = 143; + int DADD = 99; + int DALOAD = 49; + int DASTORE = 82; + int DCMPG = 152; + int DCMPL = 151; + int DCONST_0 = 14; + int DCONST_1 = 15; + int DDIV = 111; + int DLOAD = 24; + int DLOAD_0 = 38; + int DLOAD_1 = 39; + int DLOAD_2 = 40; + int DLOAD_3 = 41; + int DMUL = 107; + int DNEG = 119; + int DREM = 115; + int DRETURN = 175; + int DSTORE = 57; + int DSTORE_0 = 71; + int DSTORE_1 = 72; + int DSTORE_2 = 73; + int DSTORE_3 = 74; + int DSUB = 103; + int DUP = 89; + int DUP2 = 92; + int DUP2_X1 = 93; + int DUP2_X2 = 94; + int DUP_X1 = 90; + int DUP_X2 = 91; + int F2D = 141; + int F2I = 139; + int F2L = 140; + int FADD = 98; + int FALOAD = 48; + int FASTORE = 81; + int FCMPG = 150; + int FCMPL = 149; + int FCONST_0 = 11; + int FCONST_1 = 12; + int FCONST_2 = 13; + int FDIV = 110; + int FLOAD = 23; + int FLOAD_0 = 34; + int FLOAD_1 = 35; + int FLOAD_2 = 36; + int FLOAD_3 = 37; + int FMUL = 106; + int FNEG = 118; + int FREM = 114; + int FRETURN = 174; + int FSTORE = 56; + int FSTORE_0 = 67; + int FSTORE_1 = 68; + int FSTORE_2 = 69; + int FSTORE_3 = 70; + int FSUB = 102; + int GETFIELD = 180; + int GETSTATIC = 178; + int GOTO = 167; + int GOTO_W = 200; + int I2B = 145; + int I2C = 146; + int I2D = 135; + int I2F = 134; + int I2L = 133; + int I2S = 147; + int IADD = 96; + int IALOAD = 46; + int IAND = 126; + int IASTORE = 79; + int ICONST_0 = 3; + int ICONST_1 = 4; + int ICONST_2 = 5; + int ICONST_3 = 6; + int ICONST_4 = 7; + int ICONST_5 = 8; + int ICONST_M1 = 2; + int IDIV = 108; + int IFEQ = 153; + int IFGE = 156; + int IFGT = 157; + int IFLE = 158; + int IFLT = 155; + int IFNE = 154; + int IFNONNULL = 199; + int IFNULL = 198; + int IF_ACMPEQ = 165; + int IF_ACMPNE = 166; + int IF_ICMPEQ = 159; + int IF_ICMPGE = 162; + int IF_ICMPGT = 163; + int IF_ICMPLE = 164; + int IF_ICMPLT = 161; + int IF_ICMPNE = 160; + int IINC = 132; + int ILOAD = 21; + int ILOAD_0 = 26; + int ILOAD_1 = 27; + int ILOAD_2 = 28; + int ILOAD_3 = 29; + int IMUL = 104; + int INEG = 116; + int INSTANCEOF = 193; + int INVOKEINTERFACE = 185; + int INVOKESPECIAL = 183; + int INVOKESTATIC = 184; + int INVOKEVIRTUAL = 182; + int IOR = 128; + int IREM = 112; + int IRETURN = 172; + int ISHL = 120; + int ISHR = 122; + int ISTORE = 54; + int ISTORE_0 = 59; + int ISTORE_1 = 60; + int ISTORE_2 = 61; + int ISTORE_3 = 62; + int ISUB = 100; + int IUSHR = 124; + int IXOR = 130; + int JSR = 168; + int JSR_W = 201; + int L2D = 138; + int L2F = 137; + int L2I = 136; + int LADD = 97; + int LALOAD = 47; + int LAND = 127; + int LASTORE = 80; + int LCMP = 148; + int LCONST_0 = 9; + int LCONST_1 = 10; + int LDC = 18; + int LDC2_W = 20; + int LDC_W = 19; + int LDIV = 109; + int LLOAD = 22; + int LLOAD_0 = 30; + int LLOAD_1 = 31; + int LLOAD_2 = 32; + int LLOAD_3 = 33; + int LMUL = 105; + int LNEG = 117; + int LOOKUPSWITCH = 171; + int LOR = 129; + int LREM = 113; + int LRETURN = 173; + int LSHL = 121; + int LSHR = 123; + int LSTORE = 55; + int LSTORE_0 = 63; + int LSTORE_1 = 64; + int LSTORE_2 = 65; + int LSTORE_3 = 66; + int LSUB = 101; + int LUSHR = 125; + int LXOR = 131; + int MONITORENTER = 194; + int MONITOREXIT = 195; + int MULTIANEWARRAY = 197; + int NEW = 187; + int NEWARRAY = 188; + int NOP = 0; + int POP = 87; + int POP2 = 88; + int PUTFIELD = 181; + int PUTSTATIC = 179; + int RET = 169; + int RETURN = 177; + int SALOAD = 53; + int SASTORE = 86; + int SIPUSH = 17; + int SWAP = 95; + int TABLESWITCH = 170; + int WIDE = 196; + + /* array-type code for the newarray instruction */ + + int T_BOOLEAN = 4; + int T_CHAR = 5; + int T_FLOAT = 6; + int T_DOUBLE = 7; + int T_BYTE = 8; + int T_SHORT = 9; + int T_INT = 10; + int T_LONG = 11; + + /* how many values are pushed on the operand stack. */ + int[] STACK_GROW = { + 0, // nop, 0 + 1, // aconst_null, 1 + 1, // iconst_m1, 2 + 1, // iconst_0, 3 + 1, // iconst_1, 4 + 1, // iconst_2, 5 + 1, // iconst_3, 6 + 1, // iconst_4, 7 + 1, // iconst_5, 8 + 2, // lconst_0, 9 + 2, // lconst_1, 10 + 1, // fconst_0, 11 + 1, // fconst_1, 12 + 1, // fconst_2, 13 + 2, // dconst_0, 14 + 2, // dconst_1, 15 + 1, // bipush, 16 + 1, // sipush, 17 + 1, // ldc, 18 + 1, // ldc_w, 19 + 2, // ldc2_w, 20 + 1, // iload, 21 + 2, // lload, 22 + 1, // fload, 23 + 2, // dload, 24 + 1, // aload, 25 + 1, // iload_0, 26 + 1, // iload_1, 27 + 1, // iload_2, 28 + 1, // iload_3, 29 + 2, // lload_0, 30 + 2, // lload_1, 31 + 2, // lload_2, 32 + 2, // lload_3, 33 + 1, // fload_0, 34 + 1, // fload_1, 35 + 1, // fload_2, 36 + 1, // fload_3, 37 + 2, // dload_0, 38 + 2, // dload_1, 39 + 2, // dload_2, 40 + 2, // dload_3, 41 + 1, // aload_0, 42 + 1, // aload_1, 43 + 1, // aload_2, 44 + 1, // aload_3, 45 + -1, // iaload, 46 + 0, // laload, 47 + -1, // faload, 48 + 0, // daload, 49 + -1, // aaload, 50 + -1, // baload, 51 + -1, // caload, 52 + -1, // saload, 53 + -1, // istore, 54 + -2, // lstore, 55 + -1, // fstore, 56 + -2, // dstore, 57 + -1, // astore, 58 + -1, // istore_0, 59 + -1, // istore_1, 60 + -1, // istore_2, 61 + -1, // istore_3, 62 + -2, // lstore_0, 63 + -2, // lstore_1, 64 + -2, // lstore_2, 65 + -2, // lstore_3, 66 + -1, // fstore_0, 67 + -1, // fstore_1, 68 + -1, // fstore_2, 69 + -1, // fstore_3, 70 + -2, // dstore_0, 71 + -2, // dstore_1, 72 + -2, // dstore_2, 73 + -2, // dstore_3, 74 + -1, // astore_0, 75 + -1, // astore_1, 76 + -1, // astore_2, 77 + -1, // astore_3, 78 + -3, // iastore, 79 + -4, // lastore, 80 + -3, // fastore, 81 + -4, // dastore, 82 + -3, // aastore, 83 + -3, // bastore, 84 + -3, // castore, 85 + -3, // sastore, 86 + -1, // pop, 87 + -2, // pop2, 88 + 1, // dup, 89 + 1, // dup_x1, 90 + 1, // dup_x2, 91 + 2, // dup2, 92 + 2, // dup2_x1, 93 + 2, // dup2_x2, 94 + 0, // swap, 95 + -1, // iadd, 96 + -2, // ladd, 97 + -1, // fadd, 98 + -2, // dadd, 99 + -1, // isub, 100 + -2, // lsub, 101 + -1, // fsub, 102 + -2, // dsub, 103 + -1, // imul, 104 + -2, // lmul, 105 + -1, // fmul, 106 + -2, // dmul, 107 + -1, // idiv, 108 + -2, // ldiv, 109 + -1, // fdiv, 110 + -2, // ddiv, 111 + -1, // irem, 112 + -2, // lrem, 113 + -1, // frem, 114 + -2, // drem, 115 + 0, // ineg, 116 + 0, // lneg, 117 + 0, // fneg, 118 + 0, // dneg, 119 + -1, // ishl, 120 + -1, // lshl, 121 + -1, // ishr, 122 + -1, // lshr, 123 + -1, // iushr, 124 + -1, // lushr, 125 + -1, // iand, 126 + -2, // land, 127 + -1, // ior, 128 + -2, // lor, 129 + -1, // ixor, 130 + -2, // lxor, 131 + 0, // iinc, 132 + 1, // i2l, 133 + 0, // i2f, 134 + 1, // i2d, 135 + -1, // l2i, 136 + -1, // l2f, 137 + 0, // l2d, 138 + 0, // f2i, 139 + 1, // f2l, 140 + 1, // f2d, 141 + -1, // d2i, 142 + 0, // d2l, 143 + -1, // d2f, 144 + 0, // i2b, 145 + 0, // i2c, 146 + 0, // i2s, 147 + -3, // lcmp, 148 + -1, // fcmpl, 149 + -1, // fcmpg, 150 + -3, // dcmpl, 151 + -3, // dcmpg, 152 + -1, // ifeq, 153 + -1, // ifne, 154 + -1, // iflt, 155 + -1, // ifge, 156 + -1, // ifgt, 157 + -1, // ifle, 158 + -2, // if_icmpeq, 159 + -2, // if_icmpne, 160 + -2, // if_icmplt, 161 + -2, // if_icmpge, 162 + -2, // if_icmpgt, 163 + -2, // if_icmple, 164 + -2, // if_acmpeq, 165 + -2, // if_acmpne, 166 + 0, // goto, 167 + 1, // jsr, 168 + 0, // ret, 169 + -1, // tableswitch, 170 + -1, // lookupswitch, 171 + -1, // ireturn, 172 + -2, // lreturn, 173 + -1, // freturn, 174 + -2, // dreturn, 175 + -1, // areturn, 176 + 0, // return, 177 + 0, // getstatic, 178 depends on the type + 0, // putstatic, 179 depends on the type + 0, // getfield, 180 depends on the type + 0, // putfield, 181 depends on the type + 0, // invokevirtual, 182 depends on the type + 0, // invokespecial, 183 depends on the type + 0, // invokestatic, 184 depends on the type + 0, // invokeinterface, 185 depends on the type + 0, // undefined, 186 + 1, // new, 187 + 0, // newarray, 188 + 0, // anewarray, 189 + 0, // arraylength, 190 + 0, // athrow, 191 stack is cleared + 0, // checkcast, 192 + 0, // instanceof, 193 + -1, // monitorenter, 194 + -1, // monitorexit, 195 + 0, // wide, 196 depends on the following opcode + 0, // multianewarray, 197 depends on the dimensions + -1, // ifnull, 198 + -1, // ifnonnull, 199 + 0, // goto_w, 200 + 1 // jsr_w, 201 + }; +} diff --git a/src/main/javassist/bytecode/SourceFileAttribute.java b/src/main/javassist/bytecode/SourceFileAttribute.java new file mode 100644 index 00000000..22e6f6f9 --- /dev/null +++ b/src/main/javassist/bytecode/SourceFileAttribute.java @@ -0,0 +1,80 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Map; + +/** + * <code>SourceFile_attribute</code>. + */ +public class SourceFileAttribute extends AttributeInfo { + /** + * The name of this attribute <code>"SourceFile"</code>. + */ + public static final String tag = "SourceFile"; + + SourceFileAttribute(ConstPool cp, int n, DataInputStream in) + throws IOException + { + super(cp, n, in); + } + + /** + * Constructs a SourceFile attribute. + * + * @param cp a constant pool table. + * @param filename the name of the source file. + */ + public SourceFileAttribute(ConstPool cp, String filename) { + super(cp, tag); + int index = cp.addUtf8Info(filename); + byte[] bvalue = new byte[2]; + bvalue[0] = (byte)(index >>> 8); + bvalue[1] = (byte)index; + set(bvalue); + } + + /** + * Returns the file name indicated by <code>sourcefile_index</code>. + */ + public String getFileName() { + return getConstPool().getUtf8Info(ByteArray.readU16bit(get(), 0)); + } + + /** + * Makes a copy. Class names are replaced according to the + * given <code>Map</code> object. + * + * @param newCp the constant pool table used by the new copy. + * @param classnames pairs of replaced and substituted + * class names. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) { + return new SourceFileAttribute(newCp, getFileName()); + } +} diff --git a/src/main/javassist/bytecode/SyntheticAttribute.java b/src/main/javassist/bytecode/SyntheticAttribute.java new file mode 100644 index 00000000..63d559ce --- /dev/null +++ b/src/main/javassist/bytecode/SyntheticAttribute.java @@ -0,0 +1,66 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Map; + +/** + * <code>Synthetic_attribute</code>. + */ +public class SyntheticAttribute extends AttributeInfo { + /** + * The name of this attribute <code>"Synthetic"</code>. + */ + public static final String tag = "Synthetic"; + + SyntheticAttribute(ConstPool cp, int n, DataInputStream in) + throws IOException + { + super(cp, n, in); + } + + /** + * Constructs a Synthetic attribute. + * + * @param cp a constant pool table. + * @param filename the name of the source file. + */ + public SyntheticAttribute(ConstPool cp) { + super(cp, tag, new byte[0]); + } + + /** + * Makes a copy. + * + * @param newCp the constant pool table used by the new copy. + * @param classnames should be null. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) { + return new SyntheticAttribute(newCp); + } +} diff --git a/src/main/javassist/compiler/CodeGen.java b/src/main/javassist/compiler/CodeGen.java new file mode 100644 index 00000000..9a5f6771 --- /dev/null +++ b/src/main/javassist/compiler/CodeGen.java @@ -0,0 +1,1559 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler; + +import java.util.ArrayList; +import javassist.compiler.ast.*; +import javassist.bytecode.*; + +/* The code generator is implemeted by three files: + * CodeGen.java, MemberCodeGen.java, and JvstCodeGen. + * I just wanted to split a big file into three smaller ones. + */ + +public abstract class CodeGen extends Visitor implements Opcode, TokenId { + static final String javaLangObject = "java.lang.Object"; + static final String jvmJavaLangObject = "java/lang/Object"; + + static final String javaLangString = "java.lang.String"; + static final String jvmJavaLangString = "java/lang/String"; + + protected Bytecode bytecode; + private int tempVar; + + /** + * Must be true if compilation is for a static method. + */ + public boolean inStaticMethod; + + protected ArrayList breakList, continueList; + + /* The following fields are used by atXXX() methods + * for returning the type of the compiled expression. + */ + protected int exprType; // VOID, NULL, CLASS, BOOLEAN, INT, ... + protected int arrayDim; + protected String className; // JVM-internal representation + + public CodeGen(Bytecode b) { + bytecode = b; + tempVar = -1; + inStaticMethod = false; + breakList = null; + continueList = null; + } + + protected static void fatal() throws CompileError { + throw new CompileError("fatal"); + } + + public static boolean is2word(int type, int dim) { + return dim == 0 && (type == DOUBLE || type == LONG); + } + + public int getMaxLocals() { return bytecode.getMaxLocals(); } + + public void setMaxLocals(int n) { + bytecode.setMaxLocals(n); + } + + protected void incMaxLocals(int size) { + bytecode.incMaxLocals(size); + } + + /** + * Returns a local variable that single or double words can be + * stored in. + */ + protected int getTempVar() { + if (tempVar < 0) { + tempVar = getMaxLocals(); + incMaxLocals(2); + } + + return tempVar; + } + + protected int getLocalVar(Declarator d) { + int v = d.getLocalVar(); + if (v < 0) { + v = getMaxLocals(); // delayed variable allocation. + d.setLocalVar(v); + incMaxLocals(1); + } + + return v; + } + + /** + * Returns the JVM-internal representation of this class name. + */ + protected abstract String getThisName(); + + /** + * Returns the JVM-internal representation of this super class name. + */ + protected abstract String getSuperName() throws CompileError; + + /* Converts a class name into a JVM-internal representation. + * + * It may also expand a simple class name to java.lang.*. + * For example, this converts Object into java/lang/Object. + */ + protected abstract String resolveClassName(ASTList name) + throws CompileError; + + /* Expands a simple class name to java.lang.*. + * For example, this converts Object into java/lang/Object. + */ + protected abstract String resolveClassName(String jvmClassName) + throws CompileError; + + /** + * @param name the JVM-internal representation. + * name is not exapnded to java.lang.*. + */ + protected static String toJvmArrayName(String name, int dim) { + if (name == null) + return null; + + if (dim == 0) + return name; + else { + StringBuffer sbuf = new StringBuffer(); + int d = dim; + while (d-- > 0) + sbuf.append('['); + + sbuf.append('L'); + sbuf.append(name); + sbuf.append(';'); + + return sbuf.toString(); + } + } + + protected static String toJvmTypeName(int type, int dim) { + char c = 'I'; + switch(type) { + case BOOLEAN : + c = 'Z'; + break; + case BYTE : + c = 'B'; + break; + case CHAR : + c = 'C'; + break; + case SHORT : + c = 'S'; + break; + case INT : + c = 'I'; + break; + case LONG : + c = 'J'; + break; + case FLOAT : + c = 'F'; + break; + case DOUBLE : + c = 'D'; + break; + case VOID : + c = 'V'; + break; + } + + StringBuffer sbuf = new StringBuffer(); + while (dim-- > 0) + sbuf.append('['); + + sbuf.append(c); + return sbuf.toString(); + } + + + public void atASTList(ASTList n) throws CompileError { fatal(); } + + public void atPair(Pair n) throws CompileError { fatal(); } + + public void atSymbol(Symbol n) throws CompileError { fatal(); } + + public void atFieldDecl(FieldDecl field) throws CompileError { + field.getInit().accept(this); + } + + public void atMethodDecl(MethodDecl method) throws CompileError { + ASTList mods = method.getModifiers(); + setMaxLocals(1); + while (mods != null) { + Keyword k = (Keyword)mods.head(); + mods = mods.tail(); + if (k.get() == STATIC) { + setMaxLocals(0); + inStaticMethod = true; + } + } + + ASTList params = method.getParams(); + while (params != null) { + atDeclarator((Declarator)params.head()); + params = params.tail(); + } + + Stmnt s = method.getBody(); + atMethodBody(s, method.isConstructor(), + method.getReturn().getType() == VOID); + } + + public void atMethodBody(Stmnt s, boolean isCons, boolean isVoid) + throws CompileError + { + if (s == null) + return; + + if (isCons && needsSuperCall(s)) + insertDefaultSuperCall(); + + s.accept(this); + if (isVoid + && (bytecode.read(bytecode.currentPc() - 1) & 0xff) + != Opcode.RETURN) { + bytecode.addOpcode(Opcode.RETURN); + } + } + + private boolean needsSuperCall(Stmnt body) { + if (body.getOperator() == BLOCK) { + Stmnt first = (Stmnt)body.head(); + if (first != null && first.getOperator() == EXPR) { + ASTree expr = first.head(); + if (expr != null && expr instanceof Expr + && ((Expr)expr).getOperator() == CALL) { + ASTree target = ((Expr)expr).head(); + if (target instanceof Keyword) { + int token = ((Keyword)target).get(); + return token != THIS && token != SUPER; + } + } + } + } + + return true; + } + + protected abstract void insertDefaultSuperCall() throws CompileError; + + public void atStmnt(Stmnt st) throws CompileError { + if (st == null) + return; // empty + + int op = st.getOperator(); + if (op == EXPR) { + ASTree expr = st.getLeft(); + if (expr instanceof AssignExpr) + atAssignExpr((AssignExpr)expr, false); + else if (isPlusPlusExpr(expr)) { + Expr e = (Expr)expr; + atPlusPlus(e.getOperator(), e.oprand1(), e, false); + } + else { + expr.accept(this); + if (is2word(exprType, arrayDim)) + bytecode.addOpcode(POP2); + else if (exprType != VOID) + bytecode.addOpcode(POP); + } + } + else if (op == DECL || op == BLOCK) { + ASTList list = st; + while (list != null) { + ASTree h = list.head(); + list = list.tail(); + if (h != null) + h.accept(this); + } + } + else if (op == IF) + atIfStmnt(st); + else if (op == WHILE || op == DO) + atWhileStmnt(st, op == WHILE); + else if (op == FOR) + atForStmnt(st); + else if (op == BREAK || op == CONTINUE) + atBreakStmnt(st, op == BREAK); + else if (op == TokenId.RETURN) + atReturnStmnt(st); + else if (op == THROW) + atThrowStmnt(st); + else if (op == TRY) + atTryStmnt(st); + else // LABEL, SWITCH label stament might be null?. + throw new CompileError( + "sorry, not supported statement: TokenId " + op); + } + + private void atIfStmnt(Stmnt st) throws CompileError { + ASTree expr = st.head(); + Stmnt thenp = (Stmnt)st.tail().head(); + Stmnt elsep = (Stmnt)st.tail().tail().head(); + booleanExpr(false, expr); + int pc = bytecode.currentPc(); + int pc2 = 0; + bytecode.addIndex(0); // correct later + + if (thenp != null) + thenp.accept(this); + + if (elsep != null) { + bytecode.addOpcode(Opcode.GOTO); + pc2 = bytecode.currentPc(); + bytecode.addIndex(0); + } + + bytecode.write16bit(pc, bytecode.currentPc() - pc + 1); + + if (elsep != null) { + elsep.accept(this); + bytecode.write16bit(pc2, bytecode.currentPc() - pc2 + 1); + } + } + + private void atWhileStmnt(Stmnt st, boolean notDo) throws CompileError { + ArrayList prevBreakList = breakList; + ArrayList prevContList = continueList; + breakList = new ArrayList(); + continueList = new ArrayList(); + + ASTree expr = st.head(); + Stmnt body = (Stmnt)st.tail(); + + int pc = 0; + if (notDo) { + bytecode.addOpcode(Opcode.GOTO); + pc = bytecode.currentPc(); + bytecode.addIndex(0); + } + + int pc2 = bytecode.currentPc(); + if (body != null) + body.accept(this); + + int pc3 = bytecode.currentPc(); + if (notDo) + bytecode.write16bit(pc, pc3 - pc + 1); + + booleanExpr(true, expr); + bytecode.addIndex(pc2 - bytecode.currentPc() + 1); + + patchGoto(breakList, bytecode.currentPc()); + patchGoto(continueList, pc3); + continueList = prevContList; + breakList = prevBreakList; + } + + private void patchGoto(ArrayList list, int targetPc) { + int n = list.size(); + for (int i = 0; i < n; ++i) { + int pc = ((Integer)list.get(i)).intValue(); + bytecode.write16bit(pc, targetPc - pc + 1); + } + } + + private void atForStmnt(Stmnt st) throws CompileError { + ArrayList prevBreakList = breakList; + ArrayList prevContList = continueList; + breakList = new ArrayList(); + continueList = new ArrayList(); + + Stmnt init = (Stmnt)st.head(); + ASTList p = st.tail(); + ASTree expr = p.head(); + p = p.tail(); + Stmnt update = (Stmnt)p.head(); + Stmnt body = (Stmnt)p.tail(); + + if (init != null) + init.accept(this); + + int pc = bytecode.currentPc(); + int pc2 = 0; + if (expr != null) { + booleanExpr(false, expr); + pc2 = bytecode.currentPc(); + bytecode.addIndex(0); + } + + if (body != null) + body.accept(this); + + int pc3 = bytecode.currentPc(); + if (update != null) + update.accept(this); + + bytecode.addOpcode(Opcode.GOTO); + bytecode.addIndex(pc - bytecode.currentPc() + 1); + + int pc4 = bytecode.currentPc(); + if (expr != null) + bytecode.write16bit(pc2, pc4 - pc2 + 1); + + patchGoto(breakList, pc4); + patchGoto(continueList, pc3); + continueList = prevContList; + breakList = prevBreakList; + } + + private void atBreakStmnt(Stmnt st, boolean notCont) + throws CompileError + { + if (st.head() != null) + throw new CompileError( + "sorry, not support labeled break or continue"); + + bytecode.addOpcode(Opcode.GOTO); + Integer pc = new Integer(bytecode.currentPc()); + bytecode.addIndex(0); + if (notCont) + breakList.add(pc); + else + continueList.add(pc); + } + + protected void atReturnStmnt(Stmnt st) throws CompileError { + atReturnStmnt2(st.getLeft()); + } + + protected final void atReturnStmnt2(ASTree result) throws CompileError { + int op; + if (result == null) + op = Opcode.RETURN; + else { + result.accept(this); + if (arrayDim > 0) + op = ARETURN; + else { + int type = exprType; + if (type == DOUBLE) + op = DRETURN; + else if (type == FLOAT) + op = FRETURN; + else if (type == LONG) + op = LRETURN; + else if (isRefType(type)) + op = ARETURN; + else + op = IRETURN; + } + } + + bytecode.addOpcode(op); + } + + private void atThrowStmnt(Stmnt st) throws CompileError { + ASTree e = st.getLeft(); + e.accept(this); + if (exprType != CLASS || arrayDim > 0) + throw new CompileError("bad throw statement"); + + bytecode.addOpcode(ATHROW); + } + + protected abstract void atTryStmnt(Stmnt st) throws CompileError; + + private static boolean isPlusPlusExpr(ASTree expr) { + if (expr instanceof Expr) { + int op = ((Expr)expr).getOperator(); + return op == PLUSPLUS || op == MINUSMINUS; + } + + return false; + } + + public void atDeclarator(Declarator d) throws CompileError { + d.setLocalVar(getMaxLocals()); + d.setClassName(resolveClassName(d.getClassName())); + + int size; + if (is2word(d.getType(), d.getArrayDim())) + size = 2; + else + size = 1; + + incMaxLocals(size); + + /* NOTE: Array initializers has not been supported. + */ + ASTree init = d.getInitializer(); + if (init != null) + atVariableAssign(null, '=', null, d, init, false); + } + + public abstract void atNewExpr(NewExpr n) throws CompileError; + + public void atAssignExpr(AssignExpr expr) throws CompileError { + atAssignExpr(expr, true); + } + + protected void atAssignExpr(AssignExpr expr, boolean doDup) + throws CompileError + { + // =, %=, &=, *=, /=, +=, -=, ^=, |=, <<=, >>=, >>>= + int op = expr.getOperator(); + ASTree left = expr.oprand1(); + ASTree right = expr.oprand2(); + if (left instanceof Variable) + atVariableAssign(expr, op, (Variable)left, + ((Variable)left).getDeclarator(), + right, doDup); + else { + if (left instanceof Expr) { + Expr e = (Expr)left; + if (e.getOperator() == ARRAY) { + atArrayAssign(expr, op, (Expr)left, right, doDup); + return; + } + } + + atFieldAssign(expr, op, left, right, doDup); + } + } + + protected static void badAssign(Expr expr) throws CompileError { + String msg; + if (expr == null) + msg = "incompatible type for assignment"; + else + msg = "incompatible type for " + expr.getName(); + + throw new CompileError(msg); + } + + /* op is either =, %=, &=, *=, /=, +=, -=, ^=, |=, <<=, >>=, or >>>=. + * + * expr and var can be null. + */ + private void atVariableAssign(Expr expr, int op, Variable var, + Declarator d, ASTree right, + boolean doDup) throws CompileError + { + int varType = d.getType(); + int varArray = d.getArrayDim(); + String varClass = d.getClassName(); + int varNo = getLocalVar(d); + + if (op != '=') + atVariable(var); + + atAssignCore(expr, op, right, varType, varArray, varClass); + + if (doDup) + if (is2word(varType, varArray)) + bytecode.addOpcode(DUP2); + else + bytecode.addOpcode(DUP); + + if (varArray > 0) + bytecode.addAstore(varNo); + else if (varType == DOUBLE) + bytecode.addDstore(varNo); + else if (varType == FLOAT) + bytecode.addFstore(varNo); + else if (varType == LONG) + bytecode.addLstore(varNo); + else if (isRefType(varType)) + bytecode.addAstore(varNo); + else + bytecode.addIstore(varNo); + + exprType = varType; + arrayDim = varArray; + className = varClass; + } + + private void atArrayAssign(Expr expr, int op, Expr array, + ASTree right, boolean doDup) throws CompileError + { + arrayAccess(array.oprand1(), array.oprand2()); + + if (op != '=') { + bytecode.addOpcode(DUP2); + bytecode.addOpcode(getArrayReadOp(exprType, arrayDim)); + } + + int aType = exprType; + int aDim = arrayDim; + String cname = className; + + atAssignCore(expr, op, right, aType, aDim, cname); + + if (doDup) + if (is2word(aType, aDim)) + bytecode.addOpcode(DUP2_X2); + else + bytecode.addOpcode(DUP_X2); + + bytecode.addOpcode(getArrayWriteOp(aType, aDim)); + exprType = aType; + arrayDim = aDim; + className = cname; + } + + protected abstract void atFieldAssign(Expr expr, int op, ASTree left, + ASTree right, boolean doDup) throws CompileError; + + protected void atAssignCore(Expr expr, int op, ASTree right, + int type, int dim, String cname) + throws CompileError + { + right.accept(this); + if (invalidDim(exprType, arrayDim, className, type, dim, cname, false) + || (op != '=' && dim > 0)) + badAssign(expr); + + if (op == PLUS_E && dim == 0 && type == CLASS) + atStringConcatExpr(expr, type, dim, cname); + else if (op != '=') { + int token = assignOps[op - MOD_E]; + int k = lookupBinOp(token); + if (k < 0) + fatal(); + + atArithBinExpr(expr, token, k, type); + } + + if (op != '=' || (dim == 0 && !isRefType(type))) + atNumCastExpr(exprType, type); + + // type check should be done here. + } + + private boolean invalidDim(int srcType, int srcDim, String srcClass, + int destType, int destDim, String destClass, + boolean isCast) + { + if (srcDim != destDim) + if (srcType == NULL) + return false; + else if (destDim == 0 && destType == CLASS + && jvmJavaLangObject.equals(destClass)) + return false; + else if (isCast && srcDim == 0 && srcType == CLASS + && jvmJavaLangObject.equals(srcClass)) + return false; + else + return true; + + return false; + } + + public void atCondExpr(CondExpr expr) throws CompileError { + booleanExpr(false, expr.condExpr()); + int pc = bytecode.currentPc(); + bytecode.addIndex(0); // correct later + expr.thenExpr().accept(this); + bytecode.addOpcode(Opcode.GOTO); + int pc2 = bytecode.currentPc(); + bytecode.addIndex(0); + bytecode.write16bit(pc, bytecode.currentPc() - pc + 1); + expr.elseExpr().accept(this); + bytecode.write16bit(pc2, bytecode.currentPc() - pc2 + 1); + } + + private final int[] binOp = { + '+', DADD, FADD, LADD, IADD, + '-', DSUB, FSUB, LSUB, ISUB, + '*', DMUL, FMUL, LMUL, IMUL, + '/', DDIV, FDIV, LDIV, IDIV, + '%', DREM, FREM, LREM, IREM, + '|', NOP, NOP, LOR, IOR, + '^', NOP, NOP, LXOR, IXOR, + '&', NOP, NOP, LAND, IAND, + LSHIFT, NOP, NOP, LSHL, ISHL, + RSHIFT, NOP, NOP, LSHR, ISHR, + ARSHIFT, NOP, NOP, LUSHR, IUSHR }; + + private int lookupBinOp(int token) { + int[] code = binOp; + int s = code.length; + for (int k = 0; k < s; k = k + 5) + if (code[k] == token) + return k; + + return -1; + } + + public void atBinExpr(BinExpr expr) throws CompileError { + int token = expr.getOperator(); + + /* arithmetic operators: +, -, *, /, %, |, ^, &, <<, >>, >>> + */ + int k = lookupBinOp(token); + if (k >= 0) { + expr.oprand1().accept(this); + int type1 = exprType; + int dim1 = arrayDim; + String cname1 = className; + expr.oprand2().accept(this); + if (dim1 != arrayDim) + throw new CompileError("incompatible array types"); + + if (token == '+' && dim1 == 0 + && (type1 == CLASS || exprType == CLASS)) + atStringConcatExpr(expr, type1, dim1, cname1); + else + atArithBinExpr(expr, token, k, type1); + + return; + } + + /* equation: &&, ||, ==, !=, <=, >=, <, > + */ + booleanExpr(true, expr); + bytecode.addIndex(7); + bytecode.addIconst(0); // false + bytecode.addOpcode(Opcode.GOTO); + bytecode.addIndex(4); + bytecode.addIconst(1); // true + } + + /* arrayDim values of the two oprands must be equal. + * If an oprand type is not a numeric type, this method + * throws an exception. + */ + private void atArithBinExpr(Expr expr, int token, + int index, int type1) throws CompileError + { + if (arrayDim != 0) + badTypes(expr); + + int type2 = exprType; + if (token == LSHIFT || token == RSHIFT || token == ARSHIFT) + if (type2 == INT || type2 == SHORT + || type2 == CHAR || type2 == BYTE) + exprType = type1; + else + badTypes(expr); + else + convertOprandTypes(type1, type2, expr); + + int p = typePrecedence(exprType); + if (p >= 0) { + int op = binOp[index + p + 1]; + if (op != NOP) { + if (p == P_INT) + exprType = INT; // type1 may be BYTE, ... + + bytecode.addOpcode(op); + return; + } + } + + badTypes(expr); + } + + private void atStringConcatExpr(Expr expr, int type1, int dim1, + String cname1) throws CompileError + { + int type2 = exprType; + int dim2 = arrayDim; + boolean type2Is2 = is2word(type2, dim2); + boolean type2IsString + = (type2 == CLASS && jvmJavaLangString.equals(className)); + + if (type2Is2) + convToString(type2, dim2); + + if (is2word(type1, dim1)) { + bytecode.addOpcode(DUP_X2); + bytecode.addOpcode(POP); + } + else + bytecode.addOpcode(SWAP); + + convToString(type1, dim1); + bytecode.addOpcode(SWAP); + + if (!type2Is2 && !type2IsString) + convToString(type2, dim2); + + bytecode.addInvokevirtual(javaLangString, "concat", + "(Ljava/lang/String;)Ljava/lang/String;"); + exprType = CLASS; + arrayDim = 0; + className = jvmJavaLangString; + } + + private void convToString(int type, int dim) throws CompileError { + final String method = "valueOf"; + + if (isRefType(type) || dim > 0) + bytecode.addInvokestatic(javaLangString, method, + "(Ljava/lang/Object;)Ljava/lang/String;"); + else if (type == DOUBLE) + bytecode.addInvokestatic(javaLangString, method, + "(D)Ljava/lang/String;"); + else if (type == FLOAT) + bytecode.addInvokestatic(javaLangString, method, + "(F)Ljava/lang/String;"); + else if (type == LONG) + bytecode.addInvokestatic(javaLangString, method, + "(J)Ljava/lang/String;"); + else if (type == BOOLEAN) + bytecode.addInvokestatic(javaLangString, method, + "(Z)Ljava/lang/String;"); + else if (type == CHAR) + bytecode.addInvokestatic(javaLangString, method, + "(C)Ljava/lang/String;"); + else if (type == VOID) + throw new CompileError("void type expression"); + else /* INT, BYTE, SHORT */ + bytecode.addInvokestatic(javaLangString, method, + "(I)Ljava/lang/String;"); + } + + /* Produces the opcode to branch if the condition is true. + * The oprand is not produced. + * + * If false is returned, the branch occurs if the condition is + * false. + */ + private void booleanExpr(boolean branchIf, ASTree expr) + throws CompileError + { + boolean isAndAnd; + int op = getCompOperator(expr); + if (op == EQ) { // ==, !=, ... + BinExpr bexpr = (BinExpr)expr; + int type1 = compileOprands(bexpr); + compareExpr(branchIf, bexpr.getOperator(), type1, bexpr); + } + else if (op == '!') + booleanExpr(!branchIf, ((Expr)expr).oprand1()); + else if ((isAndAnd = (op == ANDAND)) || op == OROR) { + BinExpr bexpr = (BinExpr)expr; + booleanExpr(!isAndAnd, bexpr.oprand1()); + int pc = bytecode.currentPc(); + bytecode.addIndex(0); // correct later + + booleanExpr(isAndAnd, bexpr.oprand2()); + bytecode.write16bit(pc, bytecode.currentPc() - pc + 3); + if (branchIf != isAndAnd) { + bytecode.addIndex(6); // skip GOTO instruction + bytecode.addOpcode(Opcode.GOTO); + } + } + else { // others + expr.accept(this); + bytecode.addOpcode(branchIf ? IFNE : IFEQ); + } + + exprType = BOOLEAN; + arrayDim = 0; + } + + private static int getCompOperator(ASTree expr) throws CompileError { + if (expr instanceof Expr) { + Expr bexpr = (Expr)expr; + int token = bexpr.getOperator(); + if (token == '!') + return '!'; + else if ((bexpr instanceof BinExpr) + && token != OROR && token != ANDAND + && token != '&' && token != '|') + return EQ; // ==, !=, ... + else + return token; + } + + return ' '; // others + } + + private int compileOprands(BinExpr expr) throws CompileError { + expr.oprand1().accept(this); + int type1 = exprType; + int dim1 = arrayDim; + expr.oprand2().accept(this); + if (dim1 != arrayDim) + throw new CompileError("incompatible array types"); + + return type1; + } + + private final int ifOp[] = { EQ, IF_ICMPEQ, IF_ICMPNE, + NEQ, IF_ICMPNE, IF_ICMPEQ, + LE, IF_ICMPLE, IF_ICMPGT, + GE, IF_ICMPGE, IF_ICMPLT, + '<', IF_ICMPLT, IF_ICMPGE, + '>', IF_ICMPGT, IF_ICMPLE }; + + private final int ifOp2[] = { EQ, IFEQ, IFNE, + NEQ, IFNE, IFEQ, + LE, IFLE, IFGT, + GE, IFGE, IFLT, + '<', IFLT, IFGE, + '>', IFGT, IFLE }; + + /* Produces the opcode to branch if the condition is true. + * The oprands are not produced. + * + * Parameter expr - compare expression ==, !=, <=, >=, <, > + */ + private void compareExpr(boolean branchIf, + int token, int type1, BinExpr expr) + throws CompileError + { + if (arrayDim == 0) + convertOprandTypes(type1, exprType, expr); + + int p = typePrecedence(exprType); + if (p == P_OTHER || arrayDim > 0) + if (token == EQ) + bytecode.addOpcode(branchIf ? IF_ACMPEQ : IF_ACMPNE); + else if (token == NEQ) + bytecode.addOpcode(branchIf ? IF_ACMPNE : IF_ACMPEQ); + else + badTypes(expr); + else + if (p == P_INT) { + int op[] = ifOp; + for (int i = 0; i < op.length; i += 3) + if (op[i] == token) { + bytecode.addOpcode(op[i + (branchIf ? 1 : 2)]); + return; + } + + badTypes(expr); + } + else { + if (p == P_DOUBLE) + if (token == '<' || token == LE) + bytecode.addOpcode(DCMPG); + else + bytecode.addOpcode(DCMPL); + else if (p == P_FLOAT) + if (token == '<' || token == LE) + bytecode.addOpcode(FCMPG); + else + bytecode.addOpcode(FCMPL); + else if (p == P_LONG) + bytecode.addOpcode(LCMP); // 1: >, 0: =, -1: < + else + fatal(); + + int[] op = ifOp2; + for (int i = 0; i < op.length; i += 3) + if (op[i] == token) { + bytecode.addOpcode(op[i + (branchIf ? 1 : 2)]); + return; + } + + badTypes(expr); + } + } + + protected static void badTypes(Expr expr) throws CompileError { + throw new CompileError("invalid types for " + expr.getName()); + } + + private static final int P_DOUBLE = 0; + private static final int P_FLOAT = 1; + private static final int P_LONG = 2; + private static final int P_INT = 3; + private static final int P_OTHER = -1; + + protected static boolean isRefType(int type) { + return type == CLASS || type == NULL; + } + + private static int typePrecedence(int type) { + if (type == DOUBLE) + return P_DOUBLE; + else if (type == FLOAT) + return P_FLOAT; + else if (type == LONG) + return P_LONG; + else if (isRefType(type)) + return P_OTHER; + else if (type == VOID) + return P_OTHER; // this is wrong, but ... + else + return P_INT; // BOOLEAN, BYTE, CHAR, SHORT, INT + } + + private static final int[] castOp = { + /* D F L I */ + /* double */ NOP, D2F, D2L, D2I, + /* float */ F2D, NOP, F2L, F2I, + /* long */ L2D, L2F, NOP, L2I, + /* other */ I2D, I2F, I2L, NOP }; + + /* do implicit type conversion. + * arrayDim values of the two oprands must be zero. + */ + private void convertOprandTypes(int type1, int type2, Expr expr) + throws CompileError + { + boolean rightStrong; + int type1_p = typePrecedence(type1); + int type2_p = typePrecedence(type2); + + if (type2_p < 0 && type1_p < 0) // not primitive types + return; + + if (type2_p < 0 || type1_p < 0) // either is not a primitive type + badTypes(expr); + + int op, result_type; + if (type1_p <= type2_p) { + rightStrong = false; + exprType = type1; + op = castOp[type2_p * 4 + type1_p]; + result_type = type1_p; + } + else { + rightStrong = true; + op = castOp[type1_p * 4 + type2_p]; + result_type = type2_p; + } + + if (rightStrong) { + if (result_type == P_DOUBLE || result_type == P_LONG) { + if (type1_p == P_DOUBLE || type1_p == P_LONG) + bytecode.addOpcode(DUP2_X2); + else + bytecode.addOpcode(DUP2_X1); + + bytecode.addOpcode(POP2); + bytecode.addOpcode(op); + bytecode.addOpcode(DUP2_X2); + bytecode.addOpcode(POP2); + } + else if (result_type == P_FLOAT) { + bytecode.addOpcode(SWAP); + bytecode.addOpcode(op); + bytecode.addOpcode(SWAP); + } + else + fatal(); + } + else if (op != NOP) + bytecode.addOpcode(op); + } + + public void atCastExpr(CastExpr expr) throws CompileError { + String cname = resolveClassName(expr.getClassName()); + String toClass = checkCastExpr(expr, cname); + int srcType = exprType; + exprType = expr.getType(); + arrayDim = expr.getArrayDim(); + className = cname; + if (toClass == null) + atNumCastExpr(srcType, exprType); // built-in type + else + bytecode.addCheckcast(toClass); + } + + public void atInstanceOfExpr(InstanceOfExpr expr) throws CompileError { + String cname = resolveClassName(expr.getClassName()); + String toClass = checkCastExpr(expr, cname); + bytecode.addInstanceof(toClass); + exprType = BOOLEAN; + arrayDim = 0; + } + + private String checkCastExpr(CastExpr expr, String name) + throws CompileError + { + final String msg = "invalid cast"; + ASTree oprand = expr.getOprand(); + int dim = expr.getArrayDim(); + int type = expr.getType(); + oprand.accept(this); + int srcType = exprType; + if (invalidDim(srcType, arrayDim, className, type, dim, name, true) + || srcType == VOID || type == VOID) + throw new CompileError(msg); + + if (type == CLASS) { + if (!isRefType(srcType)) + throw new CompileError(msg); + + return toJvmArrayName(name, dim); + } + else + if (dim > 0) + return toJvmTypeName(type, dim); + else + return null; // built-in type + } + + void atNumCastExpr(int srcType, int destType) + throws CompileError + { + if (srcType == destType) + return; + + int op, op2; + int stype = typePrecedence(srcType); + int dtype = typePrecedence(destType); + if (0 <= stype && stype < 3) + op = castOp[stype * 4 + dtype]; + else + op = NOP; + + if (destType == DOUBLE) + op2 = I2D; + else if (destType == FLOAT) + op2 = I2F; + else if (destType == LONG) + op2 = I2L; + else if (destType == SHORT) + op2 = I2S; + else if (destType == CHAR) + op2 = I2C; + else if (destType == BYTE) + op2 = I2B; + else + op2 = NOP; + + if (op != NOP) + bytecode.addOpcode(op); + + if (op == NOP || op == L2I || op == F2I || op == D2I) + if (op2 != NOP) + bytecode.addOpcode(op2); + } + + public void atExpr(Expr expr) throws CompileError { + // method call, array access, member access, + // (unary) +, (unary) -, ++, --, !, ~ + + int token = expr.getOperator(); + ASTree oprand = expr.oprand1(); + if (token == CALL) // method call + atMethodCall(expr); + else if (token == '.') + if (((Symbol)expr.oprand2()).get().equals("length")) + atArrayLength(expr); + else + atFieldRead(expr); + else if (token == MEMBER) { // field read + if (!atClassObject(expr)) // .class + atFieldRead(expr); + } + else if (token == ARRAY) + atArrayRead(oprand, expr.oprand2()); + else if (token == PLUSPLUS || token == MINUSMINUS) + atPlusPlus(token, oprand, expr, true); + else if (token == '!') { + booleanExpr(false, expr); + bytecode.addIndex(7); + bytecode.addIconst(1); + bytecode.addOpcode(Opcode.GOTO); + bytecode.addIndex(4); + bytecode.addIconst(0); + } + else { + expr.oprand1().accept(this); + int type = typePrecedence(exprType); + if (arrayDim > 0) + badType(expr); + + if (token == '-') { + if (type == P_DOUBLE) + bytecode.addOpcode(DNEG); + else if (type == P_FLOAT) + bytecode.addOpcode(FNEG); + else if (type == P_LONG) + bytecode.addOpcode(LNEG); + else if (type == P_INT) { + bytecode.addOpcode(INEG); + exprType = INT; // type may be BYTE, ... + } + else + badType(expr); + } + else if (token == '~') { + if (type == P_INT) { + bytecode.addIconst(-1); + bytecode.addOpcode(IXOR); + exprType = INT; // type may be BYTE. ... + } + else if (type == P_LONG) { + bytecode.addLconst(-1); + bytecode.addOpcode(LXOR); + } + else + badType(expr); + + } + else if (token == '+') { + if (type == P_OTHER) + badType(expr); + + // do nothing. ignore. + } + else + fatal(); + } + } + + protected static void badType(Expr expr) throws CompileError { + throw new CompileError("invalid type for " + expr.getName()); + } + + protected abstract void atMethodCall(Expr expr) throws CompileError; + + protected abstract void atFieldRead(ASTree expr) throws CompileError; + + public boolean atClassObject(Expr expr) throws CompileError { + if (!((Symbol)expr.oprand2()).get().equals("class")) + return false; + + if (resolveClassName((ASTList)expr.oprand1()) == null) + return false; + + throw new CompileError(".class is not supported"); + } + + public void atArrayLength(Expr expr) throws CompileError { + expr.oprand1().accept(this); + if (arrayDim == 0) + throw new CompileError(".length applied to a non array"); + + bytecode.addOpcode(ARRAYLENGTH); + exprType = INT; + arrayDim = 0; + } + + public void atArrayRead(ASTree array, ASTree index) + throws CompileError + { + int op; + arrayAccess(array, index); + bytecode.addOpcode(getArrayReadOp(exprType, arrayDim)); + } + + protected void arrayAccess(ASTree array, ASTree index) + throws CompileError + { + array.accept(this); + int type = exprType; + int dim = arrayDim; + if (dim == 0) + throw new CompileError("bad array access"); + + String cname = className; + + index.accept(this); + if (typePrecedence(exprType) != P_INT || arrayDim > 0) + throw new CompileError("bad array index"); + + exprType = type; + arrayDim = dim - 1; + className = cname; + } + + protected static int getArrayReadOp(int type, int dim) { + int op; + if (dim > 0) + return AALOAD; + + switch (type) { + case DOUBLE : + return DALOAD; + case FLOAT : + return FALOAD; + case LONG : + return LALOAD; + case INT : + return IALOAD; + case SHORT : + return SALOAD; + case CHAR : + return CALOAD; + case BYTE : + case BOOLEAN : + return BALOAD; + default : + return AALOAD; + } + } + + protected static int getArrayWriteOp(int type, int dim) { + int op; + if (dim > 0) + return AASTORE; + + switch (type) { + case DOUBLE : + return DASTORE; + case FLOAT : + return FASTORE; + case LONG : + return LASTORE; + case INT : + return IASTORE; + case CHAR : + return CASTORE; + case BYTE : + case BOOLEAN : + return BASTORE; + default : + return AASTORE; + } + } + + private void atPlusPlus(int token, ASTree oprand, Expr expr, + boolean doDup) throws CompileError + { + boolean isPost = oprand == null; // ++i or i++? + if (isPost) + oprand = expr.oprand2(); + + if (oprand instanceof Variable) { + Declarator d = ((Variable)oprand).getDeclarator(); + int t = exprType = d.getType(); + arrayDim = d.getArrayDim(); + int var = getLocalVar(d); + if (arrayDim > 0) + badType(expr); + + if (t == DOUBLE) { + bytecode.addDload(var); + if (doDup && isPost) + bytecode.addOpcode(DUP2); + + bytecode.addDconst(1.0); + bytecode.addOpcode(token == PLUSPLUS ? DADD : DSUB); + if (doDup && !isPost) + bytecode.addOpcode(DUP2); + + bytecode.addDstore(var); + } + else if (t == LONG) { + bytecode.addLload(var); + if (doDup && isPost) + bytecode.addOpcode(DUP2); + + bytecode.addLconst((long)1); + bytecode.addOpcode(token == PLUSPLUS ? LADD : LSUB); + if (doDup && !isPost) + bytecode.addOpcode(DUP2); + + bytecode.addLstore(var); + } + else if (t == FLOAT) { + bytecode.addFload(var); + if (doDup && isPost) + bytecode.addOpcode(DUP); + + bytecode.addFconst(1.0f); + bytecode.addOpcode(token == PLUSPLUS ? FADD : FSUB); + if (doDup && !isPost) + bytecode.addOpcode(DUP); + + bytecode.addFstore(var); + } + else if (t == BYTE || t == CHAR || t == SHORT || t == INT) { + if (doDup && isPost) + bytecode.addIload(var); + + bytecode.addOpcode(IINC); + bytecode.add(var); + bytecode.add(token == PLUSPLUS ? 1 : -1); + + if (doDup && !isPost) + bytecode.addIload(var); + } + else + badType(expr); + } + else { + if (oprand instanceof Expr) { + Expr e = (Expr)oprand; + if (e.getOperator() == ARRAY) { + atArrayPlusPlus(token, isPost, e, doDup); + return; + } + } + + atFieldPlusPlus(token, isPost, oprand, expr, doDup); + } + } + + public void atArrayPlusPlus(int token, boolean isPost, + Expr expr, boolean doDup) throws CompileError + { + arrayAccess(expr.oprand1(), expr.oprand2()); + int t = exprType; + int dim = arrayDim; + if (dim > 0) + badType(expr); + + bytecode.addOpcode(DUP2); + bytecode.addOpcode(getArrayReadOp(t, arrayDim)); + int dup_code = is2word(t, dim) ? DUP2_X2 : DUP_X2; + atPlusPlusCore(dup_code, doDup, token, isPost, expr); + bytecode.addOpcode(getArrayWriteOp(t, dim)); + } + + protected void atPlusPlusCore(int dup_code, boolean doDup, + int token, boolean isPost, + Expr expr) throws CompileError + { + int t = exprType; + + if (doDup && isPost) + bytecode.addOpcode(dup_code); + + if (t == INT || t == BYTE || t == CHAR || t == SHORT) { + bytecode.addIconst(1); + bytecode.addOpcode(token == PLUSPLUS ? IADD : ISUB); + exprType = INT; + } + else if (t == LONG) { + bytecode.addLconst((long)1); + bytecode.addOpcode(token == PLUSPLUS ? LADD : LSUB); + } + else if (t == FLOAT) { + bytecode.addFconst(1.0f); + bytecode.addOpcode(token == PLUSPLUS ? FADD : FSUB); + } + else if (t == DOUBLE) { + bytecode.addDconst(1.0); + bytecode.addOpcode(token == PLUSPLUS ? DADD : DSUB); + } + else + badType(expr); + + if (doDup && !isPost) + bytecode.addOpcode(dup_code); + } + + protected abstract void atFieldPlusPlus(int token, boolean isPost, + ASTree oprand, Expr expr, boolean doDup) throws CompileError; + + public abstract void atMember(Member n) throws CompileError; + + public void atVariable(Variable v) throws CompileError { + Declarator d = v.getDeclarator(); + exprType = d.getType(); + arrayDim = d.getArrayDim(); + className = d.getClassName(); + int var = getLocalVar(d); + + if (arrayDim > 0) + bytecode.addAload(var); + else + switch (exprType) { + case CLASS : + bytecode.addAload(var); + break; + case LONG : + bytecode.addLload(var); + break; + case FLOAT : + bytecode.addFload(var); + break; + case DOUBLE : + bytecode.addDload(var); + break; + default : // BOOLEAN, BYTE, CHAR, SHORT, INT + bytecode.addIload(var); + break; + } + } + + public void atKeyword(Keyword k) throws CompileError { + arrayDim = 0; + int token = k.get(); + switch (token) { + case TRUE : + bytecode.addIconst(1); + exprType = BOOLEAN; + break; + case FALSE : + bytecode.addIconst(0); + exprType = BOOLEAN; + break; + case NULL : + bytecode.addOpcode(ACONST_NULL); + exprType = NULL; + break; + case THIS : + case SUPER : + if (inStaticMethod) + throw new CompileError("not-available: " + + (token == THIS ? "this" : "super")); + + bytecode.addAload(0); + exprType = CLASS; + if (token == THIS) + className = getThisName(); + else + className = getSuperName(); + break; + default : + fatal(); + } + } + + public void atStringL(StringL s) throws CompileError { + exprType = CLASS; + arrayDim = 0; + className = "java/lang/String"; + bytecode.addLdc(s.get()); + } + + public void atIntConst(IntConst i) throws CompileError { + arrayDim = 0; + long value = i.get(); + int type = i.getType(); + if (type == IntConstant || type == CharConstant) { + exprType = (type == IntConstant ? INT : CHAR); + bytecode.addIconst((int)value); + } + else { + exprType = LONG; + bytecode.addLconst(value); + } + } + + public void atDoubleConst(DoubleConst d) throws CompileError { + arrayDim = 0; + if (d.getType() == DoubleConstant) { + exprType = DOUBLE; + bytecode.addDconst(d.get()); + } + else { + exprType = FLOAT; + bytecode.addFconst((float)d.get()); + } + } +} diff --git a/src/main/javassist/compiler/CompileError.java b/src/main/javassist/compiler/CompileError.java new file mode 100644 index 00000000..98b97ef5 --- /dev/null +++ b/src/main/javassist/compiler/CompileError.java @@ -0,0 +1,49 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler; + +public class CompileError extends Exception { + private Lex lex; + private String reason; + + public CompileError(String s, Lex l) { + reason = s; + lex = l; + } + + public CompileError(String s) { + reason = s; + lex = null; + } + + public String getMessage() { + return reason; + } + + public String toString() { + return "compile error: " + reason; + } +} diff --git a/src/main/javassist/compiler/Javac.java b/src/main/javassist/compiler/Javac.java new file mode 100644 index 00000000..8f15ca81 --- /dev/null +++ b/src/main/javassist/compiler/Javac.java @@ -0,0 +1,380 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler; + +import javassist.CtClass; +import javassist.CtMember; +import javassist.CtField; +import javassist.CtBehavior; +import javassist.CtMethod; +import javassist.CtConstructor; +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.Modifier; +import javassist.bytecode.Bytecode; +import javassist.NotFoundException; + +import javassist.compiler.ast.*; + +public class Javac { + JvstCodeGen gen; + SymbolTable stable; + private Bytecode bytecode; + + public static final String param0Name = "$0"; + public static final String resultVarName = "$_"; + public static final String proceedName = "$proceed"; + + /** + * Constructs a compiler. + * + * @param thisClass the class that a compiled method/field + * belongs to. + */ + public Javac(CtClass thisClass) { + this(new Bytecode(thisClass.getClassFile2().getConstPool(), 0, 0), + thisClass); + } + + /** + * Constructs a compiler. + * The produced bytecode is stored in the <code>Bytecode</code> object + * specified by <code>b</code>. + * + * @param thisClass the class that a compiled method/field + * belongs to. + */ + public Javac(Bytecode b, CtClass thisClass) { + gen = new JvstCodeGen(b, thisClass, thisClass.getClassPool()); + stable = new SymbolTable(); + bytecode = b; + } + + /** + * Returns the produced bytecode. + */ + public Bytecode getBytecode() { return bytecode; } + + /** + * Compiles a method, constructor, or field declaration + * to a class. + * A field declaration can declare only one field. + * + * <p>In a method or constructor body, $0, $1, ... and $_ + * are not available. + * + * @return a <code>CtMethod</code>, <code>CtConstructor</code>, + * or <code>CtField</code> object. + * @see #recordProceed(String,String) + */ + public CtMember compile(String src) throws CompileError { + Parser p = new Parser(new Lex(src)); + ASTList mem = p.parseMember1(stable); + try { + if (mem instanceof FieldDecl) + return compileField((FieldDecl)mem); + else + return compileMethod(p, (MethodDecl)mem); + } + catch (CannotCompileException e) { + throw new CompileError(e.getMessage()); + } + } + + public static class CtFieldWithInit extends CtField { + private ASTree init; + + CtFieldWithInit(CtClass type, String name, CtClass declaring) + throws CannotCompileException + { + super(type, name, declaring); + init = null; + } + + protected void setInit(ASTree i) { init = i; } + + protected ASTree getInitAST() { + return init; + } + } + + private CtField compileField(FieldDecl fd) + throws CompileError, CannotCompileException + { + CtFieldWithInit f; + Declarator d = fd.getDeclarator(); + f = new CtFieldWithInit(gen.lookupClass(d), d.getVariable().get(), + gen.getThisClass()); + f.setModifiers(gen.getModifiers(fd.getModifiers())); + if (fd.getInit() != null) + f.setInit(fd.getInit()); + + return f; + } + + private CtMember compileMethod(Parser p, MethodDecl md) + throws CompileError + { + int mod = gen.getModifiers(md.getModifiers()); + CtClass[] plist = gen.makeParamList(md); + CtClass[] tlist = gen.makeThrowsList(md); + recordParams(plist, Modifier.isStatic(mod)); + md = p.parseMethod2(stable, md); + try { + if (md.isConstructor()) { + CtConstructor cons = new CtConstructor(plist, + gen.getThisClass()); + cons.setModifiers(mod); + md.accept(gen); + cons.getMethodInfo().setCodeAttribute( + bytecode.toCodeAttribute()); + cons.setExceptionTypes(tlist); + return cons; + } + else { + Declarator r = md.getReturn(); + CtClass rtype = gen.lookupClass(r); + recordReturnType(rtype, false); + CtMethod method = new CtMethod(rtype, r.getVariable().get(), + plist, gen.getThisClass()); + method.setModifiers(mod); + gen.setThisMethod(method); + md.accept(gen); + if (md.getBody() != null) + method.getMethodInfo().setCodeAttribute( + bytecode.toCodeAttribute()); + else + method.setModifiers(mod | Modifier.ABSTRACT); + + method.setExceptionTypes(tlist); + return method; + } + } + catch (NotFoundException e) { + throw new CompileError(e.toString()); + } + } + + /** + * Compiles a method (or constructor) body. + */ + public Bytecode compileBody(CtBehavior method, String src) + throws CompileError + { + try { + int mod = method.getModifiers(); + recordParams(method.getParameterTypes(), Modifier.isStatic(mod)); + + CtClass rtype; + if (method instanceof CtMethod) { + gen.setThisMethod((CtMethod)method); + rtype = ((CtMethod)method).getReturnType(); + } + else + rtype = CtClass.voidType; + + recordReturnType(rtype, false); + boolean isVoid = rtype == CtClass.voidType; + + Parser p = new Parser(new Lex(src)); + SymbolTable stb = new SymbolTable(stable); + Stmnt s = p.parseStatement(stb); + gen.atMethodBody(s, method instanceof CtConstructor, isVoid); + return bytecode; + } + catch (NotFoundException e) { + throw new CompileError(e.toString()); + } + } + + /** + * Makes variables $0 (this), $1, $2, ..., and $args represent method + * parameters. $args represents an array of all the parameters. + * It also makes $$ available as a parameter list of method call. + * + * <p>This must be called before calling <code>compileStmnt()</code> and + * <code>compileExpr()</code>. The correct value of + * <code>isStatic</code> must be recorded before compilation. + */ + public void recordParams(CtClass[] params, boolean isStatic) + throws CompileError + { + gen.recordParams(params, isStatic, "$", "$args", "$$", stable); + } + + /** + * Makes variables $0, $1, $2, ..., and $args represent method + * parameters. $args represents an array of all the parameters. + * It also makes $$ available as a parameter list of method call. + * $0 can represent a local variable other than THIS (variable 0). + * + * <p>This must be called before calling <code>compileStmnt()</code> and + * <code>compileExpr()</code>. The correct value of + * <code>isStatic</code> must be recorded before compilation. + * + * @paaram use0 true if $0 is used. + * @param varNo the register number of $0 (use0 is true) + * or $1 (otherwise). + * @param target the type of $0 (it can be null if use0 is false). + * @param isStatic true if the method in which the compiled bytecode + * is embedded is static. + */ + public void recordParams(String target, CtClass[] params, + boolean use0, int varNo, boolean isStatic) + throws CompileError + { + gen.recordParams(params, isStatic, "$", "$args", "$$", + use0, varNo, target, stable); + } + + /** + * Prepares to use cast $r, $w, $_, and $type. + * It also enables to write a return statement with a return value + * for void method. + * + * <p>If the return type is void, ($r) does nothing. + * The type of $_ is java.lang.Object. + * + * @param useResultVar true if $_ is used. + * @return -1 or the variable index assigned to $_. + */ + public int recordReturnType(CtClass type, boolean useResultVar) + throws CompileError + { + gen.recordType(type); + return gen.recordReturnType(type, "$r", + (useResultVar ? resultVarName : null), stable); + } + + /** + * Prepares to use $type. Note that recordReturnType() overwrites + * the value of $type. + */ + public void recordType(CtClass t) { + gen.recordType(t); + } + + /** + * Makes the given variable available. + * + * @param type variable type + * @param name variable name + */ + public int recordVariable(CtClass type, String name) + throws CompileError + { + return gen.recordVariable(type, name, stable); + } + + /** + * Prepares to use $proceed(). + * If the return type of $proceed() is void, null is pushed on the + * stack. + * + * @param target an expression specifying the target object. + * if null, "this" is the target. + * @param method the method name. + */ + public void recordProceed(String target, String method) + throws CompileError + { + Parser p = new Parser(new Lex(target)); + final ASTree texpr = p.parseExpression(stable); + final String m = method; + + ProceedHandler h = new ProceedHandler() { + public void doit(JvstCodeGen gen, Bytecode b, ASTList args) + throws CompileError + { + ASTree expr = new Member(m); + if (texpr != null) + expr = Expr.make('.', texpr, expr); + + expr = Expr.make(TokenId.CALL, expr, args); + expr.accept(gen); + gen.addNullIfVoid(); + } + }; + + gen.setProceedHandler(h, proceedName); + } + + /** + * Prepares to use $proceed(). + */ + public void recordProceed(ProceedHandler h) { + gen.setProceedHandler(h, proceedName); + } + + /** + * Compiles a statement (or a block). + * <code>recordParams()</code> must be called before invoking + * this method. + * + * <p>Local variables that are not declared + * in the compiled source text are not accessible within that + * source text. Fields and method parameters + * ($0, $1, ..) are available. + */ + public void compileStmnt(String src) throws CompileError { + Parser p = new Parser(new Lex(src)); + SymbolTable stb = new SymbolTable(stable); + // while (p.hasMore()) { + Stmnt s = p.parseStatement(stb); + if (s != null) + s.accept(gen); + // } + } + + /** + * Compiles an exression. <code>recordParams()</code> must be + * called before invoking this method. + * + * <p>Local variables are not accessible + * within the compiled source text. Fields and method parameters + * ($0, $1, ..) are available if <code>recordParams()</code> + * have been invoked. + */ + public void compileExpr(String src) throws CompileError { + Parser p = new Parser(new Lex(src)); + ASTree e = p.parseExpression(stable); + compileExpr(e); + } + + /** + * Compiles an exression. <code>recordParams()</code> must be + * called before invoking this method. + * + * <p>Local variables are not accessible + * within the compiled source text. Fields and method parameters + * ($0, $1, ..) are available if <code>recordParams()</code> + * have been invoked. + */ + public void compileExpr(ASTree e) throws CompileError { + if (e != null) + e.accept(gen); + } +} diff --git a/src/main/javassist/compiler/JvstCodeGen.java b/src/main/javassist/compiler/JvstCodeGen.java new file mode 100644 index 00000000..6a365e16 --- /dev/null +++ b/src/main/javassist/compiler/JvstCodeGen.java @@ -0,0 +1,651 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler; + +import javassist.*; +import javassist.bytecode.*; +import javassist.compiler.ast.*; + +/* Code generator methods for extensions by Javassist. + */ +public class JvstCodeGen extends MemberCodeGen { + private String paramArrayName = null; + private String paramListName = null; + private CtClass[] paramTypeList = null; + private int paramVarBase = 0; // variable index for $0 or $1. + private boolean useParam0 = false; // true if $0 is used. + private String param0Type = null; // JVM name + private static final String sigName = "$sig"; + private static final String dollarTypeName = "$type"; + private static final String clazzName = "$class"; + private CtClass dollarType = null; + private CtClass returnType = null; + private String returnCastName = null; + private String returnVarName = null; // null if $_ is not used. + private static final String wrapperCastName = "$w"; + private String proceedName = null; + private static final String cflowName = "$cflow"; + private ProceedHandler procHandler = null; // null if not used. + + public JvstCodeGen(Bytecode b, CtClass cc, ClassPool cp) { + super(b, cc, cp); + } + + /* Index of $1. + */ + private int indexOfParam1() { + return paramVarBase + (useParam0 ? 1 : 0); + } + + /* Records a ProceedHandler obejct. + * + * @param name the name of the special method call. + * it is usually $proceed. + */ + public void setProceedHandler(ProceedHandler h, String name) { + proceedName = name; + procHandler = h; + } + + /* If the type of the expression compiled last is void, + * add ACONST_NULL and change exprType, arrayDim, className. + */ + public void addNullIfVoid() { + if (exprType == VOID) { + bytecode.addOpcode(ACONST_NULL); + exprType = CLASS; + arrayDim = 0; + className = jvmJavaLangObject; + } + } + + /* To support $args, $sig, and $type. + * $args is an array of parameter list. + */ + public void atMember(Member mem) throws CompileError { + String name = mem.get(); + if (name.equals(paramArrayName)) { + compileParameterList(bytecode, paramTypeList, indexOfParam1()); + exprType = CLASS; + arrayDim = 1; + className = jvmJavaLangObject; + } + else if (name.equals(sigName)) { + bytecode.addLdc(Descriptor.ofMethod(returnType, paramTypeList)); + bytecode.addInvokestatic("javassist/runtime/Desc", "getParams", + "(Ljava/lang/String;)[Ljava/lang/Class;"); + exprType = CLASS; + arrayDim = 1; + className = "java/lang/Class"; + } + else if (name.equals(dollarTypeName)) { + if (dollarType == null) + throw new CompileError(dollarType + " is not available"); + + bytecode.addLdc(Descriptor.of(dollarType)); + callGetType("getType"); + } + else if (name.equals(clazzName)) { + if (param0Type == null) + throw new CompileError(clazzName + " is not available"); + + bytecode.addLdc(param0Type); + callGetType("getClazz"); + } + else + super.atMember(mem); + } + + private void callGetType(String method) { + bytecode.addInvokestatic("javassist/runtime/Desc", method, + "(Ljava/lang/String;)Ljava/lang/Class;"); + exprType = CLASS; + arrayDim = 0; + className = "java/lang/Class"; + } + + private void atSigOrType(String sig) throws CompileError { + } + + protected void atFieldAssign(Expr expr, int op, ASTree left, + ASTree right, boolean doDup) throws CompileError + { + if (left instanceof Member + && ((Member)left).get().equals(paramArrayName)) { + if (op != '=') + throw new CompileError("bad operator for " + paramArrayName); + + right.accept(this); + if (arrayDim != 1 || exprType != CLASS) + throw new CompileError("invalid type for " + paramArrayName); + + atAssignParamList(paramTypeList, bytecode); + if (!doDup) + bytecode.addOpcode(POP); + } + else + super.atFieldAssign(expr, op, left, right, doDup); + } + + protected void atAssignParamList(CtClass[] params, Bytecode code) + throws CompileError + { + if (params == null) + return; + + int varNo = indexOfParam1(); + int n = params.length; + for (int i = 0; i < n; ++i) { + code.addOpcode(DUP); + code.addIconst(i); + code.addOpcode(AALOAD); + compileUnwrapValue(params[i], code); + code.addStore(varNo, params[i]); + varNo += is2word(exprType, arrayDim) ? 2 : 1; + } + } + + public void atCastExpr(CastExpr expr) throws CompileError { + ASTList classname = expr.getClassName(); + if (classname != null && expr.getArrayDim() == 0) { + ASTree p = classname.head(); + if (p instanceof Symbol && classname.tail() == null) { + String typename = ((Symbol)p).get(); + if (typename.equals(returnCastName)) { + atCastToRtype(expr); + return; + } + else if (typename.equals(wrapperCastName)) { + atCastToWrapper(expr); + return; + } + } + } + + super.atCastExpr(expr); + } + + /** + * Inserts a cast operator to the return type. + * If the return type is void, this does nothing. + */ + protected void atCastToRtype(CastExpr expr) throws CompileError { + expr.getOprand().accept(this); + if (!isRefType(exprType) || arrayDim > 0) + throw new CompileError("invalid type for " + returnCastName); + + compileUnwrapValue(returnType, bytecode); + } + + protected void atCastToWrapper(CastExpr expr) throws CompileError { + expr.getOprand().accept(this); + if (isRefType(exprType) || arrayDim > 0) + return; // Object type. do nothing. + + CtClass clazz = lookupClass(exprType, arrayDim, className); + if (clazz instanceof CtPrimitiveType) { + CtPrimitiveType pt = (CtPrimitiveType)clazz; + String wrapper = pt.getWrapperName(); + bytecode.addNew(wrapper); // new <wrapper> + bytecode.addOpcode(DUP); // dup + if (pt.getDataSize() > 1) + bytecode.addOpcode(DUP2_X2); // dup2_x2 + else + bytecode.addOpcode(DUP2_X1); // dup2_x1 + + bytecode.addOpcode(POP2); // pop2 + bytecode.addInvokespecial(wrapper, "<init>", + "(" + pt.getDescriptor() + ")V"); + // invokespecial + exprType = CLASS; + arrayDim = 0; + className = jvmJavaLangObject; + } + } + + /* Delegates to a ProcHandler object if the method call is + * $proceed(). It may process $cflow(). + */ + protected void atMethodCall(Expr expr) throws CompileError { + ASTree method = expr.oprand1(); + if (method instanceof Member) { + String name = ((Member)method).get(); + if (procHandler != null && name.equals(proceedName)) { + procHandler.doit(this, bytecode, (ASTList)expr.oprand2()); + return; + } + else if (name.equals(cflowName)) { + atCflow((ASTList)expr.oprand2()); + return; + } + } + + super.atMethodCall(expr); + } + + /* To support $cflow(). + */ + protected void atCflow(ASTList cname) throws CompileError { + StringBuffer sbuf = new StringBuffer(); + if (cname == null || cname.tail() != null) + throw new CompileError("bad " + cflowName); + + makeCflowName(sbuf, cname.head()); + String name = sbuf.toString(); + Object[] names = classPool.lookupCflow(name); + if (names == null) + throw new CompileError("no such a " + cflowName + ": " + name); + + bytecode.addGetstatic((String)names[0], (String)names[1], + "Ljavassist/runtime/Cflow;"); + bytecode.addInvokevirtual("javassist.runtime.Cflow", + "value", "()I"); + exprType = INT; + arrayDim = 0; + className = null; + } + + /* Syntax: + * + * <cflow> : $cflow '(' <cflow name> ')' + * <cflow name> : <identifier> ('.' <identifier>)* + */ + private static void makeCflowName(StringBuffer sbuf, ASTree name) + throws CompileError + { + if (name instanceof Symbol) { + sbuf.append(((Symbol)name).get()); + return; + } + else if (name instanceof Expr) { + Expr expr = (Expr)name; + if (expr.getOperator() == '.') { + makeCflowName(sbuf, expr.oprand1()); + sbuf.append('.'); + makeCflowName(sbuf, expr.oprand2()); + return; + } + } + + throw new CompileError("bad " + cflowName); + } + + /* To support $$. ($$) is equivalent to ($1, ..., $n). + * It can be used only as a parameter list of method call. + */ + public boolean isParamListName(ASTList args) { + if (paramTypeList != null + && args != null && args.tail() == null) { + ASTree left = args.head(); + return (left instanceof Member + && ((Member)left).get().equals(paramListName)); + } + else + return false; + } + + /* + public int atMethodArgsLength(ASTList args) { + if (!isParamListName(args)) + return super.atMethodArgsLength(args); + + return paramTypeList.length; + } + */ + + public int atMethodArgsLength(ASTList args) { + String pname = paramListName; + int n = 0; + while (args != null) { + ASTree a = args.head(); + if (a instanceof Member && ((Member)a).get().equals(pname)) { + if (paramTypeList != null) + n += paramTypeList.length; + } + else + ++n; + + args = args.tail(); + } + + return n; + } + + public void atMethodArgs(ASTList args, int[] types, int[] dims, + String[] cnames) throws CompileError { + CtClass[] params = paramTypeList; + String pname = paramListName; + int i = 0; + while (args != null) { + ASTree a = args.head(); + if (a instanceof Member && ((Member)a).get().equals(pname)) { + if (params != null) { + int n = params.length; + int regno = indexOfParam1(); + for (int k = 0; k < n; ++k) { + CtClass p = params[k]; + regno += bytecode.addLoad(regno, p); + setType(p); + types[i] = exprType; + dims[i] = arrayDim; + cnames[i] = className; + ++i; + } + } + } + else { + a.accept(this); + types[i] = exprType; + dims[i] = arrayDim; + cnames[i] = className; + ++i; + } + + args = args.tail(); + } + } + + /* + public void atMethodArgs(ASTList args, int[] types, int[] dims, + String[] cnames) throws CompileError { + if (!isParamListName(args)) { + super.atMethodArgs(args, types, dims, cnames); + return; + } + + CtClass[] params = paramTypeList; + if (params == null) + return; + + int n = params.length; + int regno = indexOfParam1(); + for (int i = 0; i < n; ++i) { + CtClass p = params[i]; + regno += bytecode.addLoad(regno, p); + setType(p); + types[i] = exprType; + dims[i] = arrayDim; + cnames[i] = className; + } + } + */ + + /* + * Makes it valid to write "return <expr>;" for a void method. + */ + protected void atReturnStmnt(Stmnt st) throws CompileError { + ASTree result = st.getLeft(); + if (result != null && returnType == CtClass.voidType) { + result.accept(this); + if (is2word(exprType, arrayDim)) + bytecode.addOpcode(POP2); + else if (exprType != VOID) + bytecode.addOpcode(POP); + + result = null; + } + + atReturnStmnt2(result); + } + + /** + * Makes a cast to the return type ($r) available. + * It also enables $_. + * + * <p>If the return type is void, ($r) does nothing. + * The type of $_ is java.lang.Object. + * + * @param resultName null if $_ is not used. + * @return -1 or the variable index assigned to $_. + */ + public int recordReturnType(CtClass type, String castName, + String resultName, SymbolTable tbl) throws CompileError + { + returnType = type; + returnCastName = castName; + returnVarName = resultName; + if (resultName == null) + return -1; + else { + int varNo = getMaxLocals(); + int locals = varNo + recordVar(type, resultName, varNo, tbl); + setMaxLocals(locals); + return varNo; + } + } + + /** + * Makes $type available. + */ + public void recordType(CtClass t) { + dollarType = t; + } + + /** + * Makes method parameters $0, $1, ..., $args, and $$ available. + * $0 is equivalent to THIS if the method is not static. Otherwise, + * if the method is static, then $0 is not available. + */ + public void recordParams(CtClass[] params, boolean isStatic, + String prefix, String paramVarName, + String paramsName, SymbolTable tbl) + throws CompileError + { + recordParams(params, isStatic, prefix, paramVarName, + paramsName, !isStatic, 0, getThisName(), tbl); + } + + /** + * Makes method parameters $0, $1, ..., $args, and $$ available. + * $0 is available only if use0 is true. It might not be equivalent + * to THIS. + * + * @paaram use0 true if $0 is used. + * @param paramBase the register number of $0 (use0 is true) + * or $1 (otherwise). + * @param target the class of $0. If use0 is false, target + * can be null. + * @param isStatic true if the method in which the compiled bytecode + * is embedded is static. + */ + public void recordParams(CtClass[] params, boolean isStatic, + String prefix, String paramVarName, + String paramsName, boolean use0, + int paramBase, String target, + SymbolTable tbl) + throws CompileError + { + int varNo; + + paramTypeList = params; + paramArrayName = paramVarName; + paramListName = paramsName; + paramVarBase = paramBase; + useParam0 = use0; + + param0Type = jvmToJavaName(target); + + inStaticMethod = isStatic; + varNo = paramBase; + if (use0) { + String varName = prefix + "0"; + Declarator decl + = new Declarator(CLASS, javaToJvmName(target), 0, varNo++, + new Symbol(varName)); + tbl.append(varName, decl); + } + + for (int i = 0; i < params.length; ++i) + varNo += recordVar(params[i], prefix + (i + 1), varNo, tbl); + + if (getMaxLocals() < varNo) + setMaxLocals(varNo); + } + + /** + * Makes the given variable name available. + * + * @param type variable type + * @param varName variable name + */ + public int recordVariable(CtClass type, String varName, SymbolTable tbl) + throws CompileError + { + if (varName == null) + return -1; + else { + int varNo = getMaxLocals(); + int locals = varNo + recordVar(type, varName, varNo, tbl); + setMaxLocals(locals); + return varNo; + } + } + + private int recordVar(CtClass cc, String varName, int varNo, + SymbolTable tbl) throws CompileError + { + if (cc == CtClass.voidType) { + exprType = CLASS; + arrayDim = 0; + className = jvmJavaLangObject; + } + else + setType(cc); + + Declarator decl + = new Declarator(exprType, className, arrayDim, + varNo, new Symbol(varName)); + tbl.append(varName, decl); + return is2word(exprType, arrayDim) ? 2 : 1; + } + + /* compileParameterList() returns the stack size used + * by the produced code. + * + * This method correctly computes the max_stack value. + * + * @param regno the index of the local variable in which + * the first argument is received. + * (0: static method, 1: regular method.) + */ + public static int compileParameterList(Bytecode code, + CtClass[] params, int regno) { + if (params == null) { + code.addIconst(0); // iconst_0 + code.addAnewarray(javaLangObject); // anewarray Object + return 1; + } + else { + CtClass[] args = new CtClass[1]; + int n = params.length; + code.addIconst(n); // iconst_<n> + code.addAnewarray(javaLangObject); // anewarray Object + for (int i = 0; i < n; ++i) { + code.addOpcode(Bytecode.DUP); // dup + code.addIconst(i); // iconst_<i> + if (params[i].isPrimitive()) { + CtPrimitiveType pt = (CtPrimitiveType)params[i]; + String wrapper = pt.getWrapperName(); + code.addNew(wrapper); // new <wrapper> + code.addOpcode(Bytecode.DUP); // dup + int s = code.addLoad(regno, pt); // ?load <regno> + regno += s; + args[0] = pt; + code.addInvokespecial(wrapper, "<init>", + Descriptor.ofMethod(CtClass.voidType, args)); + // invokespecial + } + else { + code.addAload(regno); // aload <regno> + ++regno; + } + + code.addOpcode(Bytecode.AASTORE); // aastore + } + + return 8; + } + } + + protected void compileUnwrapValue(CtClass type, Bytecode code) + throws CompileError + { + if (type instanceof CtPrimitiveType) { + CtPrimitiveType pt = (CtPrimitiveType)type; + if (pt != CtClass.voidType) { + String wrapper = pt.getWrapperName(); + code.addCheckcast(wrapper); + code.addInvokevirtual(wrapper, pt.getGetMethodName(), + pt.getGetMethodDescriptor()); + setType(type); + } + } + else { + code.addCheckcast(type); + setType(type); + } + } + + /* Sets exprType, arrayDim, and className; + * If type is void, then this method does nothing. + */ + public void setType(CtClass type) throws CompileError { + setType(type, 0); + } + + private void setType(CtClass type, int dim) throws CompileError { + if (type.isPrimitive()) { + CtPrimitiveType pt = (CtPrimitiveType)type; + exprType = descToType(pt.getDescriptor()); + arrayDim = dim; + className = null; + } + else if (type.isArray()) + try { + setType(type.getComponentType(), dim + 1); + } + catch (NotFoundException e) { + throw new CompileError("undefined type: " + type.getName()); + } + else { + exprType = CLASS; + arrayDim = dim; + className = javaToJvmName(type.getName()); + } + } + + /* Performs implicit coercion from exprType to type. + */ + public void doNumCast(CtClass type) throws CompileError { + if (arrayDim == 0 && !isRefType(exprType)) + if (type instanceof CtPrimitiveType) { + CtPrimitiveType pt = (CtPrimitiveType)type; + atNumCastExpr(exprType, descToType(pt.getDescriptor())); + } + else + throw new CompileError("type mismatch"); + } +} diff --git a/src/main/javassist/compiler/KeywordTable.java b/src/main/javassist/compiler/KeywordTable.java new file mode 100644 index 00000000..57145bf8 --- /dev/null +++ b/src/main/javassist/compiler/KeywordTable.java @@ -0,0 +1,42 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler; + +final class KeywordTable extends java.util.HashMap { + public KeywordTable() { super(); } + + public int lookup(String name) { + Object found = get(name); + if (found == null) + return -1; + else + return ((Integer)found).intValue(); + } + + public void append(String name, int t) { + put(name, new Integer(t)); + } +} diff --git a/src/main/javassist/compiler/Lex.java b/src/main/javassist/compiler/Lex.java new file mode 100644 index 00000000..fb188de5 --- /dev/null +++ b/src/main/javassist/compiler/Lex.java @@ -0,0 +1,547 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler; + +class Token { + public Token next = null; + public int tokenId; + + public long longValue; + public double doubleValue; + public String textValue; +} + +public class Lex implements TokenId { + private int lastChar; + private StringBuffer textBuffer; + private Token currentToken; + private Token lookAheadTokens; + + private String input; + private int position, maxlen, lineNumber; + + /** + * Constructs a lexical analyzer. + */ + public Lex(String s) { + lastChar = -1; + textBuffer = new StringBuffer(); + currentToken = new Token(); + lookAheadTokens = null; + + input = s; + position = 0; + maxlen = s.length(); + lineNumber = 0; + } + + public int get() { + if (lookAheadTokens == null) + return get(currentToken); + else { + Token t; + currentToken = t = lookAheadTokens; + lookAheadTokens = lookAheadTokens.next; + return t.tokenId; + } + } + + /** + * Looks at the next token. + */ + public int lookAhead() { + return lookAhead(0); + } + + public int lookAhead(int i) { + Token tk = lookAheadTokens; + if (tk == null) { + lookAheadTokens = tk = currentToken; // reuse an object! + tk.next = null; + get(tk); + } + + for (; i-- > 0; tk = tk.next) + if (tk.next == null) { + Token tk2; + tk.next = tk2 = new Token(); + get(tk2); + } + + currentToken = tk; + return tk.tokenId; + } + + public String getString() { + return currentToken.textValue; + } + + public long getLong() { + return currentToken.longValue; + } + + public double getDouble() { + return currentToken.doubleValue; + } + + private int get(Token token) { + int t; + do { + t = readLine(token); + } while (t == '\n'); + token.tokenId = t; + return t; + } + + private int readLine(Token token) { + int c = getNextNonWhiteChar(); + if(c < 0) + return c; + else if(c == '\n') { + ++lineNumber; + return '\n'; + } + else if (c == '\'') + return readCharConst(token); + else if (c == '"') + return readStringL(token); + else if ('0' <= c && c <= '9') + return readNumber(c, token); + else if(c == '.'){ + c = getc(); + if ('0' <= c && c <= '9') { + StringBuffer tbuf = textBuffer; + tbuf.setLength(0); + tbuf.append('.'); + return readDouble(tbuf, c, token); + } + else{ + ungetc(c); + return readSeparator('.'); + } + } + else if ('A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || c == '_' + || c == '$') + return readIdentifier(c, token); + else + return readSeparator(c); + } + + private int getNextNonWhiteChar() { + int c; + do { + c = getc(); + if (c == '/') { + c = getc(); + if (c == '/') + do { + c = getc(); + } while (c != '\n' && c != '\r' && c != -1); + else if (c == '*') + while (true) { + c = getc(); + if (c == -1) + break; + else if (c == '*') + if ((c = getc()) == '/') { + c = ' '; + break; + } + else + ungetc(c); + } + else { + ungetc(c); + c = '/'; + } + } + } while(isBlank(c)); + return c; + } + + private int readCharConst(Token token) { + int c; + int value = 0; + while ((c = getc()) != '\'') + if (c == '\\') + value = readEscapeChar(); + else if (c < 0x20) { + if (c == '\n') + ++lineNumber; + + return BadToken; + } + else + value = c; + + token.longValue = value; + return CharConstant; + } + + private int readEscapeChar() { + int c = getc(); + if (c == 'n') + c = '\n'; + else if (c == 't') + c = '\t'; + else if (c == 'r') + c = '\r'; + else if (c == 'f') + c = '\f'; + else if (c == '\n') + ++lineNumber; + + return c; + } + + private int readStringL(Token token) { + int c; + StringBuffer tbuf = textBuffer; + tbuf.setLength(0); + for (;;) { + while ((c = getc()) != '"') { + if (c == '\\') + c = readEscapeChar(); + else if (c == '\n' || c < 0) { + ++lineNumber; + return BadToken; + } + + tbuf.append((char)c); + } + + for (;;) { + c = getc(); + if (c == '\n') + ++lineNumber; + else if (!isBlank(c)) + break; + } + + if (c != '"') { + ungetc(c); + break; + } + } + + token.textValue = tbuf.toString(); + return StringL; + } + + private int readNumber(int c, Token token) { + long value = 0; + int c2 = getc(); + if (c == '0') + if (c2 == 'X' || c2 == 'x') + for (;;) { + c = getc(); + if ('0' <= c && c <= '9') + value = value * 16 + (long)(c - '0'); + else if ('A' <= c && c <= 'F') + value = value * 16 + (long)(c - 'A' + 10); + else if ('a' <= c && c <= 'f') + value = value * 16 + (long)(c - 'a' + 10); + else { + token.longValue = value; + if (c == 'L' || c == 'l') + return LongConstant; + else { + ungetc(c); + return IntConstant; + } + } + } + else if ('0' <= c2 && c2 <= '7') { + value = c2 - '0'; + for (;;) { + c = getc(); + if ('0' <= c && c <= '7') + value = value * 8 + (long)(c - '0'); + else { + token.longValue = value; + if (c == 'L' || c == 'l') + return LongConstant; + else { + ungetc(c); + return IntConstant; + } + } + } + } + + value = c - '0'; + while ('0' <= c2 && c2 <= '9') { + value = value * 10 + c2 - '0'; + c2 = getc(); + } + + token.longValue = value; + if (c2 == 'F' || c2 == 'f') { + token.doubleValue = (double)value; + return FloatConstant; + } + else if (c2 == 'E' || c2 == 'e' || c2 == '.') { + StringBuffer tbuf = textBuffer; + tbuf.setLength(0); + tbuf.append(value); + return readDouble(tbuf, c2, token); + } + else if (c2 == 'L' || c2 == 'l') + return LongConstant; + else { + ungetc(c2); + return IntConstant; + } + } + + private int readDouble(StringBuffer sbuf, int c, Token token) { + if (c != 'E' && c != 'e') { + sbuf.append((char)c); + for (;;) { + c = getc(); + if ('0' <= c && c <= '9') + sbuf.append((char)c); + else + break; + } + } + + if (c == 'E' || c == 'e') { + sbuf.append((char)c); + c = getc(); + if (c == '+' || c == '-') { + sbuf.append((char)c); + c = getc(); + } + + while ('0' <= c && c <= '9') { + sbuf.append((char)c); + c = getc(); + } + } + + try { + token.doubleValue = Double.parseDouble(sbuf.toString()); + } + catch (NumberFormatException e) { + return BadToken; + } + + if (c == 'F' || c == 'f') + return FloatConstant; + else { + ungetc(c); + return DoubleConstant; + } + } + + // !"#$%&'( )*+,-./0 12345678 9:;<=>? + private static final int[] equalOps + = { NEQ, 0, 0, 0, MOD_E, AND_E, 0, 0, + 0, MUL_E, PLUS_E, 0, MINUS_E, 0, DIV_E, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, LE, EQ, GE, 0 }; + + private int readSeparator(int c) { + int c2, c3; + if ('!' <= c && c <= '?') { + int t = equalOps[c - '!']; + if (t == 0) + return c; + else { + c2 = getc(); + if (c == c2) + switch (c) { + case '=' : + return EQ; + case '+' : + return PLUSPLUS; + case '-' : + return MINUSMINUS; + case '&' : + return ANDAND; + case '<' : + c3 = getc(); + if (c3 == '=') + return LSHIFT_E; + else { + ungetc(c3); + return LSHIFT; + } + case '>' : + c3 = getc(); + if (c3 == '=') + return RSHIFT_E; + else if (c3 == '>') { + c3 = getc(); + if (c3 == '=') + return ARSHIFT_E; + else { + ungetc(c3); + return ARSHIFT; + } + } + else { + ungetc(c3); + return RSHIFT; + } + default : + break; + } + else if (c2 == '=') + return t; + } + } + else if (c == '^') { + c2 = getc(); + if (c2 == '=') + return EXOR_E; + } + else if (c == '|') { + c2 = getc(); + if (c2 == '=') + return OR_E; + else if (c2 == '|') + return OROR; + } + else + return c; + + ungetc(c2); + return c; + } + + private int readIdentifier(int c, Token token) { + StringBuffer tbuf = textBuffer; + tbuf.setLength(0); + + do { + tbuf.append((char)c); + c = getc(); + } while ('A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || c == '_' + || c == '$' || '0' <= c && c <= '9'); + + ungetc(c); + + String name = tbuf.toString(); + int t = ktable.lookup(name); + if (t >= 0) + return t; + else { + /* tbuf.toString() is executed quickly since it does not + * need memory copy. Using a hand-written extensible + * byte-array class instead of StringBuffer is not a good idea + * for execution speed. Converting a byte array to a String + * object is very slow. Using an extensible char array + * might be OK. + */ + token.textValue = name; + return Identifier; + } + } + + private static final KeywordTable ktable = new KeywordTable(); + + static { + ktable.append("abstract", ABSTRACT); + ktable.append("boolean", BOOLEAN); + ktable.append("break", BREAK); + ktable.append("byte", BYTE); + ktable.append("case", CASE); + ktable.append("catch", CATCH); + ktable.append("char", CHAR); + ktable.append("class", CLASS); + ktable.append("const", CONST); + ktable.append("continue", CONTINUE); + ktable.append("default", DEFAULT); + ktable.append("do", DO); + ktable.append("double", DOUBLE); + ktable.append("else", ELSE); + ktable.append("extends", EXTENDS); + ktable.append("false", FALSE); + ktable.append("final", FINAL); + ktable.append("finally", FINALLY); + ktable.append("float", FLOAT); + ktable.append("for", FOR); + ktable.append("goto", GOTO); + ktable.append("if", IF); + ktable.append("implements", IMPLEMENTS); + ktable.append("import", IMPORT); + ktable.append("instanceof", INSTANCEOF); + ktable.append("int", INT); + ktable.append("interface", INTERFACE); + ktable.append("long", LONG); + ktable.append("native", NATIVE); + ktable.append("new", NEW); + ktable.append("null", NULL); + ktable.append("package", PACKAGE); + ktable.append("private", PRIVATE); + ktable.append("protected", PROTECTED); + ktable.append("public", PUBLIC); + ktable.append("return", RETURN); + ktable.append("short", SHORT); + ktable.append("static", STATIC); + ktable.append("strict", STRICT); + ktable.append("super", SUPER); + ktable.append("switch", SWITCH); + ktable.append("synchronized", SYNCHRONIZED); + ktable.append("this", THIS); + ktable.append("throw", THROW); + ktable.append("throws", THROWS); + ktable.append("transient", TRANSIENT); + ktable.append("true", TRUE); + ktable.append("try", TRY); + ktable.append("void", VOID); + ktable.append("volatile", VOLATILE); + ktable.append("while", WHILE); + } + + private static boolean isBlank(int c) { + return c == ' ' || c == '\t' || c == '\f' || c == '\r' + || c == '\n'; + } + + private static boolean isDigit(int c) { + return '0' <= c && c <= '9'; + } + + private void ungetc(int c) { + lastChar = c; + } + + private int getc() { + if (lastChar < 0) + if (position < maxlen) + return input.charAt(position++); + else + return -1; + else { + int c = lastChar; + lastChar = -1; + return c; + } + } +} diff --git a/src/main/javassist/compiler/MemberCodeGen.java b/src/main/javassist/compiler/MemberCodeGen.java new file mode 100644 index 00000000..d457a836 --- /dev/null +++ b/src/main/javassist/compiler/MemberCodeGen.java @@ -0,0 +1,1045 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler; + +import java.util.List; +import javassist.*; +import javassist.bytecode.*; +import javassist.compiler.ast.*; + +/* Code generator methods depending on javassist.* classes. + */ +public class MemberCodeGen extends CodeGen { + protected ClassPool classPool; + protected CtClass thisClass; + protected MethodInfo thisMethod; + + protected boolean resultStatic; + + public MemberCodeGen(Bytecode b, CtClass cc, ClassPool cp) { + super(b); + classPool = cp; + thisClass = cc; + thisMethod = null; + } + + /** + * Records the currently compiled method. + */ + public void setThisMethod(CtMethod m) { + thisMethod = m.getMethodInfo2(); + } + + public CtClass getThisClass() { return thisClass; } + + /** + * Returns the JVM-internal representation of this class name. + */ + protected String getThisName() { + return javaToJvmName(thisClass.getName()); + } + + /** + * Returns the JVM-internal representation of this super class name. + */ + protected String getSuperName() throws CompileError { + return javaToJvmName(getSuperclass(thisClass).getName()); + } + + protected void insertDefaultSuperCall() throws CompileError { + bytecode.addAload(0); + bytecode.addInvokespecial(getSuperclass(thisClass), "<init>", "()V"); + } + + protected void atTryStmnt(Stmnt st) throws CompileError { + Stmnt body = (Stmnt)st.getLeft(); + if (body == null) + return; + + int start = bytecode.currentPc(); + body.accept(this); + int end = bytecode.currentPc(); + + bytecode.addOpcode(Opcode.GOTO); + int pc = bytecode.currentPc(); + bytecode.addIndex(0); // correct later + + int var = getMaxLocals(); + incMaxLocals(1); + ASTList catchList = (ASTList)st.getRight().getLeft(); + while (catchList != null) { + Pair p = (Pair)catchList.head(); + catchList = catchList.tail(); + Declarator decl = (Declarator)p.getLeft(); + Stmnt block = (Stmnt)p.getRight(); + + decl.setLocalVar(var); + + CtClass type = lookupClass(decl.getClassName()); + decl.setClassName(javaToJvmName(type.getName())); + bytecode.addExceptionHandler(start, end, bytecode.currentPc(), + type); + if (block != null) { + bytecode.addAstore(var); + block.accept(this); + } + + bytecode.addOpcode(Opcode.GOTO); + bytecode.addIndex(pc - bytecode.currentPc()); + } + + Stmnt finallyBlock = (Stmnt)st.getRight().getRight().getLeft(); + if (finallyBlock != null) + throw new CompileError( + "sorry, finally has not been supported yet"); + + bytecode.write16bit(pc, bytecode.currentPc() - pc + 1); + } + + public void atNewExpr(NewExpr expr) throws CompileError { + if (expr.isArray()) + atNewArrayExpr(expr); + else { + CtClass clazz = lookupClass(expr.getClassName()); + String cname = clazz.getName(); + ASTList args = expr.getArguments(); + bytecode.addNew(cname); + bytecode.addOpcode(DUP); + + atMethodCall2(clazz, MethodInfo.nameInit, args, false, true); + + exprType = CLASS; + arrayDim = 0; + className = javaToJvmName(cname); + } + } + + public void atNewArrayExpr(NewExpr expr) throws CompileError { + if (expr.getInitializer() != null) + throw new CompileError("array initializer is not supported"); + + int type = expr.getArrayType(); + ASTList size = expr.getArraySize(); + ASTList classname = expr.getClassName(); + if (size.length() > 1) { + atMultiNewArray(type, classname, size); + return; + } + + size.head().accept(this); + exprType = type; + arrayDim = 1; + if (type == CLASS) { + className = resolveClassName(classname); + bytecode.addAnewarray(jvmToJavaName(className)); + } + else { + className = null; + int atype = 0; + switch (type) { + case BOOLEAN : + atype = T_BOOLEAN; + break; + case CHAR : + atype = T_CHAR; + break; + case FLOAT : + atype = T_FLOAT; + break; + case DOUBLE : + atype = T_DOUBLE; + break; + case BYTE : + atype = T_BYTE; + break; + case SHORT : + atype = T_SHORT; + break; + case INT : + atype = T_INT; + break; + case LONG : + atype = T_LONG; + break; + default : + badNewExpr(); + break; + } + + bytecode.addOpcode(NEWARRAY); + bytecode.add(atype); + } + } + + private static void badNewExpr() throws CompileError { + throw new CompileError("bad new expression"); + } + + protected void atMultiNewArray(int type, ASTList classname, ASTList size) + throws CompileError + { + int count, dim; + dim = size.length(); + for (count = 0; size != null; size = size.tail()) { + ASTree s = size.head(); + if (s == null) + break; // int[][][] a = new int[3][4][]; + + ++count; + s.accept(this); + if (exprType != INT) + throw new CompileError("bad type for array size"); + } + + String desc; + exprType = type; + arrayDim = dim; + if (type == CLASS) { + className = resolveClassName(classname); + desc = toJvmArrayName(className, dim); + } + else + desc = toJvmTypeName(type, dim); + + bytecode.addMultiNewarray(desc, count); + } + + protected void atMethodCall(Expr expr) throws CompileError { + String mname = null; + CtClass targetClass = null; + ASTree method = expr.oprand1(); + ASTList args = (ASTList)expr.oprand2(); + boolean isStatic = false; + boolean isSpecial = false; + + if (method instanceof Member) { + mname = ((Member)method).get(); + targetClass = thisClass; + bytecode.addAload(0); // this + } + else if (method instanceof Keyword) { // constructor + isSpecial = true; + mname = MethodInfo.nameInit; // <init> + targetClass = thisClass; + bytecode.addAload(0); // this + if (((Keyword)method).get() == SUPER) + targetClass = getSuperclass(targetClass); + } + else if (method instanceof Expr) { + Expr e = (Expr)method; + mname = ((Symbol)e.oprand2()).get(); + int op = e.getOperator(); + if (op == MEMBER) { // static method + targetClass = lookupClass((ASTList)e.oprand1()); + isStatic = true; + } + else if (op == '.') { + ASTree target = e.oprand1(); + if (target instanceof Keyword) + if (((Keyword)target).get() == SUPER) + isSpecial = true; + + try { + target.accept(this); + } + catch (NoFieldException nfe) { + if (nfe.getExpr() != target) + throw nfe; + + // it should be a static method. + exprType = CLASS; + arrayDim = 0; + className = nfe.getField(); // JVM-internal + isStatic = true; + } + + if (arrayDim > 0) + targetClass = lookupClass2(javaLangObject); + else if (exprType == CLASS /* && arrayDim == 0 */) + targetClass = lookupClass(className); + else + badMethod(); + } + else + badMethod(); + } + else + fatal(); + + atMethodCall2(targetClass, mname, args, isStatic, isSpecial); + } + + private static void badMethod() throws CompileError { + throw new CompileError("bad method"); + } + + private static CtClass getSuperclass(CtClass c) throws CompileError { + try { + return c.getSuperclass(); + } + catch (NotFoundException e) { + throw new CompileError("cannot find the super class of " + + c.getName()); + } + } + + public void atMethodCall2(CtClass targetClass, String mname, + ASTList args, boolean isStatic, boolean isSpecial) + throws CompileError + { + int nargs = atMethodArgsLength(args); + int[] types = new int[nargs]; + int[] dims = new int[nargs]; + String[] cnames = new String[nargs]; + + int stack = bytecode.getStackDepth(); + + atMethodArgs(args, types, dims, cnames); + + // used by invokeinterface + int count = bytecode.getStackDepth() - stack + 1; + + Object[] found = lookupMethod(targetClass, thisMethod, mname, + types, dims, cnames, false); + if (found == null) { + String msg; + if (mname.equals(MethodInfo.nameInit)) + msg = "constructor not found"; + else + msg = "Method " + mname + " not found in " + + targetClass.getName(); + + throw new CompileError(msg); + } + + CtClass declClass = (CtClass)found[0]; + MethodInfo minfo = (MethodInfo)found[1]; + String desc = minfo.getDescriptor(); + int acc = minfo.getAccessFlags(); + + if (mname.equals(MethodInfo.nameInit)) { + isSpecial = true; + if (declClass != targetClass) + throw new CompileError("no such a constructor"); + } + else if ((acc & AccessFlag.PRIVATE) != 0) { + isSpecial = true; + if (declClass != targetClass) + throw new CompileError("Method " + mname + "is private"); + } + + boolean popTarget = false; + if ((acc & AccessFlag.STATIC) != 0) { + if (!isStatic) { + /* this method is static but the target object is + on stack. It must be popped out. + */ + isStatic = true; + popTarget = true; + } + + bytecode.addInvokestatic(declClass, mname, desc); + } + else if (isSpecial) + bytecode.addInvokespecial(declClass, mname, desc); + else if (declClass.isInterface()) + bytecode.addInvokeinterface(declClass, mname, desc, count); + else + bytecode.addInvokevirtual(declClass, mname, desc); + + setReturnType(desc, isStatic, popTarget); + } + + public int atMethodArgsLength(ASTList args) { + return ASTList.length(args); + } + + public void atMethodArgs(ASTList args, int[] types, int[] dims, + String[] cnames) throws CompileError { + int i = 0; + while (args != null) { + ASTree a = args.head(); + a.accept(this); + types[i] = exprType; + dims[i] = arrayDim; + cnames[i] = className; + ++i; + args = args.tail(); + } + } + + private void setReturnType(String desc, boolean isStatic, + boolean popTarget) + throws CompileError + { + int i = desc.indexOf(')'); + if (i < 0) + badMethod(); + + char c = desc.charAt(++i); + int dim = 0; + while (c == '[') { + ++dim; + c = desc.charAt(++i); + } + + arrayDim = dim; + if (c == 'L') { + int j = desc.indexOf(';', i + 1); + if (j < 0) + badMethod(); + + exprType = CLASS; + className = desc.substring(i + 1, j); + } + else { + exprType = descToType(c); + className = null; + } + + int etype = exprType; + if (isStatic) { + if (popTarget) { + if (is2word(etype, dim)) { + bytecode.addOpcode(DUP2_X1); + bytecode.addOpcode(POP2); + bytecode.addOpcode(POP); + } + else if (etype == VOID) + bytecode.addOpcode(POP); + else { + bytecode.addOpcode(SWAP); + bytecode.addOpcode(POP); + } + } + } + } + + private Object[] lookupMethod(CtClass clazz, MethodInfo current, + String methodName, + int[] argTypes, int[] argDims, + String[] argClassNames, boolean onlyExact) + throws CompileError + { + Object[] maybe = null; + + if (current != null) + if (current.getName().equals(methodName)) { + int res = compareSignature(current.getDescriptor(), + argTypes, argDims, argClassNames); + Object[] r = new Object[] { clazz, current }; + if (res == YES) + return r; + else if (res == MAYBE && maybe == null) + maybe = r; + } + + List list = clazz.getClassFile2().getMethods(); + int n = list.size(); + for (int i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + if (minfo.getName().equals(methodName)) { + int res = compareSignature(minfo.getDescriptor(), + argTypes, argDims, argClassNames); + Object[] r = new Object[] { clazz, minfo }; + if (res == YES) + return r; + else if (res == MAYBE && maybe == null) + maybe = r; + } + } + + try { + CtClass pclazz = clazz.getSuperclass(); + if (pclazz != null) { + Object[] r = lookupMethod(pclazz, null, methodName, argTypes, + argDims, argClassNames, + (onlyExact || maybe != null)); + if (r != null) + return r; + } + } + catch (NotFoundException e) {} + + /* -- not necessary to search implemented interfaces. + try { + CtClass[] ifs = clazz.getInterfaces(); + int size = ifs.length; + for (int i = 0; i < size; ++i) { + Object[] r = lookupMethod(ifs[i], methodName, argTypes, + argDims, argClassNames); + if (r != null) + return r; + } + } + catch (NotFoundException e) {} + */ + + if (onlyExact) + return null; + else + return maybe; + } + + private static final int YES = 2; + private static final int MAYBE = 1; + private static final int NO = 0; + + /* + * Returns YES if actual parameter types matches the given signature. + * + * argTypes, argDims, and argClassNames represent actual parameters. + * + * This method does not correctly implement the Java method dispatch + * algorithm. + */ + private int compareSignature(String desc, int[] argTypes, + int[] argDims, String[] argClassNames) + throws CompileError + { + int result = YES; + int i = 1; + int nArgs = argTypes.length; + if (nArgs != Descriptor.numOfParameters(desc)) + return NO; + + int len = desc.length(); + for (int n = 0; i < len; ++n) { + char c = desc.charAt(i++); + if (c == ')') + return (n == nArgs ? result : NO); + else if (n >= nArgs) + return NO; + + int dim = 0; + while (c == '[') { + ++dim; + c = desc.charAt(i++); + } + + if (argTypes[n] == NULL) { + if (dim == 0 && c != 'L') + return NO; + } + else if (argDims[n] != dim) { + if (!(dim == 0 && c == 'L' + && desc.startsWith("java/lang/Object;", i))) + return NO; + + // if the thread reaches here, c must be 'L'. + i = desc.indexOf(';', i) + 1; + result = MAYBE; + if (i <= 0) + return NO; // invalid descriptor? + } + else if (c == 'L') { // not compare + int j = desc.indexOf(';', i); + if (j < 0 || argTypes[n] != CLASS) + return NO; + + String cname = desc.substring(i, j); + if (!cname.equals(argClassNames[n])) { + CtClass clazz = lookupClass(argClassNames[n]); + try { + if (clazz.subtypeOf(lookupClass(cname))) + result = MAYBE; + else + return NO; + } + catch (NotFoundException e) { + result = MAYBE; // should be NO? + } + } + + i = j + 1; + } + else { + int t = descToType(c); + int at = argTypes[n]; + if (t != at) + if (t == INT + && (at == SHORT || at == BYTE || at == CHAR)) + result = MAYBE; + else + return NO; + } + } + + return NO; + } + + protected static int descToType(char c) throws CompileError { + switch (c) { + case 'Z' : + return BOOLEAN; + case 'C' : + return CHAR; + case 'B' : + return BYTE; + case 'S' : + return SHORT; + case 'I' : + return INT; + case 'J' : + return LONG; + case 'F' : + return FLOAT; + case 'D' : + return DOUBLE; + case 'V' : + return VOID; + case 'L' : + case '[' : + return CLASS; + default : + fatal(); + return VOID; + } + } + + protected void atFieldAssign(Expr expr, int op, ASTree left, + ASTree right, boolean doDup) throws CompileError + { + CtField f = fieldAccess(left); + boolean is_static = resultStatic; + if (op != '=' && !is_static) + bytecode.addOpcode(DUP); + + int fi = atFieldRead(f, is_static, op == '='); + int fType = exprType; + int fDim = arrayDim; + String cname = className; + + atAssignCore(expr, op, right, fType, fDim, cname); + + boolean is2w = is2word(fType, fDim); + if (doDup) { + int dup_code; + if (is_static) + dup_code = (is2w ? DUP2 : DUP); + else + dup_code = (is2w ? DUP2_X1 : DUP_X1); + + bytecode.addOpcode(dup_code); + } + + if (is_static) { + bytecode.add(PUTSTATIC); + bytecode.growStack(is2w ? -2 : -1); + } + else { + bytecode.add(PUTFIELD); + bytecode.growStack(is2w ? -3 : -2); + } + + bytecode.addIndex(fi); + exprType = fType; + arrayDim = fDim; + className = cname; + } + + /* overwritten in JvstCodeGen. + */ + public void atMember(Member mem) throws CompileError { + atFieldRead(mem); + } + + protected void atFieldRead(ASTree expr) throws CompileError + { + CtField f = fieldAccess(expr); + boolean is_static = resultStatic; + atFieldRead(f, is_static, false); + } + + private int atFieldRead(CtField f, boolean isStatic, boolean noRead) + throws CompileError + { + FieldInfo finfo = f.getFieldInfo2(); + String type = finfo.getDescriptor(); + + int fi = addFieldrefInfo(f, finfo, type); + + int i = 0; + char c = type.charAt(i); + boolean is2byte = (c == 'J' || c == 'D'); + exprType = descToType(c); + arrayDim = 0; + if (c == '[') { + i = 1; + while ((c = type.charAt(i)) == '[') + ++i; + + arrayDim = i; + } + + if (c == 'L') + className = type.substring(i + 1, type.indexOf(';', i + 1)); + + if (noRead) + return fi; + + if (isStatic) { + bytecode.add(GETSTATIC); + bytecode.growStack(is2byte ? 2 : 1); + } + else { + bytecode.add(GETFIELD); + bytecode.growStack(is2byte ? 1 : 0); + } + + bytecode.addIndex(fi); + return fi; + } + + protected int addFieldrefInfo(CtField f, FieldInfo finfo, String type) { + ConstPool cp = bytecode.getConstPool(); + String cname = f.getDeclaringClass().getName(); + int ci = cp.addClassInfo(cname); + String name = finfo.getName(); + return cp.addFieldrefInfo(ci, name, type); + } + + protected void atFieldPlusPlus(int token, boolean isPost, + ASTree oprand, Expr expr, boolean doDup) + throws CompileError + { + CtField f = fieldAccess(oprand); + boolean is_static = resultStatic; + if (!is_static) + bytecode.addOpcode(DUP); + + int fi = atFieldRead(f, is_static, false); + int t = exprType; + boolean is2w = is2word(t, arrayDim); + + int dup_code; + if (is_static) + dup_code = (is2w ? DUP2 : DUP); + else + dup_code = (is2w ? DUP2_X1 : DUP_X1); + + atPlusPlusCore(dup_code, doDup, token, isPost, expr); + + if (is_static) { + bytecode.add(PUTSTATIC); + bytecode.growStack(is2w ? -2 : -1); + } + else { + bytecode.add(PUTFIELD); + bytecode.growStack(is2w ? -3 : -2); + } + + bytecode.addIndex(fi); + } + + /* This method also returns a value in resultStatic. + */ + protected CtField fieldAccess(ASTree expr) throws CompileError { + CtField f = null; + boolean is_static = false; + if (expr instanceof Member) { + String name = ((Member)expr).get(); + try { + f = thisClass.getField(name); + } + catch (NotFoundException e) { + // EXPR might be part of a static member access? + throw new NoFieldException(name, expr); + } + + is_static = Modifier.isStatic(f.getModifiers()); + if (!is_static) + if (inStaticMethod) + throw new CompileError( + "not available in a static method: " + name); + else + bytecode.addAload(0); // this + } + else if (expr instanceof Expr) { + Expr e = (Expr)expr; + int op = e.getOperator(); + if (op == MEMBER) { + f = lookupField((ASTList)e.oprand1(), (Symbol)e.oprand2()); + is_static = true; + } + else if (op == '.') { + try { + e.oprand1().accept(this); + if (exprType == CLASS && arrayDim == 0) + f = lookupField(className, (Symbol)e.oprand2()); + else + badLvalue(); + + is_static = Modifier.isStatic(f.getModifiers()); + if (is_static) + bytecode.addOpcode(POP); + } + catch (NoFieldException nfe) { + if (nfe.getExpr() != e.oprand1()) + throw nfe; + + Symbol fname = (Symbol)e.oprand2(); + // it should be a static field. + try { + f = lookupField(nfe.getField(), fname); + is_static = true; + } + catch (CompileError ce) { + // EXPR might be part of a qualified class name. + throw new NoFieldException(nfe.getField() + "/" + + fname.get(), expr); + } + } + } + else + badLvalue(); + } + else + badLvalue(); + + resultStatic = is_static; + return f; + } + + private static void badLvalue() throws CompileError { + throw new CompileError("bad l-value"); + } + + public CtClass[] makeParamList(MethodDecl md) throws CompileError { + CtClass[] params; + ASTList plist = md.getParams(); + if (plist == null) + params = new CtClass[0]; + else { + int i = 0; + params = new CtClass[plist.length()]; + while (plist != null) { + params[i++] = lookupClass((Declarator)plist.head()); + plist = plist.tail(); + } + } + + return params; + } + + public CtClass[] makeThrowsList(MethodDecl md) throws CompileError { + CtClass[] clist; + ASTList list = md.getThrows(); + if (list == null) + return null; + else { + int i = 0; + clist = new CtClass[list.length()]; + while (list != null) { + clist[i++] = lookupClass((ASTList)list.head()); + list = list.tail(); + } + + return clist; + } + } + + public static int getModifiers(ASTList mods) { + int m = 0; + while (mods != null) { + Keyword k = (Keyword)mods.head(); + mods = mods.tail(); + switch (k.get()) { + case STATIC : + m |= Modifier.STATIC; + break; + case FINAL : + m |= Modifier.FINAL; + break; + case SYNCHRONIZED : + m |= Modifier.SYNCHRONIZED; + break; + case ABSTRACT : + m |= Modifier.ABSTRACT; + break; + case PUBLIC : + m |= Modifier.PUBLIC; + break; + case PROTECTED : + m |= Modifier.PROTECTED; + break; + case PRIVATE : + m |= Modifier.PRIVATE; + break; + case VOLATILE : + m |= Modifier.VOLATILE; + break; + case TRANSIENT : + m |= Modifier.TRANSIENT; + break; + case STRICT : + m |= Modifier.STRICT; + break; + } + } + + return m; + } + + /* Converts a class name into a JVM-internal representation. + * + * It may also expand a simple class name to java.lang.*. + * For example, this converts Object into java/lang/Object. + */ + protected String resolveClassName(ASTList name) throws CompileError { + if (name == null) + return null; + else + return javaToJvmName(lookupClass(name).getName()); + } + + /* Expands a simple class name to java.lang.*. + * For example, this converts Object into java/lang/Object. + */ + protected String resolveClassName(String jvmName) throws CompileError { + if (jvmName == null) + return null; + else + return javaToJvmName(lookupClass(jvmName).getName()); + } + + protected CtClass lookupClass(Declarator decl) throws CompileError { + return lookupClass(decl.getType(), decl.getArrayDim(), + decl.getClassName()); + } + + protected CtClass lookupClass(int type, int dim, String classname) + throws CompileError + { + String cname = ""; + CtClass clazz; + switch (type) { + case CLASS : + clazz = lookupClass(classname); + if (dim > 0) + cname = clazz.getName(); + else + return clazz; + + break; + case BOOLEAN : + cname = "boolean"; + break; + case CHAR : + cname = "char"; + break; + case BYTE : + cname = "byte"; + break; + case SHORT : + cname = "short"; + break; + case INT : + cname = "int"; + break; + case LONG : + cname = "long"; + break; + case FLOAT : + cname = "float"; + break; + case DOUBLE : + cname = "double"; + break; + case VOID : + cname = "void"; + break; + default : + fatal(); + } + + while (dim-- > 0) + cname += "[]"; + + return lookupClass2(cname); + } + + protected CtClass lookupClass(ASTList name) throws CompileError { + return lookupClass2(Declarator.astToClassName(name, '.')); + } + + protected CtClass lookupClass(String jvmName) throws CompileError { + return lookupClass2(jvmToJavaName(jvmName)); + } + + /** + * @param name a qualified class name. e.g. java.lang.String + */ + private CtClass lookupClass2(String name) throws CompileError { + try { + return classPool.get(name); + } + catch (NotFoundException e) {} + + try { + if (name.indexOf('.') < 0) + return classPool.get("java.lang." + name); + } + catch (NotFoundException e) {} + + throw new CompileError("no such class: " + name); + } + + public CtField lookupField(ASTList className, Symbol fieldName) + throws CompileError + { + return lookupField2(Declarator.astToClassName(className, '.'), + fieldName); + } + + public CtField lookupField(String className, Symbol fieldName) + throws CompileError + { + return lookupField2(jvmToJavaName(className), fieldName); + } + + /** + * @param name a qualified class name. e.g. java.lang.String + */ + private CtField lookupField2(String className, Symbol fieldName) + throws CompileError + { + CtClass cc = lookupClass(className); + try { + return cc.getField(fieldName.get()); + } + catch (NotFoundException e) {} + throw new CompileError("no such field: " + fieldName.get()); + } + + protected static String javaToJvmName(String classname) { + return classname.replace('.', '/'); + } + + protected static String jvmToJavaName(String classname) { + return classname.replace('/', '.'); + } +} diff --git a/src/main/javassist/compiler/NoFieldException.java b/src/main/javassist/compiler/NoFieldException.java new file mode 100644 index 00000000..a8102863 --- /dev/null +++ b/src/main/javassist/compiler/NoFieldException.java @@ -0,0 +1,49 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler; + +import javassist.compiler.ast.ASTree; + +public class NoFieldException extends CompileError { + private String fieldName; + private ASTree expr; + + /* NAME must be JVM-internal representation. + */ + public NoFieldException(String name, ASTree e) { + super("no such field: " + name); + fieldName = name; + expr = e; + } + + /* The returned name should be JVM-internal representation. + */ + public String getField() { return fieldName; } + + /* Returns the expression where this exception is thrown. + */ + public ASTree getExpr() { return expr; } +} diff --git a/src/main/javassist/compiler/Parser.java b/src/main/javassist/compiler/Parser.java new file mode 100644 index 00000000..3039e5d1 --- /dev/null +++ b/src/main/javassist/compiler/Parser.java @@ -0,0 +1,1138 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler; + +import javassist.compiler.ast.*; + +public final class Parser implements TokenId { + private Lex lex; + + public Parser(Lex lex) { + this.lex = lex; + } + + public boolean hasMore() { return lex.lookAhead() >= 0; } + + /* member.declaration + * : method.declaration | field.declaration + */ + public ASTList parseMember(SymbolTable tbl) throws CompileError { + ASTList mem = parseMember1(tbl); + if (mem instanceof MethodDecl) + return parseMethod2(tbl, (MethodDecl)mem); + else + return mem; + } + + /* A method body is not parsed. + */ + public ASTList parseMember1(SymbolTable tbl) throws CompileError { + ASTList mods = parseMemberMods(); + Declarator d; + boolean isConstructor = false; + if (lex.lookAhead() == Identifier && lex.lookAhead(1) == '(') { + d = new Declarator(VOID, 0); + isConstructor = true; + } + else + d = parseFormalType(tbl); + + if (lex.get() != Identifier) + throw new SyntaxError(lex); + + String name; + if (isConstructor) + name = MethodDecl.initName; + else + name = lex.getString(); + + d.setVariable(new Symbol(name)); + if (isConstructor || lex.lookAhead() == '(') + return parseMethod1(tbl, isConstructor, mods, d); + else + return parseField(tbl, mods, d); + } + + /* field.declaration + * : member.modifiers + * formal.type Identifier + * [ "=" expression ] ";" + */ + private FieldDecl parseField(SymbolTable tbl, ASTList mods, + Declarator d) throws CompileError + { + ASTree expr = null; + if (lex.lookAhead() == '=') { + lex.get(); + expr = parseExpression(tbl); + } + + int c = lex.get(); + if (c == ';') + return new FieldDecl(mods, new ASTList(d, new ASTList(expr))); + else if (c == ',') + throw new CompileError( + "only one field can be declared in one declaration", lex); + else + throw new SyntaxError(lex); + } + + /* method.declaration + * : member.modifiers + * [ formal.type ] + * Identifier "(" [ formal.parameter ( "," formal.parameter )* ] ")" + * array.dimension + * [ THROWS class.type ( "," class.type ) ] + * ( block.statement | ";" ) + * + * Note that a method body is not parsed. + */ + private MethodDecl parseMethod1(SymbolTable tbl, boolean isConstructor, + ASTList mods, Declarator d) + throws CompileError + { + if (lex.get() != '(') + throw new SyntaxError(lex); + + ASTList parms = null; + if (lex.lookAhead() != ')') + while (true) { + parms = ASTList.append(parms, parseFormalParam(tbl)); + int t = lex.lookAhead(); + if (t == ',') + lex.get(); + else if (t == ')') + break; + } + + lex.get(); // ')' + d.addArrayDim(parseArrayDimension()); + if (isConstructor && d.getArrayDim() > 0) + throw new SyntaxError(lex); + + ASTList throwsList = null; + if (lex.lookAhead() == THROWS) { + lex.get(); + while (true) { + throwsList = ASTList.append(throwsList, parseClassType(tbl)); + if (lex.lookAhead() == ',') + lex.get(); + else + break; + } + } + + return new MethodDecl(mods, new ASTList(d, + ASTList.make(parms, throwsList, null))); + } + + /* Parses a method body. + */ + public MethodDecl parseMethod2(SymbolTable tbl, MethodDecl md) + throws CompileError + { + Stmnt body = null; + if (lex.lookAhead() == ';') + lex.get(); + else { + body = parseBlock(tbl); + if (body == null) + body = new Stmnt(BLOCK); + } + + md.sublist(4).setHead(body); + return md; + } + + /* member.modifiers + * : ( FINAL | SYNCHRONIZED | ABSTRACT + * | PUBLIC | PROTECTED | PRIVATE | STATIC + * | VOLATILE | TRANSIENT | STRICT )* + */ + private ASTList parseMemberMods() { + int t; + ASTList list = null; + while (true) { + t = lex.lookAhead(); + if (t == ABSTRACT || t == FINAL || t == PUBLIC || t == PROTECTED + || t == PRIVATE || t == SYNCHRONIZED || t == STATIC + || t == VOLATILE || t == TRANSIENT || t == STRICT) + list = new ASTList(new Keyword(lex.get()), list); + else + break; + } + + return list; + } + + /* formal.type : ( build-in-type | class.type ) array.dimension + */ + private Declarator parseFormalType(SymbolTable tbl) throws CompileError { + int t = lex.lookAhead(); + if (isBuiltinType(t) || t == VOID) { + lex.get(); // primitive type + int dim = parseArrayDimension(); + return new Declarator(t, dim); + } + else { + ASTList name = parseClassType(tbl); + int dim = parseArrayDimension(); + return new Declarator(name, dim); + } + } + + private static boolean isBuiltinType(int t) { + return (t == BOOLEAN || t == BYTE || t == CHAR || t == SHORT + || t == INT || t == LONG || t == FLOAT || t == DOUBLE); + } + + /* formal.parameter : formal.type Identifier array.dimension + */ + private Declarator parseFormalParam(SymbolTable tbl) + throws CompileError + { + Declarator d = parseFormalType(tbl); + if (lex.get() != Identifier) + throw new SyntaxError(lex); + + String name = lex.getString(); + d.setVariable(new Symbol(name)); + d.addArrayDim(parseArrayDimension()); + tbl.append(name, d); + return d; + } + + /* statement : [ label ":" ]* labeled.statement + * + * labeled.statement + * : block.statement + * | if.statement + * | while.statement + * | do.statement + * | for.statement + * | switch.statement + * | try.statement + * | return.statement + * | thorw.statement + * | break.statement + * | continue.statement + * | declaration.or.expression + * | ";" + * + * This method may return null (empty statement). + */ + public Stmnt parseStatement(SymbolTable tbl) + throws CompileError + { + int t = lex.lookAhead(); + if (t == '{') + return parseBlock(tbl); + else if (t == ';') { + lex.get(); + return new Stmnt(BLOCK); // empty statement + } + else if (t == Identifier && lex.lookAhead(1) == ':') { + lex.get(); // Identifier + String label = lex.getString(); + lex.get(); // ':' + return Stmnt.make(LABEL, new Symbol(label), parseStatement(tbl)); + } + else if (t == IF) + return parseIf(tbl); + else if (t == WHILE) + return parseWhile(tbl); + else if (t == DO) + return parseDo(tbl); + else if (t == FOR) + return parseFor(tbl); + else if (t == TRY) + return parseTry(tbl); + else if (t == SWITCH) + return parseSwitch(tbl); + else if (t == RETURN) + return parseReturn(tbl); + else if (t == THROW) + return parseThrow(tbl); + else if (t == BREAK) + return parseBreak(tbl); + else if (t == CONTINUE) + return parseContinue(tbl); + else + return parseDeclarationOrExpression(tbl, false); + } + + /* block.statement : "{" statement* "}" + */ + private Stmnt parseBlock(SymbolTable tbl) throws CompileError { + if (lex.get() != '{') + throw new SyntaxError(lex); + + Stmnt body = null; + SymbolTable tbl2 = new SymbolTable(tbl); + while (lex.lookAhead() != '}') { + Stmnt s = parseStatement(tbl2); + if (s != null) + body = (Stmnt)ASTList.concat(body, new Stmnt(BLOCK, s)); + } + + lex.get(); // '}' + if (body == null) + return new Stmnt(BLOCK); // empty block + else + return body; + } + + /* if.statement : IF "(" expression ")" statement + * [ ELSE statement ] + */ + private Stmnt parseIf(SymbolTable tbl) throws CompileError { + int t = lex.get(); // IF + if (lex.get() != '(') + throw new SyntaxError(lex); + + ASTree expr = parseExpression(tbl); + if (lex.get() != ')') + throw new SyntaxError(lex); + + Stmnt thenp = parseStatement(tbl); + Stmnt elsep; + if (lex.lookAhead() == ELSE) { + lex.get(); + elsep = parseStatement(tbl); + } + else + elsep = null; + + return new Stmnt(t, expr, new ASTList(thenp, new ASTList(elsep))); + } + + /* while.statement : WHILE "(" expression ")" statement + */ + private Stmnt parseWhile(SymbolTable tbl) + throws CompileError + { + int t = lex.get(); // WHILE + if (lex.get() != '(') + throw new SyntaxError(lex); + + ASTree expr = parseExpression(tbl); + if (lex.get() != ')') + throw new SyntaxError(lex); + + Stmnt body = parseStatement(tbl); + return new Stmnt(t, expr, body); + } + + /* do.statement : DO statement WHILE "(" expression ")" ";" + */ + private Stmnt parseDo(SymbolTable tbl) throws CompileError { + int t = lex.get(); // DO + Stmnt body = parseStatement(tbl); + if (lex.get() != WHILE || lex.get() != '(') + throw new SyntaxError(lex); + + ASTree expr = parseExpression(tbl); + if (lex.get() != ')' || lex.get() != ';') + throw new SyntaxError(lex); + + return new Stmnt(t, expr, body); + } + + /* for.statement : FOR "(" decl.or.expr expression ";" expression ")" + * statement + */ + private Stmnt parseFor(SymbolTable tbl) throws CompileError { + Stmnt expr1, expr3; + ASTree expr2; + int t = lex.get(); // FOR + + SymbolTable tbl2 = new SymbolTable(tbl); + + if (lex.get() != '(') + throw new SyntaxError(lex); + + if (lex.lookAhead() == ';') { + lex.get(); + expr1 = null; + } + else + expr1 = parseDeclarationOrExpression(tbl2, true); + + if (lex.lookAhead() == ';') + expr2 = null; + else + expr2 = parseExpression(tbl2); + + if (lex.get() != ';') + throw new CompileError("; is missing", lex); + + if (lex.lookAhead() == ')') + expr3 = null; + else + expr3 = parseExprList(tbl2); + + if (lex.get() != ')') + throw new CompileError(") is missing", lex); + + Stmnt body = parseStatement(tbl2); + return new Stmnt(t, expr1, new ASTList(expr2, + new ASTList(expr3, body))); + } + + /* switch.statement : SWITCH "(" expression ")" "{" switch.block "}" + * + * swtich.block : ( switch.label* statement )* + * + * swtich.label : DEFAULT ":" + * | CASE const.expression ":" + */ + private Stmnt parseSwitch(SymbolTable tbl) throws CompileError { + throw new CompileError("switch is not supported", lex); + } + + /* try.statement + * : TRY block.statement + * [ CATCH "(" class.type Identifier ")" block.statement ]* + * [ FINALLY block.statement ]* + */ + private Stmnt parseTry(SymbolTable tbl) throws CompileError { + lex.get(); // TRY + Stmnt block = parseBlock(tbl); + ASTList catchList = null; + while (lex.lookAhead() == CATCH) { + lex.get(); // CATCH + if (lex.get() != '(') + throw new SyntaxError(lex); + + SymbolTable tbl2 = new SymbolTable(tbl); + Declarator d = parseFormalParam(tbl2); + if (d.getArrayDim() > 0 || d.getType() != CLASS) + throw new SyntaxError(lex); + + if (lex.get() != ')') + throw new SyntaxError(lex); + + Stmnt b = parseBlock(tbl2); + catchList = ASTList.append(catchList, new Pair(d, b)); + } + + Stmnt finallyBlock = null; + if (lex.lookAhead() == FINALLY) { + lex.get(); // FINALLY + finallyBlock = parseBlock(tbl); + } + + return Stmnt.make(TRY, block, catchList, finallyBlock); + } + + /* return.statement : RETURN [ expression ] ";" + */ + private Stmnt parseReturn(SymbolTable tbl) throws CompileError { + int t = lex.get(); // RETURN + Stmnt s = new Stmnt(t); + if (lex.lookAhead() != ';') + s.setLeft(parseExpression(tbl)); + + if (lex.get() != ';') + throw new CompileError("; is missing", lex); + + return s; + } + + /* throw.statement : THROW expression ";" + */ + private Stmnt parseThrow(SymbolTable tbl) throws CompileError { + int t = lex.get(); // THROW + ASTree expr = parseExpression(tbl); + if (lex.get() != ';') + throw new CompileError("; is missing", lex); + + return new Stmnt(t, expr); + } + + /* break.statement : BREAK [ Identifier ] ";" + */ + private Stmnt parseBreak(SymbolTable tbl) + throws CompileError + { + return parseContinue(tbl); + } + + /* continue.statement : CONTINUE [ Identifier ] ";" + */ + private Stmnt parseContinue(SymbolTable tbl) + throws CompileError + { + int t = lex.get(); // CONTINUE + Stmnt s = new Stmnt(t); + int t2 = lex.get(); + if (t2 == Identifier) { + s.setLeft(new Symbol(lex.getString())); + t2 = lex.get(); + } + + if (t2 != ';') + throw new CompileError("; is missing", lex); + + return s; + } + + /* declaration.or.expression + * : [ FINAL ] built-in-type array.dimension declarators + * | [ FINAL ] class.type array.dimension declarators + * | expression ';' + * | expr.list ';' if exprList is true + * + * Note: FINAL is currently ignored. This must be fixed + * in future. + */ + private Stmnt parseDeclarationOrExpression(SymbolTable tbl, + boolean exprList) + throws CompileError + { + int t = lex.lookAhead(); + while (t == FINAL) { + lex.get(); + t = lex.lookAhead(); + } + + if (isBuiltinType(t)) { + t = lex.get(); + int dim = parseArrayDimension(); + return parseDeclarators(tbl, new Declarator(t, dim)); + } + else if (t == Identifier) { + int i = nextIsClassType(0); + if (i >= 0) + if (lex.lookAhead(i) == Identifier) { + ASTList name = parseClassType(tbl); + int dim = parseArrayDimension(); + return parseDeclarators(tbl, new Declarator(name, dim)); + } + } + + Stmnt expr; + if (exprList) + expr = parseExprList(tbl); + else + expr = new Stmnt(EXPR, parseExpression(tbl)); + + if (lex.get() != ';') + throw new CompileError("; is missing", lex); + + return expr; + } + + /* expr.list : ( expression ',')* expression + */ + private Stmnt parseExprList(SymbolTable tbl) throws CompileError { + Stmnt expr = null; + for (;;) { + Stmnt e = new Stmnt(EXPR, parseExpression(tbl)); + expr = (Stmnt)ASTList.concat(expr, new Stmnt(BLOCK, e)); + if (lex.lookAhead() == ',') + lex.get(); + else + return expr; + } + } + + /* declarators : declarator [ ',' declarator ]* ';' + */ + private Stmnt parseDeclarators(SymbolTable tbl, Declarator d) + throws CompileError + { + Stmnt decl = null; + for (;;) { + decl = (Stmnt)ASTList.concat(decl, + new Stmnt(DECL, parseDeclarator(tbl, d))); + int t = lex.get(); + if (t == ';') + return decl; + else if (t != ',') + throw new CompileError("; is missing", lex); + } + } + + /* declarator : Identifier array.dimension [ '=' initializer ] + */ + private Declarator parseDeclarator(SymbolTable tbl, Declarator d) + throws CompileError + { + if (lex.get() != Identifier || d.getType() == VOID) + throw new SyntaxError(lex); + + String name = lex.getString(); + Symbol symbol = new Symbol(name); + int dim = parseArrayDimension(); + ASTree init = null; + if (lex.lookAhead() == '=') { + lex.get(); + init = parseInitializer(tbl); + } + + Declarator decl = d.make(symbol, dim, init); + tbl.append(name, decl); + return decl; + } + + /* initializer : expression | array.initializer + */ + private ASTree parseInitializer(SymbolTable tbl) throws CompileError { + if (lex.lookAhead() == '{') + return parseArrayInitializer(tbl); + else + return parseExpression(tbl); + } + + /* array.initializer : + * '{' (( array.initializer | expression ) ',')* '}' + */ + private ASTree parseArrayInitializer(SymbolTable tbl) + throws CompileError + { + lex.get(); // '{' + throw new CompileError("array initializer is not supported", lex); + } + + /* expression : conditional.expr + * | conditional.expr assign.op expression (right-to-left) + */ + public ASTree parseExpression(SymbolTable tbl) throws CompileError { + ASTree left = parseConditionalExpr(tbl); + if (!isAssignOp(lex.lookAhead())) + return left; + + int t = lex.get(); + ASTree right = parseExpression(tbl); + return AssignExpr.makeAssign(t, left, right); + } + + private static boolean isAssignOp(int t) { + return t == '=' || t == MOD_E || t == AND_E + || t == MUL_E || t == PLUS_E || t == MINUS_E || t == DIV_E + || t == EXOR_E || t == OR_E || t == LSHIFT_E + || t == RSHIFT_E || t == ARSHIFT_E; + } + + /* conditional.expr (right-to-left) + * : logical.or.expr [ '?' expression ':' conditional.expr ] + */ + private ASTree parseConditionalExpr(SymbolTable tbl) throws CompileError { + ASTree cond = parseBinaryExpr(tbl); + if (lex.lookAhead() == '?') { + lex.get(); + ASTree thenExpr = parseExpression(tbl); + if (lex.get() != ':') + throw new CompileError(": is missing", lex); + + ASTree elseExpr = parseExpression(tbl); + return new CondExpr(cond, thenExpr, elseExpr); + } + else + return cond; + } + + /* logical.or.expr 10 (operator precedence) + * : logical.and.expr + * | logical.or.expr OROR logical.and.expr left-to-right + * + * logical.and.expr 9 + * : inclusive.or.expr + * | logical.and.expr ANDAND inclusive.or.expr + * + * inclusive.or.expr 8 + * : exclusive.or.expr + * | inclusive.or.expr "|" exclusive.or.expr + * + * exclusive.or.expr 7 + * : and.expr + * | exclusive.or.expr "^" and.expr + * + * and.expr 6 + * : equality.expr + * | and.expr "&" equality.expr + * + * equality.expr 5 + * : relational.expr + * | equality.expr (EQ | NEQ) relational.expr + * + * relational.expr 4 + * : shift.expr + * | relational.expr (LE | GE | "<" | ">") shift.expr + * | relational.expr INSTANCEOF class.type ("[" "]")* + * + * shift.expr 3 + * : additive.expr + * | shift.expr (LSHIFT | RSHIFT | ARSHIFT) additive.expr + * + * additive.expr 2 + * : multiply.expr + * | additive.expr ("+" | "-") multiply.expr + * + * multiply.expr 1 + * : unary.expr + * | multiply.expr ("*" | "/" | "%") unary.expr + */ + private ASTree parseBinaryExpr(SymbolTable tbl) throws CompileError { + ASTree expr = parseUnaryExpr(tbl); + for (;;) { + int t = lex.lookAhead(); + int p = getOpPrecedence(t); + if (p == 0) + return expr; + else + expr = binaryExpr2(tbl, expr, p); + } + } + + private ASTree parseInstanceOf(SymbolTable tbl, ASTree expr) + throws CompileError + { + int t = lex.lookAhead(); + if (isBuiltinType(t)) { + lex.get(); // primitive type + int dim = parseArrayDimension(); + return new InstanceOfExpr(t, dim, expr); + } + else { + ASTList name = parseClassType(tbl); + int dim = parseArrayDimension(); + return new InstanceOfExpr(name, dim, expr); + } + } + + private ASTree binaryExpr2(SymbolTable tbl, ASTree expr, int prec) + throws CompileError + { + int t = lex.get(); + if (t == INSTANCEOF) + return parseInstanceOf(tbl, expr); + + ASTree expr2 = parseUnaryExpr(tbl); + for (;;) { + int t2 = lex.lookAhead(); + int p2 = getOpPrecedence(t2); + if (p2 != 0 && prec > p2) + expr2 = binaryExpr2(tbl, expr2, p2); + else + return BinExpr.makeBin(t, expr, expr2); + } + } + + // !"#$%&'( )*+,-./0 12345678 9:;<=>? + private static final int[] binaryOpPrecedence + = { 0, 0, 0, 0, 1, 6, 0, 0, + 0, 1, 2, 0, 2, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 4, 0, 4, 0 }; + + private int getOpPrecedence(int c) { + if ('!' <= c && c <= '?') + return binaryOpPrecedence[c - '!']; + else if (c == '^') + return 7; + else if (c == '|') + return 8; + else if (c == ANDAND) + return 9; + else if (c == OROR) + return 10; + else if (c == EQ || c == NEQ) + return 5; + else if (c == LE || c == GE || c == INSTANCEOF) + return 4; + else if (c == LSHIFT || c == RSHIFT || c == ARSHIFT) + return 3; + else + return 0; // not a binary operator + } + + /* unary.expr : "++"|"--" unary.expr + | "+"|"-" unary.expr + | "!"|"~" unary.expr + | cast.expr + | postfix.expr + + unary.expr.not.plus.minus is a unary expression starting without + "+", "-", "++", or "--". + */ + private ASTree parseUnaryExpr(SymbolTable tbl) throws CompileError { + int t; + switch (lex.lookAhead()) { + case '+' : + case '-' : + case PLUSPLUS : + case MINUSMINUS : + case '!' : + case '~' : + t = lex.get(); + return new Expr(t, parseUnaryExpr(tbl)); + case '(' : + return parseCast(tbl); + default : + return parsePostfix(tbl); + } + } + + /* cast.expr : "(" builtin.type ("[" "]")* ")" unary.expr + | "(" class.type ("[" "]")* ")" unary.expr2 + + unary.expr2 is a unary.expr begining with "(", NULL, StringL, + Identifier, THIS, SUPER, or NEW. + */ + private ASTree parseCast(SymbolTable tbl) throws CompileError { + int t = lex.lookAhead(1); + if (isBuiltinType(t)) { + lex.get(); // '(' + lex.get(); // primitive type + int dim = parseArrayDimension(); + if (lex.get() != ')') + throw new CompileError(") is missing", lex); + + return new CastExpr(t, dim, parseUnaryExpr(tbl)); + } + else if (t == Identifier && nextIsClassCast()) { + lex.get(); // '(' + ASTList name = parseClassType(tbl); + int dim = parseArrayDimension(); + if (lex.get() != ')') + throw new CompileError(") is missing", lex); + + return new CastExpr(name, dim, parseUnaryExpr(tbl)); + } + else + return parsePostfix(tbl); + } + + private boolean nextIsClassCast() { + int i = nextIsClassType(1); + if (i < 0) + return false; + + int t = lex.lookAhead(i); + if (t != ')') + return false; + + t = lex.lookAhead(i + 1); + return t == '(' || t == NULL || t == StringL + || t == Identifier || t == THIS || t == SUPER || t == NEW + || t == TRUE || t == FALSE || t == LongConstant + || t == IntConstant || t == CharConstant + || t == DoubleConstant || t == FloatConstant; + } + + private int nextIsClassType(int i) { + int t; + while (lex.lookAhead(++i) == '.') + if (lex.lookAhead(++i) != Identifier) + return -1; + + while ((t = lex.lookAhead(i++)) == '[') + if (lex.lookAhead(i++) != ']') + return -1; + + return i - 1; + } + + /* array.dimension : [ "[" "]" ]* + */ + private int parseArrayDimension() throws CompileError { + int arrayDim = 0; + while (lex.lookAhead() == '[') { + ++arrayDim; + lex.get(); + if (lex.get() != ']') + throw new CompileError("] is missing", lex); + } + + return arrayDim; + } + + /* class.type : Identifier ( "." Identifier )* + */ + private ASTList parseClassType(SymbolTable tbl) throws CompileError { + ASTList list = null; + for (;;) { + if (lex.get() != Identifier) + throw new SyntaxError(lex); + + list = ASTList.append(list, new Symbol(lex.getString())); + if (lex.lookAhead() == '.') + lex.get(); + else + break; + } + + return list; + } + + /* postfix.expr : number.literal + * | primary.expr + * | method.expr + * | postfix.expr "++" | "--" + * | postfix.expr "[" array.size "]" + * | postfix.expr "." Identifier + * | postfix.expr "#" Identifier + * + * "#" is not an operator of regular Java. It separates + * a class name and a member name in an expression for static member + * access. For example, + * java.lang.Integer.toString(3) in regular Java + * must be written like this: + * java.lang.Integer#toString(3) for this compiler. + */ + private ASTree parsePostfix(SymbolTable tbl) throws CompileError { + int token = lex.lookAhead(); + switch (token) { + case LongConstant : + case IntConstant : + case CharConstant : + lex.get(); + return new IntConst(lex.getLong(), token); + case DoubleConstant : + case FloatConstant : + lex.get(); + return new DoubleConst(lex.getDouble(), token); + default : + break; + } + + String str; + ASTree index; + ASTree expr = parsePrimaryExpr(tbl); + int t; + while (true) { + switch (lex.lookAhead()) { + case '(' : + expr = parseMethodCall(tbl, expr); + break; + case '[' : + index = parseArrayIndex(tbl); + if (index == null) + throw new SyntaxError(lex); + + expr = Expr.make(ARRAY, expr, index); + break; + case PLUSPLUS : + case MINUSMINUS : + t = lex.get(); + expr = Expr.make(t, null, expr); + break; + case '.' : + lex.get(); + if (lex.get() != Identifier) + throw new CompileError("missing member name", lex); + + expr = Expr.make('.', expr, new Member(lex.getString())); + break; + case '#' : + lex.get(); + t = lex.get(); + if (t == CLASS) + str = "class"; + else if (t == Identifier) + str = lex.getString(); + else + throw new CompileError("missing static member name", lex); + + expr = Expr.make(MEMBER, toClassName(expr, null), + new Member(str)); + break; + default : + return expr; + } + } + } + + /* method.call : method.expr "(" argument.list ")" + * method.expr : THIS | SUPER | Identifier + * | postfix.expr "." Identifier + * | postfix.expr "#" Identifier + */ + private ASTree parseMethodCall(SymbolTable tbl, ASTree expr) + throws CompileError + { + if (expr instanceof Keyword) { + int token = ((Keyword)expr).get(); + if (token != THIS && token != SUPER) + throw new SyntaxError(lex); + } + else if (expr instanceof Symbol) // Identifier + ; + else if (expr instanceof Expr) { + int op = ((Expr)expr).getOperator(); + if (op != '.' && op != MEMBER) + throw new SyntaxError(lex); + } + + return Expr.make(CALL, expr, parseArgumentList(tbl)); + } + + + private ASTList toClassName(ASTree name, ASTList tail) + throws CompileError + { + if (name instanceof Symbol) + return new ASTList(name, tail); + else if (name instanceof Expr) { + Expr expr = (Expr)name; + if (expr.getOperator() == '.') + return toClassName(expr.oprand1(), + new ASTList(expr.oprand2(), tail)); + } + + throw new CompileError("bad static member access", lex); + } + + /* primary.expr : THIS | SUPER | TRUE | FALSE | NULL + * | StringL + * | Identifier + * | NEW new.expr + * | "(" expression ")" + * + * Identifier represents either a local variable name, a member name, + * or a class name. + */ + private ASTree parsePrimaryExpr(SymbolTable tbl) throws CompileError { + int t; + String name; + Declarator decl; + ASTree expr; + + switch (t = lex.get()) { + case THIS : + case SUPER : + case TRUE : + case FALSE : + case NULL : + return new Keyword(t); + case Identifier : + name = lex.getString(); + decl = tbl.lookup(name); + if (decl == null) + return new Member(name); // this or static member + else + return new Variable(name, decl); // local variable + case StringL : + return new StringL(lex.getString()); + case NEW : + return parseNew(tbl); + case '(' : + expr = parseExpression(tbl); + if (lex.get() == ')') + return expr; + else + throw new CompileError(") is missing", lex); + default : + throw new SyntaxError(lex); + } + } + + /* new.expr : class.type "(" argument.list ")" + * | class.type array.size [ array.initializer ] + * | primitive.type array.size [ array.initializer ] + */ + private NewExpr parseNew(SymbolTable tbl) throws CompileError { + ASTree init = null; + int t = lex.lookAhead(); + if (isBuiltinType(t)) { + lex.get(); + ASTList size = parseArraySize(tbl); + if (lex.lookAhead() == '{') + init = parseArrayInitializer(tbl); + + return new NewExpr(t, size, init); + } + else if (t == Identifier) { + ASTList name = parseClassType(tbl); + t = lex.lookAhead(); + if (t == '(') { + ASTList args = parseArgumentList(tbl); + return new NewExpr(name, args); + } + else if (t == '[') { + ASTList size = parseArraySize(tbl); + if (lex.lookAhead() == '{') + init = parseArrayInitializer(tbl); + + return NewExpr.makeObjectArray(name, size, init); + } + } + + throw new SyntaxError(lex); + } + + /* array.size : [ array.index ]* + */ + private ASTList parseArraySize(SymbolTable tbl) throws CompileError { + ASTList list = null; + while (lex.lookAhead() == '[') + list = ASTList.append(list, parseArrayIndex(tbl)); + + return list; + } + + /* array.index : "[" [ expression ] "]" + */ + private ASTree parseArrayIndex(SymbolTable tbl) throws CompileError { + lex.get(); // '[' + if (lex.lookAhead() == ']') { + lex.get(); + return null; + } + else { + ASTree index = parseExpression(tbl); + if (lex.get() != ']') + throw new CompileError("] is missing", lex); + + return index; + } + } + + /* argument.list : "(" [ expression [ "," expression ]* ] ")" + */ + private ASTList parseArgumentList(SymbolTable tbl) throws CompileError { + if (lex.get() != '(') + throw new CompileError("( is missing", lex); + + ASTList list = null; + if (lex.lookAhead() != ')') + for (;;) { + list = ASTList.append(list, parseExpression(tbl)); + if (lex.lookAhead() == ',') + lex.get(); + else + break; + } + + if (lex.get() != ')') + throw new CompileError(") is missing", lex); + + return list; + } +} + diff --git a/src/main/javassist/compiler/ProceedHandler.java b/src/main/javassist/compiler/ProceedHandler.java new file mode 100644 index 00000000..a787223e --- /dev/null +++ b/src/main/javassist/compiler/ProceedHandler.java @@ -0,0 +1,40 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler; + +import javassist.bytecode.Bytecode; +import javassist.compiler.ast.ASTList; + +/** + * An interface to an object for implementing $proceed(). + * + * @see javassist.compiler.JvstCodeGen#setProceedHandler(ProceedHandler, String) + * @see javassist.compiler.JvstCodeGen#atMethodCall(Expr) + */ +public interface ProceedHandler { + void doit(JvstCodeGen gen, Bytecode b, ASTList args) + throws CompileError; +} diff --git a/src/main/javassist/compiler/SymbolTable.java b/src/main/javassist/compiler/SymbolTable.java new file mode 100644 index 00000000..be318811 --- /dev/null +++ b/src/main/javassist/compiler/SymbolTable.java @@ -0,0 +1,54 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler; + +import java.util.HashMap; +import javassist.compiler.ast.Declarator; + +public final class SymbolTable extends HashMap { + private SymbolTable parent; + + public SymbolTable() { this(null); } + + public SymbolTable(SymbolTable p) { + super(); + parent = p; + } + + public SymbolTable getParent() { return parent; } + + public Declarator lookup(String name) { + Declarator found = (Declarator)get(name); + if (found == null && parent != null) + return parent.lookup(name); + else + return found; + } + + public void append(String name, Declarator value) { + put(name, value); + } +} diff --git a/src/main/javassist/compiler/SyntaxError.java b/src/main/javassist/compiler/SyntaxError.java new file mode 100644 index 00000000..f4c06d8f --- /dev/null +++ b/src/main/javassist/compiler/SyntaxError.java @@ -0,0 +1,32 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler; + +public class SyntaxError extends CompileError { + public SyntaxError(Lex l) { + super("syntax error", l); + } +} diff --git a/src/main/javassist/compiler/TokenId.java b/src/main/javassist/compiler/TokenId.java new file mode 100644 index 00000000..8b78bf00 --- /dev/null +++ b/src/main/javassist/compiler/TokenId.java @@ -0,0 +1,134 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler; + +public interface TokenId { + int ABSTRACT = 300; + int BOOLEAN = 301; + int BREAK = 302; + int BYTE = 303; + int CASE = 304; + int CATCH = 305; + int CHAR = 306; + int CLASS = 307; + int CONST = 308; // reserved keyword + int CONTINUE = 309; + int DEFAULT = 310; + int DO = 311; + int DOUBLE = 312; + int ELSE = 313; + int EXTENDS = 314; + int FINAL = 315; + int FINALLY = 316; + int FLOAT = 317; + int FOR = 318; + int GOTO = 319; // reserved keyword + int IF = 320; + int IMPLEMENTS = 321; + int IMPORT = 322; + int INSTANCEOF = 323; + int INT = 324; + int INTERFACE = 325; + int LONG = 326; + int NATIVE = 327; + int NEW = 328; + int PACKAGE = 329; + int PRIVATE = 330; + int PROTECTED = 331; + int PUBLIC = 332; + int RETURN = 333; + int SHORT = 334; + int STATIC = 335; + int SUPER = 336; + int SWITCH = 337; + int SYNCHRONIZED = 338; + int THIS = 339; + int THROW = 340; + int THROWS = 341; + int TRANSIENT = 342; + int TRY = 343; + int VOID = 344; + int VOLATILE = 345; + int WHILE = 346; + int STRICT = 347; + + int NEQ = 350; // != + int MOD_E = 351; // %= + int AND_E = 352; // &= + int MUL_E = 353; // *= + int PLUS_E = 354; // += + int MINUS_E = 355; // -= + int DIV_E = 356; // /= + int LE = 357; // <= + int EQ = 358; // == + int GE = 359; // >= + int EXOR_E = 360; // ^= + int OR_E = 361; // |= + int PLUSPLUS = 362; // ++ + int MINUSMINUS = 363; // -- + int LSHIFT = 364; // << + int LSHIFT_E = 365; // <<= + int RSHIFT = 366; // >> + int RSHIFT_E = 367; // >>= + int OROR = 368; // || + int ANDAND = 369; // && + int ARSHIFT = 370; // >>> + int ARSHIFT_E = 371; // >>>= + + // operators from NEQ to ARSHIFT_E + String opNames[] = { "!=", "%=", "&=", "*=", "+=", "-=", "/=", + "<=", "==", ">=", "^=", "|=", "++", "--", + "<<", "<<=", ">>", ">>=", "||", "&&", ">>>", + ">>>=" }; + + // operators from MOD_E to ARSHIFT_E + int assignOps[] = { '%', '&', '*', '+', '-', '/', 0, 0, 0, + '^', '|', 0, 0, 0, LSHIFT, 0, RSHIFT, 0, 0, 0, + ARSHIFT }; + + int Identifier = 400; + int CharConstant = 401; + int IntConstant = 402; + int LongConstant = 403; + int FloatConstant = 404; + int DoubleConstant = 405; + int StringL = 406; + + int TRUE = 410; + int FALSE = 411; + int NULL = 412; + + int CALL = 'C'; // method call + int ARRAY = 'A'; // array access + int MEMBER = '#'; // static member access + + int EXPR = 'E'; // expression statement + int LABEL = 'L'; // label statement + int BLOCK = 'B'; // block statement + int DECL = 'D'; // declaration statement + + int BadToken = 500; +} diff --git a/src/main/javassist/compiler/ast/ASTList.java b/src/main/javassist/compiler/ast/ASTList.java new file mode 100644 index 00000000..887a528a --- /dev/null +++ b/src/main/javassist/compiler/ast/ASTList.java @@ -0,0 +1,169 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * A linked list. + * The right subtree must be an ASTList object or null. + */ +public class ASTList extends ASTree { + private ASTree left; + private ASTList right; + + public ASTList(ASTree _head, ASTList _tail) { + left = _head; + right = _tail; + } + + public ASTList(ASTree _head) { + left = _head; + right = null; + } + + public static ASTList make(ASTree e1, ASTree e2, ASTree e3) { + return new ASTList(e1, new ASTList(e2, new ASTList(e3))); + } + + public ASTree getLeft() { return left; } + + public ASTree getRight() { return right; } + + public void setLeft(ASTree _left) { left = _left; } + + public void setRight(ASTree _right) { + right = (ASTList)_right; + } + + /** + * Returns the car part of the list. + */ + public ASTree head() { return left; } + + public void setHead(ASTree _head) { + left = _head; + } + + /** + * Returns the cdr part of the list. + */ + public ASTList tail() { return right; } + + public void setTail(ASTList _tail) { + right = _tail; + } + + public void accept(Visitor v) throws CompileError { v.atASTList(this); } + + public String toString() { + StringBuffer sbuf = new StringBuffer(); + sbuf.append("(<"); + sbuf.append(getTag()); + sbuf.append('>'); + ASTList list = this; + while (list != null) { + sbuf.append(' '); + ASTree a = list.left; + sbuf.append(a == null ? "<null>" : a.toString()); + list = list.right; + } + + sbuf.append(')'); + return sbuf.toString(); + } + + /** + * Returns the number of the elements in this list. + */ + public int length() { + return length(this); + } + + public static int length(ASTList list) { + if (list == null) + return 0; + + int n = 0; + while (list != null) { + list = list.right; + ++n; + } + + return n; + } + + /** + * Returns a sub list of the list. The sub list begins with the + * n-th element of the list. + * + * @param nth zero or more than zero. + */ + public ASTList sublist(int nth) { + ASTList list = this; + while (nth-- > 0) + list = list.right; + + return list; + } + + /** + * Substitutes <code>newObj</code> for <code>oldObj</code> in the + * list. + */ + public boolean subst(ASTree newObj, ASTree oldObj) { + for (ASTList list = this; list != null; list = list.right) + if (list.left == oldObj) { + list.left = newObj; + return true; + } + + return false; + } + + /** + * Appends an object to a list. + */ + public static ASTList append(ASTList a, ASTree b) { + return concat(a, new ASTList(b)); + } + + /** + * Concatenates two lists. + */ + public static ASTList concat(ASTList a, ASTList b) { + if (a == null) + return b; + else { + ASTList list = a; + while (list.right != null) + list = list.right; + + list.right = b; + return a; + } + } +} diff --git a/src/main/javassist/compiler/ast/ASTree.java b/src/main/javassist/compiler/ast/ASTree.java new file mode 100644 index 00000000..72825142 --- /dev/null +++ b/src/main/javassist/compiler/ast/ASTree.java @@ -0,0 +1,68 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler.ast; + +import java.io.Serializable; +import javassist.compiler.CompileError; + +/** + * Abstract Syntax Tree. An ASTree object represents a node of + * a binary tree. If the node is a leaf node, both <code>getLeft()</code> + * and <code>getRight()</code> returns null. + */ +public abstract class ASTree implements Serializable { + public ASTree getLeft() { return null; } + + public ASTree getRight() { return null; } + + public void setLeft(ASTree _left) {} + + public void setRight(ASTree _right) {} + + /** + * Is a method for the visitor pattern. It calls + * <code>atXXX()</code> on the given visitor, where + * <code>XXX</code> is the class name of the node object. + */ + public abstract void accept(Visitor v) throws CompileError; + + public String toString() { + StringBuffer sbuf = new StringBuffer(); + sbuf.append('<'); + sbuf.append(getTag()); + sbuf.append('>'); + return sbuf.toString(); + } + + /** + * Returns the type of this node. This method is used by + * <code>toString()</code>. + */ + protected String getTag() { + String name = getClass().getName(); + return name.substring(name.lastIndexOf('.') + 1); + } +} diff --git a/src/main/javassist/compiler/ast/AssignExpr.java b/src/main/javassist/compiler/ast/AssignExpr.java new file mode 100644 index 00000000..0a062d46 --- /dev/null +++ b/src/main/javassist/compiler/ast/AssignExpr.java @@ -0,0 +1,50 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * Assignment expression. + */ +public class AssignExpr extends Expr { + /* operator must be either of: + * =, %=, &=, *=, +=, -=, /=, ^=, |=, <<=, >>=, >>>= + */ + + public AssignExpr(int op, ASTree _head, ASTList _tail) { + super(op, _head, _tail); + } + + public static AssignExpr makeAssign(int op, ASTree oprand1, + ASTree oprand2) { + return new AssignExpr(op, oprand1, new ASTList(oprand2)); + } + + public void accept(Visitor v) throws CompileError { + v.atAssignExpr(this); + } +} diff --git a/src/main/javassist/compiler/ast/BinExpr.java b/src/main/javassist/compiler/ast/BinExpr.java new file mode 100644 index 00000000..a8fdeb62 --- /dev/null +++ b/src/main/javassist/compiler/ast/BinExpr.java @@ -0,0 +1,48 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * Binary expression. + */ +public class BinExpr extends Expr { + /* operator must be either of: + * ||, &&, |, ^, &, ==, !=, <=, >=, <, >, + * <<, >>, >>>, +, -, *, /, % + */ + + public BinExpr(int op, ASTree _head, ASTList _tail) { + super(op, _head, _tail); + } + + public static BinExpr makeBin(int op, ASTree oprand1, ASTree oprand2) { + return new BinExpr(op, oprand1, new ASTList(oprand2)); + } + + public void accept(Visitor v) throws CompileError { v.atBinExpr(this); } +} diff --git a/src/main/javassist/compiler/ast/CastExpr.java b/src/main/javassist/compiler/ast/CastExpr.java new file mode 100644 index 00000000..7f8775cb --- /dev/null +++ b/src/main/javassist/compiler/ast/CastExpr.java @@ -0,0 +1,63 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler.ast; + +import javassist.compiler.TokenId; +import javassist.compiler.CompileError; + +/** + * Cast expression. + */ +public class CastExpr extends ASTList implements TokenId { + protected int castType; + protected int arrayDim; + + public CastExpr(ASTList className, int dim, ASTree expr) { + super(className, new ASTList(expr)); + castType = CLASS; + arrayDim = dim; + } + + public CastExpr(int type, int dim, ASTree expr) { + super(null, new ASTList(expr)); + castType = type; + arrayDim = dim; + } + + /* Returns CLASS, BOOLEAN, INT, or ... + */ + public int getType() { return castType; } + + public int getArrayDim() { return arrayDim; } + + public ASTList getClassName() { return (ASTList)getLeft(); } + + public ASTree getOprand() { return getRight().getLeft(); } + + public String getTag() { return "cast:" + castType + ":" + arrayDim; } + + public void accept(Visitor v) throws CompileError { v.atCastExpr(this); } +} diff --git a/src/main/javassist/compiler/ast/CondExpr.java b/src/main/javassist/compiler/ast/CondExpr.java new file mode 100644 index 00000000..d4a8a108 --- /dev/null +++ b/src/main/javassist/compiler/ast/CondExpr.java @@ -0,0 +1,47 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * Conditional expression. + */ +public class CondExpr extends ASTList { + public CondExpr(ASTree cond, ASTree thenp, ASTree elsep) { + super(cond, new ASTList(thenp, new ASTList(elsep))); + } + + public ASTree condExpr() { return head(); } + + public ASTree thenExpr() { return tail().head(); } + + public ASTree elseExpr() { return tail().tail().head(); } + + public String getTag() { return "?:"; } + + public void accept(Visitor v) throws CompileError { v.atCondExpr(this); } +} diff --git a/src/main/javassist/compiler/ast/Declarator.java b/src/main/javassist/compiler/ast/Declarator.java new file mode 100644 index 00000000..edeb839d --- /dev/null +++ b/src/main/javassist/compiler/ast/Declarator.java @@ -0,0 +1,128 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler.ast; + +import javassist.compiler.TokenId; +import javassist.compiler.CompileError; + +/** + * Variable declarator. + */ +public class Declarator extends ASTList implements TokenId { + protected int varType; + protected int arrayDim; + protected int localVar; + protected String qualifiedClass; // JVM-internal representation + + public Declarator(int type, int dim) { + super(null); + varType = type; + arrayDim = dim; + localVar = -1; + qualifiedClass = null; + } + + public Declarator(ASTList className, int dim) { + super(null); + varType = CLASS; + arrayDim = dim; + localVar = -1; + qualifiedClass = astToClassName(className, '/'); + } + + /* For declaring a pre-defined? local variable. + */ + public Declarator(int type, String jvmClassName, int dim, + int var, Symbol sym) { + super(null); + varType = type; + arrayDim = dim; + localVar = var; + qualifiedClass = jvmClassName; + setLeft(sym); + append(this, null); // initializer + } + + public Declarator make(Symbol sym, int dim, ASTree init) { + Declarator d = new Declarator(this.varType, this.arrayDim + dim); + d.qualifiedClass = this.qualifiedClass; + d.setLeft(sym); + d.append(d, init); + return d; + } + + /* Returns CLASS, BOOLEAN, BYTE, CHAR, SHORT, INT, LONG, FLOAT, + * or DOUBLE (or VOID) + */ + public int getType() { return varType; } + + public int getArrayDim() { return arrayDim; } + + public void addArrayDim(int d) { arrayDim += d; } + + public String getClassName() { return qualifiedClass; } + + public void setClassName(String s) { qualifiedClass = s; } + + public Symbol getVariable() { return (Symbol)getLeft(); } + + public void setVariable(Symbol sym) { setLeft(sym); } + + public ASTree getInitializer() { + ASTList t = tail(); + if (t != null) + return t.head(); + else + return null; + } + + public void setLocalVar(int n) { localVar = n; } + + public int getLocalVar() { return localVar; } + + public String getTag() { return "decl"; } + + public void accept(Visitor v) throws CompileError { + v.atDeclarator(this); + } + + public static String astToClassName(ASTList name, char sep) { + if (name == null) + return null; + + StringBuffer sbuf = new StringBuffer(); + for (;;) { + sbuf.append(((Symbol)name.head()).get()); + name = name.tail(); + if (name == null) + break; + + sbuf.append(sep); + } + + return sbuf.toString(); + } +} diff --git a/src/main/javassist/compiler/ast/DoubleConst.java b/src/main/javassist/compiler/ast/DoubleConst.java new file mode 100644 index 00000000..459f030f --- /dev/null +++ b/src/main/javassist/compiler/ast/DoubleConst.java @@ -0,0 +1,50 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * Double constant. + */ +public class DoubleConst extends ASTree { + protected double value; + protected int type; + + public DoubleConst(double v, int tokenId) { value = v; type = tokenId; } + + public double get() { return value; } + + /* Returns DoubleConstant or FloatConstant + */ + public int getType() { return type; } + + public String toString() { return Double.toString(value); } + + public void accept(Visitor v) throws CompileError { + v.atDoubleConst(this); + } +} diff --git a/src/main/javassist/compiler/ast/Expr.java b/src/main/javassist/compiler/ast/Expr.java new file mode 100644 index 00000000..1fecc9ce --- /dev/null +++ b/src/main/javassist/compiler/ast/Expr.java @@ -0,0 +1,80 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler.ast; + +import javassist.compiler.TokenId; +import javassist.compiler.CompileError; + +/** + * Expression. + */ +public class Expr extends ASTList implements TokenId { + /* operator must be either of: + * (unary) +, (unary) -, ++, --, !, ~, + * CALL, ARRAY, . (dot), MEMBER (static member access). + * Otherwise, the object should be an instance of a subclass. + */ + + protected int operatorId; + + public Expr(int op, ASTree _head, ASTList _tail) { + super(_head, _tail); + operatorId = op; + } + + public Expr(int op, ASTree _head) { + super(_head); + operatorId = op; + } + + public static Expr make(int op, ASTree oprand1, ASTree oprand2) { + return new Expr(op, oprand1, new ASTList(oprand2)); + } + + public int getOperator() { return operatorId; } + + public ASTree oprand1() { return getLeft(); } + + public ASTree oprand2() { return getRight().getLeft(); } + + public void accept(Visitor v) throws CompileError { v.atExpr(this); } + + public String getName() { + int id = operatorId; + if (id < 128) + return String.valueOf((char)id); + else if (NEQ <= id && id <= ARSHIFT_E) + return opNames[id - NEQ]; + else if (id == INSTANCEOF) + return "instanceof"; + else + return String.valueOf(id); + } + + protected String getTag() { + return "op:" + getName(); + } +} diff --git a/src/main/javassist/compiler/ast/FieldDecl.java b/src/main/javassist/compiler/ast/FieldDecl.java new file mode 100644 index 00000000..d57dd632 --- /dev/null +++ b/src/main/javassist/compiler/ast/FieldDecl.java @@ -0,0 +1,44 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +public class FieldDecl extends ASTList { + public FieldDecl(ASTree _head, ASTList _tail) { + super(_head, _tail); + } + + public ASTList getModifiers() { return (ASTList)getLeft(); } + + public Declarator getDeclarator() { return (Declarator)tail().head(); } + + public ASTree getInit() { return (ASTree)sublist(2).head(); } + + public void accept(Visitor v) throws CompileError { + v.atFieldDecl(this); + } +} diff --git a/src/main/javassist/compiler/ast/InstanceOfExpr.java b/src/main/javassist/compiler/ast/InstanceOfExpr.java new file mode 100644 index 00000000..03d6bc1f --- /dev/null +++ b/src/main/javassist/compiler/ast/InstanceOfExpr.java @@ -0,0 +1,49 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * Instanceof expression. + */ +public class InstanceOfExpr extends CastExpr { + public InstanceOfExpr(ASTList className, int dim, ASTree expr) { + super(className, dim, expr); + } + + public InstanceOfExpr(int type, int dim, ASTree expr) { + super(type, dim, expr); + } + + public String getTag() { + return "instanceof:" + castType + ":" + arrayDim; + } + + public void accept(Visitor v) throws CompileError { + v.atInstanceOfExpr(this); + } +} diff --git a/src/main/javassist/compiler/ast/IntConst.java b/src/main/javassist/compiler/ast/IntConst.java new file mode 100644 index 00000000..63ce70cb --- /dev/null +++ b/src/main/javassist/compiler/ast/IntConst.java @@ -0,0 +1,50 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * Integer constant. + */ +public class IntConst extends ASTree { + protected long value; + protected int type; + + public IntConst(long v, int tokenId) { value = v; type = tokenId; } + + public long get() { return value; } + + /* Returns IntConstant, CharConstant, or LongConstant. + */ + public int getType() { return type; } + + public String toString() { return Long.toString(value); } + + public void accept(Visitor v) throws CompileError { + v.atIntConst(this); + } +} diff --git a/src/main/javassist/compiler/ast/Keyword.java b/src/main/javassist/compiler/ast/Keyword.java new file mode 100644 index 00000000..9e1798ec --- /dev/null +++ b/src/main/javassist/compiler/ast/Keyword.java @@ -0,0 +1,45 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * Keyword. + */ +public class Keyword extends ASTree { + protected int tokenId; + + public Keyword(int token) { + tokenId = token; + } + + public int get() { return tokenId; } + + public String toString() { return "id:" + tokenId; } + + public void accept(Visitor v) throws CompileError { v.atKeyword(this); } +} diff --git a/src/main/javassist/compiler/ast/Member.java b/src/main/javassist/compiler/ast/Member.java new file mode 100644 index 00000000..a4825874 --- /dev/null +++ b/src/main/javassist/compiler/ast/Member.java @@ -0,0 +1,39 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * Member name. + */ +public class Member extends Symbol { + public Member(String name) { + super(name); + } + + public void accept(Visitor v) throws CompileError { v.atMember(this); } +} diff --git a/src/main/javassist/compiler/ast/MethodDecl.java b/src/main/javassist/compiler/ast/MethodDecl.java new file mode 100644 index 00000000..3c529014 --- /dev/null +++ b/src/main/javassist/compiler/ast/MethodDecl.java @@ -0,0 +1,55 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +public class MethodDecl extends ASTList { + public static final String initName = "<init>"; + + public MethodDecl(ASTree _head, ASTList _tail) { + super(_head, _tail); + } + + public boolean isConstructor() { + Symbol sym = getReturn().getVariable(); + return sym != null && initName.equals(sym.get()); + } + + public ASTList getModifiers() { return (ASTList)getLeft(); } + + public Declarator getReturn() { return (Declarator)tail().head(); } + + public ASTList getParams() { return (ASTList)sublist(2).head(); } + + public ASTList getThrows() { return (ASTList)sublist(3).head(); } + + public Stmnt getBody() { return (Stmnt)sublist(4).head(); } + + public void accept(Visitor v) throws CompileError { + v.atMethodDecl(this); + } +} diff --git a/src/main/javassist/compiler/ast/NewExpr.java b/src/main/javassist/compiler/ast/NewExpr.java new file mode 100644 index 00000000..33c721ed --- /dev/null +++ b/src/main/javassist/compiler/ast/NewExpr.java @@ -0,0 +1,87 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler.ast; + +import javassist.compiler.TokenId; +import javassist.compiler.CompileError; + +/** + * New Expression. + */ +public class NewExpr extends ASTList implements TokenId { + protected boolean newArray; + protected int arrayType; + + public NewExpr(ASTList className, ASTList args) { + super(className, new ASTList(args)); + newArray = false; + arrayType = CLASS; + } + + public NewExpr(int type, ASTList arraySize, ASTree init) { + super(null, new ASTList(arraySize)); + newArray = true; + arrayType = type; + if (init != null) + append(this, init); + } + + public static NewExpr makeObjectArray(ASTList className, + ASTList arraySize, ASTree init) { + NewExpr e = new NewExpr(className, arraySize); + e.newArray = true; + if (init != null) + append(e, init); + + return e; + } + + public boolean isArray() { return newArray; } + + /* TokenId.CLASS, TokenId.INT, ... + */ + public int getArrayType() { return arrayType; } + + public ASTList getClassName() { return (ASTList)getLeft(); } + + public ASTList getArguments() { return (ASTList)getRight().getLeft(); } + + public ASTList getArraySize() { return getArguments(); } + + public ASTree getInitializer() { + ASTree t = getRight().getRight(); + if (t == null) + return null; + else + return t.getLeft(); + } + + public void accept(Visitor v) throws CompileError { v.atNewExpr(this); } + + protected String getTag() { + return newArray ? "new[]" : "new"; + } +} diff --git a/src/main/javassist/compiler/ast/Pair.java b/src/main/javassist/compiler/ast/Pair.java new file mode 100644 index 00000000..520207a8 --- /dev/null +++ b/src/main/javassist/compiler/ast/Pair.java @@ -0,0 +1,61 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * A node of a a binary tree. This class provides concrete methods + * overriding abstract methods in ASTree. + */ +public class Pair extends ASTree { + protected ASTree left, right; + + public Pair(ASTree _left, ASTree _right) { + left = _left; + right = _right; + } + + public void accept(Visitor v) throws CompileError { v.atPair(this); } + + public String toString() { + StringBuffer sbuf = new StringBuffer(); + sbuf.append("(<Pair> "); + sbuf.append(left == null ? "<null>" : left.toString()); + sbuf.append(" . "); + sbuf.append(right == null ? "<null>" : right.toString()); + sbuf.append(')'); + return sbuf.toString(); + } + + public ASTree getLeft() { return left; } + + public ASTree getRight() { return right; } + + public void setLeft(ASTree _left) { left = _left; } + + public void setRight(ASTree _right) { right = _right; } +} diff --git a/src/main/javassist/compiler/ast/Stmnt.java b/src/main/javassist/compiler/ast/Stmnt.java new file mode 100644 index 00000000..c6b9a97e --- /dev/null +++ b/src/main/javassist/compiler/ast/Stmnt.java @@ -0,0 +1,69 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler.ast; + +import javassist.compiler.TokenId; +import javassist.compiler.CompileError; + +/** + * Statement. + */ +public class Stmnt extends ASTList implements TokenId { + protected int operatorId; + + public Stmnt(int op, ASTree _head, ASTList _tail) { + super(_head, _tail); + operatorId = op; + } + + public Stmnt(int op, ASTree _head) { + super(_head); + operatorId = op; + } + + public Stmnt(int op) { + this(op, null); + } + + public static Stmnt make(int op, ASTree oprand1, ASTree oprand2) { + return new Stmnt(op, oprand1, new ASTList(oprand2)); + } + + public static Stmnt make(int op, ASTree op1, ASTree op2, ASTree op3) { + return new Stmnt(op, op1, new ASTList(op2, new ASTList(op3))); + } + + public void accept(Visitor v) throws CompileError { v.atStmnt(this); } + + public int getOperator() { return operatorId; } + + protected String getTag() { + if (operatorId < 128) + return "stmnt:" + (char)operatorId; + else + return "stmnt:" + operatorId; + } +} diff --git a/src/main/javassist/compiler/ast/StringL.java b/src/main/javassist/compiler/ast/StringL.java new file mode 100644 index 00000000..5f173f50 --- /dev/null +++ b/src/main/javassist/compiler/ast/StringL.java @@ -0,0 +1,45 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * String literal. + */ +public class StringL extends ASTree { + protected String text; + + public StringL(String t) { + text = t; + } + + public String get() { return text; } + + public String toString() { return "\"" + text + "\""; } + + public void accept(Visitor v) throws CompileError { v.atStringL(this); } +} diff --git a/src/main/javassist/compiler/ast/Symbol.java b/src/main/javassist/compiler/ast/Symbol.java new file mode 100644 index 00000000..c8e753bf --- /dev/null +++ b/src/main/javassist/compiler/ast/Symbol.java @@ -0,0 +1,45 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * Identifier. + */ +public class Symbol extends ASTree { + protected String identifier; + + public Symbol(String sym) { + identifier = sym; + } + + public String get() { return identifier; } + + public String toString() { return identifier; } + + public void accept(Visitor v) throws CompileError { v.atSymbol(this); } +} diff --git a/src/main/javassist/compiler/ast/Variable.java b/src/main/javassist/compiler/ast/Variable.java new file mode 100644 index 00000000..16112a99 --- /dev/null +++ b/src/main/javassist/compiler/ast/Variable.java @@ -0,0 +1,48 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * Variable. + */ +public class Variable extends Symbol { + protected Declarator declarator; + + public Variable(String sym, Declarator d) { + super(sym); + declarator = d; + } + + public Declarator getDeclarator() { return declarator; } + + public String toString() { + return identifier + ":" + declarator.getType(); + } + + public void accept(Visitor v) throws CompileError { v.atVariable(this); } +} diff --git a/src/main/javassist/compiler/ast/Visitor.java b/src/main/javassist/compiler/ast/Visitor.java new file mode 100644 index 00000000..685713de --- /dev/null +++ b/src/main/javassist/compiler/ast/Visitor.java @@ -0,0 +1,59 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * The visitor pattern. + * + * @see ast.ASTree#accept(Visitor) + */ +public class Visitor { + public void atASTList(ASTList n) throws CompileError {} + public void atPair(Pair n) throws CompileError {} + + public void atFieldDecl(FieldDecl n) throws CompileError {} + public void atMethodDecl(MethodDecl n) throws CompileError {} + public void atStmnt(Stmnt n) throws CompileError {} + public void atDeclarator(Declarator n) throws CompileError {} + + public void atAssignExpr(AssignExpr n) throws CompileError {} + public void atCondExpr(CondExpr n) throws CompileError {} + public void atBinExpr(BinExpr n) throws CompileError {} + public void atExpr(Expr n) throws CompileError {} + public void atCastExpr(CastExpr n) throws CompileError {} + public void atInstanceOfExpr(InstanceOfExpr n) throws CompileError {} + public void atNewExpr(NewExpr n) throws CompileError {} + + public void atSymbol(Symbol n) throws CompileError {} + public void atMember(Member n) throws CompileError {} + public void atVariable(Variable n) throws CompileError {} + public void atKeyword(Keyword n) throws CompileError {} + public void atStringL(StringL n) throws CompileError {} + public void atIntConst(IntConst n) throws CompileError {} + public void atDoubleConst(DoubleConst n) throws CompileError {} +} diff --git a/src/main/javassist/convert/TransformAfter.java b/src/main/javassist/convert/TransformAfter.java new file mode 100644 index 00000000..e9704348 --- /dev/null +++ b/src/main/javassist/convert/TransformAfter.java @@ -0,0 +1,56 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.convert; + +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; +import javassist.bytecode.*; +import javassist.CannotCompileException; + +public class TransformAfter extends TransformBefore { + public TransformAfter(Transformer next, + CtMethod origMethod, CtMethod afterMethod) + throws NotFoundException + { + super(next, origMethod, afterMethod); + } + + protected int match2(int pos, CodeIterator iterator) throws BadBytecode { + iterator.move(pos); + iterator.insert(saveCode); + iterator.insert(loadCode); + int p = iterator.insertGap(3); + iterator.insert(loadCode); + pos = iterator.next(); + iterator.writeByte(iterator.byteAt(pos), p); + iterator.write16bit(iterator.u16bitAt(pos + 1), p + 1); + iterator.writeByte(INVOKESTATIC, pos); + iterator.write16bit(newIndex, pos + 1); + iterator.move(p); + return iterator.next(); + } +} diff --git a/src/main/javassist/convert/TransformBefore.java b/src/main/javassist/convert/TransformBefore.java new file mode 100644 index 00000000..79deb10f --- /dev/null +++ b/src/main/javassist/convert/TransformBefore.java @@ -0,0 +1,114 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.convert; + +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; +import javassist.bytecode.*; +import javassist.CannotCompileException; + +public class TransformBefore extends TransformCall { + protected CtClass[] parameterTypes; + protected int locals; + protected int maxLocals; + protected byte[] saveCode, loadCode; + + public TransformBefore(Transformer next, + CtMethod origMethod, CtMethod beforeMethod) + throws NotFoundException + { + super(next, origMethod, beforeMethod); + parameterTypes = origMethod.getParameterTypes(); + locals = 0; + maxLocals = 0; + saveCode = loadCode = null; + } + + public void initialize(ConstPool cp, CodeAttribute attr) { + super.initialize(cp, attr); + locals = 0; + maxLocals = attr.getMaxLocals(); + saveCode = loadCode = null; + } + + protected int match(int c, int pos, CodeIterator iterator, + int typedesc, ConstPool cp) throws BadBytecode + { + if (newIndex == 0) { + String desc = Descriptor.ofParameters(parameterTypes) + 'V'; + desc = Descriptor.insertParameter(classname, desc); + int nt = cp.addNameAndTypeInfo(newMethodname, desc); + int ci = cp.addClassInfo(newClassname); + newIndex = cp.addMethodrefInfo(ci, nt); + constPool = cp; + } + + if (saveCode == null) + makeCode(parameterTypes, cp); + + return match2(pos, iterator); + } + + protected int match2(int pos, CodeIterator iterator) throws BadBytecode { + iterator.move(pos); + iterator.insert(saveCode); + iterator.insert(loadCode); + int p = iterator.insertGap(3); + iterator.writeByte(INVOKESTATIC, p); + iterator.write16bit(newIndex, p + 1); + iterator.insert(loadCode); + return iterator.next(); + } + + public int extraLocals() { return locals; } + + protected void makeCode(CtClass[] paramTypes, ConstPool cp) { + Bytecode save = new Bytecode(cp, 0, 0); + Bytecode load = new Bytecode(cp, 0, 0); + + int var = maxLocals; + int len = (paramTypes == null) ? 0 : paramTypes.length; + load.addAload(var); + makeCode2(save, load, 0, len, paramTypes, var + 1); + save.addAstore(var); + + saveCode = save.get(); + loadCode = load.get(); + } + + private void makeCode2(Bytecode save, Bytecode load, + int i, int n, CtClass[] paramTypes, int var) + { + if (i < n) { + int size = load.addLoad(var, paramTypes[i]); + makeCode2(save, load, i + 1, n, paramTypes, var + size); + save.addStore(var, paramTypes[i]); + } + else + locals = var - maxLocals; + } +} diff --git a/src/main/javassist/convert/TransformCall.java b/src/main/javassist/convert/TransformCall.java new file mode 100644 index 00000000..985a8bbb --- /dev/null +++ b/src/main/javassist/convert/TransformCall.java @@ -0,0 +1,96 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.convert; + +import javassist.CtClass; +import javassist.CtMethod; +import javassist.bytecode.*; +import javassist.CannotCompileException; + +public class TransformCall extends Transformer { + protected String classname, methodname, methodDescriptor; + protected String newClassname, newMethodname; + + /* cache */ + protected int newIndex; + protected ConstPool constPool; + + public TransformCall(Transformer next, CtMethod origMethod, + CtMethod substMethod) + { + super(next); + this.classname = origMethod.getDeclaringClass().getName(); + this.methodname = origMethod.getName(); + this.methodDescriptor = origMethod.getMethodInfo2().getDescriptor(); + this.newClassname = substMethod.getDeclaringClass().getName(); + this.newMethodname = substMethod.getName(); + this.constPool = null; + } + + public void initialize(ConstPool cp, CodeAttribute attr) { + if (constPool != cp) + newIndex = 0; + } + + /** + * Modify INVOKEINTERFACE, INVOKESPECIAL, INVOKESTATIC and INVOKEVIRTUAL + * so that a different method is invoked. + */ + public int transform(CtClass clazz, int pos, CodeIterator iterator, + ConstPool cp) throws BadBytecode + { + int c = iterator.byteAt(pos); + if (c == INVOKEINTERFACE || c == INVOKESPECIAL + || c == INVOKESTATIC || c == INVOKEVIRTUAL) { + int index = iterator.u16bitAt(pos + 1); + int typedesc = cp.isMember(classname, methodname, index); + if (typedesc != 0) + if (cp.getUtf8Info(typedesc).equals(methodDescriptor)) + pos = match(c, pos, iterator, typedesc, cp); + } + + return pos; + } + + protected int match(int c, int pos, CodeIterator iterator, + int typedesc, ConstPool cp) throws BadBytecode + { + if (newIndex == 0) { + int nt = cp.addNameAndTypeInfo(cp.addUtf8Info(newMethodname), + typedesc); + int ci = cp.addClassInfo(newClassname); + if (c == INVOKEINTERFACE) + newIndex = cp.addInterfaceMethodrefInfo(ci, nt); + else + newIndex = cp.addMethodrefInfo(ci, nt); + + constPool = cp; + } + + iterator.write16bit(newIndex, pos + 1); + return pos; + } +} diff --git a/src/main/javassist/convert/TransformFieldAccess.java b/src/main/javassist/convert/TransformFieldAccess.java new file mode 100644 index 00000000..bce010da --- /dev/null +++ b/src/main/javassist/convert/TransformFieldAccess.java @@ -0,0 +1,92 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.convert; + +import javassist.bytecode.*; +import javassist.CtClass; +import javassist.CtField; +import javassist.Modifier; +import javassist.CannotCompileException; + +final public class TransformFieldAccess extends Transformer { + private String newClassname, newFieldname; + private String fieldname; + private CtClass fieldClass; + private boolean isPrivate; + + /* cache */ + private int newIndex; + private ConstPool constPool; + + public TransformFieldAccess(Transformer next, CtField field, + String newClassname, String newFieldname) + { + super(next); + this.fieldClass = field.getDeclaringClass(); + this.fieldname = field.getName(); + this.isPrivate = Modifier.isPrivate(field.getModifiers()); + this.newClassname = newClassname; + this.newFieldname = newFieldname; + this.constPool = null; + } + + public void initialize(ConstPool cp, CodeAttribute attr) { + if (constPool != cp) + newIndex = 0; + } + + /** + * Modify GETFIELD, GETSTATIC, PUTFIELD, and PUTSTATIC so that + * a different field is accessed. The new field must be declared + * in a superclass of the class in which the original field is + * declared. + */ + public int transform(CtClass clazz, int pos, + CodeIterator iterator, ConstPool cp) + { + int c = iterator.byteAt(pos); + if (c == GETFIELD || c == GETSTATIC + || c == PUTFIELD || c == PUTSTATIC) { + int index = iterator.u16bitAt(pos + 1); + String typedesc + = TransformReadField.isField(clazz.getClassPool(), cp, + fieldClass, fieldname, isPrivate, index); + if (typedesc != null) { + if (newIndex == 0) { + int nt = cp.addNameAndTypeInfo(newFieldname, + typedesc); + newIndex = cp.addFieldrefInfo( + cp.addClassInfo(newClassname), nt); + constPool = cp; + } + + iterator.write16bit(newIndex, pos + 1); + } + } + + return pos; + } +} diff --git a/src/main/javassist/convert/TransformNew.java b/src/main/javassist/convert/TransformNew.java new file mode 100644 index 00000000..a69918d8 --- /dev/null +++ b/src/main/javassist/convert/TransformNew.java @@ -0,0 +1,102 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.convert; + +import javassist.bytecode.*; +import javassist.CtClass; +import javassist.CannotCompileException; + +final public class TransformNew extends Transformer { + private int nested; + private String classname, trapClass, trapMethod; + + public TransformNew(Transformer next, + String classname, String trapClass, String trapMethod) { + super(next); + this.classname = classname; + this.trapClass = trapClass; + this.trapMethod = trapMethod; + } + + public void initialize(ConstPool cp, CodeAttribute attr) { + nested = 0; + } + + /** + * Replace a sequence of + * NEW classname + * DUP + * ... + * INVOKESPECIAL + * with + * NOP + * NOP + * ... + * INVOKESTATIC trapMethod in trapClass + */ + public int transform(CtClass clazz, int pos, CodeIterator iterator, + ConstPool cp) throws CannotCompileException + { + int index; + int c = iterator.byteAt(pos); + if (c == NEW) { + index = iterator.u16bitAt(pos + 1); + if (cp.getClassInfo(index).equals(classname)) { + if (iterator.byteAt(pos + 3) != DUP) + throw new CannotCompileException( + "NEW followed by no DUP was found"); + + iterator.writeByte(NOP, pos); + iterator.writeByte(NOP, pos + 1); + iterator.writeByte(NOP, pos + 2); + iterator.writeByte(NOP, pos + 3); + ++nested; + } + } + else if (c == INVOKESPECIAL) { + index = iterator.u16bitAt(pos + 1); + int typedesc = cp.isConstructor(classname, index); + if (typedesc != 0 && nested > 0) { + int methodref = computeMethodref(typedesc, cp); + iterator.writeByte(INVOKESTATIC, pos); + iterator.write16bit(methodref, pos + 1); + --nested; + } + } + + return pos; + } + + private int computeMethodref(int typedesc, ConstPool cp) { + int classIndex = cp.addClassInfo(trapClass); + int mnameIndex = cp.addUtf8Info(trapMethod); + typedesc = cp.addUtf8Info( + Descriptor.changeReturnType(classname, + cp.getUtf8Info(typedesc))); + return cp.addMethodrefInfo(classIndex, + cp.addNameAndTypeInfo(mnameIndex, typedesc)); + } +} diff --git a/src/main/javassist/convert/TransformReadField.java b/src/main/javassist/convert/TransformReadField.java new file mode 100644 index 00000000..fda192c1 --- /dev/null +++ b/src/main/javassist/convert/TransformReadField.java @@ -0,0 +1,94 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.convert; + +import javassist.bytecode.*; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtField; +import javassist.CannotCompileException; +import javassist.NotFoundException; +import javassist.Modifier; + +public class TransformReadField extends Transformer { + protected String fieldname; + protected CtClass fieldClass; + protected boolean isPrivate; + protected String methodClassname, methodName; + + public TransformReadField(Transformer next, CtField field, + String methodClassname, String methodName) + { + super(next); + this.fieldClass = field.getDeclaringClass(); + this.fieldname = field.getName(); + this.methodClassname = methodClassname; + this.methodName = methodName; + this.isPrivate = Modifier.isPrivate(field.getModifiers()); + } + + static String isField(ClassPool pool, ConstPool cp, CtClass fclass, + String fname, boolean is_private, int index) { + if (!cp.getFieldrefName(index).equals(fname)) + return null; + + try { + CtClass c = pool.get(cp.getFieldrefClassName(index)); + if (is_private ? c == fclass : c.subclassOf(fclass)) + return cp.getFieldrefType(index); + } + catch (NotFoundException e) {} + return null; + } + + public int transform(CtClass tclazz, int pos, CodeIterator iterator, + ConstPool cp) throws BadBytecode + { + int c = iterator.byteAt(pos); + if (c == GETFIELD || c == GETSTATIC) { + int index = iterator.u16bitAt(pos + 1); + String typedesc = isField(tclazz.getClassPool(), cp, + fieldClass, fieldname, isPrivate, index); + if (typedesc != null) { + if (c == GETSTATIC) { + iterator.move(pos); + iterator.insertGap(1); // insertGap() may insert 4 bytes. + iterator.writeByte(ACONST_NULL, pos); + pos = iterator.next(); + } + + String type = "(Ljava/lang/Object;)" + typedesc; + int mi = cp.addClassInfo(methodClassname); + int methodref = cp.addMethodrefInfo(mi, methodName, type); + iterator.writeByte(INVOKESTATIC, pos); + iterator.write16bit(methodref, pos + 1); + return pos; + } + } + + return pos; + } +} diff --git a/src/main/javassist/convert/TransformWriteField.java b/src/main/javassist/convert/TransformWriteField.java new file mode 100644 index 00000000..8c37c7fd --- /dev/null +++ b/src/main/javassist/convert/TransformWriteField.java @@ -0,0 +1,82 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.convert; + +import javassist.CtClass; +import javassist.CtField; +import javassist.bytecode.*; +import javassist.CannotCompileException; + +final public class TransformWriteField extends TransformReadField { + public TransformWriteField(Transformer next, CtField field, + String methodClassname, String methodName) + { + super(next, field, methodClassname, methodName); + } + + public int transform(CtClass tclazz, int pos, CodeIterator iterator, + ConstPool cp) throws BadBytecode + { + int c = iterator.byteAt(pos); + if (c == PUTFIELD || c == PUTSTATIC) { + int index = iterator.u16bitAt(pos + 1); + String typedesc = isField(tclazz.getClassPool(), cp, + fieldClass, fieldname, isPrivate, index); + if (typedesc != null) { + if (c == PUTSTATIC) { + CodeAttribute ca = iterator.get(); + iterator.move(pos); + char c0 = typedesc.charAt(0); + if (c0 == 'J' || c0 == 'D') { // long or double + // insertGap() may insert 4 bytes. + iterator.insertGap(3); + iterator.writeByte(ACONST_NULL, pos); + iterator.writeByte(DUP_X2, pos + 1); + iterator.writeByte(POP, pos + 2); + ca.setMaxStack(ca.getMaxStack() + 2); + } + else { + // insertGap() may insert 4 bytes. + iterator.insertGap(2); + iterator.writeByte(ACONST_NULL, pos); + iterator.writeByte(SWAP, pos + 1); + ca.setMaxStack(ca.getMaxStack() + 1); + } + + pos = iterator.next(); + } + + int mi = cp.addClassInfo(methodClassname); + String type = "(Ljava/lang/Object;" + typedesc + ")V"; + int methodref = cp.addMethodrefInfo(mi, methodName, type); + iterator.writeByte(INVOKESTATIC, pos); + iterator.write16bit(methodref, pos + 1); + } + } + + return pos; + } +} diff --git a/src/main/javassist/convert/Transformer.java b/src/main/javassist/convert/Transformer.java new file mode 100644 index 00000000..f1a4b566 --- /dev/null +++ b/src/main/javassist/convert/Transformer.java @@ -0,0 +1,55 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.convert; + +import javassist.bytecode.*; +import javassist.CtClass; +import javassist.CannotCompileException; + +/** + * Transformer and its subclasses are used for executing + * code transformation specified by CodeConverter. + * + * @see javassist.CodeConverter + */ +public abstract class Transformer implements Opcode { + private Transformer next; + + public Transformer(Transformer t) { + next = t; + } + + public Transformer getNext() { return next; } + + public void initialize(ConstPool cp, CodeAttribute attr) {} + + public void clean() {} + + public abstract int transform(CtClass clazz, int pos, CodeIterator it, + ConstPool cp) throws CannotCompileException, BadBytecode; + + public int extraLocals() { return 0; } +} diff --git a/src/main/javassist/expr/Cast.java b/src/main/javassist/expr/Cast.java new file mode 100644 index 00000000..44648e78 --- /dev/null +++ b/src/main/javassist/expr/Cast.java @@ -0,0 +1,162 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.expr; + +import javassist.*; +import javassist.bytecode.*; +import javassist.compiler.*; +import javassist.compiler.ast.ASTList; + +/** + * Explicit type cast. + */ +public class Cast extends Expr { + /** + * Undocumented constructor. Do not use; internal-use only. + */ + Cast(int pos, CodeIterator i, CtClass declaring, MethodInfo m) { + super(pos, i, declaring, m); + } + + /** + * Returns the method or constructor containing the type cast + * expression represented by this object. + */ + public CtBehavior where() { return super.where(); } + + /** + * Returns the line number of the source line containing the + * type-cast expression. + * + * @return -1 if this information is not available. + */ + public int getLineNumber() { + return super.getLineNumber(); + } + + /** + * Returns the source file containing the type-cast expression. + * + * @return null if this information is not available. + */ + public String getFileName() { + return super.getFileName(); + } + + /** + * Returns the <code>CtClass</code> object representing + * the type specified by the cast. + */ + public CtClass getType() throws NotFoundException { + ConstPool cp = getConstPool(); + int pos = currentPos; + int index = iterator.u16bitAt(pos + 1); + String name = cp.getClassInfo(index); + return Descriptor.toCtClass(name, thisClass.getClassPool()); + } + + /** + * Returns the list of exceptions that the expression may throw. + * This list includes both the exceptions that the try-catch statements + * including the expression can catch and the exceptions that + * the throws declaration allows the method to throw. + */ + public CtClass[] mayThrow() { + return super.mayThrow(); + } + + /** + * Replaces the explicit cast operator with the bytecode derived from + * the given source text. + * + * <p>$0 is available but the value is <code>null</code>. + * + * @param statement a Java statement. + */ + public void replace(String statement) throws CannotCompileException { + ConstPool constPool = getConstPool(); + int pos = currentPos; + int index = iterator.u16bitAt(pos + 1); + + Javac jc = new Javac(thisClass); + ClassPool cp = thisClass.getClassPool(); + CodeAttribute ca = iterator.get(); + + try { + CtClass[] params + = new CtClass[] { cp.get(javaLangObject) }; + CtClass retType = getType(); + + int paramVar = ca.getMaxLocals(); + jc.recordParams(javaLangObject, params, true, paramVar, + withinStatic()); + int retVar = jc.recordReturnType(retType, true); + jc.recordProceed(new ProceedForCast(index, retType)); + + /* Is $_ included in the source code? + */ + checkResultValue(retType, statement); + + Bytecode bytecode = jc.getBytecode(); + storeStack(params, true, paramVar, bytecode); + jc.compileStmnt(statement); + bytecode.addLoad(retVar, retType); + + replace0(pos, bytecode, 3); + } + catch (CompileError e) { throw new CannotCompileException(e); } + catch (NotFoundException e) { throw new CannotCompileException(e); } + catch (BadBytecode e) { + throw new CannotCompileException("broken method"); + } + } + + /* <type> $proceed(Object obj) + */ + static class ProceedForCast implements ProceedHandler { + int index; + CtClass retType; + + ProceedForCast(int i, CtClass t) { + index = i; + retType = t; + } + + public void doit(JvstCodeGen gen, Bytecode bytecode, ASTList args) + throws CompileError + { + if (gen.atMethodArgsLength(args) != 1) + throw new CompileError(Javac.proceedName + + "() cannot take more than one parameter " + + "for cast"); + + gen.atMethodArgs(args, new int[1], new int[1], new String[1]); + bytecode.addOpcode(Opcode.CHECKCAST); + bytecode.addIndex(index); + gen.setType(retType); + } + } +} diff --git a/src/main/javassist/expr/Expr.java b/src/main/javassist/expr/Expr.java new file mode 100644 index 00000000..d88d7939 --- /dev/null +++ b/src/main/javassist/expr/Expr.java @@ -0,0 +1,223 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.expr; + +import javassist.*; +import javassist.bytecode.*; +import javassist.compiler.*; +import java.util.LinkedList; +import java.util.Iterator; + +/** + * Caller-side expression. + */ +abstract class Expr implements Opcode { + int currentPos; + CodeIterator iterator; + CtClass thisClass; + MethodInfo thisMethod; + + boolean edited; + int maxLocals, maxStack; + + static final String javaLangObject = "java.lang.Object"; + + Expr(int pos, CodeIterator i, CtClass declaring, MethodInfo m) { + currentPos = pos; + iterator = i; + thisClass = declaring; + thisMethod = m; + } + + final ConstPool getConstPool() { + return thisMethod.getConstPool(); + } + + final boolean edited() { return edited; } + + final int locals() { return maxLocals; } + + final int stack() { return maxStack; } + + /** + * Returns true if this method is static. + */ + final boolean withinStatic() { + return (thisMethod.getAccessFlags() & AccessFlag.STATIC) != 0; + } + + /** + * Returns the constructor or method containing the expression. + */ + public CtBehavior where() { + MethodInfo mi = thisMethod; + CtBehavior[] cb = thisClass.getDeclaredBehaviors(); + for (int i = cb.length - 1; i >= 0; --i) + if (cb[i].getMethodInfo() == mi) + return cb[i]; + + throw new RuntimeException("fatal: not found"); + } + + /** + * Returns the list of exceptions that the expression may throw. + * This list includes both the exceptions that the try-catch statements + * including the expression can catch and the exceptions that + * the throws declaration allows the method to throw. + */ + public CtClass[] mayThrow() { + ClassPool pool = thisClass.getClassPool(); + ConstPool cp = thisMethod.getConstPool(); + LinkedList list = new LinkedList(); + try { + CodeAttribute ca = thisMethod.getCodeAttribute(); + ExceptionTable et = ca.getExceptionTable(); + int pos = currentPos; + int n = et.size(); + for (int i = 0; i < n; ++i) + if (et.startPc(i) <= pos && pos < et.endPc(i)) { + int t = et.catchType(i); + if (t > 0) + try { + addClass(list, pool.get(cp.getClassInfo(t))); + } + catch (NotFoundException e) {} + } + } + catch (NullPointerException e) {} + + ExceptionsAttribute ea = thisMethod.getExceptionsAttribute(); + if (ea != null) { + String[] exceptions = ea.getExceptions(); + if (exceptions != null) { + int n = exceptions.length; + for (int i = 0; i < n; ++i) + try { + addClass(list, pool.get(exceptions[i])); + } + catch (NotFoundException e) {} + } + } + + return (CtClass[])list.toArray(new CtClass[list.size()]); + } + + private static void addClass(LinkedList list, CtClass c) { + Iterator it = list.iterator(); + while (it.hasNext()) + if (it.next() == c) + return; + + list.add(c); + } + + /** + * Returns the line number of the source line containing the + * expression. + * + * @return -1 if this information is not available. + */ + public int getLineNumber() { + return thisMethod.getLineNumber(currentPos); + } + + /** + * Returns the source file containing the expression. + * + * @return null if this information is not available. + */ + public String getFileName() { + ClassFile cf = thisClass.getClassFile2(); + if (cf == null) + return null; + else + return cf.getSourceFile(); + } + + static final boolean checkResultValue(CtClass retType, String prog) + throws CannotCompileException + { + /* Is $_ included in the source code? + */ + boolean hasIt = (prog.indexOf(Javac.resultVarName) >= 0); + if (!hasIt && retType != CtClass.voidType) + throw new CannotCompileException( + "the resulting value is not stored in " + + Javac.resultVarName); + + return hasIt; + } + + /* If isStaticCall is true, null is assigned to $0. So $0 must + * be declared by calling Javac.recordParams(). + * + * After executing this method, the current stack depth might + * be less than 0. + */ + static final void storeStack(CtClass[] params, boolean isStaticCall, + int regno, Bytecode bytecode) { + storeStack0(0, params.length, params, regno + 1, bytecode); + if (isStaticCall) + bytecode.addOpcode(ACONST_NULL); + + bytecode.addAstore(regno); + } + + private static void storeStack0(int i, int n, CtClass[] params, + int regno, Bytecode bytecode) { + if (i >= n) + return; + else { + CtClass c = params[i]; + int size; + if (c instanceof CtPrimitiveType) + size = ((CtPrimitiveType)c).getDataSize(); + else + size = 1; + + storeStack0(i + 1, n, params, regno + size, bytecode); + bytecode.addStore(regno, c); + } + } + + protected void replace0(int pos, Bytecode bytecode, int size) + throws BadBytecode + { + byte[] code = bytecode.get(); + edited = true; + int gap = code.length - size; + if (gap > 0) + iterator.insertGap(pos, gap); + else + for (int i = 0; i < size; ++i) + iterator.writeByte(NOP, pos + i); + + iterator.write(code, pos); + iterator.insert(bytecode.getExceptionTable(), pos); + maxLocals = bytecode.getMaxLocals(); + maxStack = bytecode.getMaxStack(); + } +} diff --git a/src/main/javassist/expr/ExprEditor.java b/src/main/javassist/expr/ExprEditor.java new file mode 100644 index 00000000..c6814ef5 --- /dev/null +++ b/src/main/javassist/expr/ExprEditor.java @@ -0,0 +1,211 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.expr; + +import javassist.bytecode.*; +import javassist.CtClass; +import javassist.CannotCompileException; + +/** + * A translator of method bodies. + * + * <p>The users can define a subclass of this class to customize how to + * modify a method body. The overall architecture is similar to the + * strategy pattern. + * + * <p>If <code>instrument()</code> is called in + * <code>CtMethod</code>, the method body is scanned from the beginning + * to the end. + * Whenever an expression, such as a method call and a <tt>new</tt> + * expression (object creation), + * is found, <code>edit()</code> is called in <code>ExprEdit</code>. + * <code>edit()</code> can inspect and modify the given expression. + * The modification is reflected on the original method body. If + * <code>edit()</code> does nothing, the original method body is not + * changed. + * + * <p>The following code is an example: + * + * <ul><pre> + * CtMethod cm = ...; + * cm.instrument(new ExprEditor() { + * public void edit(MethodCall m) throws CannotCompileException { + * if (m.getClassName().equals("Point")) { + * System.out.println(m.getMethodName() + " line: " + * + m.getLineNumber()); + * } + * }); + * </pre></ul> + * + * <p>This code inspects all method calls appearing in the method represented + * by <code>cm</code> and it prints the names and the line numbers of the + * methods declared in class <code>Point</code>. This code does not modify + * the body of the method represented by <code>cm</code>. If the method + * body must be modified, call <code>replace()</code> + * in <code>MethodCall</code>. + * + * @see javassist.CtClass#instrument(ExprEditor) + * @see javassist.CtMethod#instrument(ExprEditor) + * @see javassist.CtConstructor#instrument(ExprEditor) + * @see MethodCall + * @see NewExpr + * @see FieldAccess + * + * @see javassist.CodeConverter + */ +public class ExprEditor { + /** + * Default constructor. It does nothing. + */ + public ExprEditor() {} + + static class NewOp { + NewOp next; + int pos; + String type; + + NewOp(NewOp n, int p, String t) { + next = n; + pos = p; + type = t; + } + } + + /** + * Undocumented method. Do not use; internal-use only. + */ + public boolean doit(CtClass clazz, MethodInfo minfo) + throws CannotCompileException + { + CodeAttribute codeAttr = minfo.getCodeAttribute(); + if (codeAttr == null) + return false; + + CodeIterator iterator = codeAttr.iterator(); + boolean edited = false; + int maxLocals = codeAttr.getMaxLocals(); + int maxStack = 0; + + NewOp newList = null; + ConstPool cp = minfo.getConstPool(); + + while (iterator.hasNext()) + try { + Expr expr = null; + int pos = iterator.next(); + int c = iterator.byteAt(pos); + + if (c == Opcode.INVOKESTATIC || c == Opcode.INVOKEINTERFACE + || c == Opcode.INVOKEVIRTUAL) { + expr = new MethodCall(pos, iterator, clazz, minfo); + edit((MethodCall)expr); + } + else if (c == Opcode.GETFIELD || c == Opcode.GETSTATIC + || c == Opcode.PUTFIELD || c == Opcode.PUTSTATIC) { + expr = new FieldAccess(pos, iterator, clazz, minfo, c); + edit((FieldAccess)expr); + } + else if (c == Opcode.NEW) { + int index = iterator.u16bitAt(pos + 1); + newList = new NewOp(newList, pos, + cp.getClassInfo(index)); + } + else if (c == Opcode.INVOKESPECIAL) { + if (newList != null && cp.isConstructor(newList.type, + iterator.u16bitAt(pos + 1)) > 0) { + expr = new NewExpr(pos, iterator, clazz, minfo, + newList.type, newList.pos); + edit((NewExpr)expr); + newList = newList.next; + } + else { + expr = new MethodCall(pos, iterator, clazz, minfo); + MethodCall mcall = (MethodCall)expr; + if (!mcall.getMethodName().equals( + MethodInfo.nameInit)) + edit(mcall); + } + } + else if (c == Opcode.INSTANCEOF) { + expr = new Instanceof(pos, iterator, clazz, minfo); + edit((Instanceof)expr); + } + else if (c == Opcode.CHECKCAST) { + expr = new Cast(pos, iterator, clazz, minfo); + edit((Cast)expr); + } + + if (expr != null && expr.edited()) { + edited = true; + if (maxLocals < expr.locals()) + maxLocals = expr.locals(); + + if (maxStack < expr.stack()) + maxStack = expr.stack(); + } + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + + codeAttr.setMaxLocals(maxLocals); + codeAttr.setMaxStack(codeAttr.getMaxStack() + maxStack); + return edited; + } + + /** + * Edits a <tt>new</tt> expression (overridable). + * The default implementation performs nothing. + * + * @param e the <tt>new</tt> expression creating an object. + */ + public void edit(NewExpr e) throws CannotCompileException {} + + /** + * Edits a method call (overridable). + * The default implementation performs nothing. + */ + public void edit(MethodCall m) throws CannotCompileException {} + + /** + * Edits a field-access expression (overridable). + * Field access means both read and write. + * The default implementation performs nothing. + */ + public void edit(FieldAccess f) throws CannotCompileException {} + + /** + * Edits an instanceof expression (overridable). + * The default implementation performs nothing. + */ + public void edit(Instanceof i) throws CannotCompileException {} + + /** + * Edits an expression for explicit type casting (overridable). + * The default implementation performs nothing. + */ + public void edit(Cast c) throws CannotCompileException {} +} diff --git a/src/main/javassist/expr/FieldAccess.java b/src/main/javassist/expr/FieldAccess.java new file mode 100644 index 00000000..a524b07d --- /dev/null +++ b/src/main/javassist/expr/FieldAccess.java @@ -0,0 +1,300 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.expr; + +import javassist.*; +import javassist.bytecode.*; +import javassist.compiler.*; +import javassist.compiler.ast.ASTList; + +/** + * Expression for accessing a field. + */ +public class FieldAccess extends Expr { + int opcode; + + FieldAccess(int pos, CodeIterator i, CtClass declaring, MethodInfo m, + int op) { + super(pos, i, declaring, m); + opcode = op; + } + + /** + * Returns the method or constructor containing the field-access + * expression represented by this object. + */ + public CtBehavior where() { return super.where(); } + + /** + * Returns the line number of the source line containing the + * field access. + * + * @return -1 if this information is not available. + */ + public int getLineNumber() { + return super.getLineNumber(); + } + + /** + * Returns the source file containing the field access. + * + * @return null if this information is not available. + */ + public String getFileName() { + return super.getFileName(); + } + + /** + * Returns true if the field is static. + */ + public boolean isStatic() { + return isStatic(opcode); + } + + static boolean isStatic(int c) { + return c == Opcode.GETSTATIC || c == Opcode.PUTSTATIC; + } + + /** + * Returns true if the field is read. + */ + public boolean isReader() { + return opcode == Opcode.GETFIELD || opcode == Opcode.GETSTATIC; + } + + /** + * Returns true if the field is written in. + */ + public boolean isWriter() { + return opcode == Opcode.PUTFIELD || opcode == Opcode.PUTSTATIC; + } + + /** + * Returns the class in which the field is declared. + */ + private CtClass getCtClass() throws NotFoundException { + return thisClass.getClassPool().get(getClassName()); + } + + /** + * Returns the name of the class in which the field is declared. + */ + public String getClassName() { + int index = iterator.u16bitAt(currentPos + 1); + return getConstPool().getFieldrefClassName(index); + } + + /** + * Returns the name of the field. + */ + public String getFieldName() { + int index = iterator.u16bitAt(currentPos + 1); + return getConstPool().getFieldrefName(index); + } + + /** + * Returns the field accessed by this expression. + */ + public CtField getField() throws NotFoundException { + CtClass cc = getCtClass(); + return cc.getField(getFieldName()); + } + + /** + * Returns the list of exceptions that the expression may throw. + * This list includes both the exceptions that the try-catch statements + * including the expression can catch and the exceptions that + * the throws declaration allows the method to throw. + */ + public CtClass[] mayThrow() { + return super.mayThrow(); + } + + /* + * Returns the type of the field. + + public CtClass getFieldType() throws NotFoundException { + int index = iterator.u16bitAt(currentPos + 1); + String type = getConstPool().getFieldrefType(index); + return Descriptor.toCtClass(type, thisClass.getClassPool()); + } + */ + + /** + * Replaces the method call with the bytecode derived from + * the given source text. + * + * <p>$0 is available even if the called method is static. + * If the field access is writing, $_ is available but the value + * of $_ is ignored. + * + * @param statement a Java statement. + */ + public void replace(String statement) throws CannotCompileException { + ConstPool constPool = getConstPool(); + int pos = currentPos; + int index = iterator.u16bitAt(pos + 1); + + Javac jc = new Javac(thisClass); + CodeAttribute ca = iterator.get(); + try { + CtClass[] params; + CtClass retType; + CtClass fieldType + = Descriptor.toCtClass(constPool.getFieldrefType(index), + thisClass.getClassPool()); + boolean read = isReader(); + if (read) { + params = new CtClass[0]; + retType = fieldType; + } + else { + params = new CtClass[1]; + params[0] = fieldType; + retType = CtClass.voidType; + } + + int paramVar = ca.getMaxLocals(); + jc.recordParams(constPool.getFieldrefClassName(index), params, + true, paramVar, withinStatic()); + + /* Is $_ included in the source code? + */ + boolean included = checkResultValue(retType, statement); + + int retVar = jc.recordReturnType(retType, included); + if (read) + jc.recordProceed(new ProceedForRead(retType, opcode, + index, paramVar)); + else { + // because $type is not the return type... + jc.recordType(fieldType); + jc.recordProceed(new ProceedForWrite(params[0], opcode, + index, paramVar)); + } + + Bytecode bytecode = jc.getBytecode(); + storeStack(params, isStatic(), paramVar, bytecode); + jc.compileStmnt(statement); + if (read) + bytecode.addLoad(retVar, retType); + + replace0(pos, bytecode, 3); + } + catch (CompileError e) { throw new CannotCompileException(e); } + catch (NotFoundException e) { throw new CannotCompileException(e); } + catch (BadBytecode e) { + throw new CannotCompileException("broken method"); + } + } + + /* <field type> $proceed() + */ + static class ProceedForRead implements ProceedHandler { + CtClass fieldType; + int opcode; + int targetVar, index; + + ProceedForRead(CtClass type, int op, int i, int var) { + fieldType = type; + targetVar = var; + opcode = op; + index = i; + } + + public void doit(JvstCodeGen gen, Bytecode bytecode, ASTList args) + throws CompileError + { + if (args != null && !gen.isParamListName(args)) + throw new CompileError(Javac.proceedName + + "() cannot take a parameter for field reading"); + + int stack; + if (isStatic(opcode)) + stack = 0; + else { + stack = -1; + bytecode.addAload(targetVar); + } + + if (fieldType instanceof CtPrimitiveType) + stack += ((CtPrimitiveType)fieldType).getDataSize(); + else + ++stack; + + bytecode.add(opcode); + bytecode.addIndex(index); + bytecode.growStack(stack); + gen.setType(fieldType); + } + } + + /* void $proceed(<field type>) + * the return type is not the field type but void. + */ + static class ProceedForWrite implements ProceedHandler { + CtClass fieldType; + int opcode; + int targetVar, index; + + ProceedForWrite(CtClass type, int op, int i, int var) { + fieldType = type; + targetVar = var; + opcode = op; + index = i; + } + + public void doit(JvstCodeGen gen, Bytecode bytecode, ASTList args) + throws CompileError + { + if (gen.atMethodArgsLength(args) != 1) + throw new CompileError(Javac.proceedName + + "() cannot take more than one parameter " + + "for field writing"); + + int stack; + if (isStatic(opcode)) + stack = 0; + else { + stack = -1; + bytecode.addAload(targetVar); + } + + gen.atMethodArgs(args, new int[1], new int[1], new String[1]); + gen.doNumCast(fieldType); + if (fieldType instanceof CtPrimitiveType) + stack -= ((CtPrimitiveType)fieldType).getDataSize(); + else + --stack; + + bytecode.add(opcode); + bytecode.addIndex(index); + bytecode.growStack(stack); + gen.setType(CtClass.voidType); + gen.addNullIfVoid(); + } + } +} diff --git a/src/main/javassist/expr/Instanceof.java b/src/main/javassist/expr/Instanceof.java new file mode 100644 index 00000000..5b980f8e --- /dev/null +++ b/src/main/javassist/expr/Instanceof.java @@ -0,0 +1,165 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.expr; + +import javassist.*; +import javassist.bytecode.*; +import javassist.compiler.*; +import javassist.compiler.ast.ASTList; + +/** + * Instanceof operator. + */ +public class Instanceof extends Expr { + /** + * Undocumented constructor. Do not use; internal-use only. + */ + Instanceof(int pos, CodeIterator i, CtClass declaring, MethodInfo m) { + super(pos, i, declaring, m); + } + + /** + * Returns the method or constructor containing the instanceof + * expression represented by this object. + */ + public CtBehavior where() { return super.where(); } + + /** + * Returns the line number of the source line containing the + * instanceof expression. + * + * @return -1 if this information is not available. + */ + public int getLineNumber() { + return super.getLineNumber(); + } + + /** + * Returns the source file containing the + * instanceof expression. + * + * @return null if this information is not available. + */ + public String getFileName() { + return super.getFileName(); + } + + /** + * Returns the <code>CtClass</code> object representing + * the type name on the right hand side + * of the instanceof operator. + */ + public CtClass getType() throws NotFoundException { + ConstPool cp = getConstPool(); + int pos = currentPos; + int index = iterator.u16bitAt(pos + 1); + String name = cp.getClassInfo(index); + return Descriptor.toCtClass(name, thisClass.getClassPool()); + } + + /** + * Returns the list of exceptions that the expression may throw. + * This list includes both the exceptions that the try-catch statements + * including the expression can catch and the exceptions that + * the throws declaration allows the method to throw. + */ + public CtClass[] mayThrow() { + return super.mayThrow(); + } + + /** + * Replaces the instanceof operator with the bytecode derived from + * the given source text. + * + * <p>$0 is available but the value is <code>null</code>. + * + * @param statement a Java statement. + */ + public void replace(String statement) throws CannotCompileException { + ConstPool constPool = getConstPool(); + int pos = currentPos; + int index = iterator.u16bitAt(pos + 1); + + Javac jc = new Javac(thisClass); + ClassPool cp = thisClass.getClassPool(); + CodeAttribute ca = iterator.get(); + + try { + CtClass[] params + = new CtClass[] { cp.get(javaLangObject) }; + CtClass retType = CtClass.booleanType; + + int paramVar = ca.getMaxLocals(); + jc.recordParams(javaLangObject, params, true, paramVar, + withinStatic()); + int retVar = jc.recordReturnType(retType, true); + jc.recordProceed(new ProceedForInstanceof(index)); + + // because $type is not the return type... + jc.recordType(getType()); + + /* Is $_ included in the source code? + */ + checkResultValue(retType, statement); + + Bytecode bytecode = jc.getBytecode(); + storeStack(params, true, paramVar, bytecode); + jc.compileStmnt(statement); + bytecode.addLoad(retVar, retType); + + replace0(pos, bytecode, 3); + } + catch (CompileError e) { throw new CannotCompileException(e); } + catch (NotFoundException e) { throw new CannotCompileException(e); } + catch (BadBytecode e) { + throw new CannotCompileException("broken method"); + } + } + + /* boolean $proceed(Object obj) + */ + static class ProceedForInstanceof implements ProceedHandler { + int index; + + ProceedForInstanceof(int i) { + index = i; + } + + public void doit(JvstCodeGen gen, Bytecode bytecode, ASTList args) + throws CompileError + { + if (gen.atMethodArgsLength(args) != 1) + throw new CompileError(Javac.proceedName + + "() cannot take more than one parameter " + + "for instanceof"); + + gen.atMethodArgs(args, new int[1], new int[1], new String[1]); + bytecode.addOpcode(Opcode.INSTANCEOF); + bytecode.addIndex(index); + gen.setType(CtClass.booleanType); + } + } +} diff --git a/src/main/javassist/expr/MethodCall.java b/src/main/javassist/expr/MethodCall.java new file mode 100644 index 00000000..747380b4 --- /dev/null +++ b/src/main/javassist/expr/MethodCall.java @@ -0,0 +1,220 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.expr; + +import javassist.*; +import javassist.bytecode.*; +import javassist.compiler.*; + +/** + * Method invocation (caller-side expression). + */ +public class MethodCall extends Expr { + /** + * Undocumented constructor. Do not use; internal-use only. + */ + MethodCall(int pos, CodeIterator i, CtClass declaring, MethodInfo m) { + super(pos, i, declaring, m); + } + + private int getNameAndType(ConstPool cp) { + String cname; + int pos = currentPos; + int c = iterator.byteAt(pos); + int index = iterator.u16bitAt(pos + 1); + + if (c == INVOKEINTERFACE) + return cp.getInterfaceMethodrefNameAndType(index); + else + return cp.getMethodrefNameAndType(index); + } + + /** + * Returns the method or constructor containing the method-call + * expression represented by this object. + */ + public CtBehavior where() { return super.where(); } + + /** + * Returns the line number of the source line containing the + * method call. + * + * @return -1 if this information is not available. + */ + public int getLineNumber() { + return super.getLineNumber(); + } + + /** + * Returns the source file containing the method call. + * + * @return null if this information is not available. + */ + public String getFileName() { + return super.getFileName(); + } + + /** + * Returns the class of the target object, + * which the method is called on. + */ + private CtClass getCtClass() throws NotFoundException { + return thisClass.getClassPool().get(getClassName()); + } + + /** + * Returns the class name of the target object, + * which the method is called on. + */ + public String getClassName() { + String cname; + + ConstPool cp = getConstPool(); + int pos = currentPos; + int c = iterator.byteAt(pos); + int index = iterator.u16bitAt(pos + 1); + + if (c == INVOKEINTERFACE) + cname = cp.getInterfaceMethodrefClassName(index); + else + cname = cp.getMethodrefClassName(index); + + return cname; + } + + /** + * Returns the name of the called method. + */ + public String getMethodName() { + ConstPool cp = getConstPool(); + int nt = getNameAndType(cp); + return cp.getUtf8Info(cp.getNameAndTypeName(nt)); + } + + /** + * Returns the called method. + */ + public CtMethod getMethod() throws NotFoundException { + return getCtClass().getMethod(getMethodName(), getMethodDesc()); + } + + private String getMethodDesc() { + ConstPool cp = getConstPool(); + int nt = getNameAndType(cp); + return cp.getUtf8Info(cp.getNameAndTypeDescriptor(nt)); + } + + /** + * Returns the list of exceptions that the expression may throw. + * This list includes both the exceptions that the try-catch statements + * including the expression can catch and the exceptions that + * the throws declaration allows the method to throw. + */ + public CtClass[] mayThrow() { + return super.mayThrow(); + } + + /* + * Returns the parameter types of the called method. + + public CtClass[] getParameterTypes() throws NotFoundException { + return Descriptor.getParameterTypes(getMethodDesc(), + thisClass.getClassPool()); + } + */ + + /* + * Returns the return type of the called method. + + public CtClass getReturnType() throws NotFoundException { + return Descriptor.getReturnType(getMethodDesc(), + thisClass.getClassPool()); + } + */ + + /** + * Replaces the method call with the bytecode derived from + * the given source text. + * + * <p>$0 is available even if the called method is static. + * + * @param statement a Java statement. + */ + public void replace(String statement) throws CannotCompileException { + ConstPool constPool = getConstPool(); + int pos = currentPos; + int index = iterator.u16bitAt(pos + 1); + + String classname, methodname, signature; + int opcodeSize; + int c = iterator.byteAt(pos); + if (c == INVOKEINTERFACE) { + opcodeSize = 5; + classname = constPool.getInterfaceMethodrefClassName(index); + methodname = constPool.getInterfaceMethodrefName(index); + signature = constPool.getInterfaceMethodrefType(index); + } + else if (c == INVOKESTATIC + || c == INVOKESPECIAL || c == INVOKEVIRTUAL) { + opcodeSize = 3; + classname = constPool.getMethodrefClassName(index); + methodname = constPool.getMethodrefName(index); + signature = constPool.getMethodrefType(index); + } + else + throw new CannotCompileException("not method invocation"); + + Javac jc = new Javac(thisClass); + ClassPool cp = thisClass.getClassPool(); + CodeAttribute ca = iterator.get(); + try { + CtClass[] params = Descriptor.getParameterTypes(signature, cp); + CtClass retType = Descriptor.getReturnType(signature, cp); + int paramVar = ca.getMaxLocals(); + jc.recordParams(classname, params, + true, paramVar, withinStatic()); + int retVar = jc.recordReturnType(retType, true); + jc.recordProceed(Javac.param0Name, methodname); + + /* Is $_ included in the source code? + */ + checkResultValue(retType, statement); + + Bytecode bytecode = jc.getBytecode(); + storeStack(params, c == INVOKESTATIC, paramVar, bytecode); + jc.compileStmnt(statement); + if (retType != CtClass.voidType) + bytecode.addLoad(retVar, retType); + + replace0(pos, bytecode, opcodeSize); + } + catch (CompileError e) { throw new CannotCompileException(e); } + catch (NotFoundException e) { throw new CannotCompileException(e); } + catch (BadBytecode e) { + throw new CannotCompileException("broken method"); + } + } +} diff --git a/src/main/javassist/expr/NewExpr.java b/src/main/javassist/expr/NewExpr.java new file mode 100644 index 00000000..73546238 --- /dev/null +++ b/src/main/javassist/expr/NewExpr.java @@ -0,0 +1,224 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.expr; + +import javassist.*; +import javassist.bytecode.*; +import javassist.compiler.*; +import javassist.compiler.ast.ASTree; +import javassist.compiler.ast.ASTList; + +/** + * Object creation (<tt>new</tt> expression). + */ +public class NewExpr extends Expr { + String newTypeName; + int newPos; + + /** + * Undocumented constructor. Do not use; internal-use only. + */ + NewExpr(int pos, CodeIterator i, CtClass declaring, MethodInfo m, + String type, int np) + { + super(pos, i, declaring, m); + newTypeName = type; + newPos = np; + } + + private int getNameAndType(ConstPool cp) { + String cname; + int pos = currentPos; + int c = iterator.byteAt(pos); + int index = iterator.u16bitAt(pos + 1); + + if (c == INVOKEINTERFACE) + return cp.getInterfaceMethodrefNameAndType(index); + else + return cp.getMethodrefNameAndType(index); + } + + /** + * Returns the method or constructor containing the <tt>new</tt> + * expression represented by this object. + */ + public CtBehavior where() { return super.where(); } + + /** + * Returns the line number of the source line containing the + * <tt>new</tt> expression. + * + * @return -1 if this information is not available. + */ + public int getLineNumber() { + return super.getLineNumber(); + } + + /** + * Returns the source file containing the <tt>new</tt> expression. + * + * @return null if this information is not available. + */ + public String getFileName() { + return super.getFileName(); + } + + /** + * Returns the class of the created object. + */ + private CtClass getCtClass() throws NotFoundException { + return thisClass.getClassPool().get(newTypeName); + } + + /** + * Returns the class name of the created object. + */ + public String getClassName() { + return newTypeName; + } + + /** + * Returns the constructor called for creating the object. + */ + public CtConstructor getConstructor() throws NotFoundException { + ConstPool cp = getConstPool(); + int index = iterator.u16bitAt(currentPos + 1); + String desc = cp.getMethodrefType(index); + return getCtClass().getConstructor(desc); + } + + /** + * Returns the list of exceptions that the expression may throw. + * This list includes both the exceptions that the try-catch statements + * including the expression can catch and the exceptions that + * the throws declaration allows the method to throw. + */ + public CtClass[] mayThrow() { + return super.mayThrow(); + } + + /* + * Returns the parameter types of the constructor. + + public CtClass[] getParameterTypes() throws NotFoundException { + ConstPool cp = getConstPool(); + int index = iterator.u16bitAt(currentPos + 1); + String desc = cp.getMethodrefType(index); + return Descriptor.getParameterTypes(desc, thisClass.getClassPool()); + } + */ + + private int canReplace() throws CannotCompileException { + int op = iterator.byteAt(newPos + 3); + if (op == Opcode.DUP) + return 4; + else if (op == Opcode.DUP_X1 + && iterator.byteAt(newPos + 4) == Opcode.SWAP) + return 5; + else + throw new CannotCompileException( + "sorry, cannot edit NEW followed by no DUP"); + } + + /** + * Replaces the <tt>new</tt> expression with the bytecode derived from + * the given source text. + * + * <p>$0 is available but the value is null. + * + * @param statement a Java statement. + */ + public void replace(String statement) throws CannotCompileException { + final int bytecodeSize = 3; + int pos = newPos; + + int newIndex = iterator.u16bitAt(pos + 1); + + /* delete the preceding NEW and DUP (or DUP_X1, SWAP) instructions. + */ + int end = pos + canReplace(); + for (int i = pos; i < end; ++i) + iterator.writeByte(NOP, i); + + ConstPool constPool = getConstPool(); + pos = currentPos; + int methodIndex = iterator.u16bitAt(pos + 1); // constructor + + String signature = constPool.getMethodrefType(methodIndex); + + Javac jc = new Javac(thisClass); + ClassPool cp = thisClass.getClassPool(); + CodeAttribute ca = iterator.get(); + try { + CtClass[] params = Descriptor.getParameterTypes(signature, cp); + CtClass newType = cp.get(newTypeName); + int paramVar = ca.getMaxLocals(); + jc.recordParams(newTypeName, params, + true, paramVar, withinStatic()); + int retVar = jc.recordReturnType(newType, true); + jc.recordProceed(new ProceedForNew(newType, newIndex, + methodIndex)); + + /* Is $_ included in the source code? + */ + checkResultValue(newType, statement); + + Bytecode bytecode = jc.getBytecode(); + storeStack(params, true, paramVar, bytecode); + jc.compileStmnt(statement); + bytecode.addAload(retVar); + + replace0(pos, bytecode, bytecodeSize); + } + catch (CompileError e) { throw new CannotCompileException(e); } + catch (NotFoundException e) { throw new CannotCompileException(e); } + catch (BadBytecode e) { + throw new CannotCompileException("broken method"); + } + } + + static class ProceedForNew implements ProceedHandler { + CtClass newType; + int newIndex, methodIndex; + + ProceedForNew(CtClass nt, int ni, int mi) { + newType = nt; + newIndex = ni; + methodIndex = mi; + } + + public void doit(JvstCodeGen gen, Bytecode bytecode, ASTList args) + throws CompileError + { + bytecode.addOpcode(NEW); + bytecode.addIndex(newIndex); + bytecode.addOpcode(DUP); + gen.atMethodCall2(newType, MethodInfo.nameInit, + args, false, true); + gen.setType(newType); + } + } +} diff --git a/src/main/javassist/preproc/Assistant.java b/src/main/javassist/preproc/Assistant.java new file mode 100644 index 00000000..60d7c4c2 --- /dev/null +++ b/src/main/javassist/preproc/Assistant.java @@ -0,0 +1,63 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.preproc; + +import javassist.CtClass; +import javassist.CannotCompileException; +import javassist.ClassPool; + +/** + * This is an interface for objects invoked by the + * Javassist preprocessor when the preprocessor encounters an annotated + * import declaration. + * + * @see javassist.preproc.Compiler + */ +public interface Assistant { + /** + * Is called when the Javassist preprocessor encounters an + * import declaration annotated with the "by" keyword. + * + * <p>The original import declaration is replaced with new import + * declarations of classes returned by this method. For example, + * the following implementation does not change the original + * declaration: + * + * <ul><pre> + * public CtClass[] assist(ClassPool cp, String importname, String[] args) { + * return new CtClass[] { cp.get(importname) }; + * } + * </pre></uL> + * + * @param cp class pool + * @param importname the class imported by the declaration + * @param args the parameters specified by the annotation + * @return the classes imported in the java source + * program produced by the preprocessor. + */ + public CtClass[] assist(ClassPool cp, String importname, + String[] args) throws CannotCompileException; +} diff --git a/src/main/javassist/preproc/Compiler.java b/src/main/javassist/preproc/Compiler.java new file mode 100644 index 00000000..60e6a9d7 --- /dev/null +++ b/src/main/javassist/preproc/Compiler.java @@ -0,0 +1,362 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.preproc; + +import java.io.IOException; +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.util.Vector; +import javassist.CannotCompileException; +import javassist.CtClass; +import javassist.ClassPool; + +/** + * This is a preprocessor for Java source programs using annotated + * import declarations. + * + * <ul><pre> + * import <i>class-name</i> by <i>assistant-name</i> [(<i>arg1, arg2, ...</i>)] + * </pre></ul> + * + * <p>To process this annotation, run this class as follows: + * + * <ul><pre> + * java javassist.preproc.Compiler sample.j + * </pre></ul> + * + * <p>This command produces <code>sample.java</code>, which only includes + * regular import declarations. Also, the Javassist program + * specified by <i>assistant-name</i> is executed so that it produces + * class files under the <code>./tmpjvst</code> directory. The class + * specified by <i>assistant-name</i> must implement + * <code>javassist.preproc.Assistant</code>. + * + * @see javassist.preproc.Assistant + */ + +public class Compiler { + protected BufferedReader input; + protected BufferedWriter output; + protected ClassPool classPool; + + /** + * Constructs a <code>Compiler</code> with a source file. + * + * @param inputname the name of the source file. + */ + public Compiler(String inputname) throws CannotCompileException { + try { + input = new BufferedReader(new FileReader(inputname)); + } + catch (IOException e) { + throw new CannotCompileException("cannot open: " + inputname); + } + + String outputname = getOutputFilename(inputname); + if (outputname.equals(inputname)) + throw new CannotCompileException("invalid source name: " + + inputname); + + try { + output = new BufferedWriter(new FileWriter(outputname)); + } + catch (IOException e) { + throw new CannotCompileException("cannot open: " + outputname); + } + + classPool = ClassPool.getDefault(); + } + + /** + * Starts preprocessing. + */ + public void process() throws IOException, CannotCompileException { + int c; + CommentSkipper reader = new CommentSkipper(input, output); + while ((c = reader.read()) != -1) { + output.write(c); + if (c == 'p') { + if (skipPackage(reader)) + break; + } + else if (c == 'i') + readImport(reader); + else if (c != ' ' && c != '\t' && c != '\n' && c != '\r') + break; + } + + while ((c = input.read()) != -1) + output.write(c); + + input.close(); + output.close(); + } + + private boolean skipPackage(CommentSkipper reader) throws IOException { + int c; + c = reader.read(); + output.write(c); + if (c != 'a') + return true; + + while ((c = reader.read()) != -1) { + output.write(c); + if (c == ';') + break; + } + + return false; + } + + private void readImport(CommentSkipper reader) + throws IOException, CannotCompileException + { + int word[] = new int[5]; + int c; + for (int i = 0; i < 5; ++i) { + word[i] = reader.read(); + output.write(word[i]); + } + + if (word[0] != 'm' || word[1] != 'p' || word[2] != 'o' + || word[3] != 'r' || word[4] != 't') + return; // syntax error? + + c = skipSpaces(reader, ' '); + StringBuffer classbuf = new StringBuffer(); + while (c != ' ' && c != '\t' && c != '\n' && c != '\r' + && c != ';' && c != -1) { + classbuf.append((char)c); + c = reader.read(); + } + + String importclass = classbuf.toString(); + c = skipSpaces(reader, c); + if (c == ';') { + output.write(importclass); + output.write(';'); + return; + } + if (c != 'b') + syntaxError(importclass); + + reader.read(); // skip 'y' + + StringBuffer assistant = new StringBuffer(); + Vector args = new Vector(); + c = readAssistant(reader, importclass, assistant, args); + c = skipSpaces(reader, c); + if (c != ';') + syntaxError(importclass); + + runAssistant(importclass, assistant.toString(), args); + } + + void syntaxError(String importclass) throws CannotCompileException { + throw new CannotCompileException("Syntax error. Cannot import " + + importclass); + } + + int readAssistant(CommentSkipper reader, String importclass, + StringBuffer assistant, Vector args) + throws IOException, CannotCompileException + { + int c = readArgument(reader, assistant); + c = skipSpaces(reader, c); + if (c == '(') { + do { + StringBuffer arg = new StringBuffer(); + c = readArgument(reader, arg); + args.addElement(arg.toString()); + c = skipSpaces(reader, c); + } while (c == ','); + + if (c != ')') + syntaxError(importclass); + + return reader.read(); + } + + return c; + } + + int readArgument(CommentSkipper reader, StringBuffer buf) + throws IOException + { + int c = skipSpaces(reader, ' '); + while ('A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' + || '0' <= c && c <= '9' || c == '.' || c == '_') { + buf.append((char)c); + c = reader.read(); + } + + return c; + } + + int skipSpaces(CommentSkipper reader, int c) throws IOException { + while (c == ' ' || c == '\t' || c == '\n' || c == '\r') { + if (c == '\n' || c == '\r') + output.write(c); + + c = reader.read(); + } + + return c; + } + + /** + * Is invoked if this compiler encoutenrs: + * + * <ul><pre> + * import <i>class name</i> by <i>assistant</i> (<i>args1</i>, <i>args2</i>, ...); + * </pre></ul> + * + * @param classname class name + * @param assistantname assistant + * @param argv args1, args2, ... + */ + private void runAssistant(String importname, String assistantname, + Vector argv) + throws IOException, CannotCompileException + { + Class assistant; + Assistant a; + int s = argv.size(); + String[] args = new String[s]; + for (int i = 0; i < s; ++i) + args[i] = (String)argv.elementAt(i); + + try { + assistant = Class.forName(assistantname); + } + catch (ClassNotFoundException e) { + throw new CannotCompileException("Cannot find " + assistantname); + } + + try { + a = (Assistant)assistant.newInstance(); + } + catch (Exception e) { + throw new CannotCompileException(e); + } + + CtClass[] imports = a.assist(classPool, importname, args); + s = imports.length; + if (s < 1) + output.write(" java.lang.Object;"); + else { + output.write(' '); + output.write(imports[0].getName()); + output.write(';'); + for (int i = 1; i < s; ++i) { + output.write(" import "); + output.write(imports[1].getName()); + output.write(';'); + } + } + } + + private String getOutputFilename(String input) { + int i = input.lastIndexOf('.'); + if (i < 0) + i = input.length(); + + return input.substring(0, i) + ".java"; + } + + public static void main(String[] args) { + if (args.length > 0) + try { + Compiler c = new Compiler(args[0]); + c.process(); + } + catch (IOException e) { + System.err.println(e); + } + catch (CannotCompileException e) { + System.err.println(e); + } + else { + System.err.println("Javassist version " + CtClass.version); + System.err.println("No source file is specified."); + } + } +} + +class CommentSkipper { + private BufferedReader input; + private BufferedWriter output; + + public CommentSkipper(BufferedReader reader, BufferedWriter writer) { + input = reader; + output = writer; + } + + public int read() throws IOException { + int c; + while ((c = input.read()) != -1) + if (c != '/') + return c; + else { + c = input.read(); + if (c == '/') + skipCxxComments(); + else if (c == '*') + skipCComments(); + else + output.write('/'); + } + + return c; + } + + private void skipCxxComments() throws IOException { + int c; + output.write("//"); + while ((c = input.read()) != -1) { + output.write(c); + if (c == '\n' || c == '\r') + break; + } + } + + private void skipCComments() throws IOException { + int c; + boolean star = false; + output.write("/*"); + while ((c = input.read()) != -1) { + output.write(c); + if (c == '*') + star = true; + else if(star && c == '/') + break; + else + star = false; + } + } +} diff --git a/src/main/javassist/reflect/CannotCreateException.java b/src/main/javassist/reflect/CannotCreateException.java new file mode 100644 index 00000000..168032fb --- /dev/null +++ b/src/main/javassist/reflect/CannotCreateException.java @@ -0,0 +1,39 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.reflect; + +/** + * Signals that <code>ClassMetaobject.newInstance()</code> fails. + */ +public class CannotCreateException extends Exception { + public CannotCreateException(String s) { + super(s); + } + + public CannotCreateException(Exception e) { + super("by " + e.toString()); + } +} diff --git a/src/main/javassist/reflect/CannotInvokeException.java b/src/main/javassist/reflect/CannotInvokeException.java new file mode 100644 index 00000000..455c3e4d --- /dev/null +++ b/src/main/javassist/reflect/CannotInvokeException.java @@ -0,0 +1,75 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.reflect; + +import java.lang.reflect.InvocationTargetException; +import java.lang.IllegalAccessException; + +/** + * Thrown when method invocation using the reflection API has thrown + * an exception. + * + * @see javassist.reflect.Metaobject#trapMethodcall(int, Object[]) + * @see javassist.reflect.ClassMetaobject#trapMethodcall(int, Object[]) + * @see javassist.reflect.ClassMetaobject#invoke(Object, int, Object[]) + */ +public class CannotInvokeException extends RuntimeException { + /** + * @serial + */ + private Throwable err = null; + + /** + * Constructs a CannotInvokeException with an error message. + */ + public CannotInvokeException(String reason) { + super(reason); + } + + /** + * Constructs a CannotInvokeException with an InvocationTargetException. + */ + public CannotInvokeException(InvocationTargetException e) { + super("by " + e.getTargetException().toString()); + err = e.getTargetException(); + } + + /** + * Constructs a CannotInvokeException with an IllegalAccessException. + */ + public CannotInvokeException(IllegalAccessException e) { + super("by " + e.toString()); + err = e; + } + + /** + * Constructs a CannotInvokeException with an ClassNotFoundException. + */ + public CannotInvokeException(ClassNotFoundException e) { + super("by " + e.toString()); + err = e; + } +} diff --git a/src/main/javassist/reflect/ClassMetaobject.java b/src/main/javassist/reflect/ClassMetaobject.java new file mode 100644 index 00000000..6c0de555 --- /dev/null +++ b/src/main/javassist/reflect/ClassMetaobject.java @@ -0,0 +1,312 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.reflect; + +import java.lang.reflect.*; +import java.io.Serializable; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import javassist.CtClass; + +/** + * A runtime class metaobject. + * + * <p>A <code>ClassMetaobject</code> is created for every + * class of reflective objects. It can be used to hold values + * shared among the reflective objects of the same class. + * + * @see javassist.reflect.Metaobject + */ +public class ClassMetaobject implements Serializable { + /** + * The base-level methods controlled by a metaobject + * are renamed so that they begin with + * <code>methodPrefix "_m_"</code>. + */ + static final String methodPrefix = "_m_"; + static final int methodPrefixLen = 3; + + private Class javaClass; + private Constructor[] constructors; + private Method[] methods; + + /** + * Specifies how a <code>java.lang.Class</code> object is loaded. + * + * <p>If true, it is loaded by: + * <ul><pre>Thread.currentThread().getContextClassLoader().loadClass()</pre></ul> + * <p>If false, it is loaded by <code>Class.forName()</code>. + * The default value is false. + */ + public static boolean useContextClassLoader = false; + + /** + * Constructs a <code>ClassMetaobject</code>. + * + * @param params <code>params[0]</code> is the name of the class + * of the reflective objects. + */ + public ClassMetaobject(String[] params) + { + try { + javaClass = getClassObject(params[0]); + } + catch (ClassNotFoundException e) { + javaClass = null; + } + + constructors = javaClass.getConstructors(); + methods = null; + } + + private void writeObject(ObjectOutputStream out) throws IOException { + out.writeUTF(javaClass.getName()); + } + + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException + { + javaClass = getClassObject(in.readUTF()); + constructors = javaClass.getConstructors(); + methods = null; + } + + private Class getClassObject(String name) throws ClassNotFoundException { + if (useContextClassLoader) + return Thread.currentThread().getContextClassLoader() + .loadClass(name); + else + return Class.forName(name); + } + + /** + * Obtains the <code>java.lang.Class</code> representing this class. + */ + public final Class getJavaClass() { + return javaClass; + } + + /** + * Obtains the name of this class. + */ + public final String getName() { + return javaClass.getName(); + } + + /** + * Returns true if <code>obj</code> is an instance of this class. + */ + public final boolean isInstance(Object obj) { + return javaClass.isInstance(obj); + } + + /** + * Creates a new instance of the class. + * + * @param args the arguments passed to the constructor. + */ + public final Object newInstance(Object[] args) + throws CannotCreateException + { + int n = constructors.length; + for (int i = 0; i < n; ++i) { + try { + return constructors[i].newInstance(args); + } + catch (IllegalArgumentException e) { + // try again + } + catch (InstantiationException e) { + throw new CannotCreateException(e); + } + catch (IllegalAccessException e) { + throw new CannotCreateException(e); + } + catch (InvocationTargetException e) { + throw new CannotCreateException(e); + } + } + + throw new CannotCreateException("no constructor matches"); + } + + /** + * Is invoked when <code>static</code> fields of the base-level + * class are read and the runtime system intercepts it. + * This method simply returns the value of the field. + * + * <p>Every subclass of this class should redefine this method. + */ + public Object trapFieldRead(String name) { + Class jc = getJavaClass(); + try { + return jc.getField(name).get(null); + } + catch (NoSuchFieldException e) { + throw new RuntimeException(e.toString()); + } + catch (IllegalAccessException e) { + throw new RuntimeException(e.toString()); + } + } + + /** + * Is invoked when <code>static</code> fields of the base-level + * class are modified and the runtime system intercepts it. + * This method simply sets the field to the given value. + * + * <p>Every subclass of this class should redefine this method. + */ + public void trapFieldWrite(String name, Object value) { + Class jc = getJavaClass(); + try { + jc.getField(name).set(null, value); + } + catch (NoSuchFieldException e) { + throw new RuntimeException(e.toString()); + } + catch (IllegalAccessException e) { + throw new RuntimeException(e.toString()); + } + } + + /** + * Invokes a method whose name begins with + * <code>methodPrefix "_m_"</code> and the identifier. + * + * @exception CannotInvokeException if the invocation fails. + */ + static public Object invoke(Object target, int identifier, Object[] args) + throws Throwable + { + Method[] allmethods = target.getClass().getMethods(); + int n = allmethods.length; + String head = methodPrefix + identifier; + for (int i = 0; i < n; ++i) + if (allmethods[i].getName().startsWith(head)) { + try { + return allmethods[i].invoke(target, args); + } catch (java.lang.reflect.InvocationTargetException e) { + throw e.getTargetException(); + } catch (java.lang.IllegalAccessException e) { + throw new CannotInvokeException(e); + } + } + + throw new CannotInvokeException("cannot find a method"); + } + + /** + * Is invoked when <code>static</code> methods of the base-level + * class are called and the runtime system intercepts it. + * This method simply executes the intercepted method invocation + * with the original parameters and returns the resulting value. + * + * <p>Every subclass of this class should redefine this method. + */ + public Object trapMethodcall(int identifier, Object[] args) + throws Throwable + { + try { + Method[] m = getReflectiveMethods(); + return m[identifier].invoke(null, args); + } + catch (java.lang.reflect.InvocationTargetException e) { + throw e.getTargetException(); + } + catch (java.lang.IllegalAccessException e) { + throw new CannotInvokeException(e); + } + } + + /** + * Returns an array of the methods defined on the given reflective + * object. This method is for the internal use only. + */ + public final Method[] getReflectiveMethods() { + if (methods != null) + return methods; + + Class baseclass = getJavaClass(); + Method[] allmethods = baseclass.getMethods(); + int n = allmethods.length; + methods = new Method[n]; + for (int i = 0; i < n; ++i) { + Method m = allmethods[i]; + if (m.getDeclaringClass() == baseclass) { + String mname = m.getName(); + if (mname.startsWith(methodPrefix)) { + int k = 0; + for (int j = methodPrefixLen;; ++j) { + char c = mname.charAt(j); + if ('0' <= c && c <= '9') + k = k * 10 + c - '0'; + else + break; + } + + methods[k] = m; + } + } + } + + return methods; + } + + /** + * Returns the name of the method specified + * by <code>identifier</code>. + */ + public final String getMethodName(int identifier) { + String mname = getReflectiveMethods()[identifier].getName(); + int j = ClassMetaobject.methodPrefixLen; + for (;;) { + char c = mname.charAt(j++); + if (c < '0' || '9' < c) + break; + } + + return mname.substring(j); + } + + /** + * Returns an array of <code>Class</code> objects representing the + * formal parameter types of the method specified + * by <code>identifier</code>. + */ + public final Class[] getParameterTypes(int identifier) { + return getReflectiveMethods()[identifier].getParameterTypes(); + } + + /** + * Returns a <code>Class</code> objects representing the + * return type of the method specified by <code>identifier</code>. + */ + public final Class getReturnType(int identifier) { + return getReflectiveMethods()[identifier].getReturnType(); + } +} diff --git a/src/main/javassist/reflect/Compiler.java b/src/main/javassist/reflect/Compiler.java new file mode 100644 index 00000000..6e0b854e --- /dev/null +++ b/src/main/javassist/reflect/Compiler.java @@ -0,0 +1,169 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.reflect; + +import javassist.CtClass; +import javassist.ClassPool; +import java.io.PrintStream; + +class CompiledClass { + public String classname; + public String metaobject; + public String classobject; +} + +/** + * A bytecode translator for reflection. + * + * <p>This translator directly modifies class files on a local disk so that + * the classes represented by those class files are reflective. + * After the modification, the class files can be run with the standard JVM + * without <code>javassist.reflect.Loader</code> + * or any other user-defined class loader. + * + * <p>The modified class files are given as the command-line parameters, + * which are a sequence of fully-qualified class names followed by options: + * + * <p><code>-m <i>classname</i></code> : specifies the class of the + * metaobjects associated with instances of the class followed by + * this option. The default is <code>javassit.reflect.Metaobject</code>. + * + * <p><code>-c <i>classname</i></code> : specifies the class of the + * class metaobjects associated with instances of the class followed by + * this option. The default is <code>javassit.reflect.ClassMetaobject</code>. + * + * <p>If a class name is not followed by any options, the class indicated + * by that class name is not reflective. + * + * <p>For example, + * <ul><pre>% java Compiler Dog -m MetaDog -c CMetaDog Cat -m MetaCat Cow + * </pre></ul> + * + * <p>modifies class files <code>Dog.class</code>, <code>Cat.class</code>, + * and <code>Cow.class</code>. + * The metaobject of a Dog object is a MetaDog object and the class + * metaobject is a CMetaDog object. + * The metaobject of a Cat object is a MetaCat object but + * the class metaobject is a default one. + * Cow objects are not reflective. + * + * <p>Note that if the super class is also made reflective, it must be done + * before the sub class. + * + * @see javassist.reflect.Metaobject + * @see javassist.reflect.ClassMetaobject + * @see javassist.reflect.Reflection + */ +public class Compiler { + + public static void main(String[] args) throws Exception { + if (args.length == 0) { + help(System.err); + return; + } + + CompiledClass[] entries = new CompiledClass[args.length]; + int n = parse(args, entries); + + if (n < 1) { + System.err.println("bad parameter."); + return; + } + + processClasses(entries, n); + } + + private static void processClasses(CompiledClass[] entries, int n) + throws Exception + { + Reflection implementor = new Reflection(); + ClassPool pool = ClassPool.getDefault(implementor); + + for (int i = 0; i < n; ++i) { + CtClass c = pool.get(entries[i].classname); + if (entries[i].metaobject != null + || entries[i].classobject != null) { + String metaobj, classobj; + + if (entries[i].metaobject == null) + metaobj = "javassist.reflect.Metaobject"; + else + metaobj = entries[i].metaobject; + + if (entries[i].classobject == null) + classobj = "javassist.reflect.ClassMetaobject"; + else + classobj = entries[i].classobject; + + if (!implementor.makeReflective(c, pool.get(metaobj), + pool.get(classobj))) + System.err.println("Warning: " + c.getName() + + " is reflective. It was not changed."); + + System.err.println(c.getName() + ": " + metaobj + ", " + + classobj); + } + else + System.err.println(c.getName() + ": not reflective"); + } + + for (int i = 0; i < n; ++i) + pool.writeFile(entries[i].classname); + } + + private static int parse(String[] args, CompiledClass[] result) { + int n = -1; + for (int i = 0; i < args.length; ++i) { + String a = args[i]; + if (a.equals("-m")) + if (n < 0 || i + 1 > args.length) + return -1; + else + result[n].metaobject = args[++i]; + else if (a.equals("-c")) + if (n < 0 || i + 1 > args.length) + return -1; + else + result[n].classobject = args[++i]; + else if (a.charAt(0) == '-') + return -1; + else { + CompiledClass cc = new CompiledClass(); + cc.classname = a; + cc.metaobject = null; + cc.classobject = null; + result[++n] = cc; + } + } + + return n + 1; + } + + private static void help(PrintStream out) { + out.println("Usage: java javassist.reflect.Compiler"); + out.println(" (<class> [-m <metaobject>] [-c <class metaobject>])+"); + } +} diff --git a/src/main/javassist/reflect/Loader.java b/src/main/javassist/reflect/Loader.java new file mode 100644 index 00000000..5287593d --- /dev/null +++ b/src/main/javassist/reflect/Loader.java @@ -0,0 +1,168 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.reflect; + +import javassist.CannotCompileException; +import javassist.NotFoundException; +import javassist.ClassPool; + +/** + * A class loader for reflection. + * + * <p>To run a program, say <code>MyApp</code>, + * including a reflective class, + * you must write a start-up program as follows: + * + * <ul><pre> + * public class Main { + * public static void main(String[] args) throws Throwable { + * javassist.reflect.Loader cl + * = (javassist.reflect.Loader)Main.class.getClassLoader(); + * cl.makeReflective("Person", "MyMetaobject", + * "javassist.reflect.ClassMetaobject"); + * cl.run("MyApp", args); + * } + * } + * </pre></ul> + * + * <p>Then run this program as follows: + * + * <ul><pre>% java javassist.reflect.Loader Main arg1, ...</pre></ul> + * + * <p>This command runs <code>Main.main()</code> with <code>arg1</code>, ... + * and <code>Main.main()</code> runs <code>MyApp.main()</code> with + * <code>arg1</code>, ... + * The <code>Person</code> class is modified + * to be a reflective class. Method calls on a <code>Person</code> + * object are intercepted by an instance of <code>MyMetaobject</code>. + * + * <p>Also, you can run <code>MyApp</code> in a slightly different way: + * + * <ul><pre> + * public class Main2 { + * public static void main(String[] args) throws Throwable { + * javassist.reflect.Loader cl = new javassist.reflect.Loader(); + * cl.makeReflective("Person", "MyMetaobject", + * "javassist.reflect.ClassMetaobject"); + * cl.run("MyApp", args); + * } + * } + * </pre></ul> + * + * <p>This program is run as follows: + * + * <ul><pre>% java Main2 arg1, ...</code> + * + * <p>The difference from the former one is that the class <code>Main</code> + * is loaded by <code>javassist.reflect.Loader</code> whereas the class + * <code>Main2</code> is not. Thus, <code>Main</code> belongs + * to the same name space (security domain) as <code>MyApp</code> + * whereas <code>Main2</code> does not; <code>Main2</code> belongs + * to the same name space as <code>javassist.reflect.Loader</code>. + * For more details, + * see the notes in the manual page of <code>javassist.Loader</code>. + * + * <p>The class <code>Main2</code> is equivalent to this class: + * + * <ul><pre> + * public class Main3 { + * public static void main(String[] args) throws Throwable { + * Reflection reflection = new Reflection(); + * javassist.Loader cl + * = new javassist.Loader(ClassPool.getDefault(reflection)); + * reflection.makeReflective("Person", "MyMetaobject", + * "javassist.reflect.ClassMetaobject"); + * cl.run("MyApp", args); + * } + * } + * </pre></ul> + * + * <p><b>Note:</b> + * + * <p><code>javassist.reflect.Loader</code> does not make a class reflective + * if that class is in a <code>java.*</code> or + * <code>javax.*</code> pacakge because of the specifications + * on the class loading algorithm of Java. The JVM does not allow to + * load such a system class with a user class loader. + * + * <p>To avoid this limitation, those classes should be statically + * modified with <code>javassist.reflect.Compiler</code> and the original + * class files should be replaced. + * + * @see javassist.reflect.Reflection + * @see javassist.reflect.Compiler + * @see javassist.Loader + */ +public class Loader extends javassist.Loader { + protected Reflection reflection; + + /** + * Loads a class with an instance of <code>Loader</code> + * and calls <code>main()</code> in that class. + * + * @param args[0] class name to be loaded. + * @param args[1-n] parameters passed to <code>main()</code>. + */ + public static void main(String[] args) throws Throwable { + Loader cl = new Loader(); + cl.run(args); + } + + /** + * Constructs a new class loader. + */ + public Loader() { + super(); + delegateLoadingOf("javassist.reflect.Loader"); + + reflection = new Reflection(); + setClassPool(ClassPool.getDefault(reflection)); + } + + /** + * Produces a reflective class. + * If the super class is also made reflective, it must be done + * before the sub class. + * + * @param clazz the reflective class. + * @param metaobject the class of metaobjects. + * It must be a subclass of + * <code>Metaobject</code>. + * @param metaclass the class of the class metaobject. + * It must be a subclass of + * <code>ClassMetaobject</code>. + * @return <code>false</code> if the class is already reflective. + * + * @see javassist.reflect.Metaobject + * @see javassist.reflect.ClassMetaobject + */ + public boolean makeReflective(String clazz, + String metaobject, String metaclass) + throws CannotCompileException, NotFoundException + { + return reflection.makeReflective(clazz, metaobject, metaclass); + } +} diff --git a/src/main/javassist/reflect/Metalevel.java b/src/main/javassist/reflect/Metalevel.java new file mode 100644 index 00000000..96e53d52 --- /dev/null +++ b/src/main/javassist/reflect/Metalevel.java @@ -0,0 +1,48 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.reflect; + +/** + * An interface to access a metaobject and a class metaobject. + * This interface is implicitly implemented by the reflective + * class. + */ +public interface Metalevel { + /** + * Obtains the class metaobject associated with this object. + */ + ClassMetaobject _getClass(); + + /** + * Obtains the metaobject associated with this object. + */ + Metaobject _getMetaobject(); + + /** + * Changes the metaobject associated with this object. + */ + void _setMetaobject(Metaobject m); +} diff --git a/src/main/javassist/reflect/Metaobject.java b/src/main/javassist/reflect/Metaobject.java new file mode 100644 index 00000000..8f1d02bc --- /dev/null +++ b/src/main/javassist/reflect/Metaobject.java @@ -0,0 +1,239 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.reflect; + +import java.lang.reflect.Method; +import java.io.Serializable; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +/** + * A runtime metaobject. + * + * <p>A <code>Metaobject</code> is created for + * every object at the base level. A different reflective object is + * associated with a different metaobject. + * + * <p>The metaobject intercepts method calls + * on the reflective object at the base-level. To change the behavior + * of the method calls, a subclass of <code>Metaobject</code> + * should be defined. + * + * @see javassist.reflect.ClassMetaobject + */ +public class Metaobject implements Serializable { + protected ClassMetaobject classmetaobject; + protected Metalevel baseobject; + protected Method[] methods; + + /** + * Constructs a <code>Metaobject</code>. The metaobject is + * constructed before the constructor is called on the base-level + * object. + * + * @param self the object that this metaobject is associated with. + * @param args the parameters passed to the constructor of + * <code>self</code>. + */ + public Metaobject(Object self, Object[] args) { + baseobject = (Metalevel)self; + classmetaobject = baseobject._getClass(); + methods = classmetaobject.getReflectiveMethods(); + } + + /** + * Constructs a <code>Metaobject</code> without initialization. + * If calling this constructor, a subclass should be responsible + * for initialization. + */ + protected Metaobject() { + baseobject = null; + classmetaobject = null; + methods = null; + } + + private void writeObject(ObjectOutputStream out) throws IOException { + out.writeObject(baseobject); + } + + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException + { + baseobject = (Metalevel)in.readObject(); + classmetaobject = baseobject._getClass(); + methods = classmetaobject.getReflectiveMethods(); + } + + /** + * Obtains the class metaobject associated with this metaobject. + * + * @see javassist.reflect.ClassMetaobject + */ + public final ClassMetaobject getClassMetaobject() { + return classmetaobject; + } + + /** + * Obtains the object controlled by this metaobject. + */ + public final Object getObject() { + return baseobject; + } + + /** + * Changes the object controlled by this metaobject. + * + * @param self the object + */ + public final void setObject(Object self) { + baseobject = (Metalevel)self; + classmetaobject = baseobject._getClass(); + methods = classmetaobject.getReflectiveMethods(); + + // call _setMetaobject() after the metaobject is settled. + baseobject._setMetaobject(this); + } + + /** + * Returns the name of the method specified + * by <code>identifier</code>. + */ + public final String getMethodName(int identifier) { + String mname = methods[identifier].getName(); + int j = ClassMetaobject.methodPrefixLen; + for (;;) { + char c = mname.charAt(j++); + if (c < '0' || '9' < c) + break; + } + + return mname.substring(j); + } + + /** + * Returns an array of <code>Class</code> objects representing the + * formal parameter types of the method specified + * by <code>identifier</code>. + */ + public final Class[] getParameterTypes(int identifier) { + return methods[identifier].getParameterTypes(); + } + + /** + * Returns a <code>Class</code> objects representing the + * return type of the method specified by <code>identifier</code>. + */ + public final Class getReturnType(int identifier) { + return methods[identifier].getReturnType(); + } + + /** + * Is invoked when public fields of the base-level + * class are read and the runtime system intercepts it. + * This method simply returns the value of the field. + * + * <p>Every subclass of this class should redefine this method. + */ + public Object trapFieldRead(String name) { + Class jc = getClassMetaobject().getJavaClass(); + try { + return jc.getField(name).get(getObject()); + } + catch (NoSuchFieldException e) { + throw new RuntimeException(e.toString()); + } + catch (IllegalAccessException e) { + throw new RuntimeException(e.toString()); + } + } + + /** + * Is invoked when public fields of the base-level + * class are modified and the runtime system intercepts it. + * This method simply sets the field to the given value. + * + * <p>Every subclass of this class should redefine this method. + */ + public void trapFieldWrite(String name, Object value) { + Class jc = getClassMetaobject().getJavaClass(); + try { + jc.getField(name).set(getObject(), value); + } + catch (NoSuchFieldException e) { + throw new RuntimeException(e.toString()); + } + catch (IllegalAccessException e) { + throw new RuntimeException(e.toString()); + } + } + + /** + * Is invoked when base-level method invocation is intercepted. + * This method simply executes the intercepted method invocation + * with the original parameters and returns the resulting value. + * + * <p>Every subclass of this class should redefine this method. + * + * <p>Note: this method is not invoked if the base-level method + * is invoked by a constructor in the super class. For example, + * + * <ul><pre>abstract class A { + * abstract void initialize(); + * A() { + * initialize(); // not intercepted + * } + * } + * + * class B extends A { + * void initialize() { System.out.println("initialize()"); } + * B() { + * super(); + * initialize(); // intercepted + * } + * }</pre></ul> + * + * <p>if an instance of B is created, + * the invocation of initialize() in B is intercepted only once. + * The first invocation by the constructor in A is not intercepted. + * This is because the link between a base-level object and a + * metaobject is not created until the execution of a + * constructor of the super class finishes. + */ + public Object trapMethodcall(int identifier, Object[] args) + throws Throwable + { + try { + return methods[identifier].invoke(getObject(), args); + } + catch (java.lang.reflect.InvocationTargetException e) { + throw e.getTargetException(); + } + catch (java.lang.IllegalAccessException e) { + throw new CannotInvokeException(e); + } + } +} diff --git a/src/main/javassist/reflect/Reflection.java b/src/main/javassist/reflect/Reflection.java new file mode 100644 index 00000000..f76b396e --- /dev/null +++ b/src/main/javassist/reflect/Reflection.java @@ -0,0 +1,375 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.reflect; + +import javassist.*; +import java.io.IOException; +import javassist.CtMethod.ConstParameter; + +/** + * The class implementing the reflection mechanism. + * + * <p>This class is used with <code>ClassPool</code>. + * Note that it implements an interface <code>javassist.Translator</code>. + * + * <p>If a class is reflective, + * then all the method invocations on every + * instance of that class are intercepted by the runtime + * metaobject controlling that instance. + * To do this, the original class file representing a reflective class: + * + * <ul><pre> + * class Person { + * public int f(int i) { return i + 1; } + * public int value; + * } + * </pre></ul> + * + * <p>is modified so that it represents a class: + * + * <ul><pre> + * class Person implements Metalevel { + * public int _original_f(int i) { return i + 1; } + * public int f(int i) { <i>delegate to the metaobject</i> } + * + * public int value; + * public int _r_value() { <i>read "value"</i> } + * public void _w_value(int v) { <i>write "value"</i> } + * + * public ClassMetaobject _getClass() { <i>return a class metaobject</i> } + * public Metaobject _getMetaobject() { <i>return a metaobject</i> } + * public void _setMetaobject(Metaobject m) { <i>change a metaobject</i> } + * } + * </pre></ul> + * + * @see javassist.reflect.ClassMetaobject + * @see javassist.reflect.Metaobject + * @see javassist.reflect.Loader + * @see javassist.reflect.Compiler + * @see javassist.ClassPool + * @see javassist.Translator + */ +public class Reflection implements Translator { + + static final String classobjectField = "_classobject"; + static final String classobjectAccessor = "_getClass"; + static final String metaobjectField = "_metaobject"; + static final String metaobjectGetter = "_getMetaobject"; + static final String metaobjectSetter = "_setMetaobject"; + static final String readPrefix = "_r_"; + static final String writePrefix = "_w_"; + + protected CtMethod trapMethod, trapStaticMethod; + protected CtMethod trapRead, trapWrite; + protected CtClass[] readParam; + + protected ClassPool classPool; + protected CodeConverter converter; + + private boolean isExcluded(String name) { + return name.startsWith(ClassMetaobject.methodPrefix) + || name.equals(classobjectAccessor) + || name.equals(metaobjectSetter) + || name.equals(metaobjectGetter) + || name.startsWith(readPrefix) + || name.startsWith(writePrefix); + } + + /** + * Constructs a new <code>Reflection</code> object. + */ + public Reflection() { + classPool = null; + converter = new CodeConverter(); + } + + /** + * Initializes. + */ + public void start(ClassPool pool) throws NotFoundException { + classPool = pool; + final String msg + = "javassist.reflect.Sample is not found or broken."; + try { + CtClass c = classPool.get("javassist.reflect.Sample"); + trapMethod = c.getDeclaredMethod("trap"); + trapStaticMethod = c.getDeclaredMethod("trapStatic"); + trapRead = c.getDeclaredMethod("trapRead"); + trapWrite = c.getDeclaredMethod("trapWrite"); + readParam + = new CtClass[] { classPool.get("java.lang.Object") }; + } + catch (NotFoundException e) { + throw new RuntimeException(msg); + } + } + + /** + * Inserts hooks for intercepting accesses to the fields declared + * in reflective classes. + */ + public void onWrite(ClassPool pool, String classname) + throws CannotCompileException, NotFoundException + { + CtClass c = pool.get(classname); + c.instrument(converter); + } + + /** + * Produces a reflective class. + * If the super class is also made reflective, it must be done + * before the sub class. + * + * @param classname the name of the reflective class + * @param metaobject the class name of metaobjects. + * @param metaclass the class name of the class metaobject. + * @return <code>false</code> if the class is already reflective. + * + * @see javassist.reflect.Metaobject + * @see javassist.reflect.ClassMetaobject + */ + public boolean makeReflective(String classname, + String metaobject, String metaclass) + throws CannotCompileException, NotFoundException + { + return makeReflective(classPool.get(classname), + classPool.get(metaobject), + classPool.get(metaclass)); + } + + /** + * Produces a reflective class. + * If the super class is also made reflective, it must be done + * before the sub class. + * + * @param clazz the reflective class. + * @param metaobject the class of metaobjects. + * It must be a subclass of + * <code>Metaobject</code>. + * @param metaclass the class of the class metaobject. + * It must be a subclass of + * <code>ClassMetaobject</code>. + * @return <code>false</code> if the class is already reflective. + * + * @see javassist.reflect.Metaobject + * @see javassist.reflect.ClassMetaobject + */ + public boolean makeReflective(Class clazz, + Class metaobject, Class metaclass) + throws CannotCompileException, NotFoundException + { + return makeReflective(clazz.getName(), metaobject.getName(), + metaclass.getName()); + } + + /** + * Produces a reflective class. It modifies the given + * <code>CtClass</code> object and makes it reflective. + * If the super class is also made reflective, it must be done + * before the sub class. + * + * @param clazz the reflective class. + * @param metaobject the class of metaobjects. + * It must be a subclass of + * <code>Metaobject</code>. + * @param metaclass the class of the class metaobject. + * It must be a subclass of + * <code>ClassMetaobject</code>. + * @return <code>false</code> if the class is already reflective. + * + * @see javassist.reflect.Metaobject + * @see javassist.reflect.ClassMetaobject + */ + public boolean makeReflective(CtClass clazz, + CtClass metaobject, CtClass metaclass) + throws CannotCompileException, NotFoundException + { + registerReflectiveClass(clazz); + return modifyClassfile(clazz, metaobject, metaclass); + } + + /** + * Registers a reflective class. The field accesses to the instances + * of this class are instrumented. + */ + private void registerReflectiveClass(CtClass clazz) { + CtField[] fs = clazz.getDeclaredFields(); + for (int i = 0; i < fs.length; ++i) { + CtField f = fs[i]; + int mod = f.getModifiers(); + if ((mod & Modifier.PUBLIC) != 0 && (mod & Modifier.FINAL) == 0) { + String name = f.getName(); + converter.replaceFieldRead(f, clazz, readPrefix + name); + converter.replaceFieldWrite(f, clazz, writePrefix + name); + } + } + } + + private boolean modifyClassfile(CtClass clazz, CtClass metaobject, + CtClass metaclass) + throws CannotCompileException, NotFoundException + { + if (clazz.getAttribute("Reflective") != null) + return false; // this is already reflective. + else + clazz.setAttribute("Reflective", new byte[0]); + + CtClass mlevel = classPool.get("javassist.reflect.Metalevel"); + boolean addMeta = !clazz.subtypeOf(mlevel); + if (addMeta) + clazz.addInterface(mlevel); + + processMethods(clazz, addMeta); + processFields(clazz); + + CtField f; + if (addMeta) { + f = new CtField(classPool.get("javassist.reflect.Metaobject"), + metaobjectField, clazz); + f.setModifiers(Modifier.PROTECTED); + clazz.addField(f, CtField.Initializer.byNewWithParams(metaobject)); + + clazz.addMethod(CtNewMethod.getter(metaobjectGetter, f)); + clazz.addMethod(CtNewMethod.setter(metaobjectSetter, f)); + } + + f = new CtField(classPool.get("javassist.reflect.ClassMetaobject"), + classobjectField, clazz); + f.setModifiers(Modifier.PRIVATE | Modifier.STATIC); + clazz.addField(f, CtField.Initializer.byNew(metaclass, + new String[] { clazz.getName() })); + + clazz.addMethod(CtNewMethod.getter(classobjectAccessor, f)); + return true; + } + + private void processMethods(CtClass clazz, boolean dontSearch) + throws CannotCompileException, NotFoundException + { + CtMethod[] ms = clazz.getMethods(); + int identifier = 0; + for (int i = 0; i < ms.length; ++i) { + CtMethod m = ms[i]; + int mod = m.getModifiers(); + if (Modifier.isPublic(mod) && !Modifier.isAbstract(mod)) + processMethods0(mod, clazz, m, i, dontSearch); + } + } + + private void processMethods0(int mod, CtClass clazz, + CtMethod m, int identifier, boolean dontSearch) + throws CannotCompileException, NotFoundException + { + CtMethod body; + String name = m.getName(); + + if (isExcluded(name)) // internally-used method inherited + return; // from a reflective class. + + CtMethod m2; + if (m.getDeclaringClass() == clazz) { + if (Modifier.isNative(mod)) + return; + + m2 = m; + } + else { + if (Modifier.isFinal(mod)) + return; + + mod &= ~Modifier.NATIVE; + m2 = CtNewMethod.delegator(findOriginal(m, dontSearch), clazz); + m2.setModifiers(mod); + clazz.addMethod(m2); + } + + m2.setName(ClassMetaobject.methodPrefix + identifier + + "_" + name); + + if (Modifier.isStatic(mod)) + body = trapStaticMethod; + else + body = trapMethod; + + CtMethod wmethod + = CtNewMethod.wrapped(m.getReturnType(), name, + m.getParameterTypes(), m.getExceptionTypes(), + body, ConstParameter.integer(identifier), + clazz); + wmethod.setModifiers(mod); + clazz.addMethod(wmethod); + } + + private CtMethod findOriginal(CtMethod m, boolean dontSearch) + throws NotFoundException + { + if (dontSearch) + return m; + + String name = m.getName(); + CtMethod[] ms = m.getDeclaringClass().getDeclaredMethods(); + for (int i = 0; i < ms.length; ++i) { + String orgName = ms[i].getName(); + if (orgName.endsWith(name) + && orgName.startsWith(ClassMetaobject.methodPrefix) + && ms[i].getSignature().equals(m.getSignature())) + return ms[i]; + } + + return m; + } + + private void processFields(CtClass clazz) + throws CannotCompileException, NotFoundException + { + CtField[] fs = clazz.getDeclaredFields(); + for (int i = 0; i < fs.length; ++i) { + CtField f = fs[i]; + int mod = f.getModifiers(); + if ((mod & Modifier.PUBLIC) != 0 && (mod & Modifier.FINAL) == 0) { + mod |= Modifier.STATIC; + String name = f.getName(); + CtClass ftype = f.getType(); + CtMethod wmethod + = CtNewMethod.wrapped(ftype, readPrefix + name, + readParam, null, trapRead, + ConstParameter.string(name), + clazz); + wmethod.setModifiers(mod); + clazz.addMethod(wmethod); + CtClass[] writeParam = new CtClass[2]; + writeParam[0] = classPool.get("java.lang.Object"); + writeParam[1] = ftype; + wmethod = CtNewMethod.wrapped(CtClass.voidType, + writePrefix + name, + writeParam, null, trapWrite, + ConstParameter.string(name), clazz); + wmethod.setModifiers(mod); + clazz.addMethod(wmethod); + } + } + } +} diff --git a/src/main/javassist/reflect/Sample.java b/src/main/javassist/reflect/Sample.java new file mode 100644 index 00000000..0b760de2 --- /dev/null +++ b/src/main/javassist/reflect/Sample.java @@ -0,0 +1,66 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.reflect; + +/** + * A template used for defining a reflective class. + */ +public class Sample { + private Metaobject _metaobject; + private static ClassMetaobject _classobject; + + public Object trap(Object[] args, int identifier) throws Throwable { + Metaobject mobj; + mobj = _metaobject; + if (mobj == null) + return ClassMetaobject.invoke(this, identifier, args); + else + return mobj.trapMethodcall(identifier, args); + } + + public static Object trapStatic(Object[] args, int identifier) + throws Throwable + { + return _classobject.trapMethodcall(identifier, args); + } + + public static Object trapRead(Object[] args, String name) { + if (args[0] == null) + return _classobject.trapFieldRead(name); + else + return ((Metalevel)args[0])._getMetaobject().trapFieldRead(name); + } + + public static Object trapWrite(Object[] args, String name) { + Metalevel base = (Metalevel)args[0]; + if (base == null) + _classobject.trapFieldWrite(name, args[1]); + else + base._getMetaobject().trapFieldWrite(name, args[1]); + + return null; + } +} diff --git a/src/main/javassist/rmi/AppletServer.java b/src/main/javassist/rmi/AppletServer.java new file mode 100644 index 00000000..2b8c30e5 --- /dev/null +++ b/src/main/javassist/rmi/AppletServer.java @@ -0,0 +1,259 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.rmi; + +import java.io.*; +import javassist.web.*; +import javassist.CannotCompileException; +import javassist.NotFoundException; +import javassist.ClassPool; +import java.lang.reflect.Method; +import java.util.Hashtable; +import java.util.Vector; + +/** + * An AppletServer object is a web server that an ObjectImporter + * communicates with. It makes the objects specified by + * <code>exportObject()</code> remotely accessible from applets. + * If the classes of the exported objects are requested by the client-side + * JVM, this web server sends proxy classes for the requested classes. + * + * @see javassist.rmi.ObjectImporter + */ +public class AppletServer extends Webserver { + private StubGenerator stubGen; + private Hashtable exportedNames; + private Vector exportedObjects; + + private static final byte[] okHeader + = "HTTP/1.0 200 OK\r\n\r\n".getBytes(); + + /** + * Constructs a web server. + * + * @param port port number + */ + public AppletServer(String port) + throws IOException, NotFoundException, CannotCompileException + { + this(Integer.parseInt(port)); + } + + /** + * Constructs a web server. + * + * @param port port number + */ + public AppletServer(int port) + throws IOException, NotFoundException, CannotCompileException + { + this(ClassPool.getDefault(new StubGenerator()), port); + } + + /** + * Constructs a web server. + * + * @param port port number + * @param src the source of classs files. + */ + public AppletServer(int port, ClassPool src) + throws IOException, NotFoundException, CannotCompileException + { + this(new ClassPool(src, new StubGenerator()), port); + } + + private AppletServer(ClassPool loader, int port) + throws IOException, NotFoundException, CannotCompileException + { + super(port); + exportedNames = new Hashtable(); + exportedObjects = new Vector(); + stubGen = (StubGenerator)loader.getTranslator(); + setClassPool(loader); + } + + /** + * Begins the HTTP service. + */ + public void run() { + super.run(); + } + + /** + * Exports an object. + * This method produces the bytecode of the proxy class used + * to access the exported object. A remote applet can load + * the proxy class and call a method on the exported object. + * + * @param name the name used for looking the object up. + * @param object the exported object. + * @return the object identifier + * + * @see javassist.rmi.ObjectImporter#lookupObject(String) + */ + public synchronized int exportObject(String name, Object obj) + throws CannotCompileException + { + Class clazz = obj.getClass(); + ExportedObject eo = new ExportedObject(); + eo.object = obj; + eo.methods = clazz.getMethods(); + exportedObjects.addElement(eo); + eo.identifier = exportedObjects.size() - 1; + if (name != null) + exportedNames.put(name, eo); + + try { + stubGen.makeProxyClass(clazz); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + + return eo.identifier; + } + + /** + * Processes a request from a web browser (an ObjectImporter). + */ + public void doReply(InputStream in, OutputStream out, String cmd) + throws IOException, BadHttpRequest + { + if (cmd.startsWith("POST /rmi ")) + processRMI(in, out); + else if (cmd.startsWith("POST /lookup ")) + lookupName(cmd, in, out); + else + super.doReply(in, out, cmd); + } + + private void processRMI(InputStream ins, OutputStream outs) + throws IOException + { + ObjectInputStream in = new ObjectInputStream(ins); + + int objectId = in.readInt(); + int methodId = in.readInt(); + Exception err = null; + Object rvalue = null; + try { + ExportedObject eo + = (ExportedObject)exportedObjects.elementAt(objectId); + Object[] args = readParameters(in); + rvalue = convertRvalue(eo.methods[methodId].invoke(eo.object, + args)); + } + catch(Exception e) { + err = e; + logging2(e.toString()); + } + + outs.write(okHeader); + ObjectOutputStream out = new ObjectOutputStream(outs); + if (err != null) { + out.writeBoolean(false); + out.writeUTF(err.toString()); + } + else + try { + out.writeBoolean(true); + out.writeObject(rvalue); + } + catch (NotSerializableException e) { + logging2(e.toString()); + } + catch (InvalidClassException e) { + logging2(e.toString()); + } + + out.flush(); + out.close(); + in.close(); + } + + private Object[] readParameters(ObjectInputStream in) + throws IOException, ClassNotFoundException + { + int n = in.readInt(); + Object[] args = new Object[n]; + for (int i = 0; i < n; ++i) { + Object a = in.readObject(); + if (a instanceof RemoteRef) { + RemoteRef ref = (RemoteRef)a; + ExportedObject eo + = (ExportedObject)exportedObjects.elementAt(ref.oid); + a = eo.object; + } + + args[i] = a; + } + + return args; + } + + private Object convertRvalue(Object rvalue) + throws CannotCompileException + { + if (rvalue == null) + return null; // the return type is void. + + String classname = rvalue.getClass().getName(); + if (stubGen.isProxyClass(classname)) + return new RemoteRef(exportObject(null, rvalue), classname); + else + return rvalue; + } + + private void lookupName(String cmd, InputStream ins, OutputStream outs) + throws IOException + { + ObjectInputStream in = new ObjectInputStream(ins); + String name = DataInputStream.readUTF(in); + ExportedObject found = (ExportedObject)exportedNames.get(name); + outs.write(okHeader); + ObjectOutputStream out = new ObjectOutputStream(outs); + if (found == null) { + logging2(name + "not found."); + out.writeInt(-1); // error code + out.writeUTF("error"); + } + else { + logging2(name); + out.writeInt(found.identifier); + out.writeUTF(found.object.getClass().getName()); + } + + out.flush(); + out.close(); + in.close(); + } +} + +class ExportedObject { + public int identifier; + public Object object; + public Method[] methods; +} diff --git a/src/main/javassist/rmi/ObjectImporter.java b/src/main/javassist/rmi/ObjectImporter.java new file mode 100644 index 00000000..19b6cb4e --- /dev/null +++ b/src/main/javassist/rmi/ObjectImporter.java @@ -0,0 +1,308 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.rmi; + +import java.io.*; +import java.net.*; +import java.awt.*; +import java.applet.Applet; +import java.lang.reflect.*; + +/** + * The object importer enables applets to call a method on a remote + * object running on the <code>Webserver</code>. + * + * <p>To access the remote + * object, the applet first calls <code>lookupObject()</code> and + * obtains a proxy object, which is a reference to that object. + * The class name of the proxy object is identical to that of + * the remote object. + * The proxy object provides the same set of methods as the remote object. + * If one of the methods is invoked on the proxy object, + * the invocation is delegated to the remote object. + * From the viewpoint of the applet, therefore, the two objects are + * identical. The applet can access the object on the server + * with the regular Java syntax without concern about the actual + * location. + * + * <p>The methods remotely called by the applet must be <code>public</code>. + * This is true even if the applet's class and the remote object's classs + * belong to the same package. + * + * <p>If class X is a class of remote objects, a subclass of X must be + * also a class of remote objects. On the other hand, this restriction + * is not applied to the superclass of X. The class X does not have to + * contain a constructor taking no arguments. + * + * <p>The parameters to a remote method is passed in the <i>call-by-value</i> + * manner. Thus all the parameter classes must implement + * <code>java.io.Serializable</code>. However, if the parameter is the + * proxy object, the reference to the remote object instead of a copy of + * the object is passed to the method. + * + * <p>Because of the limitations of the current implementation, + * <ul> + * <li>The parameter objects cannot contain the proxy + * object as a field value. + * <li>If class <code>C</code> is of the remote object, then + * the applet cannot instantiate <code>C</code> locally or remotely. + * </ul> + * + * <p>All the exceptions thrown by the remote object are converted + * into <code>RemoteException</code>. Since this exception is a subclass + * of <code>RuntimeException</code>, the caller method does not need + * to catch the exception. However, good programs should catch + * the <code>RuntimeException</code>. + * + * @see javassist.rmi.AppletServer + * @see javassist.rmi.RemoteException + * @see javassist.web.Viewer + */ +public class ObjectImporter implements java.io.Serializable { + private final byte[] endofline = { 0x0d, 0x0a }; + private String servername, orgServername; + private int port, orgPort; + + private byte[] lookupCommand = "POST /lookup HTTP/1.0".getBytes(); + private byte[] rmiCommand = "POST /rmi HTTP/1.0".getBytes(); + + /** + * Constructs an object importer. + * + * <p>Remote objects are imported from the web server that the given + * applet has been loaded from. + * + * @param applet the applet loaded from the <code>Webserver</code>. + */ + public ObjectImporter(Applet applet) { + URL codebase = applet.getCodeBase(); + orgServername = servername = codebase.getHost(); + orgPort = port = codebase.getPort(); + } + + /** + * Constructs an object importer. + * + * <p>If you run a program with <code>javassist.web.Viewer</code>, + * you can construct an object importer as follows: + * + * <ul><pre> + * Viewer v = (Viewer)this.getClass().getClassLoader(); + * ObjectImporter oi = new ObjectImporter(v.getServer(), v.getPort()); + * </pre></ul> + * + * @see javassist.web.Viewer + */ + public ObjectImporter(String servername, int port) { + this.orgServername = this.servername = servername; + this.orgPort = this.port = port; + } + + /** + * Finds the object exported by a server with the specified name. + * If the object is not found, this method returns null. + * + * @param name the name of the exported object. + * @return the proxy object or null. + */ + public Object getObject(String name) { + try { + return lookupObject(name); + } + catch (ObjectNotFoundException e) { + return null; + } + } + + /** + * Sets an http proxy server. After this method is called, the object + * importer connects a server through the http proxy server. + */ + public void setHttpProxy(String host, int port) { + String proxyHeader = "POST http://" + orgServername + ":" + orgPort; + String cmd = proxyHeader + "/lookup HTTP/1.0"; + lookupCommand = cmd.getBytes(); + cmd = proxyHeader + "/rmi HTTP/1.0"; + rmiCommand = cmd.getBytes(); + this.servername = host; + this.port = port; + } + + /** + * Finds the object exported by the server with the specified name. + * It sends a POST request to the server (via an http proxy server + * if needed). + * + * @param name the name of the exported object. + * @return the proxy object. + */ + public Object lookupObject(String name) throws ObjectNotFoundException + { + try { + Socket sock = new Socket(servername, port); + OutputStream out = sock.getOutputStream(); + out.write(lookupCommand); + out.write(endofline); + out.write(endofline); + + ObjectOutputStream dout = new ObjectOutputStream(out); + dout.writeUTF(name); + dout.flush(); + + InputStream in = new BufferedInputStream(sock.getInputStream()); + skipHeader(in); + ObjectInputStream din = new ObjectInputStream(in); + int n = din.readInt(); + String classname = din.readUTF(); + din.close(); + dout.close(); + sock.close(); + + if (n >= 0) + return createProxy(n, classname); + } + catch (Exception e) { + e.printStackTrace(); + throw new ObjectNotFoundException(name, e); + } + + throw new ObjectNotFoundException(name); + } + + private static final Class[] proxyConstructorParamTypes + = new Class[] { ObjectImporter.class, int.class }; + + private Object createProxy(int oid, String classname) throws Exception { + Class c = Class.forName(classname); + Constructor cons = c.getConstructor(proxyConstructorParamTypes); + return cons.newInstance(new Object[] { this, new Integer(oid) }); + } + + /** + * Calls a method on a remote object. + * It sends a POST request to the server (via an http proxy server + * if needed). + * + * <p>This method is called by only proxy objects. + */ + public Object call(int objectid, int methodid, Object[] args) + throws RemoteException + { + boolean result; + Object rvalue; + String errmsg; + + try { + /* This method establishes a raw tcp connection for sending + * a POST message. Thus the object cannot communicate a + * remote object beyond a fire wall. To avoid this problem, + * the connection should be established with a mechanism + * collaborating a proxy server. Unfortunately, java.lang.URL + * does not seem to provide such a mechanism. + * + * You might think that using HttpURLConnection is a better + * way than constructing a raw tcp connection. Unfortunately, + * URL.openConnection() does not return an HttpURLConnection + * object in Netscape's JVM. It returns a + * netscape.net.URLConnection object. + * + * lookupObject() has the same problem. + */ + Socket sock = new Socket(servername, port); + OutputStream out = new BufferedOutputStream( + sock.getOutputStream()); + out.write(rmiCommand); + out.write(endofline); + out.write(endofline); + + ObjectOutputStream dout = new ObjectOutputStream(out); + dout.writeInt(objectid); + dout.writeInt(methodid); + writeParameters(dout, args); + dout.flush(); + + InputStream ins = new BufferedInputStream(sock.getInputStream()); + skipHeader(ins); + ObjectInputStream din = new ObjectInputStream(ins); + result = din.readBoolean(); + rvalue = null; + errmsg = null; + if (result) + rvalue = din.readObject(); + else + errmsg = din.readUTF(); + + din.close(); + dout.close(); + sock.close(); + + if (rvalue instanceof RemoteRef) { + RemoteRef ref = (RemoteRef)rvalue; + rvalue = createProxy(ref.oid, ref.classname); + } + } + catch (ClassNotFoundException e) { + throw new RemoteException(e); + } + catch (IOException e) { + throw new RemoteException(e); + } + catch (Exception e) { + throw new RemoteException(e); + } + + if (result) + return rvalue; + else + throw new RemoteException(errmsg); + } + + private void skipHeader(InputStream in) throws IOException { + int len; + do { + int c; + len = 0; + while ((c = in.read()) >= 0 && c != 0x0d) + ++len; + + in.read(); /* skip 0x0a (LF) */ + } while (len > 0); + } + + private void writeParameters(ObjectOutputStream dout, Object[] params) + throws IOException + { + int n = params.length; + dout.writeInt(n); + for (int i = 0; i < n; ++i) + if (params[i] instanceof Proxy) { + Proxy p = (Proxy)params[i]; + dout.writeObject(new RemoteRef(p._getObjectId())); + } + else + dout.writeObject(params[i]); + } +} diff --git a/src/main/javassist/rmi/ObjectNotFoundException.java b/src/main/javassist/rmi/ObjectNotFoundException.java new file mode 100644 index 00000000..e31db370 --- /dev/null +++ b/src/main/javassist/rmi/ObjectNotFoundException.java @@ -0,0 +1,36 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.rmi; + +public class ObjectNotFoundException extends Exception { + public ObjectNotFoundException(String name) { + super(name + " is not exported"); + } + + public ObjectNotFoundException(String name, Exception e) { + super(name + " because of " + e.toString()); + } +} diff --git a/src/main/javassist/rmi/Proxy.java b/src/main/javassist/rmi/Proxy.java new file mode 100644 index 00000000..f310d358 --- /dev/null +++ b/src/main/javassist/rmi/Proxy.java @@ -0,0 +1,35 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.rmi; + +/** + * An interface implemented by proxy classes. + * + * @see javassist.rmi.StubGenerator + */ +public interface Proxy { + int _getObjectId(); +} diff --git a/src/main/javassist/rmi/RemoteException.java b/src/main/javassist/rmi/RemoteException.java new file mode 100644 index 00000000..6b89c64b --- /dev/null +++ b/src/main/javassist/rmi/RemoteException.java @@ -0,0 +1,40 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.rmi; + +/** + * <code>RemoteException</code> represents any exception thrown + * during remote method invocation. + */ +public class RemoteException extends RuntimeException { + public RemoteException(String msg) { + super(msg); + } + + public RemoteException(Exception e) { + super("by " + e.toString()); + } +} diff --git a/src/main/javassist/rmi/RemoteRef.java b/src/main/javassist/rmi/RemoteRef.java new file mode 100644 index 00000000..0017ecc1 --- /dev/null +++ b/src/main/javassist/rmi/RemoteRef.java @@ -0,0 +1,45 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.rmi; + +/** + * Remote reference. This class is internally used for sending a remote + * reference through a network stream. + */ +public class RemoteRef implements java.io.Serializable { + public int oid; + public String classname; + + public RemoteRef(int i) { + oid = i; + classname = null; + } + + public RemoteRef(int i, String name) { + oid = i; + classname = name; + } +} diff --git a/src/main/javassist/rmi/Sample.java b/src/main/javassist/rmi/Sample.java new file mode 100644 index 00000000..fee85d27 --- /dev/null +++ b/src/main/javassist/rmi/Sample.java @@ -0,0 +1,46 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.rmi; + +/** + * A template used for defining a proxy class. + * The class file of this class is read by the <code>StubGenerator</code> + * class. + */ +public class Sample { + private ObjectImporter importer; + private int objectId; + + public Object forward(Object[] args, int identifier) { + return importer.call(objectId, identifier, args); + } + + public static Object forwardStatic(Object[] args, int identifier) + throws RemoteException + { + throw new RemoteException("cannot call a static method."); + } +} diff --git a/src/main/javassist/rmi/StubGenerator.java b/src/main/javassist/rmi/StubGenerator.java new file mode 100644 index 00000000..9e77c383 --- /dev/null +++ b/src/main/javassist/rmi/StubGenerator.java @@ -0,0 +1,261 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.rmi; + +import java.io.*; +import javassist.*; +import java.lang.reflect.Method; +import java.util.Hashtable; +import javassist.CtMethod.ConstParameter; + +/** + * A stub-code generator. It is used for producing a proxy class. + * + * <p>The proxy class for class A is as follows: + * + * <ul><pre>public class A implements Proxy, Serializable { + * private ObjectImporter importer; + * private int objectId; + * public int _getObjectId() { return objectId; } + * public A(ObjectImporter oi, int id) { + * importer = oi; objectId = id; + * } + * + * ... the same methods that the original class A declares ... + * }</pre></ul> + * + * <p>Instances of the proxy class is created by an + * <code>ObjectImporter</code> object. + */ +public class StubGenerator implements Translator { + private static final String fieldImporter = "importer"; + private static final String fieldObjectId = "objectId"; + private static final String accessorObjectId = "_getObjectId"; + private static final String sampleClass = "javassist.rmi.Sample"; + + private ClassPool classPool; + private Hashtable proxyClasses; + private CtMethod forwardMethod; + private CtMethod forwardStaticMethod; + + private CtClass[] proxyConstructorParamTypes; + private CtClass[] interfacesForProxy; + private CtClass[] exceptionForProxy; + + /** + * Constructs a stub-code generator. + */ + public StubGenerator() { + proxyClasses = new Hashtable(); + } + + /** + * Is a method declared in javassist.Translator. + * + * @see javassist.Translator#start(ClassPool) + */ + public void start(ClassPool pool) throws NotFoundException { + classPool = pool; + CtClass c = pool.get(sampleClass); + forwardMethod = c.getDeclaredMethod("forward"); + forwardStaticMethod = c.getDeclaredMethod("forwardStatic"); + + proxyConstructorParamTypes + = pool.get(new String[] { "javassist.rmi.ObjectImporter", + "int" }); + interfacesForProxy + = pool.get(new String[] { "java.io.Serializable", + "javassist.rmi.Proxy" }); + exceptionForProxy + = new CtClass[] { pool.get("javassist.rmi.RemoteException") }; + } + + public void onWrite(ClassPool pool, String classname) {} + + /** + * Returns <code>true</code> if the specified class is a proxy class + * recorded by <code>makeProxyClass()</code>. + * + * @param name a fully-qualified class name + */ + public boolean isProxyClass(String name) { + return proxyClasses.get(name) != null; + } + + /** + * Makes a proxy class. The produced class is substituted + * for the original class. + * + * @param clazz the class referenced + * through the proxy class. + * @return <code>false</code> if the proxy class + * has been already produced. + */ + public synchronized boolean makeProxyClass(Class clazz) + throws CannotCompileException, NotFoundException + { + String classname = clazz.getName(); + if (proxyClasses.get(classname) != null) + return false; + else { + CtClass ctclazz = produceProxyClass(classPool.get(classname), + clazz); + proxyClasses.put(classname, ctclazz); + modifySuperclass(ctclazz); + return true; + } + } + + private CtClass produceProxyClass(CtClass orgclass, Class orgRtClass) + throws CannotCompileException, NotFoundException + { + int modify = orgclass.getModifiers(); + if (Modifier.isAbstract(modify) || Modifier.isNative(modify) + || !Modifier.isPublic(modify)) + throw new CannotCompileException(orgclass.getName() + + " must be public, non-native, and non-abstract."); + + CtClass proxy = classPool.makeClass(orgclass.getName(), + orgclass.getSuperclass()); + + proxy.setInterfaces(interfacesForProxy); + + CtField f + = new CtField(classPool.get("javassist.rmi.ObjectImporter"), + fieldImporter, proxy); + f.setModifiers(Modifier.PRIVATE); + proxy.addField(f, CtField.Initializer.byParameter(0)); + + f = new CtField(CtClass.intType, fieldObjectId, proxy); + f.setModifiers(Modifier.PRIVATE); + proxy.addField(f, CtField.Initializer.byParameter(1)); + + proxy.addMethod(CtNewMethod.getter(accessorObjectId, f)); + + proxy.addConstructor(CtNewConstructor.defaultConstructor(proxy)); + CtConstructor cons + = CtNewConstructor.skeleton(proxyConstructorParamTypes, + null, proxy); + proxy.addConstructor(cons); + + try { + addMethods(proxy, orgRtClass.getMethods()); + return proxy; + } + catch (SecurityException e) { + throw new CannotCompileException(e); + } + } + + private CtClass toCtClass(Class rtclass) throws NotFoundException { + String name; + if (!rtclass.isArray()) + name = rtclass.getName(); + else { + StringBuffer sbuf = new StringBuffer(); + do { + sbuf.append("[]"); + rtclass = rtclass.getComponentType(); + } while(rtclass.isArray()); + sbuf.insert(0, rtclass.getName()); + name = sbuf.toString(); + } + + return classPool.get(name); + } + + private CtClass[] toCtClass(Class[] rtclasses) throws NotFoundException { + int n = rtclasses.length; + CtClass[] ctclasses = new CtClass[n]; + for (int i = 0; i < n; ++i) + ctclasses[i] = toCtClass(rtclasses[i]); + + return ctclasses; + } + + /* ms must not be an array of CtMethod. To invoke a method ms[i] + * on a server, a client must send i to the server. + */ + private void addMethods(CtClass proxy, Method[] ms) + throws CannotCompileException, NotFoundException + { + CtMethod wmethod; + for (int i = 0; i < ms.length; ++i) { + Method m = ms[i]; + int mod = m.getModifiers(); + if (m.getDeclaringClass() != Object.class + && !Modifier.isFinal(mod)) + if (Modifier.isPublic(mod)) { + CtMethod body; + if (Modifier.isStatic(mod)) + body = forwardStaticMethod; + else + body = forwardMethod; + + wmethod + = CtNewMethod.wrapped(toCtClass(m.getReturnType()), + m.getName(), + toCtClass(m.getParameterTypes()), + exceptionForProxy, + body, + ConstParameter.integer(i), + proxy); + wmethod.setModifiers(mod); + proxy.addMethod(wmethod); + } + else if (!Modifier.isProtected(mod) + && !Modifier.isPrivate(mod)) + // if package method + throw new CannotCompileException( + "the methods must be public, protected, or private."); + } + } + + /** + * Adds a default constructor to the super classes. + */ + private void modifySuperclass(CtClass orgclass) + throws CannotCompileException, NotFoundException + { + CtClass superclazz; + for (;; orgclass = superclazz) { + superclazz = orgclass.getSuperclass(); + if (superclazz == null) + break; + + String name = superclazz.getName(); + try { + superclazz.getDeclaredConstructor(null); + break; // the constructor with no arguments is found. + } + catch (NotFoundException e) { + } + + superclazz.addConstructor( + CtNewConstructor.defaultConstructor(superclazz)); + } + } +} diff --git a/src/main/javassist/runtime/Cflow.java b/src/main/javassist/runtime/Cflow.java new file mode 100644 index 00000000..693da245 --- /dev/null +++ b/src/main/javassist/runtime/Cflow.java @@ -0,0 +1,62 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.runtime; + +/** + * A support class for implementing <code>$cflow</code>. + * This support class is required at runtime + * only if <code>$cflow</code> is used. + * + * @see javassist.CtBehavior#useCflow(String) + */ +public class Cflow extends ThreadLocal { + private static class Depth { + private int depth; + Depth() { depth = 0; } + int get() { return depth; } + void inc() { ++depth; } + void dec() { --depth; } + } + + protected synchronized Object initialValue() { + return new Depth(); + } + + /** + * Increments the counter. + */ + public void enter() { ((Depth)get()).inc(); } + + /** + * Decrements the counter. + */ + public void exit() { ((Depth)get()).dec(); } + + /** + * Returns the value of the counter. + */ + public int value() { return ((Depth)get()).get(); } +} diff --git a/src/main/javassist/runtime/Desc.java b/src/main/javassist/runtime/Desc.java new file mode 100644 index 00000000..0bf49bf2 --- /dev/null +++ b/src/main/javassist/runtime/Desc.java @@ -0,0 +1,167 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.runtime; + +/** + * A support class for implementing <code>$sig</code> and + * <code>$type</code>. + * This support class is required at runtime + * only if <code>$sig</code> or <code>$type</code> is used. + */ +public class Desc { + + /** + * Specifies how a <code>java.lang.Class</code> object is loaded. + * + * <p>If true, it is loaded by: + * <ul><pre>Thread.currentThread().getContextClassLoader().loadClass()</pre></ul> + * <p>If false, it is loaded by <code>Class.forName()</code>. + * The default value is false. + */ + public static boolean useContextClassLoader = false; + + private static Class getClassObject(String name) + throws ClassNotFoundException + { + if (useContextClassLoader) + return Thread.currentThread().getContextClassLoader() + .loadClass(name); + else + return Class.forName(name); + } + + /** + * Interprets the given class name. + * It is used for implementing <code>$class</code>. + */ + public static Class getClazz(String name) { + try { + return getClassObject(name); + } + catch (ClassNotFoundException e) { + throw new RuntimeException("$class: internal error"); + } + } + + /** + * Interprets the given type descriptor representing a method + * signature. It is used for implementing <code>$sig</code>. + */ + public static Class[] getParams(String desc) { + if (desc.charAt(0) != '(') + throw new RuntimeException("$sig: internal error"); + + return getType(desc, desc.length(), 1, 0); + } + + /** + * Interprets the given type descriptor. + * It is used for implementing <code>$type</code>. + */ + public static Class getType(String desc) { + Class[] result = getType(desc, desc.length(), 0, 0); + if (result == null || result.length != 1) + throw new RuntimeException("$type: internal error"); + + return result[0]; + } + + private static Class[] getType(String desc, int descLen, + int start, int num) { + Class clazz; + if (start >= descLen) + return new Class[num]; + + char c = desc.charAt(start); + switch (c) { + case 'Z' : + clazz = Boolean.TYPE; + break; + case 'C' : + clazz = Character.TYPE; + break; + case 'B' : + clazz = Byte.TYPE; + break; + case 'S' : + clazz = Short.TYPE; + break; + case 'I' : + clazz = Integer.TYPE; + break; + case 'J' : + clazz = Long.TYPE; + break; + case 'F' : + clazz = Float.TYPE; + break; + case 'D' : + clazz = Double.TYPE; + break; + case 'V' : + clazz = Void.TYPE; + break; + case 'L' : + case '[' : + return getClassType(desc, descLen, start, num); + default : + return new Class[num]; + } + + Class[] result = getType(desc, descLen, start + 1, num + 1); + result[num] = clazz; + return result; + } + + private static Class[] getClassType(String desc, int descLen, + int start, int num) { + int end = start; + while (desc.charAt(end) == '[') + ++end; + + if (desc.charAt(end) == 'L') { + end = desc.indexOf(';', end); + if (end < 0) + throw new IndexOutOfBoundsException("bad descriptor"); + } + + String cname; + if (desc.charAt(start) == 'L') + cname = desc.substring(start + 1, end); + else + cname = desc.substring(start, end + 1); + + Class[] result = getType(desc, descLen, end + 1, num + 1); + try { + result[num] = getClassObject(cname.replace('/', '.')); + } + catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + + return result; + } +} diff --git a/src/main/javassist/web/BadHttpRequest.java b/src/main/javassist/web/BadHttpRequest.java new file mode 100644 index 00000000..aafebce0 --- /dev/null +++ b/src/main/javassist/web/BadHttpRequest.java @@ -0,0 +1,44 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.web; + +/** + * Thrown when receiving an invalid HTTP request. + */ +public class BadHttpRequest extends Exception { + private Exception e; + + public BadHttpRequest() { e = null; } + + public BadHttpRequest(Exception _e) { e = _e; } + + public String toString() { + if (e == null) + return super.toString(); + else + return e.toString(); + } +} diff --git a/src/main/javassist/web/Viewer.java b/src/main/javassist/web/Viewer.java new file mode 100644 index 00000000..ce57c016 --- /dev/null +++ b/src/main/javassist/web/Viewer.java @@ -0,0 +1,218 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.web; + +import java.io.*; +import java.net.*; + +/** + * An applet viewer. + * + * <p>This is a sort of applet viewer that can run any program even if + * the main class is not a subclass of <code>Applet</code>. + * This viewwer first calls <code>main()</code> in the main class. + * + * <p>To run, you should type: + * + * <ul><code>% java javassist.web.Viewer <i>host port</i> Main arg1, ...</code></ul> + * + * <p>This command calls <code>Main.main()</code> with <code>arg1,...</code> + * All classes including <code>Main</code> are fetched from + * a server http://<i>host</i>:<i>port</i>. + * Only the class file for <code>Viewer</code> must exist + * on a local file system at the client side; even other + * <code>javassist.*</code> classes are not needed at the client side. + * <code>Viewer</code> uses only Java core API classes. + * + * <p>Note: since a <code>Viewer</code> object is a class loader, + * a program loaded by this object can call a method in <code>Viewer</code>. + * For example, you can write something like this: + * + * <ul><pre> + * Viewer v = (Viewer)this.getClass().getClassLoader(); + * String port = v.getPort(); + * </pre></ul> + * + */ +public class Viewer extends ClassLoader { + private String server; + private int port; + + /** + * Starts a program. + */ + public static void main(String[] args) throws Throwable { + if (args.length >= 3) { + Viewer cl = new Viewer(args[0], Integer.parseInt(args[1])); + String[] args2 = new String[args.length - 3]; + System.arraycopy(args, 3, args2, 0, args.length - 3); + cl.run(args[2], args2); + } + else + System.err.println( + "Usage: java javassist.web.Viewer <host> <port> class [args ...]"); + } + + /** + * Constructs a viewer. + * + * @param host server name + * @param p port number + */ + public Viewer(String host, int p) { + server = host; + port = p; + } + + /** + * Returns the server name. + */ + public String getServer() { return server; } + + /** + * Returns the port number. + */ + public int getPort() { return port; } + + /** + * Invokes main() in the class specified by <code>classname</code>. + * + * @param classname executed class + * @param args the arguments passed to <code>main()</code>. + */ + public void run(String classname, String[] args) + throws Throwable + { + Class c = loadClass(classname); + try { + c.getDeclaredMethod("main", new Class[] { String[].class }) + .invoke(null, new Object[] { args }); + } + catch (java.lang.reflect.InvocationTargetException e) { + throw e.getTargetException(); + } + } + + /** + * Requests the class loader to load a class. + */ + protected synchronized Class loadClass(String name, boolean resolve) + throws ClassNotFoundException + { + Class c = findLoadedClass(name); + if (c == null) + c = findClass(name); + + if (c == null) + throw new ClassNotFoundException(name); + + if (resolve) + resolveClass(c); + + return c; + } + + /** + * Finds the specified class. The implementation in this class + * fetches the class from the http server. If the class is + * either <code>java.*</code>, <code>javax.*</code>, or + * <code>Viewer</code>, then it is loaded by the parent class + * loader. + * + * <p>This method can be overridden by a subclass of + * <code>Viewer</code>. + */ + protected Class findClass(String name) throws ClassNotFoundException { + Class c = null; + if (name.startsWith("java.") || name.startsWith("javax.") + || name.equals("javassist.web.Viewer")) + c = findSystemClass(name); + + if (c == null) + try { + byte[] b = fetchClass(name); + if (b != null) + c = defineClass(name, b, 0, b.length); + } + catch (Exception e) { + } + + return c; + } + + /** + * Fetches the class file of the specified class from the http + * server. + */ + protected byte[] fetchClass(String classname) throws Exception + { + byte[] b; + URL url = new URL("http", server, port, + "/" + classname.replace('.', '/') + ".class"); + URLConnection con = url.openConnection(); + con.connect(); + int size = con.getContentLength(); + InputStream s = con.getInputStream(); + if (size <= 0) + b = readStream(s); + else { + b = new byte[size]; + int len = 0; + do { + int n = s.read(b, len, size - len); + if (n < 0) { + s.close(); + throw new IOException("the stream was closed: " + + classname); + } + len += n; + } while (len < size); + } + + s.close(); + return b; + } + + private byte[] readStream(InputStream fin) throws IOException { + byte[] buf = new byte[4096]; + int size = 0; + int len = 0; + do { + size += len; + if (buf.length - size <= 0) { + byte[] newbuf = new byte[buf.length * 2]; + System.arraycopy(buf, 0, newbuf, 0, size); + buf = newbuf; + } + + len = fin.read(buf, size, buf.length - size); + } while (len >= 0); + + byte[] result = new byte[size]; + System.arraycopy(buf, 0, result, 0, size); + return result; + } +} diff --git a/src/main/javassist/web/Webserver.java b/src/main/javassist/web/Webserver.java new file mode 100644 index 00000000..95ad05a2 --- /dev/null +++ b/src/main/javassist/web/Webserver.java @@ -0,0 +1,400 @@ +/* + * This file is part of the Javassist toolkit. + * + * 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. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * 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. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.web; + +import java.net.*; +import java.io.*; +import java.util.Hashtable; +import java.util.Date; +import javassist.ClassPool; +import javassist.Translator; + +/** + * A web server for Javassist. + * + * <p>This enables a Java program to instrument class files loaded by + * web browsers for applets. Since the (standard) security manager + * does not allow an applet to create and use a class loader, + * instrumenting class files must be done by this web server. + * + * <p>Programmers can register a <code>ClassPool</code> object for + * instrumenting class files when they are sent to web browsers. + * + * <p><b>Note:</b> although this class is included in the Javassist API, + * it is provided as a sample implementation of the web server using + * Javassist. Especially, there might be security flaws in this server. + * Please use this with YOUR OWN RISK. + */ +public class Webserver { + private ServerSocket socket; + private ClassPool classPool; + + private final static byte[] endofline = { 0x0d, 0x0a }; + private byte[] filebuffer = new byte[4096]; + + private final static int typeHtml = 1; + private final static int typeClass = 2; + private final static int typeGif = 3; + private final static int typeJpeg = 4; + private final static int typeText = 5; + private final static int typeUnknown = 6; + + /** + * If this field is not null, the class files taken from + * <code>ClassPool</code> are written out under the directory + * specified by this field. The directory name must not end + * with a directory separator. + */ + public String debugDir = null; + + /** + * The top directory of html (and .gif, .class, ...) files. + * It must end with the directory separator such as "/". + * (For portability, "/" should be used as the directory separator. + * Javassist automatically translates "/" into a platform-dependent + * character.) + * If this field is null, the top directory is the current one where + * the JVM is running. + * + * <p>If the given URL indicates a class file and the class file + * is not found under the directory specified by this variable, + * then <code>Class.getResourceAsStream()</code> is called + * for searching the Java class paths. + */ + public String htmlfileBase = null; + + /** + * Starts a web server. + * The port number is specified by the first argument. + */ + public static void main(String[] args) throws IOException { + if (args.length == 1) { + Webserver web = new Webserver(args[0]); + web.run(); + } + else + System.err.println( + "Usage: java javassist.web.Webserver <port number>"); + } + + /** + * Constructs a web server. + * + * @param port port number + */ + public Webserver(String port) throws IOException { + this(Integer.parseInt(port)); + } + + /** + * Constructs a web server. + * + * @param port port number + */ + public Webserver(int port) throws IOException { + socket = new ServerSocket(port); + classPool = null; + } + + /** + * Requests the web server to use the specified + * <code>ClassPool</code> object for obtaining a class file. + */ + public void setClassPool(ClassPool loader) { + classPool = loader; + } + + /** + * Closes the socket. + */ + public void end() throws IOException { + socket.close(); + } + + /** + * Prints a log message. + */ + public void logging(String msg) { + System.out.println(msg); + } + + /** + * Prints a log message. + */ + public void logging(String msg1, String msg2) { + System.out.print(msg1); + System.out.print(" "); + System.out.println(msg2); + } + + /** + * Prints a log message. + */ + public void logging(String msg1, String msg2, String msg3) { + System.out.print(msg1); + System.out.print(" "); + System.out.print(msg2); + System.out.print(" "); + System.out.println(msg3); + } + + /** + * Prints a log message with indentation. + */ + public void logging2(String msg) { + System.out.print(" "); + System.out.println(msg); + } + + /** + * Begins the HTTP service. + */ + public void run() { + System.err.println("ready to service..."); + for (;;) + try { + ServiceThread th = new ServiceThread(this, socket.accept()); + th.start(); + } + catch (IOException e) { + logging(e.toString()); + } + } + + final void process(Socket clnt) throws IOException { + InputStream in = new BufferedInputStream(clnt.getInputStream()); + String cmd = readLine(in); + logging(clnt.getInetAddress().getHostName(), + new Date().toString(), cmd); + while (skipLine(in) > 0){ + } + + OutputStream out = new BufferedOutputStream(clnt.getOutputStream()); + try { + doReply(in, out, cmd); + } + catch (BadHttpRequest e) { + replyError(out, e); + } + + out.flush(); + in.close(); + out.close(); + clnt.close(); + } + + private String readLine(InputStream in) throws IOException { + StringBuffer buf = new StringBuffer(); + int c; + while ((c = in.read()) >= 0 && c != 0x0d) + buf.append((char)c); + + in.read(); /* skip 0x0a (LF) */ + return buf.toString(); + } + + private int skipLine(InputStream in) throws IOException { + int c; + int len = 0; + while ((c = in.read()) >= 0 && c != 0x0d) + ++len; + + in.read(); /* skip 0x0a (LF) */ + return len; + } + + /** + * Proceses a HTTP request from a client. + * + * @param out the output stream to a client + * @param cmd the command received from a client + */ + public void doReply(InputStream in, OutputStream out, String cmd) + throws IOException, BadHttpRequest + { + int len; + int fileType; + String filename, urlName; + + if (cmd.startsWith("GET /")) + filename = urlName = cmd.substring(5, cmd.indexOf(' ', 5)); + else + throw new BadHttpRequest(); + + if (filename.endsWith(".class")) + fileType = typeClass; + else if (filename.endsWith(".html") || filename.endsWith(".htm")) + fileType = typeHtml; + else if (filename.endsWith(".gif")) + fileType = typeGif; + else if (filename.endsWith(".jpg")) + fileType = typeJpeg; + else + fileType = typeText; // or textUnknown + + len = filename.length(); + if (fileType == typeClass + && letUsersSendClassfile(out, filename, len)) + return; + + checkFilename(filename, len); + if (htmlfileBase != null) + filename = htmlfileBase + filename; + + if (File.separatorChar != '/') + filename = filename.replace('/', File.separatorChar); + + File file = new File(filename); + if (file.canRead()) { + sendHeader(out, file.length(), fileType); + FileInputStream fin = new FileInputStream(file); + for (;;) { + len = fin.read(filebuffer); + if (len <= 0) + break; + else + out.write(filebuffer, 0, len); + } + + fin.close(); + return; + } + + // If the file is not found under the html-file directory, + // then Class.getResourceAsStream() is tried. + + if (fileType == typeClass) { + InputStream fin + = getClass().getResourceAsStream("/" + urlName); + if (fin != null) { + ByteArrayOutputStream barray = new ByteArrayOutputStream(); + for (;;) { + len = fin.read(filebuffer); + if (len <= 0) + break; + else + barray.write(filebuffer, 0, len); + } + + byte[] classfile = barray.toByteArray(); + sendHeader(out, classfile.length, typeClass); + out.write(classfile); + fin.close(); + return; + } + } + + throw new BadHttpRequest(); + } + + private void checkFilename(String filename, int len) + throws BadHttpRequest + { + for (int i = 0; i < len; ++i) { + char c = filename.charAt(i); + if (!Character.isJavaIdentifierPart(c) && c != '.' && c != '/') + throw new BadHttpRequest(); + } + + if (filename.indexOf("..") >= 0) + throw new BadHttpRequest(); + } + + private boolean letUsersSendClassfile(OutputStream out, + String filename, int length) + throws IOException, BadHttpRequest + { + if (classPool == null) + return false; + + byte[] classfile; + String classname + = filename.substring(0, length - 6).replace('/', '.'); + try { + classfile = classPool.write(classname); + if (debugDir != null) + classPool.writeFile(classname, debugDir); + } + catch (Exception e) { + throw new BadHttpRequest(e); + } + + sendHeader(out, classfile.length, typeClass); + out.write(classfile); + return true; + } + + private void sendHeader(OutputStream out, long dataLength, int filetype) + throws IOException + { + out.write("HTTP/1.0 200 OK".getBytes()); + out.write(endofline); + out.write("Content-Length: ".getBytes()); + out.write(Long.toString(dataLength).getBytes()); + out.write(endofline); + if (filetype == typeClass) + out.write("Content-Type: application/octet-stream".getBytes()); + else if (filetype == typeHtml) + out.write("Content-Type: text/html".getBytes()); + else if (filetype == typeGif) + out.write("Content-Type: image/gif".getBytes()); + else if (filetype == typeJpeg) + out.write("Content-Type: image/jpg".getBytes()); + else if (filetype == typeText) + out.write("Content-Type: text/plain".getBytes()); + + out.write(endofline); + out.write(endofline); + } + + private void replyError(OutputStream out, BadHttpRequest e) + throws IOException + { + logging2("bad request: " + e.toString()); + out.write("HTTP/1.0 400 Bad Request".getBytes()); + out.write(endofline); + out.write(endofline); + out.write("<H1>Bad Request</H1>".getBytes()); + } +} + +class ServiceThread extends Thread { + Webserver web; + Socket sock; + + public ServiceThread(Webserver w, Socket s) { + web = w; + sock = s; + } + + public void run() { + try { + web.process(sock); + } + catch (IOException e) { + } + } +} diff --git a/tutorial/brown.css b/tutorial/brown.css new file mode 100644 index 00000000..b88ddb46 --- /dev/null +++ b/tutorial/brown.css @@ -0,0 +1,19 @@ +h1,h2,h3 {
+ color:#663300;
+ padding:4px 6px 6px 10px;
+ border-width:1px 0px 1px 0px;
+ border-color:#F5DEB3;
+ border-style:solid;
+}
+
+h3 {
+ padding-left: 30px;
+}
+
+h4 {
+ color:#663300;
+}
+
+em {
+ color:#cc0000;
+}
diff --git a/tutorial/overview.gif b/tutorial/overview.gif Binary files differnew file mode 100644 index 00000000..953c59a6 --- /dev/null +++ b/tutorial/overview.gif diff --git a/tutorial/sequence.gif b/tutorial/sequence.gif Binary files differnew file mode 100644 index 00000000..a59d66fd --- /dev/null +++ b/tutorial/sequence.gif diff --git a/tutorial/tutorial.html b/tutorial/tutorial.html new file mode 100644 index 00000000..3fac03ee --- /dev/null +++ b/tutorial/tutorial.html @@ -0,0 +1,560 @@ +<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Javassist Tutorial</title>
+ <link rel="stylesheet" type="text/css" href="brown.css">
+</head>
+<body>
+
+<b>
+<font size="+3">
+Getting Started with Javassist
+</font>
+
+<p><font size="+2">
+Shigeru Chiba
+</font>
+</b>
+
+<p><div align="right"><a href="tutorial2.html">Next page</a></div>
+
+<ul>1. <a href="#read">Reading bytecode</a>
+<br>2. <a href="#def">Defining a new class</a>
+<br>3. <a href="#mod">Modifying a class at load time</a>
+<br>4. <a href="#load">Class loader</a>
+<br>5. <a href="tutorial2.html#intro">Introspection and customization</a>
+</ul>
+
+<p><br>
+
+<a name="read">
+<h2>1. Reading bytecode</h2>
+
+<p>Javassist is a class library for dealing with Java bytecode.
+Java bytecode is stored in a binary file called a class file.
+Each class file contains one Java class or interface.
+
+<p>The class <code>Javassist.CtClass</code> is an abstract representation
+of a class file. A <code>CtClass</code> object is a handle for dealing
+with a class file. The following program is a very simple example:
+
+<ul><pre>
+ClassPool pool = ClassPool.getDefault();
+CtClass cc = pool.get("test.Rectangle");
+cc.setSuperclass(pool.get("test.Point"));
+pool.writeFile("test.Rectangle"); // or simply, cc.writeFile()
+</pre></ul>
+
+<p>This program first obtains a <code>ClassPool</code> object,
+which controls bytecode modification with Javassist.
+The <code>ClassPool</code> object is a container of <code>CtClass</code>
+object representing a class file.
+It reads a class file on demand for constructing a <code>CtClass</code>
+object and contains the constructed object until it is written out
+to a file or an output stream.
+
+<p>To modify the definition of a class, the users must first obtain a
+reference to the <code>CtClass</code> object representing that class.
+<code>ClassPool.get()</code> is used for this purpose.
+In the case of the program above, the <code>CtClass</code> object
+representing a class <code>test.Rectangle</code> is obtained from
+the <code>ClassPool</code> object
+and it is assigned
+to a variable <code>cc</code>. Then it is modified so that
+the superclass of <code>test.Rectangle</code> is changed into
+a class <code>test.Point</code>.
+This change is reflected on the original class file when
+<code>ClassPool.writeFile()</code> is finally called.
+
+<p>Note that <code>writeFile()</code> is a method declared in not
+<code>CtClass</code> but <code>ClassPool</code>.
+If this method is called, the <code>ClassPool</code>
+finds a <code>CtClass</code> object specified with a class name
+among the objects that the <code>ClassPool</code> contains.
+Then it translates that <code>CtClass</code> object into a class file
+and writes it on a local disk.
+
+<p>There is also <code>writeFile()</code> defined in <code>CtClass</code>.
+Thus, the last line in the program above can be rewritten into:
+
+<ul><pre>cc.writeFile();</pre></ul>
+
+<p>This method is a convenient method for invoking <code>writeFile()</code>
+in <code>ClassPool</code> with the name of the class represented by
+<code>cc</code>.
+
+<p>Javassist also provides a method for directly obtaining the
+modified bytecode. To do this, call <code>write()</code>:
+
+<ul><pre>
+byte[] b = pool.write("test.Rectangle");
+</pre></ul>
+
+<p>The contents of the class file for <code>test.Rectangle</code> are
+assigned to a variable <code>b</code> in the form of byte array.
+<code>writeFile()</code> also internally calls <code>write()</code>
+to obtain the byte array written in a class file.
+
+<p>The default <code>ClassPool</code> returned
+by a static method <code>ClassPool.getDefault()</code>
+searches the same path as the underlying JVM.
+The users can expand this class search path if needed.
+For example, the following code adds a directory
+<code>/usr/local/javalib</code>
+to the search path:
+
+<ul><pre>
+ClassPool pool = ClassPool.getDefault();
+pool.insertClassPath("/usr/local/javalib");
+</pre></ul>
+
+<p>The search path that the users can add is not only a directory but also
+a URL:
+
+<ul><pre>
+ClassPool pool = ClassPool.getDefault();
+ClassPath cp = new URLClassPath("www.foo.com", 80, "/java/", "com.foo.");
+pool.insertClassPath(cp);
+</pre></ul>
+
+<p>This program adds "http://www.foo.com:80/java/" to the class search
+path. This URL is used only for searching classes belonging to a
+package <code>com.foo</code>.
+
+<p>You can directly give a byte array to a <code>ClassPool</code> object
+and construct a <code>CtClass</code> object from that array. To do this,
+use <code>ByteArrayClassPath</code>. For example,
+
+<ul><pre>
+ClassPool cp = ClassPool.getDefault();
+byte[] b = <em>a byte array</em>;
+String name = <em>class name</em>;
+cp.insertClassPath(new ByteArrayClassPath(name, b));
+CtClass cc = cp.get(name);
+</pre></ul>
+
+<p>The obtained <code>CtClass</code> object represents
+a class defined by the class file specified by <code>b</code>.
+
+
+<p>Since <code>ClassPath</code> is an interface, the users can define
+a new class implementing this interface and they can add an instance
+of that class so that a class file is obtained from a non-standard resource.
+
+<p><br>
+
+<a name="def">
+<h2>2. Defining a new class</h2>
+
+<p>To define a new class from scratch, <code>makeClass()</code>
+must be called on a <code>ClassPool</code>.
+
+<ul><pre>
+ClassPool pool = ClassPool.getDefault();
+CtClass cc = pool.makeClass("Point");
+</pre></ul>
+
+<p>This program defines a class <code>Point</code>
+including no members.
+
+<p>A new class can be also defined as a copy of an existing class.
+The program below does that:
+
+<ul><pre>
+ClassPool pool = ClassPool.getDefault();
+CtClass cc = pool.makeClass("Point");
+cc.setName("Pair");
+</pre></ul>
+
+<p>This program first obtains the <code>CtClass</code> object
+for class <code>Point</code>. Then it gives a new name <code>Pair</code>
+to that <code>CtClass</code> object.
+If <code>get("Point")</code> is called on the <code>ClassPool</code>
+object, then a class file <code>Point.class</code> is read again and
+a new <code>CtClass</code> object for class <code>Point</code> is constructed
+again.
+
+<ul><pre>
+ClassPool pool = ClassPool.getDefault();
+CtClass cc = pool.makeClass("Point");
+CtClass cc1 = pool.get("Point"); // cc1 is identical to cc.
+cc.setName("Pair");
+CtClass cc2 = pool.get("Pair"); // cc2 is identical to cc.
+CtClass cc3 = pool.get("Point"); // cc3 is not identical to cc.
+</pre></ul>
+
+<p><br>
+
+<a name="mod">
+<h2>3. Modifying a class at load time</h2>
+
+<p>If what classes are modified is known in advance,
+the easiest way for modifying the classes is as follows:
+
+<ul><li>1. Get a <code>CtClass</code> object by calling
+ <code>ClassPool.get()</code>,
+ <li>2. Modify it, and
+ <li>3. Call <code>ClassPool.write()</code> or <code>writeFile()</code>.
+</ul>
+
+<p>If whether a class is modified or not is determined at load time,
+the users can write an event listener so that it is notified
+when a class is loaded into the JVM.
+A class loader (<code>java.lang.ClassLoader</code>) working with
+Javassist must call <code>ClassPool.write()</code> for obtaining
+a class file. The users can write an event listener so that it is
+notified when the class loader calls <code>ClassPool.write()</code>.
+The event-listener class must implement the following interface:
+
+<ul><pre>public interface Translator {
+ public void start(ClassPool pool)
+ throws NotFoundException, CannotCompileException;
+ public void onWrite(ClassPool pool, String classname)
+ throws NotFoundException, CannotCompileException;
+}</pre></ul>
+
+<p>The method <code>start()</code> is called when this event listener
+is registered to a <code>ClassPool</code> object.
+The method <code>onWrite()</code> is called when <code>write()</code>
+(or similar methods) is called on the <code>ClassPool</code> object.
+The second parameter of <code>onWrite()</code> is the name of the class
+to be written out.
+
+<p>Note that <code>start()</code> or <code>onWrite()</code> do not have
+to call <code>write()</code> or <code>writeFile()</code>. For example,
+
+<ul><pre>public class MyAnotherTranslator implements Translator {
+ public void start(ClassPool pool)
+ throws NotFoundException, CannotCompileException {}
+ public void onWrite(ClassPool pool, String classname)
+ throws NotFoundException, CannotCompileException
+ {
+ CtClass cc = pool.get(classname);
+ cc.setModifiers(Modifier.PUBLIC);
+ }
+}</pre></ul>
+
+<p>All the classes written out by <code>write()</code> are made public
+just before their definitions are translated into an byte array.
+
+<p><center><img src="overview.gif" alt="overview"></center>
+
+<p>The two methods <code>start()</code> and <code>onWrite()</code>
+can modify not only a <code>CtClass</code> object specified by
+the given <code>classname</code> but also
+<em>any</em> <code>CtClass</code> objects contained
+in the given <code>ClassPool</code>.
+They can call <code>ClassPool.get()</code> for obtaining any
+<code>CtClass</code> object.
+If a modified <code>CtClass</code> object is not written out immediately,
+the modification is recorded until that object is written out.
+
+<p><center><img src="sequence.gif" alt="sequence diagram"></center>
+
+<p>To register an event listener to a <code>ClassPool</code>,
+it must be passed to a constructor of <code>ClassPool</code>.
+Only a single event listener can be registered.
+If more than one event listeners are needed, multiple
+<code>ClassPool</code>s should be connected to be a single
+stream. For example,
+
+<ul><pre>Translator t1 = new MyTranslator();
+ClassPool c1 = new ClassPool(t1);
+Translator t2 = new MyAnotherTranslator();
+ClassPool c2 = new ClassPool(c1, t2);</pre></ul>
+
+<p>This program connects two <code>ClassPool</code>s.
+If a class loader calls <code>write()</code> on <code>c2</code>,
+the specified class file is first modified by <code>t1</code> and
+then by <code>t2</code>. <code>write()</code> returns the resulting
+class file.
+
+First, <code>onWrite()</code> on <code>t1</code> is called since
+<code>c2</code> obtains a class file by calling <code>write()</code>
+on <code>c1</code>. Then <code>onWrite()</code> on <code>t2</code>
+is called. If <code>onWrite()</code> called on <code>t2</code>
+obtains a <code>CtClass</code> object from <code>c2</code>, that
+<code>CtClass</code> object represents the class file that
+<code>t1</code> has modified.
+
+<p><center><img src="two.gif" alt="two translators"></center>
+
+<p><br>
+
+<a name="load">
+<h2>4. Class loader</h2>
+
+<p>Javassist can be used with a class loader so that bytecode can be
+modified at load time. The users of Javassist can define their own
+version of class loader but they can also use a class loader provided
+by Javassist.
+
+<p><br>
+
+<h3>4.1 Using <code>javassist.Loader</code></h3>
+
+<p>Javassist provides a class loader
+<code>javassist.Loader</code>. This class loader uses a
+<code>javassist.ClassPool</code> object for reading a class file.
+
+<p>For example, <code>javassist.Loader</code> can be used for loading
+a particular class modified with Javassist.
+
+<ul><pre>
+import javassist.*;
+import test.Rectangle;
+
+public class Main {
+ public static void main(String[] args) throws Throwable {
+ ClassPool pool = ClassPool.getDefault();
+ Loader cl = new Loader(pool);
+
+ CtClass ct = pool.get("test.Rectangle");
+ ct.setSuperclass(pool.get("test.Point"));
+
+ Class c = cl.loadClass("test.Rectangle");
+ Object rect = c.newInstance();
+ :
+ }
+}
+</pre></ul>
+
+<p>This program modifies a class <code>test.Rectangle</code>. The
+superclass of <code>test.Rectangle</code> is set to a
+<code>test.Point</code> class. Then this program loads the modified
+class into the JVM, and creates a new instance of the
+<code>test.Rectangle</code> class.
+
+<p>The users can use a <code>javassist.Translator</code> object
+for modifying class files.
+Suppose that an instance of a class <code>MyTranslator</code>,
+which implements
+<code>javassist.Translator</code>, performs modification of class files.
+To run an application class <code>MyApp</code> with the
+<code>MyTranslator</code> object, write a main class:
+
+<ul><pre>
+import javassist.*;
+
+public class Main2 {
+ public static void main(String[] args) throws Throwable {
+ Translator t = new MyTranslator();
+ ClassPool pool = ClassPool.getDefault(t);
+ Loader cl = new Loader(pool);
+ cl.run("MyApp", args);
+ }
+}
+</pre></ul>
+
+<p>To run this program, do:
+
+<ul><pre>
+% java Main <i>arg1</i> <i>arg2</i>...
+</pre></ul>
+
+<p>The class <code>MyApp</code> and the other application classes
+are translated by <code>MyTranslator</code>.
+
+<p>Note that <em>application</em> classes like <code>MyApp</code> cannot
+access the <em>loader</em> classes such as <code>Main</code>,
+<code>MyTranslator</code> and <code>ClassPool</code> because they
+are loaded by different loaders. The application classes are loaded
+by <code>javassist.Loader</code> whereas the loader classes such as
+<code>Main</code> are by the default Java class loader.
+
+<p>In Java, for security reasons, a single class file may be loaded
+into the JVM by two distinct class loaders so that two different
+classes would be created. For example,
+
+<ul><pre>class Point {
+ int x, y;
+}
+
+class Box {
+ Point base;
+ Point getBase() { return base; }
+}
+
+class Window {
+ Point size;
+ Point getSize() { return size; }
+}</pre></ul>
+
+<p>Suppose that a class <code>Box</code> is loaded by a class loader
+<code>L1</code> while a class <code>Window</code> is loaded by a class
+loader <code>L2</code>. Then, the obejcts returned by
+<code>getBase()</code> and <code>getSize()</code> are not instances of
+the same class <code>Point</code>.
+<code>getBase()</code> returns an instance of the class <code>Point</code>
+loaded by <code>L1</code> whereas <code>getSize()</code> returns an
+instance of <code>Point</code> loaded by <code>L2</code>. The two versions
+of the class <code>Point</code> are distinct. They belong to different
+name spaces. For more details, see the following paper:
+
+<ul>Sheng Liang and Gilad Bracha,
+"Dynamic Class Loading in the Java Virtual Machine",
+<br><i>ACM OOPSLA'98</i>, pp.36-44, 1998.</ul>
+
+<p>To avoid this problem, the two class loaders <code>L1</code> and
+<code>L2</code> must delegate the loading operation of the class
+<code>Point</code> to another class loader, <code>L3</code>, which is
+a parent class loader of <code>L1</code> and <code>L2</code>.
+<code>delegateLoadingOf()</code> in <code>javassist.Loader</code>
+is a method for specifying what classes should be loaded by the
+parent loader.
+
+<p>If <code>L1</code> is the parent class loader of <code>L2</code>,
+that is, if <code>L1</code> loads the class of <code>L2</code>,
+then <code>L2</code> can delegate the loading operation of
+<code>Point</code> to <code>L1</code> for avoiding the problem above.
+However, this technique does not work in the case below:
+
+<ul><pre>class Point { // loaded by L1
+ Window win;
+ int x, y;
+}
+
+class Box { // loaded by L1
+ Point base;
+ Point getBase() { return base; }
+}
+
+class Window { // loaded by L2
+ Point size;
+ Point getSize() { size.win = this; return size; }
+}</pre></ul>
+
+<p>Since all the classes included in a class definition loaded by
+a class loader <code>L1</code> are also loaded by <code>L1</code>,
+the class of the field <code>win</code> in <code>Point</code> is
+now the class <code>Window</code> loaded by <code>L1</code>.
+Thus <code>size.win = this</code> in <code>getSize()</code> raises
+a runtime exception because of type mismatch; the type of
+<code>size.win</code> is the class <code>Point</code> loaded by
+<code>L1</code> whereas the type of <code>this</code> is the class
+<code>Point</code> loaded by <code>L2</code>.
+
+<p><br>
+
+<h3>4.2 Writing a class loader</h3>
+
+<p>A simple class loader using Javassist is as follows:
+
+<ul><pre>import javassist.*;
+
+public class SimpleLoader extends ClassLoader {
+ /* Call MyApp.main().
+ */
+ public static void main(String[] args) throws Throwable {
+ SimpleLoader s = new SimpleLoader();
+ Class c = s.loadClass("MyApp");
+ c.getDeclaredMethod("main", new Class[] { String[].class })
+ .invoke(null, new Object[] { args });
+ }
+
+ private ClassPool pool;
+
+ public SimpleLoader() throws NotFoundException {
+ pool = ClassPool.getDefault();
+ pool.insertClassPath("./class"); // <em>MyApp.class must be there.</em>
+ }
+
+ /* Finds a specified class.
+ * The bytecode for that class can be modified.
+ */
+ protected Class findClass(String name) throws ClassNotFoundException {
+ try {
+ CtClass cc = pool.get(name);
+ // <em>modify the CtClass object here</em>
+ byte[] b = pool.write(name);
+ return defineClass(name, b, 0, b.length);
+ } catch (NotFoundException e) {
+ throw new ClassNotFoundException();
+ } catch (IOException e) {
+ throw new ClassNotFoundException();
+ } catch (CannotCompileException e) {
+ throw new ClassNotFoundException();
+ }
+ }
+}</pre></ul>
+
+<p>The class <code>MyApp</code> is an application program.
+To execute this program, first put the class file under the
+<code>./class</code> directory, which must <em>not</em> be included
+in the class search path. The directory name is specified by
+<code>insertClassPath()</code> in the constructor.
+You can choose a different name instead of <code>./class</code> if you want.
+Then do as follows:
+
+<ul><code>% java SimpleLoader</code></ul>
+
+<p>The class loader loads the class <code>MyApp</code>
+(<code>./class/MyApp.class</code>) and calls
+<code>MyApp.main()</code> with the command line parameters.
+Note that <code>MyApp.class</code> must not be under the directory
+that the system class loader searches. Otherwise, the system class
+loader, which is the parent loader of <code>SimpleLoader</code>,
+loads the class <code>MyApp</code>.
+
+<p>This is the simplest way of using Javassist. However, if you write
+a more complex class loader, you may need detailed knowledge of
+Java's class loading mechanism. For example, the program above puts the
+<code>MyApp</code> class in a name space separated from the name space
+that the class <code>SimpleLoader</code> belongs to because the two
+classes are loaded by different class loaders.
+Hence, the
+<code>MyApp</code> class cannot directly access the class
+<code>SimpleLoader</code>.
+
+<p><br>
+
+<h3>4.3 Modifying a system class</h3>
+
+<p>The system classes like <code>java.lang.String</code> cannot be
+loaded by a class loader other than the system class loader.
+Therefore, <code>SimpleLoader</code> or <code>javassist.Loader</code>
+shown above cannot modify the system classes at loading time.
+
+<p>If your application needs to do that, the system classes must be
+<em>statically</em> modified. For example, the following program
+adds a new field <code>hiddenValue</code> to <code>java.lang.String</code>:
+
+<ul><pre>ClassPool pool = ClassPool.getDefault();
+CtClass cc = pool.get("java.lang.String");
+cc.addField(new CtField(CtClass.intType, "hiddenValue", cc));
+pool.writeFile("java.lang.String", ".");</pre></ul>
+
+<p>This program produces a file <code>"./java/lang/String.class"</code>.
+
+<p>To run your program <code>MyApp</code>
+with this modified <code>String</code> class, do as follows:
+
+<ul><pre>
+% java -Xbootclasspath/p:. MyApp <i>arg1</i> <i>arg2</i>...
+</pre></ul>
+
+<p>Suppose that the definition of <code>MyApp</code> is as follows:
+
+<ul><pre>public class MyApp {
+ public static void main(String[] args) throws Exception {
+ System.out.println(String.class.getField("hiddenValue").getName());
+ }
+}</pre></ul>
+
+<p>If the modified <code>String</code> class is correctly loaded,
+<code>MyApp</code> prints <code>hiddenValue</code>.
+
+<p><i>Note: Applications that use this technique for the purpose of
+overriding a system class in <code>rt.jar</code> should not be
+deployed as doing so would contravene the Java 2 Runtime Environment
+binary code license.</i>
+
+<p><br>
+
+<a href="tutorial2.html">Next page</a>
+
+<hr>
+Java(TM) is a trademark of Sun Microsystems, Inc.<br>
+Copyright (C) 2000-2002 by Shigeru Chiba, All rights reserved.
+</body>
+</html>
diff --git a/tutorial/tutorial2.html b/tutorial/tutorial2.html new file mode 100644 index 00000000..b76996ce --- /dev/null +++ b/tutorial/tutorial2.html @@ -0,0 +1,1069 @@ +<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Javassist Tutorial</title>
+ <link rel="stylesheet" type="text/css" href="brown.css">
+</head>
+
+<body>
+
+<div align="right">Getting Started with Javassist</div>
+
+<div align="left"><a href="tutorial.html">Previous page</a></div>
+
+<a name="intro">
+<h2>5. Introspection and customization</h2>
+
+<p><code>CtClass</code> provides methods for introspection. The
+introspective ability of Javassist is compatible with that of
+the Java reflection API. <code>CtClass</code> provides
+<code>getName()</code>, <code>getSuperclass()</code>,
+<code>getMethods()</code>, and so on.
+<code>CtClass</code> also provides methods for modifying a class
+definition. It allows to add a new field, constructor, and method.
+Instrumenting a method body is also possible.
+
+<p><hr width="40%">
+
+<ul>
+Javassist does not allow to remove a method or field, but it allows
+to change the name. So if a method is not necessary any more, it should be
+renamed and changed to be a private method by calling
+<code>setName()</code>
+and <code>setModifiers()</code> declared in <code>CtMethod</code>.
+
+<p>Javassist does not allow to add an extra parameter to an existing
+method, either. Instead of doing that, a new method receiving the
+extra parameter as well as the other parameters should be added to the
+same class. For example, if you want to add an extra <code>int</code>
+parameter <code>newZ</code> to a method:
+
+<ul><pre>void move(int newX, int newY) { x = newX; y = newY; }</pre></ul>
+
+<p>in a <code>Point</code> class, then you should add the following
+method to the <code>Point</code> class:
+
+<ul><pre>void move(int newX, int newY, int newZ) {
+ // do what you want with newZ.
+ move(newX, newY);
+}</pre></ul>
+
+</ul>
+
+<p><hr width="40%">
+
+<p>Javassist also provides low-level API for directly editing a raw
+class file. For example, <code>getClassFile()</code> in
+<code>CtClass</code> returns a <code>ClassFile</code> object
+representing a raw class file. <code>getMethodInfo()</code> in
+<code>CtMethod</code> returns a <code>MethodInfo</code> object
+representing a <code>method_info</code> structure included in a class
+file. The low-level API uses the vocabulary from the Java Virtual
+machine specification. The users must have the knowledge about class
+files and bytecode. For more details, the users should see the
+<code>javassist.bytecode</code> package.
+
+<p><br>
+
+<h3>5.1 Inserting source text at the beginning/end of a method body</h3>
+
+<p><code>CtMethod</code> and <code>CtConstructor</code> provide
+methods <code>insertBefore()</code>, <code>insertAfter()</code>, and
+<code>addCatch()</code>. They are used for inserting a code fragment
+into the body of an existing method. The users can specify those code
+fragments with <em>source text</em> written in Java.
+Javassist includes a simple Java compiler for processing source
+text. It receives source text
+written in Java and compiles it into Java bytecode, which will be inserted
+into a method body.
+
+<p>The methods <code>insertBefore()</code>, <code>insertAfter()</code>, and
+<code>addCatch()</code> receives a <code>String</code> object representing
+a statement or a block. A statement is a single control structure like
+<code>if</code> and <code>while</code> or an expression ending with
+a semi colon (<code>;</code>). A block is a set of
+statements surrounded with braces <code>{}</code>.
+Hence each of the following lines is an example of valid statement or block:
+
+<ul><pre>System.out.println("Hello");
+{ System.out.println("Hello"); }
+if (i < 0) { i = -i; }
+</pre></ul>
+
+<p>The statement and the block can refer to fields and methods.
+However, they <em>cannot refer to local variables</em> declared in the
+method that they are inserted into.
+They can refer to the parameters
+to the method although they must use different names
+<code>$0</code>, <code>$1</code>, <code>$2</code>, ... described
+below. Declaring a local variable in the block is allowed.
+
+<!--
+<p><center><table border=8 cellspacing=0 bordercolor="#cfcfcf">
+<tr><td bgcolor="#cfcfcf">
+<b>Tip:</b>
+<br>    Local variables are not accessible.  
+</td></tr>
+</table></center>
+-->
+
+<p>The <code>String</code> object passed to the methods
+<code>insertBefore()</code>, <code>insertAfter()</code>, and
+<code>addCatch()</code> are compiled by
+the compiler included in Javassist.
+Since the compiler supports language extensions,
+several identifiers starting with <code>$</code>
+have special meaning:
+
+<ul><table border=0>
+<tr>
+<td><code>$0</code>, <code>$1</code>, <code>$2</code>, ...    </td>
+<td>Actual parameters</td>
+</tr>
+
+<tr>
+<td><code>$args</code></td>
+<td>An array of parameters.
+The type of <code>$args</code> is <code>Object[]</code>.
+</td>
+</tr>
+
+<tr>
+<td><code>$$</code></td>
+<td rowspan=2>All actual parameters.<br>
+For example, <code>m($$)</code> is equivalent to
+<code>m($1,$2,</code>...<code>)</code></td>
+</tr>
+
+<tr><td> </td></tr>
+
+<tr>
+<td><code>$cflow(</code>...<code>)</code></td>
+<td><code>cflow</code> variable</td>
+</tr>
+
+<tr>
+<td><code>$r</code></td>
+<td>The result type. It is used in a cast expression.</td>
+</tr>
+
+<tr>
+<td><code>$w</code></td>
+<td>The wrapper type. It is used in a cast expression.</td>
+</tr>
+
+<tr>
+<td><code>$_</code></td>
+<td>The resulting value</td>
+</tr>
+
+<tr>
+<td><code>$sig</code></td>
+<td>An array of <code>java.lang.Class</code> objects representing
+the formal parameter types.
+</td>
+</tr>
+
+<tr>
+<td><code>$type</code></td>
+<td>A <code>java.lang.Class</code> object representing
+the formal result type.</td>
+</tr>
+
+<tr>
+<td><code>$class</code></td>
+<td>A <code>java.lang.Class</code> object representing
+the class currently edited.</td>
+</tr>
+
+</table>
+</ul>
+
+<h4>$0, $1, $2, ...</h4>
+
+<p>The parameters passed to the methods <code>insertBefore()</code>,
+<code>insertAfter()</code>, and <code>addCatch()</code>
+are accessible with
+<code>$0</code>, <code>$1</code>, <code>$2</code>, ... instead of
+the original parameter names.
+<code>$1</code> represents the
+first parameter, <code>$2</code> represents the second parameter, and
+so on. The types of those variables are identical to the parameter
+types.
+<code>$0</code> is
+equivalent to <code>this</code>. If the method is static,
+<code>$0</code> is not available.
+
+<p>These variables are used as following. Suppose that a class
+<code>Point</code>:
+
+<pre><ul>class Point {
+ int x, y;
+ void move(int dx, int dy) { x += dx; y += dy; }
+}
+</ul></pre>
+
+<p>To print the values of <code>dx</code> and <code>dy</code>
+whenever the method <code>move()</code> is called, execute this
+program:
+
+<ul><pre>ClassPool pool = ClassPool.getDefault();
+CtClass cc = pool.get("Point");
+CtMethod m = cc.getDeclaredMethod("move");
+m.insertBefore("{ System.out.println($1); System.out.println($2); }");
+cc.writeFile();
+</pre></ul>
+
+<p>Note that the source text passed to <code>insertBefore()</code> is
+surrounded with braces <code>{}</code>.
+<code>insertBefore()</code> accepts only a single statement or a block
+surrounded with braces.
+
+<p>The definition of the class <code>Point</code> after the
+modification is like this:
+
+<pre><ul>class Point {
+ int x, y;
+ void move(int dx, int dy) {
+ { System.out.println(dx); System.out.println(dy); }
+ x += dx; y += dy;
+ }
+}
+</ul></pre>
+
+<p><code>$1</code> and <code>$2</code> are replaced with
+<code>dx</code> and <code>dy</code>, respectively.
+
+<p><code>$1</code>, <code>$2</code>, <code>$3</code> ... are
+updatable. If a new value is assigend to one of those variables,
+then the value of the parameter represented by that variable is
+also updated.
+
+
+<h4>$args</h4>
+
+<p>The variable <code>$args</code> represents an array of all the
+parameters. The type of that variable is an array of class
+<code>Object</code>. If a parameter type is a primitive type such as
+<code>int</code>, then the parameter value is converted into a wrapper
+object such as <code>java.lang.Integer</code> to store in
+<code>$args</code>. Thus, <code>$args[0]</code> is equivalent to
+<code>$1</code> unless the type of the first parameter is a primitive
+type. Note that <code>$args[0]</code> is not equivalent to
+<code>$0</code>; <code>$0</code> represents <code>this</code>.
+
+<p>If an array of <code>Object</code> is assigned to
+<code>$args</code>, then each element of that array is
+assigned to each parameter. If a parameter type is a primitive
+type, the type of the corresponding element must be a wrapper type.
+The value is converted from the wrapper type to the primitive type
+before it is assigned to the parameter.
+
+<h4>$$</h4>
+
+<p>The variable <code>$$</code> is abbreviation of a list of
+all the parameters separated by commas.
+For example, if the number of the parameters
+to method <code>move()</code> is three, then
+
+<ul><pre>move($$)</pre></ul>
+
+<p>is equivalent to this:
+
+<ul><pre>move($1, $2, $3)</pre></ul>
+
+<p>If <code>move()</code> does not take any parameters,
+then <code>move($$)</code> is
+equivalent to <code>move()</code>.
+
+<p><code>$$</code> can be used with another method.
+If you write an expression:
+
+<ul><pre>exMove($$, context)</pre></ul>
+
+<p>then this expression is equivalent to:
+
+<ul><pre>exMove($1, $2, $3, context)</pre></ul>
+
+<p>Note that <code>$$</code> enables generic notation of method call
+with respect to the number of parameters.
+It is typically used with <code>$proceed</code> shown later.
+
+<h4>$cflow</h4>
+
+<p><code>$cflow</code> means "control flow".
+This read-only variable returns the depth of the recursive calls
+to a specific method.
+
+<p>Suppose that the method shown below is represented by a
+<code>CtMethod</code> object <code>cm</code>:
+
+<ul><pre>int fact(int n) {
+ if (n <= 1)
+ return n;
+ else
+ return n * fact(n - 1);
+}</pre></ul>
+
+<p>To use <code>$cflow</code>, first declare that <code>$cflow</code>
+is used for monitoring calls to the method <code>fact()</code>:
+
+<ul><pre>CtMethod cm = ...;
+cm.useCflow("fact");</pre></ul>
+
+<p>The parameter to <code>useCflow()</code> is the identifier of the
+declared <code>$cflow</code> variable. Any valid Java name can be
+used as the identifier. Since the identifier can also include
+<code>.</code> (dot), for example, <code>"my.Test.fact"</code>
+is a valid identifier.
+
+<p>Then, <code>$cflow(fact)</code> represents the depth of the
+recursive calls to the method specified by <code>cm</code>. The value
+of <code>$cflow(fact)</code> is 0 (zero) when the method is
+first called whereas it is 1 when the method is recursively called
+within the method. For example,
+
+<ul><pre>
+cm.insertBefore("if ($cflow(fact) == 0)"
+ + " System.out.println(\"fact \" + $1);");
+</pre></ul>
+
+<p>translates the method <code>fact()</code> so that it shows the
+parameter. Since the value of <code>$cflow(fact)</code> is checked,
+the method <code>fact()</code> does not show the parameter if it is
+recursively called within <code>fact()</code>.
+
+<p>The value of <code>$cflow</code> is the number of stack frames
+associated with the specified method <code>cm</code>
+under the current topmost
+stack frame for the current thread. <code>$cflow</code> is also
+accessible within a method different from the specified method
+<code>cm</code>.
+
+<h4>$r</h4>
+
+<p><code>$r</code> represents the result type (return type) of the method.
+It must be used as the cast type in a cast expression.
+For example, this is a typical use:
+
+<ul><pre>Object result = ... ;
+$_ = ($r)result;</pre></ul>
+
+<p>If the result type is a primitive type, then <code>($r)</code>
+converts from the wrapper type to the primitive type.
+For example, if the result type is <code>int</code>, then
+<code>($r)</code> converts from <code>java.lang.Integer</code> to
+<code>int</code>.
+
+<p>If the result type is <code>void</code>, then
+<code>($r)</code> does not convert a type; it does nothing.
+Moreover, the soruce text can include a <code>return</code>
+statement with a resulting value:
+
+<ul><pre>return ($r)result;</pre></ul>
+
+<p>Here, <code>result</code> is some local variable.
+Since <code>($r)</code> is specified, the resulting value is
+discarded.
+This <code>return</code> statement is regarded as the equivalent
+of the <code>return</code> statement without a resulting value:
+
+<ul><pre>return;</pre></ul>
+
+<h4>$w</h4>
+
+<p><code>$w</code> represents a wrapper type.
+It must be used as the cast type in a cast expression.
+<code>($w)</code> converts from a primitive type to the corresponding
+wrapper type.
+
+The following code is an example:
+
+<ul><pre>Integer i = ($w)5;</pre></ul>
+
+<p>The selected wrapper type depends on the type of the expression
+following <code>($w)</code>. If the type of the expression is
+<code>double</code>, then the wrapper type is <code>java.lang.Double</code>.
+
+<p>If the type of the expression following <code>($w)</code> is not
+a primitive type, then <code>($w)</code> does nothing.
+
+<h4>$_</h4>
+
+<p><code>insertAfter()</code> in <code>CtMethod</code> and
+<code>CtConstructor</code> inserts the
+compiled code at the end of the method. In the statement given to
+<code>insertAfter()</code>, not only the variables shown above such as
+<code>$0</code>, <code>$1</code>, ... but also <code>$_</code> is
+available.
+
+<p>The variable <code>$_</code> represents the resulting value of the
+method. The type of that variable is the type of the result type (the
+return type) of the method. If the result type is <code>void</code>,
+then the type of <code>$_</code> is <code>Object</code> and the value
+of <code>$_</code> is <code>null</code>.
+
+<p>Although the compiled code inserted by <code>insertAfter()</code>
+is executed just before the control normally returns from the method,
+it can be also executed when an exception is thrown from the method.
+To execute it when an exception is thrown, the second parameter
+<code>asFinally</code> to <code>insertAfter()</code> must be
+<code>true</code>.
+
+<p>If an exception is thrown, the compiled code inserted by
+<code>insertAfter()</code> is executed as a <code>finally</code>
+clause. The value of <code>$_</code> is <code>0</code> or
+<code>null</code> in the compiled code. After the execution of the
+compiled code terminates, the exception originally thrown is re-thrown
+to the caller. Note that the value of <code>$_</code> is never thrown
+to the caller; it is rather discarded.
+
+<h4>$sig</h4>
+
+<p>The value of <code>$sig</code> is an array of
+<code>java.lang.Class</code> objects that represent the formal
+parameter types in declaration order.
+
+<h4>$type</h4>
+
+<p>The value of <code>$type</code> is an <code>java.lang.Class</code>
+object representing the formal type of the result value. This
+variable is available only in <code>insertAfter()</code> in
+<code>CtMethod</code> and <code>CtConstructor</code>.
+
+<h4>$class</h4>
+
+<p>The value of <code>$class</code> is an <code>java.lang.Class</code>
+object representing the class in which the edited method is declared.
+
+<h4>addCatch()</h4>
+
+<p><code>addCatch()</code> inserts a code fragment into a method body
+so that the code fragment is executed when the method body throws
+an exception and the control returns to the caller. In the source
+text representing the inserted code fragment, the exception value
+is referred to with the name specified by the third parameter to
+<code>addCatch()</code>.
+
+<p>For example, this program:
+
+<ul><pre>
+CtMethod m = ...;
+CtClass etype = ClassPool.getDefault().get("java.io.IOException");
+m.addCatch("{ System.out.println(e); throw e; }", etype, "e");
+</pre></ul>
+
+<p>translates the method body represented by <code>m</code> into
+something like this:
+
+<ul><pre>
+try {
+ <font face="serif"><em>the original method body</em></font>
+}
+catch (java.io.IOException e) {
+ System.out.println(e);
+ throw e;
+}
+</pre></ul>
+
+<p>Note that the inserted code fragment must end with a
+<code>throw</code> or <code>return</code> statement.
+
+<p><br>
+
+<h3>5.2 Modifying a method body</h3>
+
+<p><code>javassist.expr.ExprEditor</code> is a class
+for replacing an expression in a method body.
+The users can define a subclass of <code>ExprEditor</code>
+to specify how an expression is modified.
+
+<p>To run an <code>ExprEditor</code> object, the users must
+call <code>instrument()</code> in <code>CtMethod</code> or
+<code>CtClass</code>.
+
+For example,
+
+<ul><pre>
+CtMethod cm = ... ;
+cm.instrument(
+ new ExprEditor() {
+ public void edit(MethodCall m)
+ throws CannotCompileException
+ {
+ if (m.getClassName().equals("Point")
+ && m.getMethodName().equals("move"))
+ m.replace("{ $1 = 0; $_ = $proceed($$); }");
+ }
+ });
+</pre></ul>
+
+<p>searches the method body represented by <code>cm</code> and
+replaces all calls to <code>move()</code> in class <code>Point</code>
+with a block:
+
+<ul><pre>{ $1 = 0; $_ = $proceed($$); }
+</pre></ul>
+
+<p>so that the first parameter to <code>move()</code> is always 0.
+Note that the substituted code is not an expression but
+a statement or a block.
+
+<p>The method <code>instrument()</code> searches a method body.
+If it finds an expression such as a method call, field access, and object
+creation, then it calls <code>edit()</code> on the given
+<code>ExprEditor</code> object. The parameter to <code>edit()</code>
+is an object representing the found expression. The <code>edit()</code>
+method can inspect and replace the expression through that object.
+
+<p>Calling <code>replace()</code> on the parameter to <code>edit()</code>
+substitutes the given statement or block for the expression. If the given
+block is an empty block, that is, if <code>replace("{}")</code>
+is executed, then the expression is removed from the method body.
+
+If you want to insert a statement (or a block) before/after the
+expression, a block like the following should be passed to
+<code>replace()</code>:
+
+<ul><pre>
+{ <em>before-statements;</em>
+ $_ = $proceed($$);
+ <em>after-statements;</em> }
+</pre></ul>
+
+<p>whichever the expression is either a method call, field access,
+object creation, or others. The second statement could be:
+
+<ul><pre>$_ = $proceed();</pre></ul>
+
+<p>if the expression is read access, or
+
+<ul><pre>$proceed($$);</pre></ul>
+
+<p>if the expression is write access.
+
+<h4>javassist.expr.MethodCall</h4>
+
+<p>A <code>MethodCall</code> object represents a method call.
+The method <code>replace()</code> in
+<code>MethodCall</code> substitutes a statement or
+a block for the method call.
+It receives source text representing the substitued statement or
+block, in which the identifiers starting with <code>$</code>
+have special meaning as in the source text passed to
+<code>insertBefore()</code>.
+
+<ul><table border=0>
+<tr>
+<td><code>$0</code></td>
+<td rowspan=3>
+The target object of the method call.<br>
+This is not equivalent to <code>this</code>, which represents
+the caller-side <code>this</code> object.<br>
+<code>$0</code> is <code>null</code> if the method is static.
+</td>
+</tr>
+
+<tr><td> </td></tr>
+
+<tr><td> </td></tr>
+
+<tr>
+<td><code>$1</code>, <code>$2</code>, ...    </td>
+<td>
+The parameters of the method call.
+</td>
+</tr>
+
+<tr><td>
+<code>$_</code></td>
+<td>The resulting value of the method call.</td>
+</tr>
+
+<tr><td><code>$r</code></td>
+<td>The result type of the method call.</td>
+</tr>
+
+<tr><td><code>$class</code>    </td>
+<td>A <code>java.lang.Class</code> object representing
+the class declaring the method.
+</td>
+</tr>
+
+<tr><td><code>$sig</code>    </td>
+<td>An array of <code>java.lang.Class</code> objects representing
+the formal parameter types.</td>
+</tr>
+
+<tr><td><code>$type</code>    </td>
+<td>A <code>java.lang.Class</code> object representing
+the formal result type.</td>
+</tr>
+
+<tr><td><code>$proceed</code>    </td>
+<td>The name of the method originally called
+in the expression.</td>
+</tr>
+
+</table>
+</ul>
+
+<p>Here the method call means the one represented by the
+<code>MethodCall</code> object.
+
+<p>The other identifiers such as <code>$w</code>,
+<code>$args</code> and <code>$$</code>
+are also available.
+
+<p>Unless the result type of the method call is <code>void</code>,
+a value must be assigned to
+<code>$_</code> in the source text and the type of <code>$_</code>
+is the result type.
+If the result type is <code>void</code>, the type of <code>$_</code>
+is <code>Object</code> and the value assigned to <code>$_</code>
+is ignored.
+
+<p><code>$proceed</code> is not a <code>String</code> value but special
+syntax. It must be followed by an argument list surrounded by parentheses
+<code>( )</code>.
+
+<h4>javassist.expr.FieldAccess</h4>
+
+<p>A <code>FieldAccess</code> object represents field access.
+The method <code>edit()</code> in <code>ExprEditor</code>
+receive this object if field access is found.
+The method <code>replace()</code> in
+<code>FieldAccess</code> receives
+source text representing the substitued statement or
+block for the field access.
+
+In the source text, the identifiers starting with <code>$</code>
+have also special meaning:
+
+<ul><table border=0>
+<tr>
+<td><code>$0</code></td>
+<td rowspan=3>
+The object containing the field accessed by the expression.
+This is not equivalent to <code>this</code>.<br>
+<code>this</code> represents the object that the method including the
+expression is invoked on.<br>
+<code>$0</code> is <code>null</code> if the field is static.
+</td>
+</tr>
+
+<tr><td> </td></tr>
+
+<tr><td> </td></tr>
+
+<tr>
+<td><code>$1</code></td>
+<td rowspan=2>
+The value that would be stored in the field
+if the expression is write access.
+<br>Otherwise, <code>$1</code> is not available.
+</td>
+</tr>
+
+<tr><td> </td></tr>
+
+<tr>
+<td><code>$_</code></td>
+<td rowspan=2>
+The resulting value of the field access
+if the expression is read access.
+<br>Otherwise, the value stored in <code>$_</code> is discarded.
+</td>
+</tr>
+
+<tr><td> </td></tr>
+<tr>
+<td><code>$r</code></td>
+<td rowspan=2>
+The type of the field if the expression is read access.
+<br>Otherwise, <code>$r</code> is <code>void</code>.
+</td>
+</tr>
+
+<tr><td> </td></tr>
+
+<tr><td><code>$class</code>    </td>
+<td>A <code>java.lang.Class</code> object representing
+the class declaring the field.
+</td></tr>
+
+<tr><td><code>$type</code></td>
+<td>A <code>java.lang.Class</code> object representing
+the field type.</td>
+</tr>
+
+<tr><td><code>$proceed</code>    </td>
+<td>The name of a virtual method executing the original
+field access.
+.</td>
+</tr>
+
+</table>
+</ul>
+
+<p>The other identifiers such as <code>$w</code>,
+<code>$args</code> and <code>$$</code>
+are also available.
+
+<p>If the expression is read access, a value must be assigned to
+<code>$_</code> in the source text. The type of <code>$_</code>
+is the type of the field.
+
+<h4>javassist.expr.NewExpr</h4>
+
+<p>A <code>NewExpr</code> object represents object creation
+with the <code>new</code> operator.
+The method <code>edit()</code> in <code>ExprEditor</code>
+receive this object if object creation is found.
+The method <code>replace()</code> in
+<code>NewExpr</code> receives
+source text representing the substitued statement or
+block for the object creation.
+
+In the source text, the identifiers starting with <code>$</code>
+have also special meaning:
+
+<ul><table border=0>
+
+<tr>
+<td><code>$0</code></td>
+<td>
+<code>null</code>.
+</td>
+</tr>
+
+<tr>
+<td><code>$1</code>, <code>$2</code>, ...    </td>
+<td>
+The parameters to the constructor.
+</td>
+</tr>
+
+<tr>
+<td><code>$_</code></td>
+<td rowspan=2>
+The resulting value of the object creation.
+<br>A newly created object must be stored in this variable.
+</td>
+</tr>
+
+<tr><td> </td></tr>
+
+<tr>
+<td><code>$r</code></td>
+<td>
+The type of the created object.
+</td>
+</tr>
+
+<tr><td><code>$class</code>    </td>
+<td>A <code>java.lang.Class</code> object representing
+the class of the created object.
+</td></tr>
+
+<tr><td><code>$sig</code>    </td>
+<td>An array of <code>java.lang.Class</code> objects representing
+the formal parameter types.</td>
+</tr>
+
+<tr><td><code>$proceed</code>    </td>
+<td>The name of a virtual method executing the original
+object creation.
+.</td>
+</tr>
+
+</table>
+</ul>
+
+<p>The other identifiers such as <code>$w</code>,
+<code>$args</code> and <code>$$</code>
+are also available.
+
+<h4>javassist.expr.Instanceof</h4>
+
+<p>A <code>Instanceof</code> object represents an <code>instanceof</code>
+expression.
+The method <code>edit()</code> in <code>ExprEditor</code>
+receive this object if an instanceof expression is found.
+The method <code>replace()</code> in
+<code>Instanceof</code> receives
+source text representing the substitued statement or
+block for the expression.
+
+In the source text, the identifiers starting with <code>$</code>
+have also special meaning:
+
+<ul><table border=0>
+
+<tr>
+<td><code>$0</code></td>
+<td>
+<code>null</code>.
+</td>
+</tr>
+
+<tr>
+<td><code>$1</code></td>
+<td>
+The value on the left hand side of the original
+<code>instanceof</code> operator.
+</td>
+</tr>
+
+<tr>
+<td><code>$_</code></td>
+<td>
+The resulting value of the expression.
+The type of <code>$_</code> is <code>boolean</code>.
+</td>
+</tr>
+
+<tr>
+<td><code>$r</code></td>
+<td>
+The type on the right hand side of the <code>instanceof</code> operator.
+</td>
+</tr>
+
+<tr><td><code>$type</code></td>
+<td>A <code>java.lang.Class</code> object representing
+the type on the right hand side of the <code>instanceof</code> operator.
+</td>
+</tr>
+
+<tr><td><code>$proceed</code>    </td>
+<td rowspan=4>The name of a virtual method executing the original
+<code>instanceof</code> expression.
+<br>It takes one parameter (the type is <code>java.lang.Object</code>)
+and returns true
+<br>if the parameter value is an instance of the type on the right
+hand side of
+<br>the original <code>instanceof</code> operator.
+Otherwise, it returns false.
+</td>
+</tr>
+
+<tr><td> </td></tr>
+<tr><td> </td></tr>
+<tr><td> </td></tr>
+
+</table>
+</ul>
+
+<p>The other identifiers such as <code>$w</code>,
+<code>$args</code> and <code>$$</code>
+are also available.
+
+<h4>javassist.expr.Cast</h4>
+
+<p>A <code>Cast</code> object represents an expression for
+explicit type casting.
+The method <code>edit()</code> in <code>ExprEditor</code>
+receive this object if explicit type casting is found.
+The method <code>replace()</code> in
+<code>Cast</code> receives
+source text representing the substitued statement or
+block for the expression.
+
+In the source text, the identifiers starting with <code>$</code>
+have also special meaning:
+
+<ul><table border=0>
+
+<tr>
+<td><code>$0</code></td>
+<td>
+<code>null</code>.
+</td>
+</tr>
+
+<tr>
+<td><code>$1</code></td>
+<td>
+The value the type of which is explicitly cast.
+</td>
+</tr>
+
+<tr>
+<td><code>$_</code></td>
+<td rowspan=2>
+The resulting value of the expression.
+The type of <code>$_</code> is the same as the type
+<br>after the explicit casting, that is, the type surrounded
+by <code>( )</code>.
+</td>
+</tr>
+
+<tr><td> </td></tr>
+
+<tr>
+<td><code>$r</code></td>
+<td>the type after the explicit casting, or the type surrounded
+by <code>( )</code>.
+</td>
+</tr>
+
+<tr><td><code>$type</code></td>
+<td>A <code>java.lang.Class</code> object representing
+the same type as <code>$r</code>.
+</td>
+</tr>
+
+<tr><td><code>$proceed</code>    </td>
+<td rowspan=3>The name of a virtual method executing the original
+type casting.
+<br>It takes one parameter of the type <code>java.lang.Object</code>
+and returns it after
+<br>the explicit type casting specified by the original expression.
+
+</td>
+</tr>
+
+<tr><td> </td></tr>
+
+<tr><td> </td></tr>
+
+</table>
+</ul>
+
+<p>The other identifiers such as <code>$w</code>,
+<code>$args</code> and <code>$$</code>
+are also available.
+
+<p><br>
+
+<h3>5.3 Adding a new method or field</h3>
+
+<p>Javassist allows the users to create a new method and constructor
+from scratch. <code>CtNewMethod</code>
+and <code>CtNewConstructor</code> provide several factory methods,
+which are static methods for creating <code>CtMethod</code> or
+<code>CtConstructor</code> objects.
+Especially, <code>make()</code> creates
+a <code>CtMethod</code> or <code>CtConstructor</code> object
+from the given source text.
+
+<p>For example, this program:
+
+<ul><pre>
+CtClass point = ClassPool.getDefault().get("Point");
+CtMethod m = CtNewMethod.make(
+ "public int xmove(int dx) { x += dx; }",
+ point);
+point.addMethod(m);
+</pre></ul>
+
+<p>adds a public method <code>xmove()</code> to class <code>Point</code>.
+In this example, <code>x</code> is a <code>int</code> field in
+the class <code>Point</code>.
+
+<p>The source text passed to <code>make()</code> can refer to
+<code>$proceed</code> if the target object and the target method name
+are also given to <code>make()</code>. For example,
+
+<ul><pre>
+CtClass point = ClassPool.getDefault().get("Point");
+CtMethod m = CtNewMethod.make(
+ "public int ymove(int dy) { $proceed(0, dy); }",
+ point, "this", "move");
+</pre></ul>
+
+<p>this program creates a method <code>ymove()</code> defined below:
+
+<ul><pre>
+public int ymove(int dy) { this.move(0, dy); }
+</pre></ul>
+
+<p>Note that <code>$proceed</code> has been replaced with
+<code>this.move</code>.
+
+<p>Javassist also allows the users to create a new field.
+
+<ul><pre>
+CtClass point = ClassPool.getDefault().get("Point");
+CtField f = new CtField(CtClass.intType, "z", point);
+point.addField(f);
+</pre></ul>
+
+<p>This program adds a field named <code>z</code> to class
+<code>Point</code>.
+
+<p>If the initial value of the added field must be specified,
+the program shown above must be modified into:
+
+<ul><pre>
+CtClass point = ClassPool.getDefault().get("Point");
+CtField f = new CtField(CtClass.intType, "z", point);
+point.addField(f, "0"); <em>// initial value is 0.</em>
+</pre></ul>
+
+<p>Now, the method <code>addField()</code> receives the second parameter,
+which is the source text representing an expression computing the initial
+value. This source text can be any Java expression if the result type
+of the expression matches the type of the field. Note that an expression
+does not end with a semi colon (<code>;</code>).
+
+<p><br>
+
+<h3>5.4 Limitations</h3>
+
+<p>In the current implementation, the Java compiler included in Javassist
+has several limitations with respect to the language that the compiler can
+accept. Those limitations are:
+
+<p><li>The <code>.class</code> notation is not supported. Use the
+method <code>Class.forName()</code>.
+In regular
+Java, an expression <code>Point.class</code> means a <code>Class</code>
+object representing the <code>Point</code> class. This notation is
+not available.
+
+<p><li>Array initializers, a comma-separated list of expressions
+enclosed by braces <code>{</code> and <code>}</code>, are not
+supported.
+
+<p><li>Inner classes or anonymous classes are not supported.
+
+<p><li><code>switch</code> statements are not supported yet.
+
+<p><li>Labeled <code>continue</code> and <code>break</code> statements
+are not supported.
+
+<p><li>The <code>finally</code> clause following
+<code>try</code> and <code>catch</code> clauses is not supported.
+
+<p><li>The compiler does not correctly implement the Java method dispatch
+algorithm. The compiler may confuse if methods defined in a class
+have the same name but take different parameter lists.
+
+<p><li>The users are recommended to use <code>#</code> as the separator
+between a class name and a static method or field name.
+For example, in regular Java,
+
+<ul><pre>javassist.CtClass.intType.getName()</pre></ul>
+
+<p>calls a method <code>getName()</code> on
+the object indicated by the static field <code>intType</code>
+in <code>javassist.CtClass</code>. In Javassist, the users can
+write the expression shown above but they are recommended to
+write:
+
+<ul><pre>javassist.CtClass#intType.getName()</pre></ul>
+
+<p>so that the compiler can quickly parse the expression.
+</ul>
+
+<p><br>
+
+<a href="tutorial.html">Previous page</a>
+
+<hr>
+Java(TM) is a trademark of Sun Microsystems, Inc.<br>
+Copyright (C) 2000-2002 by Shigeru Chiba, All rights reserved.
+</body>
+</html>
diff --git a/tutorial/two.gif b/tutorial/two.gif Binary files differnew file mode 100644 index 00000000..ad6984c1 --- /dev/null +++ b/tutorial/two.gif |