1package sample.evolve;
2
3import javassist.*;
4
5/**
6 * Evolution provides a set of methods for instrumenting bytecodes.
7 *
8 * For class evolution, updatable class A is renamed to B. Then an abstract
9 * class named A is produced as the super class of B. If the original class A
10 * has a public method m(), then the abstract class A has an abstract method
11 * m().
12 *
13 * abstract class A abstract m() _makeInstance() | class A --------> class B m()
14 * m()
15 *
16 * Also, all the other classes are translated so that "new A(i)" in the methods
17 * is replaced with "_makeInstance(i)". This makes it possible to change the
18 * behavior of the instantiation of the class A.
19 */
20public class Evolution implements Translator {
21    public final static String handlerMethod = "_makeInstance";
22
23    public final static String latestVersionField = VersionManager.latestVersionField;
24
25    public final static String versionManagerMethod = "initialVersion";
26
27    private static CtMethod trapMethod;
28
29    private static final int initialVersion = 0;
30
31    private ClassPool pool;
32
33    private String updatableClassName = null;
34
35    private CtClass updatableClass = null;
36
37    public void start(ClassPool _pool) throws NotFoundException {
38        pool = _pool;
39
40        // Get the definition of Sample.make() and store it into trapMethod
41        // for later use.
42        trapMethod = _pool.getMethod("sample.evolve.Sample", "make");
43    }
44
45    public void onLoad(ClassPool _pool, String classname)
46            throws NotFoundException, CannotCompileException {
47        onLoadUpdatable(classname);
48
49        /*
50         * Replaces all the occurrences of the new operator with a call to
51         * _makeInstance().
52         */
53        CtClass clazz = _pool.get(classname);
54        CtClass absClass = updatableClass;
55        CodeConverter converter = new CodeConverter();
56        converter.replaceNew(absClass, absClass, handlerMethod);
57        clazz.instrument(converter);
58    }
59
60    private void onLoadUpdatable(String classname) throws NotFoundException,
61            CannotCompileException {
62        // if the class is a concrete class,
63        // classname is <updatableClassName>$$<version>.
64
65        int i = classname.lastIndexOf("$$");
66        if (i <= 0)
67            return;
68
69        String orgname = classname.substring(0, i);
70        if (!orgname.equals(updatableClassName))
71            return;
72
73        int version;
74        try {
75            version = Integer.parseInt(classname.substring(i + 2));
76        }
77        catch (NumberFormatException e) {
78            throw new NotFoundException(classname, e);
79        }
80
81        CtClass clazz = pool.getAndRename(orgname, classname);
82        makeConcreteClass(clazz, updatableClass, version);
83    }
84
85    /*
86     * Register an updatable class.
87     */
88    public void makeUpdatable(String classname) throws NotFoundException,
89            CannotCompileException {
90        if (pool == null)
91            throw new RuntimeException(
92                    "Evolution has not been linked to ClassPool.");
93
94        CtClass c = pool.get(classname);
95        updatableClassName = classname;
96        updatableClass = makeAbstractClass(c);
97    }
98
99    /**
100     * Produces an abstract class.
101     */
102    protected CtClass makeAbstractClass(CtClass clazz)
103            throws CannotCompileException, NotFoundException {
104        int i;
105
106        CtClass absClass = pool.makeClass(clazz.getName());
107        absClass.setModifiers(Modifier.PUBLIC | Modifier.ABSTRACT);
108        absClass.setSuperclass(clazz.getSuperclass());
109        absClass.setInterfaces(clazz.getInterfaces());
110
111        // absClass.inheritAllConstructors();
112
113        CtField fld = new CtField(pool.get("java.lang.Class"),
114                latestVersionField, absClass);
115        fld.setModifiers(Modifier.PUBLIC | Modifier.STATIC);
116
117        CtField.Initializer finit = CtField.Initializer.byCall(pool
118                .get("sample.evolve.VersionManager"), versionManagerMethod,
119                new String[] { clazz.getName() });
120        absClass.addField(fld, finit);
121
122        CtField[] fs = clazz.getDeclaredFields();
123        for (i = 0; i < fs.length; ++i) {
124            CtField f = fs[i];
125            if (Modifier.isPublic(f.getModifiers()))
126                absClass.addField(new CtField(f.getType(), f.getName(),
127                        absClass));
128        }
129
130        CtConstructor[] cs = clazz.getDeclaredConstructors();
131        for (i = 0; i < cs.length; ++i) {
132            CtConstructor c = cs[i];
133            int mod = c.getModifiers();
134            if (Modifier.isPublic(mod)) {
135                CtMethod wm = CtNewMethod.wrapped(absClass, handlerMethod, c
136                        .getParameterTypes(), c.getExceptionTypes(),
137                        trapMethod, null, absClass);
138                wm.setModifiers(Modifier.PUBLIC | Modifier.STATIC);
139                absClass.addMethod(wm);
140            }
141        }
142
143        CtMethod[] ms = clazz.getDeclaredMethods();
144        for (i = 0; i < ms.length; ++i) {
145            CtMethod m = ms[i];
146            int mod = m.getModifiers();
147            if (Modifier.isPublic(mod))
148                if (Modifier.isStatic(mod))
149                    throw new CannotCompileException(
150                            "static methods are not supported.");
151                else {
152                    CtMethod m2 = CtNewMethod.abstractMethod(m.getReturnType(),
153                            m.getName(), m.getParameterTypes(), m
154                                    .getExceptionTypes(), absClass);
155                    absClass.addMethod(m2);
156                }
157        }
158
159        return absClass;
160    }
161
162    /**
163     * Modifies the given class file so that it is a subclass of the abstract
164     * class produced by makeAbstractClass().
165     *
166     * Note: the naming convention must be consistent with
167     * VersionManager.update().
168     */
169    protected void makeConcreteClass(CtClass clazz, CtClass abstractClass,
170            int version) throws CannotCompileException, NotFoundException {
171        int i;
172        clazz.setSuperclass(abstractClass);
173        CodeConverter converter = new CodeConverter();
174        CtField[] fs = clazz.getDeclaredFields();
175        for (i = 0; i < fs.length; ++i) {
176            CtField f = fs[i];
177            if (Modifier.isPublic(f.getModifiers()))
178                converter.redirectFieldAccess(f, abstractClass, f.getName());
179        }
180
181        CtConstructor[] cs = clazz.getDeclaredConstructors();
182        for (i = 0; i < cs.length; ++i)
183            cs[i].instrument(converter);
184
185        CtMethod[] ms = clazz.getDeclaredMethods();
186        for (i = 0; i < ms.length; ++i)
187            ms[i].instrument(converter);
188    }
189}
190