From 069bceaf72fd0d6ffad14ce4e3e00ca91a280bde Mon Sep 17 00:00:00 2001 From: patriot1burke Date: Tue, 22 Apr 2003 13:47:06 +0000 Subject: 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 --- sample/Test.java | 44 +++++++ sample/duplicate/Ball.java | 44 +++++++ sample/duplicate/DuplicatedObject.java | 38 ++++++ sample/duplicate/Main.java | 44 +++++++ sample/duplicate/Viewer.java | 78 ++++++++++++ sample/evolve/CannotCreateException.java | 14 +++ sample/evolve/CannotUpdateException.java | 14 +++ sample/evolve/DemoLoader.java | 40 ++++++ sample/evolve/DemoServer.java | 102 ++++++++++++++++ sample/evolve/Evolution.java | 203 +++++++++++++++++++++++++++++++ sample/evolve/Sample.java | 12 ++ sample/evolve/VersionManager.java | 90 ++++++++++++++ sample/evolve/WebPage.class.0 | Bin 0 -> 686 bytes sample/evolve/WebPage.class.1 | Bin 0 -> 812 bytes sample/evolve/WebPage.java | 31 +++++ sample/evolve/demo.html | 38 ++++++ sample/evolve/start.html | 23 ++++ sample/evolve/update.html | 3 + sample/reflect/Main.java | 33 +++++ sample/reflect/Person.java | 51 ++++++++ sample/reflect/VerboseMetaobj.java | 29 +++++ sample/rmi/AlertDialog.java | 30 +++++ sample/rmi/CountApplet.java | 83 +++++++++++++ sample/rmi/Counter.java | 32 +++++ sample/rmi/inside.gif | Bin 0 -> 5822 bytes sample/rmi/start.html | 15 +++ sample/rmi/webdemo.html | 203 +++++++++++++++++++++++++++++++ sample/vector/Sample.java | 14 +++ sample/vector/Sample2.java | 13 ++ sample/vector/Test.j | 38 ++++++ sample/vector/VectorAssistant.java | 135 ++++++++++++++++++++ 31 files changed, 1494 insertions(+) create mode 100644 sample/Test.java create mode 100644 sample/duplicate/Ball.java create mode 100644 sample/duplicate/DuplicatedObject.java create mode 100644 sample/duplicate/Main.java create mode 100644 sample/duplicate/Viewer.java create mode 100644 sample/evolve/CannotCreateException.java create mode 100644 sample/evolve/CannotUpdateException.java create mode 100644 sample/evolve/DemoLoader.java create mode 100644 sample/evolve/DemoServer.java create mode 100644 sample/evolve/Evolution.java create mode 100644 sample/evolve/Sample.java create mode 100644 sample/evolve/VersionManager.java create mode 100644 sample/evolve/WebPage.class.0 create mode 100644 sample/evolve/WebPage.class.1 create mode 100644 sample/evolve/WebPage.java create mode 100644 sample/evolve/demo.html create mode 100644 sample/evolve/start.html create mode 100644 sample/evolve/update.html create mode 100644 sample/reflect/Main.java create mode 100644 sample/reflect/Person.java create mode 100644 sample/reflect/VerboseMetaobj.java create mode 100644 sample/rmi/AlertDialog.java create mode 100644 sample/rmi/CountApplet.java create mode 100644 sample/rmi/Counter.java create mode 100644 sample/rmi/inside.gif create mode 100644 sample/rmi/start.html create mode 100644 sample/rmi/webdemo.html create mode 100644 sample/vector/Sample.java create mode 100644 sample/vector/Sample2.java create mode 100644 sample/vector/Test.j create mode 100644 sample/vector/VectorAssistant.java (limited to 'sample') 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"; + } +} -- cgit v1.2.3