From: patriot1burke Date: Tue, 22 Apr 2003 13:47:06 +0000 (+0000) Subject: This commit was generated by cvs2svn to compensate for changes in r2, which X-Git-Tag: rel_3_17_1_ga~597 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=069bceaf72fd0d6ffad14ce4e3e00ca91a280bde;p=javassist.git This commit was generated by cvs2svn to compensate for changes in r2, which included commits to RCS files with non-trunk default branches. git-svn-id: http://anonsvn.jboss.org/repos/javassist/trunk@6 30ef5769-5b8d-40dd-aea6-55b5d6557bb3 --- diff --git a/License.html b/License.html new file mode 100644 index 00000000..dda30cc9 --- /dev/null +++ b/License.html @@ -0,0 +1,18 @@ + + +Javassist License + + + +

+Javassist, a Java-bytecode translator toolkit. +Copyright (C) 1999-2003 Shigeru Chiba. All Rights Reserved. +

+

+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. +

+ + diff --git a/Readme.html b/Readme.html new file mode 100644 index 00000000..cfb73123 --- /dev/null +++ b/Readme.html @@ -0,0 +1,453 @@ + + + + Read Me First + + + +

Javassist version 2

+ +

in February, 2003. +
Copyright (C) 2000-2003 by Shigeru Chiba, All rights reserved.

+ +


+ +

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. + +


+ +

Files

+ + + +

To extract source files from the archive, use the jar command:
+ +

+ + +


+ +

How to run sample programs

+ +

JDK 1.2.2 or later is needed. + +

1. Move to the directory where this Readme.html file is located.

+ +

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: + +

+ +

If you don't want to put the class-path option, copy +./javassist.jar to + +

+ +

<java-home> depends on the system. It is usually +/usr/local/java, c:\jdk1.2\, etc. + +

If you don't use javassist.jar, first compile Javassist: +

+ +

Then you can compile and run the sample programs without the class-path +option. + +

2. sample/Test.java

+ +

This is a very simple program using Javassist. + +

To run, type the commands: + +

+ +

For more details, see sample/Test.java + +

3. sample/reflect/*.java

+ +

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. + +

To run, type the commands: + +

+ +

Compare this result with that of the regular execution without reflection: + +

+ +

For more details, see sample/reflect/Main.java + +

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: + +

+ +

Then, +

+ +

4. sample/duplicate/*.java

+ +

This is another example of reflective programming. + +

To run, type the commands: + +

+ +

Compare this result with that of the regular execution without reflection: + +

+ +

For more details, see +sample/duplicate/Main.java + +

5. sample/vector/*.java

+ +

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 javassist.preproc package. + +

To run, type the commands: +

+ +

For more details, see +sample/vector/Test.j +and sample/vector/VectorAssistant.java + +

6. sample/rmi/*.java

+ +

This demonstrates the javassist.rmi package. + +

To run, type the commands: +

+ +

The second line starts a web server listening to port 5001. + +

Then, open sample/rmi/webdemo.html +with a web browser running + on the local host. (webdemo.html trys to fetch an applet from + http://localhost:5001/, which is the web server we started above.) + +

Otherwise, run sample.rmi.CountApplet as an application: + +

+ +

7. sample/evolve/*.java

+ +

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. + +

To run, type the commands: +

+ +

The second line starts a class loader DemoLoader, which runs a web + server DemoServer listening to port 5003. + +

Then, open http://localhost:5003/demo.html with a web browser running + on the local host. +(Or, see sample/evolve/start.html.) + +

8. Hints

+ +

Javassist provides a class file viewer for debugging. For more details, +see javassist.Dump. + +


+ +

Changes

+ +

- version 2.4 in February, 2003. +

+ +

- version 2.3 in December, 2002. +

+ +

- version 2.2 in October, 2002. +

+ +

- version 2.1 in July, 2002. +

+ +

- version 2.0 (major update) in November, 2001. +

+ +

version 1.0 in July, 2001. +

+ +

- version 0.8 +

+ +

- version 0.7 +

+ +

- version 0.6 +

+ +

- version 0.5 +

+ +

- version 0.4 +

+ +

- version 0.3 +

+ +

- version 0.2 +

+ +

-version 0.1 in April, 1999. +

+ +


+ +

Bug reports etc.

+ +
+
Bug reports: +
chiba@acm.org +
or +chiba@is.titech.ac.jp +
+ +

The home page of Javassist: +
http://www.csg.is.titech.ac.jp/~chiba/javassist + +

The Javassist mailing list: +
javassist@csg.is.titech.ac.jp +
+ +


+ +

Copyright notices

+ +

This software is subject to the Mozilla Public +License Version 1.1. + +

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. + +


+ +

Acknowledgments

+ +

The development of this software is sponsored in part by the PRESTO +program (Sakigake Kenkyu 21) of Japan +Science and Technology Corporation. + +

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. + +


+ +


+Shigeru Chiba +(Email: chiba@is.titech.ac.jp) +
Dept. of Math. and Computing Sciences, +Tokyo Institute of Technology diff --git a/build.xml b/build.xml new file mode 100644 index 00000000..5d57a3a3 --- /dev/null +++ b/build.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Javassist]]> + Javassist, a Java-bytecode translator toolkit. +Copyright (C) 1999-2003 Shigeru Chiba. All Rights Reserved.]]> + + + + + + + + + + + + + + + + + + + + + + + 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 + * + * Then DemoLoader launches sample.evolve.DemoServer with . + * 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 DemoLoader 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 "); + } + + 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 $. + + 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 " 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 new file mode 100644 index 00000000..3cc1d743 Binary files /dev/null and b/sample/evolve/WebPage.class.0 differ diff --git a/sample/evolve/WebPage.class.1 b/sample/evolve/WebPage.class.1 new file mode 100644 index 00000000..fe49380e Binary files /dev/null and b/sample/evolve/WebPage.class.1 differ 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("

Return to the home page."); + } +} +/* +// WebPage.class.1 +public class WebPage { + public void show(OutputStreamWriter out) throws IOException { + out.write("

Current Time:

"); + Calendar c = new GregorianCalendar(); + out.write("

"); + out.write(c.getTime().toString()); + out.write("


"); + out.write("

Return to the home page."); + } +} +*/ 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 @@ +

Class Evolution

+ +

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. + +

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. + + +

Run WebPage.show()

+ +

The web server creates a new WebPage object and +calls show() 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. + +

Change WebPage.class

+ +

The web server overwrites class file WebPage.class +on the local disk. Then it signals that WebPage.class +must be reloaded into the JVM. If you run WebPage.show() +again, you will see a different page on your browser. + +

Source files

+ +

Web server: DemoServer.java + +

WebPage: WebPage.java + +

Class loader: DemoLoader.java, + Evolution.java, and + VersionManager.java. 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 @@ +

Instructions

+ +

1. Compile sample/evolve/*.java. + Copy WebPage.class to WebPage.class.0. + +

2. Edit Webpage.java, compile it, + and copy WebPage.class to WebPage.class.1. +
WebPage.class.0 and + WebPage.class.1 are used + for changing the contents of WebPage.class during + the demo. + +

3. Run the server on the local host (where your web browser is running): + +

    +% java sample.evolve.DemoLoader 5003 +
+ +

4. Click below: + +

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 @@ +

WebPage.class has been changed.

+ +Return to the home page. 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 "); + } +} diff --git a/sample/rmi/inside.gif b/sample/rmi/inside.gif new file mode 100644 index 00000000..c69c8ee8 Binary files /dev/null and b/sample/rmi/inside.gif differ 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 @@ +

Instructions

+ +

1. Run the server on the local host (where your web browser is running): + +

    % java sample.rmi.Counter 5001
+ +

2. Click below: + +

+Start! +

+ +

If you don't want to use a web browser, do as follows: + +

    % java javassist.web.Viewer localhost 5001 sample.rmi.CountApplet
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 @@ + + +

Remote Method Invocation

+ +

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 javassist.rmi 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. + +

1. Sample applet

+ +

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. + +

+ + + + +
+ +

However, the program of the applet does not need to directly handle +a socket. The ObjectImporter provided by Javassist deals +with all the awkward programming. +Look at the lines shown with red: + +

Figure 1: Applet + +

+import javassist.rmi.ObjectImporter;
+
+public class CountApplet extends Applet implements ActionListener {
+  private Font font;
+  private ObjectImporter importer;
+  private Counter counter;
+  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);
+    importer = new ObjectImporter(this);
+    dialog = new AlertDialog();
+    message = "???";
+  }
+
+  public void start() {
+    String counterName = getParameter("name");
+    counter = (Counter)importer.getObject(counterName);
+    message = Integer.toString(counter.get());
+  }
+
+  /* The method called when the button is pressed.
+  */
+  public void actionPerformed(ActionEvent e) {
+    message = Integer.toString(counter.increase());
+    repaint();
+  }
+
+  public void paint(Graphics g) {
+    g.setFont(font);
+    g.drawRect(50, 50, 100, 100);
+    g.setColor(Color.blue);
+    g.drawString(message, 60, 120);
+  }
+}
+
+ +

A Counter object running on a remote host +maintains the counter number. To access this object, the applet first +calls getObject() on an ObjectImporter +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, counter.get() and counter.increase() +call methods on the remote object. + +

The definition of the Counter class is also +straightforward: + +

Figure 2: Remote object + +

+public class Counter {
+  private int count = 0;
+
+  public int get() {
+    return count;
+  }
+
+  public int increase() {
+    count += 1;
+    return count;
+  }
+}
+
+ +

Note that the javassist.rmi package does not require +the Counter class to be an interface unlike the Java RMI, +with which Counter must be an interface and it must be +implemented by another class. + +

To make the Counter object available from the applet, +it must be registered with the web server. A AppletServer +object is a simple webserver that can distribute .html files +and .class files (Java applets). + +

Figure 3: Server-side program + +

+public class MyWebServer {
+  public static void main(String[] args) throws IOException, CannotCompileException
+  {
+      AppletServer web = new AppletServer(args[0]);
+      web.exportObject("counter", new Counter());
+      web.run();
+  }
+}
+
+ +

The exportObject() method registers a remote object +with the AppletServer object. In the example above, +a Counter object is registered. The applet can access +the object with the name "counter". The web server starts the service +if the run() method is called. + +


+ +

2. Features

+ +The remote method invocation mechanism provided by Javassist has the +following features: + +
    +
  • Regular Java syntax:
    + The applet can call a method on a remote object with regular + Java syntax. +

    + +

  • No special naming convention:
    + The applet can use the same class name as the server-side program. + The reference object to a remote Foo object is + also represented by the class Foo. + Unlike other similar + systems, it is not represented by a different class such as + ProxyFoo or an interface implemented by + Foo. +

    + +

  • No extra compiler:
    + All the programs, both the applet and the server-side program, + are compiled by the regular Java compiler. No external compiler + is needed. +
+ +

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 javassist.rmi package does not +require the programmer to follow that programming convention. +It is suitable for writing simple distributed programs like applets. + +


+ +

3. Inside of the system

+ +

A key idea of the implementation is that the applet and the server-side +program must use different versions of the class Counter. +The Counter object in the applet must work as a proxy +object, which transfers the method invocations to the Counter +object in the server-side program. + +

With other systems like the Java RMI, the class of this proxy object is +produced by a special compiler such as rmic. +It must be manually maintained by the programmer. + +

+ +

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 Counter class, which is the class +of an exported object, +then the web server +transfers the version of Counter that Javassist generates +as a proxy class. + +


+ + + 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, + * + *

    import java.util.Vector by sample.vector.VectorAssistant(int)
+ * + *

requests the Javassist preprocessor to substitute the following + * lines for the original import declaration: + * + *

    + * import java.util.Vector;
    + * import sample.vector.intVector;
    + * 
+ * + *

The Javassist preprocessor calls VectorAssistant.assist() + * and produces class intVector equivalent to: + * + *

    + * 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();
    + *   }
    + * }
    + * 
+ * + *

VectorAssistant.assist() uses + * sample.vector.Sample and sample.vector.Sample2 + * as a template to produce the methods add() and + * at(). + */ +public class VectorAssistant implements Assistant { + public final String packageName = "sample.vector."; + + /** + * Calls makeSubclass() and produces a new vector class. + * This method is called by a javassist.preproc.Compiler. + * + * @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 ByteArrayClassPath contains bytes that is served as + * a class file to a ClassPool. It is useful to convert + * a byte array to a CtClass object. + * + *

For example, if you want to convert a byte array b + * into a CtClass object representing the class with a name + * classname, then do as following: + * + *

    + * ClassPool cp = ClassPool.getDefault();
    + * cp.insertClassPath(new ByteArrayClassPath(classname, b));
    + * CtClass cc = cp.get(classname);
    + * 
+ * + *

The ClassPool object cp uses the created + * ByteArrayClassPath object as the source of the class file. + * + *

A ByteArrayClassPath 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 ByteArrayClassPath 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 Exception. + */ + public CannotCompileException(Exception e) { + super("by " + e.toString()); + message = null; + } + + /** + * Constructs a CannotCompileException with a + * NotFoundException. + */ + public CannotCompileException(NotFoundException e) { + this("cannot find " + e.getMessage()); + } + + /** + * Constructs a CannotCompileException with an CompileError. + */ + public CannotCompileException(CompileError e) { + super("[source error] " + e.getMessage()); + message = null; + } + + /** + * Constructs a CannotCompileException + * with a ClassNotFoundException. + */ + 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. + * + *

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, + * + *

    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);
    + *   }
    + * }
+ * + *

This subclass maps java.lang.String to + * java2.lang.String. Note that get() + * receives and returns the internal representation of a class name. + * For example, the internal representation of java.lang.String + * is java/lang/String. + * + * @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 Class.getName(). + * 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 jvmClassName + * is mapped. A subclass of this class should override this method. + * + *

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; + +/** + * ClassPath is an interface implemented by objects + * representing a class search path. + * ClassPool uses those objects for reading class files. + * + * 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. + * + *

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. + * + *

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. + * + *

A ClassPool object can be regarded as a container + * of CtClass objects. It reads class files on demand + * from various + * sources represented by ClassPath and create + * CtClass objects representing those class files. + * The source may be another ClassPool. If so, + * write() is called on the source ClassPool + * for obtaining a class file. + * + *

A CtClass + * object contained in a ClassPool is written to an + * output stream (or a file) if write() + * (or writeFile()) is called on the + * ClassPool. + * write() is typically called by a class loader, + * which obtains the bytecode image to be loaded. + * + *

The users can modify CtClass objects + * before those objects are written out. + * To obtain a reference + * to a CtClass object contained in a + * ClassPool, get() should be + * called on the ClassPool. If a CtClass + * object is modified, then the modification is reflected on the resulting + * class file returned by write() in ClassPool. + * + *

In summary, + * + *

    + *
  • get() returns a reference to a CtClass + * object contained in a ClassPool. + * + *
  • write() translates a CtClass + * object contained in a ClassPool into a class file + * and writes it to an output stream. + *
+ * + *

The users can add a listener object receiving an event from a + * ClassPool. An event occurs when a listener is + * added to a ClassPool and when write() + * is called on a ClassPool. The listener class + * must implement Translator. A typical listener object + * is used for modifying a CtClass object on demand + * when it is written to an output stream. + * + *

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. + * + *

The default class pool searches the system search path, + * which usually includes the platform library, extension + * libraries, and the search path specified by the + * -classpath option or the CLASSPATH + * 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. + * + *

This returns the result of getDefault(null). + * + * @see #getDefault(Translator) + */ + public static ClassPool getDefault() { + return getDefault(null); + } + + /** + * Returns the class search path. + */ + public String toString() { + return source.toString(); + } + + /** + * Returns the Translator object associated with + * this ClassPool. + */ + public Translator getTranslator() { return translator; } + + /** + * Table of registered cflow variables. + */ + private Hashtable cflow = null; // should be synchronous. + + /** + * Records the $cflow variable for the field specified + * by cname and fname. + * + * @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 $cflow variable + */ + public Object[] lookupCflow(String name) { + if (cflow == null) + cflow = new Hashtable(); + + return (Object[])cflow.get(name); + } + + /** + * Writes a class file specified with classname + * in the current directory. + * It never calls onWrite() 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 classname. + * It never calls onWrite() 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 classname + * in the current directory. + * It calls onWrite() 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 classname + * on a local disk. + * It calls onWrite() 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 java.lang.Class object that has been loaded + * by writeAsClass(). Note that such a class cannot be + * obtained by java.lang.Class.forName() 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 java.lang.Class object. + * It calls write() to obtain a class file and then + * loads the obtained class file into the JVM. The returned + * Class object represents the loaded class. + * + *

This method is provided for convenience. If you need more + * complex functionality, you should write your own class loader. + * + *

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 ClassPool class. + * The internal class loader + * loads only the classes explicitly specified by this method + * writeAsClass(). The other classes are loaded + * by the parent class loader (the sytem class loader) by delegation. + * Thus, if a class X loaded by the internal class + * loader refers to a class Y, then the class + * Y 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 onWrite() 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 classname + * to a given output stream. + * It calls onWrite() on a translator. + * + *

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 CtClass + * 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 CtClass + * object representing that class file. If that class file has been + * already read, this method returns a reference to the + * CtClass created when that class file was read at the + * first time. + * + *

If classname ends with "[]", then this method + * returns a CtClass 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 + * CtClass + * objects representing those class files. + * + *

If an element of classnames ends with "[]", + * then this method + * returns a CtClass 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 + * -classpath option or the CLASSPATH + * environment variable. + */ + public void appendSystemPath() { + source.appendSystemPath(); + } + + /** + * Insert a ClassPath 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 ClassPath 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. + * + *

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 classname. + * + * @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 + * classname. + * + * @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 javassist.expr package). + * + *

Instances of this class specifies how to instrument of the + * bytecodes representing a method body. They are passed to + * CtClass.instrument() or + * CtMethod.instrument() as a parameter. + * + *

Example: + *

    + * 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);
    + * 
+ * + *

This program substitutes "Singleton.makePoint()" + * for all occurrences of "new Point()" + * appearing in methods declared in a Client 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, + * replaceNew(ctPoint, ctSingleton, "createPoint") + * (where ctPoint and ctSingleton are + * compile-time classes for class Point and class + * Singleton, respectively) + * replaces all occurrences of: + * + *

    new Point(x, y)
+ * + * in the method body with: + * + *
    Singleton.createPoint(x, y)
+ * + *

This enables to intercept instantiation of Point + * and change the samentics. For example, the following + * createPoint() implements the singleton pattern: + * + *

    public static Point createPoint(int x, int y) {
    +     *     if (aPoint == null)
    +     *         aPoint = new Point(x, y);
    +     *     return aPoint;
    +     * }
    +     * 
+ * + *

The static method call substituted for the original new + * 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. + * + *

The return type of the substituted static method must be + * the exactly same as the type of the instantiated class specified by + * newClass. + * + * @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. + * + *

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. + * + *

Also, clazz and newClass 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 static 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. + * + *

For example, the program below + * + *

    Point p = new Point();
    +     * int newX = p.x + 3;
+ * + *

can be translated into: + * + *

    Point p = new Point();
    +     * int newX = Accessor.readX(p) + 3;
+ * + *

where + * + *

    public class Accessor {
    +     *     public static int readX(Object target) { ... }
    +     * }
+ * + *

The type of the parameter of readX() must + * be java.lang.Object independently of the actual + * type of target. 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 void. + * + *

For example, the program below + * + *

    Point p = new Point();
    +     * p.x = 3;
+ * + *

can be translated into: + * + *

    Point p = new Point();
    +     * Accessor.writeX(3);
+ * + *

where + * + *

    public class Accessor {
    +     *     public static void writeX(Object target, int value) { ... }
    +     * }
+ * + *

The type of the first parameter of writeX() must + * be java.lang.Object independently of the actual + * type of target. 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. + * + *

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 + * void. 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 + * move(): + * + *

    class Point {
    +     *     Point move(int x, int y) { ... }
    +     * }
+ * + *

Then the before method must be something like this: + * + *

    class Verbose {
    +     *     static void print(Point target, int x, int y) { ... }
    +     * }
+ * + *

The CodeConverter would translate bytecode + * equivalent to: + * + *

    Point p2 = p.move(x + y, 0);
+ * + *

into the bytecode equivalent to: + * + *

    int tmp1 = x + y;
    +     * int tmp2 = 0;
    +     * Verbose.print(p, tmp1, tmp2);
    +     * Point p2 = p.move(tmp1, tmp2);
+ * + * @param origMethod the method originally invoked. + * @param beforeMethod the method invoked before + * origMethod. + */ + 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 + * void. 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 + * move(): + * + *
    class Point {
    +     *     Point move(int x, int y) { ... }
    +     * }
+ * + *

Then the after method must be something like this: + * + *

    class Verbose {
    +     *     static void print(Point target, int x, int y) { ... }
    +     * }
+ * + *

The CodeConverter would translate bytecode + * equivalent to: + * + *

    Point p2 = p.move(x + y, 0);
+ * + *

into the bytecode equivalent to: + * + *

    int tmp1 = x + y;
    +     * int tmp2 = 0;
    +     * Point p2 = p.move(tmp1, tmp2);
    +     * Verbose.print(p, tmp1, tmp2);
+ * + * @param origMethod the method originally invoked. + * @param afterMethod the method invoked after + * origMethod. + */ + 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; + +/** + * CtBehavior is the abstract super class of + * CtMethod and CtConstructor. + */ +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 + * javassist.Modifier. + * @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, getSignature() 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 $cflow for this member; + * If $cflow is used, the class files modified + * with Javassist requires a support class + * javassist.runtime.Cflow at runtime + * (other Javassist classes are not required at runtime). + * + *

Every $cflow variable is given a unique name. + * For example, if the given name is "Point.paint", + * then the variable is indicated by $cflow(Point.paint). + * + * @param name $cflow name. It can include + * alphabets, numbers, _, + * $, and . (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 CtClass represents a class. + * It is obtained from ClassPool. + * + * @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 CtClass object representing + * the boolean type. + */ + public static CtClass booleanType; + + /** + * The CtClass object representing + * the char type. + */ + public static CtClass charType; + + /** + * The CtClass object representing + * the byte type. + */ + public static CtClass byteType; + + /** + * The CtClass object representing + * the short type. + */ + public static CtClass shortType; + + /** + * The CtClass object representing + * the int type. + */ + public static CtClass intType; + + /** + * The CtClass object representing + * the long type. + */ + public static CtClass longType; + + /** + * The CtClass object representing + * the float type. + */ + public static CtClass floatType; + + /** + * The CtClass object representing + * the double type. + */ + public static CtClass doubleType; + + /** + * The CtClass object representing + * the void 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 ClassPool for this class. + */ + public ClassPool getClassPool() { return null; } + + /** + * Returns a class file for this class. + * + *

This method is not available if isFrozen() + * 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 true if this object represents a primitive + * Java type: boolean, byte, char, short, int, long, float, double, + * or void. + */ + public boolean isPrimitive() { return false; } + + /** + * Returns true 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 null. + */ + public CtClass getComponentType() throws NotFoundException { + return null; + } + + /** + * Returns true if this class extends or implements + * clazz. It also returns true if + * this class is the same as clazz. + */ + 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 null. + */ + 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 newName for all occurrences of a class + * name oldName 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 map. + * + *

All the class names appearing in the class file are tested + * with map 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 ClassMap so that get() + * 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. + * + *

This method may return null. + */ + 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 true if this object represents an interface. + */ + public boolean isInterface() { + return false; + } + + /** + * Returns the modifiers for this class, encoded in an integer. + * For decoding, use javassist.Modifier. + * + * @see Modifier + */ + public int getModifiers() { + return 0; + } + + /** + * Sets the modifiers. + * + * @param mod modifiers encoded by + * javassist.Modifier + * @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. + * + *

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 java.lang.Object. + */ + 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 CtClass objects + * representing interfaces, or + * null 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 CtField 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. + * + *

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. + * + *

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 CtConstructor 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 javassist.bytecode.Descriptor. + * + * @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 null if + * no class initializer is not declared. + * + * @see javassist.CtConstructor + */ + public CtConstructor getClassInitializer() { + return null; + } + + /** + * Returns an array containing CtMethod 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. + * + *

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. + * + *

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. + * + *

The CtField belonging to another + * CtClass 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. + * + *

The CtField belonging to another + * CtClass cannot be directly added to this class. + * Only a field created for this class can be added. + * + *

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. + * + *

    +     * cc.addField(f, "0")               // the initial value is 0.
    +     * cc.addField(f, "i + 1")           // i + 1.
    +     * cc.addField(f, "new Point()");    // a Point object.
    +     * 
+ * + *

Here, the type of variable cc is CtClass. + * The type of f is CtField. + * + * @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. + * + *

The CtField belonging to another + * CtClass cannot be directly added to this class. + * Only a field created for this class can be added. + * + *

For example, + * + *

    +     * CtClass cc = ...;
    +     * addField(new CtField(CtClass.intType, "i", cc),
    +     *          CtField.Initializer.constant(1));
    +     * 
+ * + *

This code adds an int 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 instrument() + * on every CtMethod and CtConstructor 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 instrument() + * on every CtMethod and CtConstructor object + * in the class. + * + * @param editor specifies how to modify. + */ + public void instrument(ExprEditor editor) + throws CannotCompileException + { + checkModify(); + } + + /** + * Converts this class to a java.lang.Class object. + * Once this method is called, further modifications are not + * possible any more. + * + *

This method is equivalent to: + *

    this.getClassPool().writeAsClass(this.getName())
+ * + *

See the description of ClassPool.writeAsClass() + * 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. + * + *

This method is equivalent to: + *

    this.getClassPool().write(this.getName())
+ * + * @see javassist.ClassPool#write(String) + */ + public byte[] toBytecode() + throws NotFoundException, IOException, CannotCompileException + { + return getClassPool2().write(getName()); + } + + /** + * Writes a class file represented by this CtClass + * object in the current directory. + * Once this method is called, further modifications are not + * possible any more. + * + *

This method is equivalent to: + *

    this.getClassPool().writeFile(this.getName())
+ * + * @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. + * + *

If this method is used to obtain a byte array representing + * the class file, Translator.onWrite() is never + * called on this class. ClassPool.write() should + * be used. + * + *

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(), + "", "()V"); + m.setAccessFlags(AccessFlag.STATIC); + m.setCodeAttribute(code.toCodeAttribute()); + cf.addMethod(m); + } + else { + CodeAttribute codeAttr = m.getCodeAttribute(); + if (codeAttr == null) + throw new CannotCompileException("empty "); + + try { + CodeIterator it = codeAttr.iterator(); + int pos = it.insertEx(code.get()); + it.insert(code.getExceptionTable(), pos); + int maxstack = codeAttr.getMaxStack(); + if (maxstack < stacksize) + codeAttr.setMaxStack(stacksize); + } + 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 isClassInitializer(). + * + * @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 CtClass.addConstructor(). + * + *

The created constructor does not include a constructor body, + * must be specified with setBody(). + * + * @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, "", desc); + setModifiers(Modifier.PUBLIC); + } + + /** + * Creates a copy of a CtConstructor object. + * The created constructor must be + * added to a class with CtClass.addConstructor(). + * + *

All occurrences of class names in the created constructor + * are replaced with names specified by + * map if map is not null. + * + *

By default, all the occurrences of the names of the class + * declaring src 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 map is null or not. + * To prevent this replacement, call ClassMap.fix(). + * + *

Note: if the .class notation (for example, + * String.class) 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 Class.forName() 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 null. + * + * @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 + * javassist.Modifier. + * @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 "<clinit>". + */ + 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, + * getSignature() 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. + * + *

All occurrences of the class names in the copied body + * are replaced with the names specified by + * map if map is not null. + * + * @param src the method that the body is copied from. + * @param map the hashtable associating original class names + * with substituted names. + * It can be null. + */ + 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 $cflow for this constructor. + * If $cflow is used, the class files modified + * with Javassist requires a support class + * javassist.runtime.Cflow at runtime + * (other Javassist classes are not required at runtime). + * + *

Every $cflow variable is given a unique name. + * For example, if the given name is "Point.paint", + * then the variable is indicated by $cflow(Point.paint). + * + * @param name $cflow name. It can include + * alphabets, numbers, _, + * $, and . (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 CtField object. + * The created field must be added to a class + * with CtClass.addField(). + * An initial value of the field is specified + * by a CtField.Initializer object. + * + *

If getter and setter methods are needed, + * call CtNewMethod.getter() and + * CtNewMethod.setter(). + * + * @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 CtClass.addField(). + * An initial value of the field is specified + * by a CtField.Initializer object. + * + *

If getter and setter methods are needed, + * call CtNewMethod.getter() and + * CtNewMethod.setter(). + * + * @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: + * + *

    +     * "public String name;"
    +     * "public int k = 3;"
+ * + *

Note that the source code ends with ';' + * (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. + * Initializer is passed to + * CtClass.addField() with a CtField. + * + *

This class cannot be instantiated with the new operator. + * Factory methods such as byParameter() and + * byNew + * 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 java.lang.String type. + */ + public static Initializer constant(String s) { + return new StringInitializer(s); + } + + /** + * Makes an initializer using a constructor parameter. + * + *

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. + * + *

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: + * + *

    Object obj - the object including the field.
    + *
+ * + *

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. + * + *

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: + * + *

    Object obj - the object including the field.
    + * String[] strs - the character strings specified + * by stringParams
    + *
+ * + *

If the initialized field is static, then the constructor + * receives only strs. + * + * @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. + * + *

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: + * + *

    Object obj - the object including the field.
    + * Object[] args - the parameters passed to the + * constructor of the object including the + * filed. + *
+ * + *

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. + * + *

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: + * + *

    Object obj - the object including the field.
    + * String[] strs - the character strings specified + * by stringParams
    + * Object[] args - the parameters passed to the + * constructor of the object including the + * filed. + *
+ * + *

If the initialized field is static, then the constructor receives + * only strs. + * + * @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. + * + *

This initializer calls a static method and uses the returned + * value as the initial value of the field. + * The called method receives the parameters: + * + *

    Object obj - the object including the field.
    + *
+ * + *

If the initialized field is static, then the method does + * not receive any parameters. + * + *

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. + * + *

This initializer calls a static method and uses the returned + * value as the initial value of the field. The called method + * receives the parameters: + * + *

    Object obj - the object including the field.
    + * String[] strs - the character strings specified + * by stringParams
    + *
+ * + *

If the initialized field is static, then the method + * receive only strs. + * + *

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. + * + *

This initializer calls a static method and uses the returned + * value as the initial value of the field. The called method + * receives the parameters: + * + *

    Object obj - the object including the field.
    + * Object[] args - the parameters passed to the + * constructor of the object including the + * filed. + *
+ * + *

If the initialized field is static, then the method does + * not receive any parameters. + * + *

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. + * + *

This initializer calls a static method and uses the returned + * value as the initial value of the field. The called method + * receives the parameters: + * + *

    Object obj - the object including the field.
    + * String[] strs - the character strings specified + * by stringParams
    + * Object[] args - the parameters passed to the + * constructor of the object including the + * filed. + *
+ * + *

If the initialized field is static, then the method + * receive only strs. + * + *

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 int 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, "", 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, "", 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_ + 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 CtMember 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 + * javassist.Modifier. + * @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 CtMethod 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 CtClass.addMethod(). + * + * @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 CtMethod object. + * The created method must be + * added to a class with CtClass.addMethod(). + * + *

All occurrences of class names in the created method + * are replaced with names specified by + * map if map is not null. + * + *

For example, suppose that a method at() is as + * follows: + * + *

    public X at(int i) {
    +     *     return (X)super.elementAt(i);
    +     * }
+ * + *

(X is a class name.) If map substitutes + * String for X, then the created method is: + * + *

    public String at(int i) {
    +     *     return (String)super.elementAt(i);
    +     * }
+ * + *

By default, all the occurrences of the names of the class + * declaring at() 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 map is null or not. + * To prevent this replacement, call ClassMap.fix(). + * + *

Note: if the .class notation (for example, + * String.class) 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 Class.forName() 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 null. + * + * @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 obj 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 + * javassist.Modifier. + * @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, getSignature() 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. + * + *

All occurrences of the class names in the copied method body + * are replaced with the names specified by + * map if map is not null. + * + * @param src the method that the body is copied from. + * @param map the hashtable associating original class names + * with substituted names. + * It can be null. + */ + 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 null). + * + * @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 $cflow for this method. + * If $cflow is used, the class files modified + * with Javassist requires a support class + * javassist.runtime.Cflow at runtime + * (other Javassist classes are not required at runtime). + * + *

Every $cflow variable is given a unique name. + * For example, if the given name is "Point.paint", + * then the variable is indicated by $cflow(Point.paint). + * + * @param name $cflow name. It can include + * alphabets, numbers, _, + * $, and . (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 CtNewMethod.wrapped(). + * + * @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 String 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. + * + *

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 CtConstructor. + * 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 + * Object and passed to a super-class' + * constructor. + */ + public static final int PASS_ARRAY = 1; // an array of parameters + + /** + * Specifies that parameters are passed as is + * 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 {}. + * @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 null. + * + * @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. + * + *

The created constructor takes no parameter. It calls + * super(). + */ + 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(), + "", "()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 parameters but calls the super's + * constructor without those parameters (that is, it calls the default + * constructor). + * + *

The parameters passed to the created constructor should be + * used for field initialization. CtField.Initializer + * 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 parameters 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. + * + *

If howto is PASS_PARAMS, + * 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. + * + *

If howto is PASS_NONE, + * the created constructor calls the super's default constructor. + * The superclass must contain a constructor taking no parameters. + * + *

If howto is PASS_ARRAY, + * the created constructor calls the super's constructor + * with the given parameters in the form of an array of + * Object. The signature of the super's constructor + * must be: + * + *

    constructor(Object[] params, <type> cvalue) + *
+ * + *

Here, cvalue is the constant value specified + * by cparam. + * + *

If cparam is null, the signature + * must be: + * + *

    constructor(Object[] params)
+ * + *

If body 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 body must not + * be a constructor but a method. + * + *

Since the embedded method is wrapped + * in parameter-conversion code + * as in CtNewMethod.wrapped(), + * the constructor parameters are + * passed in the form of an array of Object. + * The method specified by body must have the + * signature shown below: + * + *

    Object method(Object[] params, <type> cvalue) + *
+ * + *

If cparam is null, the signature + * must be: + * + *

    Object method(Object[] params)
+ * + *

Although the type of the returned value is Object, + * the value must be always null. + * + *

Example: + * + *

    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);
+ * + *

where the class Sample is as follows: + * + *

    public class Sample {
    +     *     public Object m(Object[] args, String msg) {
    +     *         System.out.println(msg);
    +     *         return null;
    +     *     }
    +     * }
+ * + *

This program produces the following class: + * + *

    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
    +     *     }
    +     * }
+ * + * @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 (PASS_NONE, + * PASS_ARRAY, + * or PASS_PARAMS) + * @param body appended body (may be null). + * It must be not a constructor but a method. + * @param cparam constant parameter (may be null.) + * @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 CtMethod. + * 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, + * + *
    "public Object id(Object obj) { return obj; }"
+ * + * @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, + * + *
    "public Object id(Object obj) { return obj; }"
+ * + *

If the source code includes $proceed(), 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 $proceed(). + * @param delegateMethod the name of the method + * that is called by $proceed(). + */ + 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 {}. + * @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 null. + * + * @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 null. + * + * @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 delegate 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. + * + *

The following method is an example of the created method. + * + *

    int f(int p, int q) {
    +     *     return super.f(p, q);
    +     * }
+ * + *

The name of the created method can be changed by + * setName(). + * + * @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 Object. + * + *

The body of the created method is a copy of the body of a method + * specified by body. However, it is wrapped in + * parameter-conversion code. + * + *

The method specified by body must have this singature: + * + *

    Object method(Object[] params, <type> cvalue) + *
+ * + *

The type of the cvalue depends on + * constParam. + * If constParam is null, the signature + * must be: + * + *

    Object method(Object[] params)
+ * + *

The method body copied from body is wrapped in + * parameter-conversion code, which converts parameters specified by + * parameterTypes into an array of Object. + * The returned value is also converted from the Object + * type to the type specified by returnType. Thus, + * the resulting method body is as follows: + * + *

    Object[] params = new Object[] { p0, p1, ... };
    +     * <type> cvalue = <constant-value>;
    +     *  ... copied method body ...
    +     * Object result = <returned value>
    +     * return (<returnType>)result;
    +     * 
+ * + *

The variables p0, p2, ... represent + * formal parameters of the created method. + * The value of cvalue is specified by + * constParam. + * + *

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 + * java.lang.Integer. If the type of the returned value + * is void, the returned value is discarded. + * + *

Example: + * + *

    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);
+ * + *

where the class Sample is as follows: + * + *

    public class Sample extends java.util.Vector {
    +     *     public Object add0(Object[] args) {
    +     *         super.addElement(args[0]);
    +     *         return null;
    +     *     }
    +     * }
+ * + *

This program produces a class intVector: + * + *

    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
    +     *     }
    +     * }
+ * + *

Note that the type of the parameter to add() depends + * only on the value of argTypes passed to + * CtNewMethod.wrapped(). Thus, it is easy to + * modify this program to produce a + * StringVector class, which is a vector containing + * only String 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 null). + * @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, "", "()V"); + } + else if (howToCallSuper == PASS_PARAMS) { + stacksize = code.addLoadParameters(parameters) + 1; + code.addInvokespecial(superclazz, "", + 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, "", 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 CtPrimitiveType represents a primitive type. + * It is obtained from CtClass. + */ +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 true 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 + * java.lang.Integer. + */ + 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 + * intValue. + */ + 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 + * ()I. + */ + 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 + * javassit.bytecode.Opcode.IRETURN. + */ + 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 + * javassit.bytecode.Opcode.T_INT. + */ + 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. + * + *

For example, + *

    % java javassist.Dump foo.class
+ * + *

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 "); + 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). + * + *

This is a sample class loader using ClassPool. + * Unlike a regular class loader, this class loader obtains bytecode + * from a ClassPool. + * + *

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. + * + *

Suppose that an instance of MyTranslator implementing + * the interface Translator is responsible for modifying + * class files. + * The startup program of an application using MyTranslator + * should be something like this: + * + *

    + * 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);
    + *   }
    + * }
    + * 
+ * + *

Class MyApp is the main program of the application. + * + *

This program should be executed as follows: + * + *

    + * % java Main arg1 arg2...
    + * 
+ * + *

It modifies the class MyApp with a MyTranslator + * object before loading it into the JVM. + * Then it calls main() in MyApp with arguments + * arg1, arg2, ... + * + *

This program execution is equivalent to: + * + *

    + * % java MyApp arg1 arg2...
    + * 
+ * + *

except that classes are translated by MyTranslator + * at load time. + * + *

If only a particular class is modified when it is loaded, + * a program like the following can be used: + * + *

    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();
+ * + *

This program modifies the super class of the Rectangle + * class and loads it into the JVM with a class loader cl. + * + *

Note 1: + * + *

This class loader does not allow the users to intercept the loading + * of java.* and javax.* classes unless + * Loader.doDelegation is false. Also see + * Note 2. + * + *

Note 2: + * + *

If classes are loaded with different class loaders, they belong to + * separate name spaces. If class C is loaded by a class + * loader CL, all classes that the class C + * refers to are also loaded by CL. However, if CL + * delegates the loading of the class C to CL', + * then those classes that the class C refers to + * are loaded by a parent class loader CL' + * instead of CL. + * + *

If an object of class C is assigned + * to a variable of class C belonging to a different name + * space, then a ClassCastException is thrown. + * + *

Because of the fact above, this loader delegates only the loading of + * javassist.Loader + * and classes included in package java.* and + * javax.* to the parent class + * loader. Other classes are directly loaded by this loader. + * + *

For example, suppose that java.lang.String would be loaded + * by this loader while java.io.File is loaded by the parent + * class loader. If the constructor of java.io.File is called + * with an instance of java.lang.String, then it may throw + * an exception since it accepts an instance of only the + * java.lang.String 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. + * + *

This class loader uses the parent class loader for + * java.* and javax.* classes. + * If this variable doDelegation + * is false, this class loader does not delegate those + * classes to the parent class loader. + * + *

The default value is true. + */ + 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. + * + *

If the given class name ends with . (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 ClassPool. + */ + public void setClassPool(ClassPool cp) { + source = cp; + } + + /** + * Loads a class with an instance of Loader + * and calls main() of that class. + * + *

This method calls run(). + * + * @param args[0] class name to be loaded. + * @param args[1-n] parameters passed to main(). + * + * @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 main() in that class. + * + * @param args[0] the name of the loaded class. + * @param args[1-n] parameters passed to main(). + */ + 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 main() in that class. + * + * @param classname the loaded class. + * @param args parameters passed to main(). + */ + 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 ClassPath. + * If the source throws an exception, this returns null. + * + *

This method can be overridden by a subclass of + * Loader. + */ + 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 javassist.bytecode.AccessFlag. + * + *

All the methods/constants in this class are compatible with + * ones in java.lang.reflect.Modifier. + * + * @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 public + * modifier. + */ + public static boolean isPublic(int mod) { + return (mod & PUBLIC) != 0; + } + + /** + * Returns true if the modifiers include the private + * modifier. + */ + public static boolean isPrivate(int mod) { + return (mod & PRIVATE) != 0; + } + + /** + * Returns true if the modifiers include the protected + * modifier. + */ + public static boolean isProtected(int mod) { + return (mod & PROTECTED) != 0; + } + + /** + * Returns true if the modifiers include the static + * modifier. + */ + public static boolean isStatic(int mod) { + return (mod & STATIC) != 0; + } + + /** + * Returns true if the modifiers include the final + * modifier. + */ + public static boolean isFinal(int mod) { + return (mod & FINAL) != 0; + } + + /** + * Returns true if the modifiers include the synchronized + * modifier. + */ + public static boolean isSynchronized(int mod) { + return (mod & SYNCHRONIZED) != 0; + } + + /** + * Returns true if the modifiers include the volatile + * modifier. + */ + public static boolean isVolatile(int mod) { + return (mod & VOLATILE) != 0; + } + + /** + * Returns true if the modifiers include the transient + * modifier. + */ + public static boolean isTransient(int mod) { + return (mod & TRANSIENT) != 0; + } + + /** + * Returns true if the modifiers include the native + * modifier. + */ + public static boolean isNative(int mod) { + return (mod & NATIVE) != 0; + } + + /** + * Returns true if the modifiers include the interface + * modifier. + */ + public static boolean isInterface(int mod) { + return (mod & INTERFACE) != 0; + } + + /** + * Returns true if the modifiers include the abstract + * modifier. + */ + public static boolean isAbstract(int mod) { + return (mod & ABSTRACT) != 0; + } + + /** + * Returns true if the modifiers include the strictfp + * 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 mod. + */ + 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(""); + 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(""); + 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 ClassPool. + * The users can define a class implementing this + * interface and attach an instance of that class to a + * ClassPool 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 ClassPool for initialization + * when the object is attached to a ClassPool object. + * + * @param pool the ClassPool 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 ClassPool for notifying that + * a class is written out to an output stream. + * + *

If CtClass.frozen() is true, that is, if the class has been + * already modified and written, then onWrite() is not invoked. + * + * @param pool the ClassPool 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). + * + *

This search path is used only if a requested + * class name starts with the name specified by packageName. + * If packageName is "mypack" and a requested class is + * "mypack.sub.Test", then the given URL is used for loading that class. + * If packageName is null, 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 accflags. + */ + 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(). + +/** + * attribute_info 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 attribute_info structure. + * + * @param cp constant pool table + * @param attrname attribute name + * @param attrinfo info field + * of attribute_info 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 attribute_info + * structure. + * The returned value is attribute_length + 6. + */ + public int length() { + return info.length + 6; + } + + /** + * Returns the info field + * of this attribute_info structure. + * + *

This method is not available if the object is an instance + * of CodeAttribute. + */ + public byte[] get() { return info; } + + /** + * Sets the info field + * of this attribute_info structure. + * + *

This method is not available if the object is an instance + * of CodeAttribute. + */ + public void set(byte[] newinfo) { info = newinfo; } + + /** + * Makes a copy. Class names are replaced according to the + * given Map 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. + * + *

A Bytecode object is an unbounded array + * containing bytecode. For example, + * + *

    ConstPool cp = ...;    // constant pool table
    + * Bytecode b = new Bytecode(cp, 1, 0);
    + * b.addIconst(3);
    + * b.addReturn(CtClass.intType);
    + * CodeAttribute ca = b.toCodeAttribute();
+ * + *

This program produces a Code attribute including a bytecode + * sequence: + * + *

    iconst_3
    + * ireturn
+ * + * @see ConstPool + * @see CodeAttribute + */ +public class Bytecode implements Opcode { + /** + * Represents the CtClass file using the + * constant pool table given to this Bytecode 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 Bytecode object with an empty bytecode + * sequence. + * + *

The parameters stacksize and localvars + * specify initial values + * of max_stack and max_locals. + * They can be changed later. + * + * @param cp constant pool table. + * @param stacksize max_stack. + * @param localvars max_locals. + */ + 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 exception_table. + */ + public ExceptionTable getExceptionTable() { return tryblocks; } + + /** + * Converts to a CodeAttribute. + */ + 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 max_stack. + */ + public int getMaxStack() { return maxStack; } + + /** + * Sets max_stack. + * + *

This value may be automatically updated when an instruction + * is appended. A Bytecode object maintains the current + * stack depth whenever an instruction is added + * by addOpcode(). For example, if DUP is appended, + * the current stack depth is increased by one. If the new stack + * depth is more than max_stack, then it is assigned + * to max_stack. 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 max_locals. + */ + public int getMaxLocals() { return maxLocals; } + + /** + * Sets max_locals. + */ + public void setMaxLocals(int size) { + maxLocals = size; + } + + /** + * Sets max_locals. + * + *

This computes the number of local variables + * used to pass method parameters and sets max_locals + * to that number plus locals. + * + * @param isStatic true if params 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 max_locals. + */ + public void incMaxLocals(int diff) { + maxLocals += diff; + } + + /** + * Adds a new entry of exception_table. + */ + public void addExceptionHandler(int start, int end, + int handler, CtClass type) { + addExceptionHandler(start, end, handler, + constPool.addClassInfo(type)); + } + + /** + * Adds a new entry of exception_table. + */ + 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. + * max_stack is updated if the current stack depth + * is the deepest so far. + * + *

Note: some instructions such as INVOKEVIRTUAL does not + * update the current stack depth since the increment depends + * on the method signature. + * growStack() must be explicitly called. + */ + public void addOpcode(int code) { + add(code); + growStack(STACK_GROW[code]); + } + + /** + * Increases the current stack depth. + * It also updates max_stack 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 max_stack 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_ + 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_ + 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_ -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_ + 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_ + 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_ + 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_ + 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_ + 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_ + 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_ + 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_ + 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_ + 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_ + 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_ + 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 n. + * + * @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 n. + * + * @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 CONSTANT_Class_info + * 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 CONSTANT_Class_info + * 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. + * + *

The specified method must not be an inherited method. + * It must be directly declared in the class specified + * in clazz. + * + * @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. + * + *

The specified method must not be an inherited method. + * It must be directly declared in the class specified + * in clazz. + * + * @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. + * + *

The specified method must not be an inherited method. + * It must be directly declared in the class specified + * in classname. + * + * @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. + * + *

The specified method must not be an inherited method. + * It must be directly declared in the class specified + * by clazz. + * + * @param clazz the index of CONSTANT_Class_info + * 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 CONSTANT_Class_info + * 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 String + * 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 T_BOOLEAN, T_CHAR, ... + * @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 dimensions. + */ + 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 dim. + */ + 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 dim. + */ + 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 + * java.lang.System.println(message). + * + * @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; + +/** + * ClassFile represents a Java .class 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. + * + *

Unless the old super class is + * java.lang.Object, this method substitutes the new name + * for all occurrences of the old class name in the class file. + * If the old super class is java.lang.Object, + * 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. + * + *

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 + * java/lang/Object. + * + * @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 FieldInfo. + * @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 MethodInfo. + * @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 AttributeInfo 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(""); + printAttributes(ca.getAttributes(), out); + out.println(""); + } + 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_attribute. + * + *

To browse the code field of + * a Code_attribute structure, + * use CodeIterator. + * + * @see CodeIterator + */ +public class CodeAttribute extends AttributeInfo implements Opcode { + /** + * The name of this attribute "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_attribute. + * + * @param cp constant pool table + * @param stack max_stack + * @param locals max_locals + * @param code code[] + * @param etable exception_table[] + */ + 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_attribute. + * 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 Map 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 BadBytecode + * exception is thrown, it is + * converted into + * RuntimeCopyException. + * + * @return CodeAttribute 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 copy() + * in CodeAttribute. + */ + public static class RuntimeCopyException extends RuntimeException { + /** + * Constructs an exception. + */ + public RuntimeCopyException(String s) { + super(s); + } + } + + /** + * Returns the length of this attribute_info + * structure. + * The returned value is attribute_length + 6. + */ + 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 max_stack. + */ + public int getMaxStack() { + return maxStack; + } + + /** + * Sets max_stack. + */ + public void setMaxStack(int value) { + maxStack = value; + } + + /** + * Returns max_locals. + */ + public int getMaxLocals() { + return maxLocals; + } + + /** + * Sets max_locals. + */ + public void setMaxLocals(int value) { + maxLocals = value; + } + + /** + * Returns code_length. + */ + public int getCodeLength() { + return info.length; + } + + /** + * Returns code[]. + */ + public byte[] getCode() { + return info; + } + + /** + * Sets 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 exception_table[]. + */ + public ExceptionTable getExceptionTable() { return exceptions; } + + /** + * Returns attributes[]. + * It returns a list of AttributeInfo. + * + * @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 AttributeInfo 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. + * + *

This iterator does not provide remove(). + * If a piece of code in a Code_attribute is unnecessary, + * it should be overwritten with NOP. + * + * @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. + * + *

The index of the next instruction is set to the given index. + * The successive call to next() + * returns the index that has been given to move(). + * + *

Note that the index is into the byte array returned by + * get().getCode(). + * + * @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_length of Code_attribute. + */ + 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). + * + *

Note that the index is into the byte array returned by + * get().getCode(). + * + * @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 super() or this(). + * + *

This method skips all the instructions for executing + * super() or this(), which should be + * placed at the beginning of a constructor body. + * + *

This method returns the index of INVOKESPECIAL instruction + * executing super() or this(). + * A successive call to next() returns the + * index of the next instruction following that INVOKESPECIAL. + * + *

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 super(). + * + *

This method skips all the instructions for executing + * super(), which should be + * placed at the beginning of a constructor body. + * + *

This method returns the index of INVOKESPECIAL instruction + * executing super(). + * A successive call to next() returns the + * index of the next instruction following that INVOKESPECIAL. + * + *

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 this() is found. + */ + public int skipSuperConstructor() throws BadBytecode { + return skipSuperConstructor0(0); + } + + /** + * Moves to the first instruction following explicit + * constructor invocation this(). + * + *

This method skips all the instructions for executing + * this(), which should be + * placed at the beginning of a constructor body. + * + *

This method returns the index of INVOKESPECIAL instruction + * executing this(). + * A successive call to next() returns the + * index of the next instruction following that INVOKESPECIAL. + * + *

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 super() 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 + * next() (not before the instruction returned + * by tha last call to next()). + * Branch offsets and the exception table are also updated. + * + *

If the next instruction is at the beginning of a block statement, + * then the bytecode is inserted within that block. + * + *

An extra gap may be inserted at the end of the inserted + * bytecode sequence for adjusting alignment if the code attribute + * includes LOOKUPSWITCH or TABLESWITCH. + * + * @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 pos. + * Branch offsets and the exception table are also updated. + * + *

If the instruction at the given index is at the beginning + * of a block statement, + * then the bytecode is inserted within that block. + * + *

An extra gap may be inserted at the end of the inserted + * bytecode sequence for adjusting alignment if the code attribute + * includes LOOKUPSWITCH or TABLESWITCH. + * + * @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 + * next() (not before the instruction returned + * by tha last call to next()). + * Branch offsets and the exception table are also updated. + * + *

If the next instruction is at the beginning of a block statement, + * then the bytecode is excluded from that block. + * + *

An extra gap may be inserted at the end of the inserted + * bytecode sequence for adjusting alignment if the code attribute + * includes LOOKUPSWITCH or TABLESWITCH. + * + * @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 pos. + * Branch offsets and the exception table are also updated. + * + *

If the instruction at the given index is at the beginning + * of a block statement, + * then the bytecode is excluded from that block. + * + *

An extra gap may be inserted at the end of the inserted + * bytecode sequence for adjusting alignment if the code attribute + * includes LOOKUPSWITCH or TABLESWITCH. + * + * @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 + * next() (not before the instruction returned + * by tha last call to next()). + * 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. + * + *

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 pos. + * 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. + * + *

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 + * next() (not before the instruction returned + * by tha last call to next()). + * 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. + * + *

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 pos. + * 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. + * + *

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, if_icmp, if_acmp, 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; + + /** + * CONSTANT_Class + */ + public static final int CONST_Class = ClassInfo.tag; + + /** + * CONSTANT_Fieldref + */ + public static final int CONST_Fieldref = FieldrefInfo.tag; + + /** + * CONSTANT_Methodref + */ + public static final int CONST_Methodref = MethodrefInfo.tag; + + /** + * CONSTANT_InterfaceMethodref + */ + public static final int CONST_InterfaceMethodref + = InterfaceMethodrefInfo.tag; + + /** + * CONSTANT_String + */ + public static final int CONST_String = StringInfo.tag; + + /** + * CONSTANT_Integer + */ + public static final int CONST_Integer = IntegerInfo.tag; + + /** + * CONSTANT_Float + */ + public static final int CONST_Float = IntegerInfo.tag; + + /** + * CONSTANT_Long + */ + public static final int CONST_Long = LongInfo.tag; + + /** + * CONSTANT_Double + */ + public static final int CONST_Double = DoubleInfo.tag; + + /** + * CONSTANT_NameAndType + */ + public static final int CONST_NameAndType = NameAndTypeInfo.tag; + + /** + * CONSTANT_Utf8 + */ + 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 CONSTANT_Class_info 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 tag field of the constant pool table + * entry at the given index. + */ + public int getTag(int index) { + return getItem(index).getTag(); + } + + /** + * Reads CONSTANT_Class_info structure + * at the given index. + * + * @return a fully-qualified class or interface name specified + * by name_index. + */ + public String getClassInfo(int index) { + ClassInfo c = (ClassInfo)getItem(index); + if (c == null) + return null; + else + return Descriptor.toJavaName(getUtf8Info(c.name)); + } + + /** + * Reads the name_index field of the + * CONSTANT_NameAndType_info structure + * at the given index. + */ + public int getNameAndTypeName(int index) { + NameAndTypeInfo ntinfo = (NameAndTypeInfo)getItem(index); + return ntinfo.memberName; + } + + /** + * Reads the descriptor_index field of the + * CONSTANT_NameAndType_info structure + * at the given index. + */ + public int getNameAndTypeDescriptor(int index) { + NameAndTypeInfo ntinfo = (NameAndTypeInfo)getItem(index); + return ntinfo.typeDescriptor; + } + + /** + * Reads the class_index field of the + * CONSTANT_Fieldref_info structure + * at the given index. + */ + public int getFieldrefClass(int index) { + FieldrefInfo finfo = (FieldrefInfo)getItem(index); + return finfo.classIndex; + } + + /** + * Reads the class_index field of the + * CONSTANT_Fieldref_info structure + * at the given index. + * + * @return the name of the class at that class_index. + */ + public String getFieldrefClassName(int index) { + FieldrefInfo f = (FieldrefInfo)getItem(index); + if (f == null) + return null; + else + return getClassInfo(f.classIndex); + } + + /** + * Reads the name_and_type_index field of the + * CONSTANT_Fieldref_info structure + * at the given index. + */ + public int getFieldrefNameAndType(int index) { + FieldrefInfo finfo = (FieldrefInfo)getItem(index); + return finfo.nameAndTypeIndex; + } + + /** + * Reads the name_index field of the + * CONSTANT_NameAndType_info structure + * indirectly specified by the given index. + * + * @param index an index to a CONSTANT_Fieldref_info. + * @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 descriptor_index field of the + * CONSTANT_NameAndType_info structure + * indirectly specified by the given index. + * + * @param index an index to a CONSTANT_Fieldref_info. + * @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 class_index field of the + * CONSTANT_Methodref_info structure + * at the given index. + */ + public int getMethodrefClass(int index) { + MethodrefInfo minfo = (MethodrefInfo)getItem(index); + return minfo.classIndex; + } + + /** + * Reads the class_index field of the + * CONSTANT_Methodref_info structure + * at the given index. + * + * @return the name of the class at that class_index. + */ + public String getMethodrefClassName(int index) { + MethodrefInfo minfo = (MethodrefInfo)getItem(index); + if (minfo == null) + return null; + else + return getClassInfo(minfo.classIndex); + } + + /** + * Reads the name_and_type_index field of the + * CONSTANT_Methodref_info structure + * at the given index. + */ + public int getMethodrefNameAndType(int index) { + MethodrefInfo minfo = (MethodrefInfo)getItem(index); + return minfo.nameAndTypeIndex; + } + + /** + * Reads the name_index field of the + * CONSTANT_NameAndType_info structure + * indirectly specified by the given index. + * + * @param index an index to a CONSTANT_Methodref_info. + * @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 descriptor_index field of the + * CONSTANT_NameAndType_info structure + * indirectly specified by the given index. + * + * @param index an index to a CONSTANT_Methodref_info. + * @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 class_index field of the + * CONSTANT_InterfaceMethodref_info structure + * at the given index. + */ + public int getInterfaceMethodrefClass(int index) { + InterfaceMethodrefInfo minfo + = (InterfaceMethodrefInfo)getItem(index); + return minfo.classIndex; + } + + /** + * Reads the class_index field of the + * CONSTANT_InterfaceMethodref_info structure + * at the given index. + * + * @return the name of the class at that class_index. + */ + public String getInterfaceMethodrefClassName(int index) { + InterfaceMethodrefInfo minfo + = (InterfaceMethodrefInfo)getItem(index); + return getClassInfo(minfo.classIndex); + } + + /** + * Reads the name_and_type_index field of the + * CONSTANT_InterfaceMethodref_info structure + * at the given index. + */ + public int getInterfaceMethodrefNameAndType(int index) { + InterfaceMethodrefInfo minfo + = (InterfaceMethodrefInfo)getItem(index); + return minfo.nameAndTypeIndex; + } + + /** + * Reads the name_index field of the + * CONSTANT_NameAndType_info structure + * indirectly specified by the given index. + * + * @param index an index to + * a CONSTANT_InterfaceMethodref_info. + * @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 descriptor_index field of the + * CONSTANT_NameAndType_info structure + * indirectly specified by the given index. + * + * @param index an index to + * a CONSTANT_InterfaceMethodref_info. + * @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 CONSTANT_Integer_info, _Float_info, + * _Long_info, _Double_info, or + * _String_info structure. + * These are used with the LDC instruction. + * + * @return a String 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 CONSTANT_Integer_info 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 CONSTANT_Float_info 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 CONSTANT_Long_info 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 CONSTANT_Double_info 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 CONSTANT_String_info structure + * at the given index. + * + * @return the string specified by string_index. + */ + public String getStringInfo(int index) { + StringInfo si = (StringInfo)getItem(index); + return getUtf8Info(si.string); + } + + /** + * Reads CONSTANT_utf8_info 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 CONSTANT_Methodref_info + * structure at the given index represents the constructor + * of the given class. + * + * @return the descriptor_index specifying + * the type descriptor of the that constructor. + * If it is not that constructor, + * isConstructor() returns 0. + */ + public int isConstructor(String classname, int index) { + return isMember(classname, MethodInfo.nameInit, index); + } + + /** + * Determines whether CONSTANT_Methodref_info, + * CONSTANT_Fieldref_info, or + * CONSTANT_InterfaceMethodref_info 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 descriptor_index specifying + * the type descriptor of that member. + * If it is not that member, + * isMember() 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 n-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 CONSTANT_Class_info structure. + * + *

This also adds a CONSTANT_Utf8_info 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;" instead of "". + // + // note: toJvmName(toJvmName(c)) is equal to toJvmName(c). + + return addClassInfo(Descriptor.toJvmName(c)); + } + } + + /** + * Adds a new CONSTANT_Class_info structure. + * + *

This also adds a CONSTANT_Utf8_info 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 CONSTANT_NameAndType_info structure. + * + *

This also adds CONSTANT_Utf8_info structures. + * + * @param name name_index + * @param type descriptor_index + * @return the index of the added entry. + */ + public int addNameAndTypeInfo(String name, String type) { + return addNameAndTypeInfo(addUtf8Info(name), addUtf8Info(type)); + } + + /** + * Adds a new CONSTANT_NameAndType_info structure. + * + * @param name name_index + * @param type descriptor_index + * @return the index of the added entry. + */ + public int addNameAndTypeInfo(int name, int type) { + return addItem(new NameAndTypeInfo(name, type)); + } + + /** + * Adds a new CONSTANT_Fieldref_info structure. + * + *

This also adds a new CONSTANT_NameAndType_info + * structure. + * + * @param classInfo class_index + * @param name name_index + * of CONSTANT_NameAndType_info. + * @param type descriptor_index + * of CONSTANT_NameAndType_info. + * @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 CONSTANT_Fieldref_info structure. + * + * @param classInfo class_index + * @param nameandtypeinfo name_and_type_index. + * @return the index of the added entry. + */ + public int addFieldrefInfo(int classInfo, int nameAndTypeInfo) { + return addItem(new FieldrefInfo(classInfo, nameAndTypeInfo)); + } + + /** + * Adds a new CONSTANT_Methodref_info structure. + * + *

This also adds a new CONSTANT_NameAndType_info + * structure. + * + * @param classInfo class_index + * @param name name_index + * of CONSTANT_NameAndType_info. + * @param type descriptor_index + * of CONSTANT_NameAndType_info. + * @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 CONSTANT_Methodref_info structure. + * + * @param classInfo class_index + * @param nameandtypeinfo name_and_type_index. + * @return the index of the added entry. + */ + public int addMethodrefInfo(int classInfo, int nameAndTypeInfo) { + return addItem(new MethodrefInfo(classInfo, nameAndTypeInfo)); + } + + /** + * Adds a new CONSTANT_InterfaceMethodref_info + * structure. + * + *

This also adds a new CONSTANT_NameAndType_info + * structure. + * + * @param classInfo class_index + * @param name name_index + * of CONSTANT_NameAndType_info. + * @param type descriptor_index + * of CONSTANT_NameAndType_info. + * @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 CONSTANT_InterfaceMethodref_info + * structure. + * + * @param classInfo class_index + * @param nameandtypeinfo name_and_type_index. + * @return the index of the added entry. + */ + public int addInterfaceMethodrefInfo(int classInfo, + int nameAndTypeInfo) { + return addItem(new InterfaceMethodrefInfo(classInfo, + nameAndTypeInfo)); + } + + /** + * Adds a new CONSTANT_String_info + * structure. + * + *

This also adds a new CONSTANT_Utf8_info + * structure. + * + * @return the index of the added entry. + */ + public int addStringInfo(String str) { + return addItem(new StringInfo(addUtf8Info(str))); + } + + /** + * Adds a new CONSTANT_Integer_info + * structure. + * + * @return the index of the added entry. + */ + public int addIntegerInfo(int i) { + return addItem(new IntegerInfo(i)); + } + + /** + * Adds a new CONSTANT_Float_info + * structure. + * + * @return the index of the added entry. + */ + public int addFloatInfo(float f) { + return addItem(new FloatInfo(f)); + } + + /** + * Adds a new CONSTANT_Long_info + * 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 CONSTANT_Double_info + * 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 CONSTANT_Utf8_info + * structure. + * + *

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(, 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; + +/** + * ConstantValue_attribute. + */ +public class ConstantAttribute extends AttributeInfo { + /** + * The name of this attribute "ConstantValue". + */ + 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 constantvalue_index + * of ConstantValue_attribute. + */ + 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 constantvalue_index. + */ + public int getConstantValue() { + return ByteArray.readU16bit(get(), 0); + } + + /** + * Makes a copy. Class names are replaced according to the + * given Map 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. + * + *

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. + * + *

Note that toJvmName(toJvmName(s)) is equivalent + * to toJvmName(s). + */ + 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 map. + * + * @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 int, + * then this method returns "(II)". + * + * @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. + * + *

classname 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. + * + *

classname 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. + * + *

classname 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 CtClass objects representing the parameter + * types specified by the given descriptor. + * + * @param desc descriptor + * @param cp the class pool used for obtaining + * a CtClass 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 CtClass object representing the return + * type specified by the given descriptor. + * + * @param desc descriptor + * @param cp the class pool used for obtaining + * a CtClass 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 CtClass object representing the type + * specified by the given descriptor. + * + *

This method works even if the package-class separator is + * not / but . (period). For example, + * it accepts Ljava.lang.Object; + * as well as Ljava/lang/Object;. + * + * @param desc descriptor + * @param cp the class pool used for obtaining + * a CtClass 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. + * + *

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; + } +} + +/** + * exception_table[] of Code_attribute. + */ +public class ExceptionTable { + private ConstPool constPool; + private ArrayList entries; + + /** + * Constructs an exception_table[]. + * + * @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 exception_table_length, which is the number + * of entries in the exception_table[]. + */ + public int size() { + return entries.size(); + } + + /** + * Returns startPc of the n-th entry. + * + * @param nth the n-th (>= 0). + */ + public int startPc(int nth) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth); + return e.startPc; + } + + /** + * Sets startPc of the n-th entry. + * + * @param nth the n-th (>= 0). + * @param value new value. + */ + public void setStartPc(int nth, int value) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth); + e.startPc = value; + } + + /** + * Returns endPc of the n-th entry. + * + * @param nth the n-th (>= 0). + */ + public int endPc(int nth) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth); + return e.endPc; + } + + /** + * Sets endPc of the n-th entry. + * + * @param nth the n-th (>= 0). + * @param value new value. + */ + public void setEndPc(int nth, int value) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth); + e.endPc = value; + } + + /** + * Returns handlerPc of the n-th entry. + * + * @param nth the n-th (>= 0). + */ + public int handlerPc(int nth) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth); + return e.handlerPc; + } + + /** + * Sets handlerPc of the n-th entry. + * + * @param nth the n-th (>= 0). + * @param value new value. + */ + public void setHandlerPc(int nth, int value) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth); + e.handlerPc = value; + } + + /** + * Returns catchType of the n-th entry. + * + * @param nth the n-th (>= 0). + */ + public int catchType(int nth) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth); + return e.catchType; + } + + /** + * Sets catchType of the n-th entry. + * + * @param nth the n-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 startPc + * @param end endPc + * @param handler handlerPc + * @param type catchType + */ + 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 startPc + * @param end endPc + * @param handler handlerPc + * @param type catchType + */ + 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 exception_table[]. + * Class names are replaced according to the + * given Map 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; + +/** + * Exceptions_attribute. + */ +public class ExceptionsAttribute extends AttributeInfo { + /** + * The name of this attribute "Exceptions". + */ + 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 Map 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 exception_index_table[]. + */ + 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 exception_index_table[]. + */ + 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 number_of_exceptions. + */ + public int length() { return info.length / 2 - 1; } + + /** + * Returns the value of exception_index_table[nth]. + */ + 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; + +/** + * field_info 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 field_info 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 field_info. + */ + 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 AttributeInfo 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; + +/** + * InnerClasses_attribute. + */ +public class InnerClassesAttribute extends AttributeInfo { + /** + * The name of this attribute "InnerClasses". + */ + 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 number_of_classes. + */ + public int length() { return ByteArray.readU16bit(get(), 0); } + + /** + * Returns classes[nth].inner_class_info_index. + */ + public int innerClass(int nth) { + return ByteArray.readU16bit(get(), nth * 8 + 2); + } + + /** + * Returns classes[nth].outer_class_info_index. + */ + public int outerClass(int nth) { + return ByteArray.readU16bit(get(), nth * 8 + 4); + } + + /** + * Returns classes[nth].inner_name_index. + */ + public int innerName(int nth) { + return ByteArray.readU16bit(get(), nth * 8 + 6); + } + + /** + * Returns classes[nth].inner_class_access_flags. + */ + public int accessFlags(int nth) { + return ByteArray.readU16bit(get(), nth * 8 + 8); + } + + /** + * Makes a copy. Class names are replaced according to the + * given Map 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; + +/** + * LineNumberTablec_attribute. + */ +public class LineNumberAttribute extends AttributeInfo { + /** + * The name of this attribute "LineNumberTable". + */ + 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 line_number_table_length. + * This represents the number of entries in the table. + */ + public int tableLength() { + return ByteArray.readU16bit(info, 0); + } + + /** + * Returns line_number_table[i].start_pc. + * 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 line_number_table[i].line_number. + * 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; + +/** + * method_info 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: <init>. + */ + public static final String nameInit = ""; + + /** + * The name of class initializer (static initializer): + * <clinit>. + */ + public static final String nameClinit = ""; + + private MethodInfo(ConstPool cp) { + constPool = cp; + attribute = null; + } + + /** + * Constructs a method_info 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 method_info structure. + * Class names appearing in the source method_info + * are renamed according to classnameMap. + * + *

Note: only Code and Exceptions + * 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 method_info + * @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 AttributeInfo 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 AttributeInfo 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. + * + *

The added attribute must share the same constant pool table + * as this method_info 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. + * + *

The added attribute must share the same constant pool table + * as this method_info 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. + * + *

This method modifies a call to super(), + * 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. + * + *

This method should be called when the super class + * of the class declaring this method is changed. + * + *

This method does not perform anything unless this + * MethodInfo 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. + * + *

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). + * + *

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. + * + *

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; + +/** + * SourceFile_attribute. + */ +public class SourceFileAttribute extends AttributeInfo { + /** + * The name of this attribute "SourceFile". + */ + 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 sourcefile_index. + */ + public String getFileName() { + return getConstPool().getUtf8Info(ByteArray.readU16bit(get(), 0)); + } + + /** + * Makes a copy. Class names are replaced according to the + * given Map 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; + +/** + * Synthetic_attribute. + */ +public class SyntheticAttribute extends AttributeInfo { + /** + * The name of this attribute "Synthetic". + */ + 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 Bytecode object + * specified by b. + * + * @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. + * + *

In a method or constructor body, $0, $1, ... and $_ + * are not available. + * + * @return a CtMethod, CtConstructor, + * or CtField 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. + * + *

This must be called before calling compileStmnt() and + * compileExpr(). The correct value of + * isStatic 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). + * + *

This must be called before calling compileStmnt() and + * compileExpr(). The correct value of + * isStatic 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. + * + *

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). + * recordParams() must be called before invoking + * this method. + * + *

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. recordParams() must be + * called before invoking this method. + * + *

Local variables are not accessible + * within the compiled source text. Fields and method parameters + * ($0, $1, ..) are available if recordParams() + * 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. recordParams() must be + * called before invoking this method. + * + *

Local variables are not accessible + * within the compiled source text. Fields and method parameters + * ($0, $1, ..) are available if recordParams() + * 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 + 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, "", + "(" + 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 '(' ')' + * : ('.' )* + */ + 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 ;" 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 $_. + * + *

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_ + code.addAnewarray(javaLangObject); // anewarray Object + for (int i = 0; i < n; ++i) { + code.addOpcode(Bytecode.DUP); // dup + code.addIconst(i); // iconst_ + if (params[i].isPrimitive()) { + CtPrimitiveType pt = (CtPrimitiveType)params[i]; + String wrapper = pt.getWrapperName(); + code.addNew(wrapper); // new + code.addOpcode(Bytecode.DUP); // dup + int s = code.addLoad(regno, pt); // ?load + regno += s; + args[0] = pt; + code.addInvokespecial(wrapper, "", + Descriptor.ofMethod(CtClass.voidType, args)); + // invokespecial + } + else { + code.addAload(regno); // aload + ++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), "", "()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; // + 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 ? "" : 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 newObj for oldObj 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 getLeft() + * and getRight() 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 + * atXXX() on the given visitor, where + * XXX 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 + * toString(). + */ + 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 = ""; + + 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("( "); + sbuf.append(left == null ? "" : left.toString()); + sbuf.append(" . "); + sbuf.append(right == 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 CtClass 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. + * + *

$0 is available but the value is null. + * + * @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"); + } + } + + /* $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. + * + *

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. + * + *

If instrument() is called in + * CtMethod, the method body is scanned from the beginning + * to the end. + * Whenever an expression, such as a method call and a new + * expression (object creation), + * is found, edit() is called in ExprEdit. + * edit() can inspect and modify the given expression. + * The modification is reflected on the original method body. If + * edit() does nothing, the original method body is not + * changed. + * + *

The following code is an example: + * + *

    + * 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());
    + *     }
    + * });
    + * 
+ * + *

This code inspects all method calls appearing in the method represented + * by cm and it prints the names and the line numbers of the + * methods declared in class Point. This code does not modify + * the body of the method represented by cm. If the method + * body must be modified, call replace() + * in MethodCall. + * + * @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 new expression (overridable). + * The default implementation performs nothing. + * + * @param e the new 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. + * + *

$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"); + } + } + + /* $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() + * 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 CtClass 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. + * + *

$0 is available but the value is null. + * + * @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. + * + *

$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 (new 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 new + * expression represented by this object. + */ + public CtBehavior where() { return super.where(); } + + /** + * Returns the line number of the source line containing the + * new expression. + * + * @return -1 if this information is not available. + */ + public int getLineNumber() { + return super.getLineNumber(); + } + + /** + * Returns the source file containing the new 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 new expression with the bytecode derived from + * the given source text. + * + *

$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. + * + *

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: + * + *

    +     * public CtClass[] assist(ClassPool cp, String importname, String[] args) {
    +     *     return new CtClass[] { cp.get(importname) };
    +     * }
    +     * 
+ * + * @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. + * + *
    + * import class-name by assistant-name [(arg1, arg2, ...)]
    + * 
+ * + *

To process this annotation, run this class as follows: + * + *

    + * java javassist.preproc.Compiler sample.j
    + * 
+ * + *

This command produces sample.java, which only includes + * regular import declarations. Also, the Javassist program + * specified by assistant-name is executed so that it produces + * class files under the ./tmpjvst directory. The class + * specified by assistant-name must implement + * javassist.preproc.Assistant. + * + * @see javassist.preproc.Assistant + */ + +public class Compiler { + protected BufferedReader input; + protected BufferedWriter output; + protected ClassPool classPool; + + /** + * Constructs a Compiler 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: + * + *

    +     * import class name by assistant (args1, args2, ...);
    +     * 
+ * + * @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 ClassMetaobject.newInstance() 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. + * + *

A ClassMetaobject 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 + * methodPrefix "_m_". + */ + static final String methodPrefix = "_m_"; + static final int methodPrefixLen = 3; + + private Class javaClass; + private Constructor[] constructors; + private Method[] methods; + + /** + * Specifies how a java.lang.Class object is loaded. + * + *

If true, it is loaded by: + *

    Thread.currentThread().getContextClassLoader().loadClass()
+ *

If false, it is loaded by Class.forName(). + * The default value is false. + */ + public static boolean useContextClassLoader = false; + + /** + * Constructs a ClassMetaobject. + * + * @param params params[0] 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 java.lang.Class 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 obj 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 static fields of the base-level + * class are read and the runtime system intercepts it. + * This method simply returns the value of the field. + * + *

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 static fields of the base-level + * class are modified and the runtime system intercepts it. + * This method simply sets the field to the given value. + * + *

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 + * methodPrefix "_m_" 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 static 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. + * + *

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 identifier. + */ + 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 Class objects representing the + * formal parameter types of the method specified + * by identifier. + */ + public final Class[] getParameterTypes(int identifier) { + return getReflectiveMethods()[identifier].getParameterTypes(); + } + + /** + * Returns a Class objects representing the + * return type of the method specified by identifier. + */ + 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. + * + *

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 javassist.reflect.Loader + * or any other user-defined class loader. + * + *

The modified class files are given as the command-line parameters, + * which are a sequence of fully-qualified class names followed by options: + * + *

-m classname : specifies the class of the + * metaobjects associated with instances of the class followed by + * this option. The default is javassit.reflect.Metaobject. + * + *

-c classname : specifies the class of the + * class metaobjects associated with instances of the class followed by + * this option. The default is javassit.reflect.ClassMetaobject. + * + *

If a class name is not followed by any options, the class indicated + * by that class name is not reflective. + * + *

For example, + *

    % java Compiler Dog -m MetaDog -c CMetaDog Cat -m MetaCat Cow
    + * 
+ * + *

modifies class files Dog.class, Cat.class, + * and Cow.class. + * 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. + * + *

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(" ( [-m ] [-c ])+"); + } +} 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. + * + *

To run a program, say MyApp, + * including a reflective class, + * you must write a start-up program as follows: + * + *

    + * 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);
    + *   }
    + * }
    + * 
+ * + *

Then run this program as follows: + * + *

    % java javassist.reflect.Loader Main arg1, ...
+ * + *

This command runs Main.main() with arg1, ... + * and Main.main() runs MyApp.main() with + * arg1, ... + * The Person class is modified + * to be a reflective class. Method calls on a Person + * object are intercepted by an instance of MyMetaobject. + * + *

Also, you can run MyApp in a slightly different way: + * + *

    + * 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);
    + *   }
    + * }
    + * 
+ * + *

This program is run as follows: + * + *

+ +


+ +
Previous page + +


+Java(TM) is a trademark of Sun Microsystems, Inc.
+Copyright (C) 2000-2002 by Shigeru Chiba, All rights reserved. + + diff --git a/tutorial/two.gif b/tutorial/two.gif new file mode 100644 index 00000000..ad6984c1 Binary files /dev/null and b/tutorial/two.gif differ