KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectweb > fractal > julia > asm > MixinClassGenerator


1 /***
2  * Julia: France Telecom's implementation of the Fractal API
3  * Copyright (C) 2001-2002 France Telecom R&D
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18  *
19  * Contact: Eric.Bruneton@rd.francetelecom.com
20  *
21  * Author: Eric Bruneton
22  */

23
24 package org.objectweb.fractal.julia.asm;
25
26 import org.objectweb.fractal.julia.loader.Generated;
27 import org.objectweb.fractal.julia.loader.Loader;
28 import org.objectweb.fractal.julia.loader.Tree;
29
30 import org.objectweb.asm.ClassAdapter;
31 import org.objectweb.asm.ClassReader;
32 import org.objectweb.asm.ClassVisitor;
33 import org.objectweb.asm.ClassWriter;
34 import org.objectweb.asm.CodeAdapter;
35 import org.objectweb.asm.CodeVisitor;
36 import org.objectweb.asm.Constants;
37 import org.objectweb.asm.Label;
38 import org.objectweb.asm.Type;
39 import org.objectweb.asm.Attribute;
40
41 import java.io.IOException JavaDoc;
42 import java.lang.reflect.Field JavaDoc;
43 import java.lang.reflect.Method JavaDoc;
44 import java.util.ArrayList JavaDoc;
45 import java.util.HashMap JavaDoc;
46 import java.util.HashSet JavaDoc;
47 import java.util.List JavaDoc;
48 import java.util.Map JavaDoc;
49 import java.util.Set JavaDoc;
50
51 /**
52  * A class generator to mix several mixin classes into a single class. A mixin
53  * class is a class of the following form:
54  * <pre>
55  * public abstract class XMixin implements I {
56  *
57  * // private constructor
58  * private XMixin () {}
59  *
60  * // fields and methods added or overriden by the mixin class
61  * public int i;
62  * public void m (String s) {
63  * _this_n();
64  * _super_m(_this_header + s + i);
65  * }
66  * // ...
67  *
68  * // fields and methods required by the mixin class in other mixins
69  * String _this_header;
70  * abstract void _this_n ();
71  * // original methods, overriden in this mixin class
72  * abstract void _super_m (String s);
73  * // ...
74  * }
75  * </pre>
76  * The mix of several mixin classes M1 ... Mn, in this order, is a class that is
77  * equivalent to a class Mn subclassing Mn-1 ... sub classing M1. For example,
78  * if YMixin is a mixin of the following form:
79  * <pre>
80  * public abstract class YMixin {
81  *
82  * // private constructor
83  * private YMixin () {}
84  *
85  * // fields and methods added or overriden by the mixin class
86  * public String header;
87  * public void n () { ... }
88  * public void m (String s) {
89  * return header + s;
90  * }
91  * }
92  * </pre>
93  * then the mix of YMixin and XMixin, in this order, is the following class:
94  * <pre>
95  * public class <i>XYZ</i> implements I, Generated {
96  * // from XMixin:
97  * public int i;
98  * public void m (String s) {
99  * n();
100  * return m$$0(header + s + i);
101  * }
102  * // from YMixin:
103  * public String header;
104  * public void n () { ... }
105  * private void m$$0 (String s) {
106  * return header + s;
107  * }
108  * // other methods
109  * public String getFcGeneratorParameters () {
110  * return "(...MixinClassGenerator <i>source</i> YMixin XMixin)";
111  * }
112  * }
113  * </pre>
114  * As can be seen, the code of the mixin classes is copied into the mixed class,
115  * with some small transformations: fields and methods that are static or whose
116  * name begins with <tt>_this_</tt> or <tt>_super_</tt> are not copied, and
117  * overriden methods are renamed with a "$$<i>n</i>" suffix to avoid name
118  * clashes.
119  * <p>
120  * Several mixin classes can be mixed, in a specific order, only if the fields
121  * and methods required by each mixin class are provided by previous mixins
122  * classes in the mixin class list. For example, the XMixin can only be used
123  * if one or more previous mixin classes, in the mixed class list, provide a
124  * <tt>header</tt> field, a <tt>void n ()</tt> method, and a <tt>void m (String
125  * s)</tt> method.
126  * <p>
127  * <b>Notes</b>:
128  * <ul>
129  * <li>since static fields and methods are not copied, and since the generated
130  * class will generally not be in the same package than the original classes,
131  * all the static fields and methods used by the non static methods must be
132  * public (but those that are used only by static methods may be private).</li>
133  * <li>the constructors are not mixed (this limitation will perhaps be removed
134  * in future versions).</li>
135  * </ul>
136  */

137
138 public class MixinClassGenerator implements ClassGenerator, Constants {
139
140   /**
141    * Internal name of the mixed class generated by this generator.
142    */

143
144   String JavaDoc mixedClassName;
145
146   /**
147    * The mixin classes to be mixed. This list is a list of Class objects.
148    */

149
150   List JavaDoc mixins;
151
152   /**
153    * Index in {@link #mixins mixins} of the current mixin class that is being
154    * added to the mixed class.
155    */

156
157   int currentMixin;
158
159   /**
160    * Internal name of the current mixin class that is being added to the mixed
161    * class.
162    */

163
164   String JavaDoc currentMixinClass;
165
166   /**
167    * A map associating a counter to each method name and descriptor. These
168    * Integer counters are used to rename overriden methods with "$$x" suffixes.
169    * Each counter corresponds to the number of time the corresponding method
170    * is overriden by the following mixin classes, minus one.
171    */

172
173   Map JavaDoc counters;
174
175   /**
176    * The prefix used in mixin classes to denote required fields and methods.
177    */

178
179   final static String JavaDoc THIS = "_this_";
180
181   /**
182    * The prefix used in mixin classes to denote overriden methods.
183    */

184
185   final static String JavaDoc SUPER = "_super_";
186
187   /**
188    * Generates a class by adding mixin classes to the given class.
189    *
190    * @param name the name of the class to be generated.
191    * @param args the descriptor of the class to be generated. This
192    * descriptor <i>must</i> be of the form (object descriptor source
193    * mixinClass1 ... mixinClassN), where source is a symbolic name to
194    * designate the mixed class - this name appears in stack traces, and
195    * where mixinClass1 ... mixinClassN are the class descriptors of the
196    * classes to be mixed.
197    * @param loader the loader to be used to load or generate auxiliary classes,
198    * if needed.
199    * @param classLoader the class loader to be used to load auxiliary classes,
200    * if needed.
201    * @return a sub class of the given class constructed by adding the applicable
202    * mixins classes to it.
203    * @throws ClassGenerationException if any other problem occurs.
204    */

205
206   public byte[] generateClass (
207     final String JavaDoc name,
208     final Tree args,
209     final Loader loader,
210     final ClassLoader JavaDoc classLoader) throws ClassGenerationException
211   {
212     // checks the mixin classes
213
Set JavaDoc providedMethods = new HashSet JavaDoc();
214     Set JavaDoc providedFields = new HashSet JavaDoc();
215     Set JavaDoc interfaces = new HashSet JavaDoc();
216     mixins = new ArrayList JavaDoc();
217
218     String JavaDoc source = args.getSubTree(1).toString();
219
220     for (int i = 2; i < args.getSize(); ++i) {
221       Class JavaDoc mixinClass;
222       try {
223         mixinClass = loader.loadClass(args.getSubTree(i), classLoader);
224       } catch (ClassNotFoundException JavaDoc e) {
225         throw new ClassGenerationException(
226           e,
227           args.toString(),
228           "Cannot load the '" + args.getSubTree(i) + "' mixin class");
229       }
230
231       // checks that each method of the mixin class whose name
232
// begins with the SUPER prefix is provided by a previous mixin class
233
Method JavaDoc[] meths = mixinClass.getDeclaredMethods();
234       for (int j = 0; j < meths.length; ++j) {
235         String JavaDoc m = meths[j].getName() + Type.getMethodDescriptor(meths[j]);
236         if (m.startsWith(THIS)) {
237           if (!providedMethods.contains(m)) {
238             throw new ClassGenerationException(
239               null,
240               args.toString(),
241               "The method '" + m + "' required by the '" +
242               mixinClass.getName() + "' mixin is missing");
243           }
244         } else if (m.startsWith(SUPER)) {
245           m = m.substring(SUPER.length());
246           // checks that m is effectively overriden by this mixin
247
boolean overriden = false;
248           for (int k = 0; k < meths.length; ++k) {
249             String JavaDoc n = meths[k].getName() + Type.getMethodDescriptor(meths[k]);
250             if (n.equals(m)) {
251               overriden = true;
252               break;
253             }
254           }
255           if (!overriden) {
256             throw new ClassGenerationException(
257               null,
258               args.toString(),
259               "Illegal method '" + meths[j] + "' in '" + mixinClass.getName() +
260               "': this method is not overriden by this mixin class");
261           }
262           if (!providedMethods.contains(THIS + m)) {
263             throw new ClassGenerationException(
264               null,
265               args.toString(),
266               "The method '" + meths[j] + "' overriden in '" +
267               mixinClass.getName() +
268               "' is not provided by the preceding mixins");
269           }
270         }
271       }
272       for (int j = 0; j < meths.length; ++j) {
273         String JavaDoc m = meths[j].getName() + Type.getMethodDescriptor(meths[j]);
274         if (!m.startsWith(THIS) && !m.startsWith(SUPER)) {
275           providedMethods.add(THIS + m);
276         }
277       }
278
279       // checks that each field of the mixin class whose name
280
// begins with the SUPER prefix is provided by a previous mixin class
281
Field JavaDoc[] fields = mixinClass.getDeclaredFields();
282       for (int j = 0; j < fields.length; ++j) {
283         String JavaDoc f = fields[j].getName();
284         if (f.startsWith(THIS)) {
285           if (!providedFields.contains(f)) {
286             throw new ClassGenerationException(
287               null,
288               args.toString(),
289               "The field '" + f + "' required by the '" + mixinClass.getName() +
290               "' mixin is missing");
291           }
292         } else if (f.startsWith(SUPER)) {
293           throw new ClassGenerationException(
294             null,
295             args.toString(),
296             "Illegal field '" + f + "' in '" + mixinClass.getName() +
297             "': " + SUPER + " must not be used for fields");
298         } else {
299           if (providedFields.contains(THIS + f)) {
300             throw new ClassGenerationException(
301               null,
302               args.toString(),
303               "The field '" + f + "' provided by the '" + mixinClass.getName() +
304               "': mixin is already provided by a preceding mixin");
305           }
306           providedFields.add(THIS + f);
307         }
308       }
309
310       mixins.add(mixinClass);
311       Class JavaDoc[] itfs = mixinClass.getInterfaces();
312       for (int j = 0; j < itfs.length; ++j) {
313         interfaces.add(Type.getInternalName(itfs[j]));
314       }
315     }
316
317     // creates the mixed class
318
mixedClassName = name.replace('.', '/');
319     ClassWriter cw = new ClassWriter(false);
320     interfaces.add(Type.getInternalName(Generated.class));
321     cw.visit(
322       V1_1,
323       ACC_PUBLIC,
324       mixedClassName,
325       "java/lang/Object",
326       (String JavaDoc[])interfaces.toArray(new String JavaDoc[interfaces.size()]),
327       "MIXIN["+source+']');
328
329     // generates the default constructor
330
CodeVisitor mw = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
331     mw.visitVarInsn(ALOAD, 0);
332     mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
333     mw.visitInsn(RETURN);
334     mw.visitMaxs(1, 1);
335
336     // generates the 'getFcGeneratorParameters' method
337
String JavaDoc mName = "getFcGeneratorParameters";
338     String JavaDoc mDesc = "()Ljava/lang/String;";
339     CodeVisitor mv = cw.visitMethod(ACC_PUBLIC, mName, mDesc, null, null);
340     mv.visitLdcInsn(args.toString());
341     mv.visitInsn(ARETURN);
342     mv.visitMaxs(1, 1);
343
344     // adds the mixin classes, in reverse order
345
counters = new HashMap JavaDoc();
346     for (currentMixin = mixins.size() - 1; currentMixin >= 0; --currentMixin) {
347       Class JavaDoc c = (Class JavaDoc)mixins.get(currentMixin);
348       currentMixinClass = Type.getInternalName(c);
349       MixinClassAdapter mca = new MixinClassAdapter(cw);
350       try {
351         getClassReader(c).accept(mca, false);
352       } catch (IOException JavaDoc e) {
353         throw new ClassGenerationException(
354           e, args.toString(), "Cannot read the mixin class " + c.getName());
355       }
356       updateCounters(c);
357     }
358
359     // returns the mixed class
360
return cw.toByteArray();
361   }
362
363   /**
364    * Returns a {@link ClassReader} to analyze the given class.
365    *
366    * @param c a class.
367    * @return a {@link ClassReader} to analyze the given class.
368    * @throws IOException if the bytecode of the class cannot be found.
369    */

370
371   ClassReader getClassReader (final Class JavaDoc c) throws IOException JavaDoc {
372     try {
373       return new ClassReader(c.getName());
374     } catch (IOException JavaDoc e) {
375       // an exception can occur if c is a dynamically generated class;
376
// indeed, in this case, it can not be found by the system class loader.
377
String JavaDoc s = Type.getInternalName(c) + ".class";
378       return new ClassReader(c.getClassLoader().getResourceAsStream(s));
379     }
380   }
381
382   /**
383    * Returns the counter associated to the given method.
384    *
385    * @param name the method's name.
386    * @param desc the method's descriptor.
387    * @return the counter associated to the given method.
388    */

389
390   int getCounter (final String JavaDoc name, final String JavaDoc desc) {
391     Integer JavaDoc value = (Integer JavaDoc)counters.get(name + desc);
392     return value == null ? -1 : value.intValue();
393   }
394
395   /**
396    * Updates the {@link #counters counters} map with the given class. This
397    * method increments the counter associated to each non static method of the
398    * given class.
399    *
400    * @param c a class.
401    */

402
403   void updateCounters (final Class JavaDoc c) {
404     Method JavaDoc[] meths = c.getDeclaredMethods();
405     for (int i = 0; i < meths.length; ++i) {
406       Method JavaDoc meth = meths[i];
407       String JavaDoc key = meth.getName();
408       if (key.startsWith(SUPER) ||
409           key.startsWith(THIS) ||
410           (meth.getModifiers() & (ACC_STATIC | ACC_ABSTRACT)) == 0)
411       {
412         key += Type.getMethodDescriptor(meth);
413         Integer JavaDoc value = (Integer JavaDoc)counters.get(key);
414         int count = value == null ? 0 : value.intValue() + 1;
415         counters.put(key, new Integer JavaDoc(count));
416       }
417     }
418   }
419
420   /**
421    * Tests if the given method is provided by the given class.
422    *
423    * @param m the name and descriptor of a method. The name of this method is
424    * supposed to start with the {@link #SUPER SUPER} prefix.
425    * @param c a class.
426    * @return <tt>true</tt> if <tt>c</tt> provides a method with same name
427    * (excluding the {@link #SUPER SUPER} prefix), the same formal
428    * parameter types, and the same return type as <tt>m</tt>.
429    */

430
431   static boolean providesMethod (final String JavaDoc m, final Class JavaDoc c) {
432     Method JavaDoc[] meths = c.getMethods();
433     for (int i = 0; i < meths.length; ++i) {
434       if (equals(m, meths[i])) {
435         return true;
436       }
437     }
438     return false;
439   }
440
441   /**
442    * Tests if the given field is provided by the given class.
443    *
444    * @param f the name of a field.
445    * @param c a class.
446    * @return <tt>true</tt> if <tt>c</tt> provides a field with same name
447    * as <tt>f</tt>.
448    */

449
450   static boolean providesField (final String JavaDoc f, final Class JavaDoc c) {
451     Field JavaDoc[] fields = c.getFields();
452     for (int i = 0; i < fields.length; ++i) {
453       if (f.equals(fields[i].getName())) {
454         return true;
455       }
456     }
457     return false;
458   }
459
460   /**
461    * Tests if two methods have the same name and the same signature.
462    *
463    * @param m the name and descriptor of a method. The name of this method is
464    * supposed to start with the {@link #SUPER SUPER} prefix.
465    * @param n the method to be compared to <tt>m</tt>. The name of this method
466    * is supposed <tt>not</tt> to start with the {@link #SUPER SUPER}
467    * prefix.
468    * @return <tt>true</tt> if the two methods have the same name (excluding the
469    * {@link #SUPER SUPER} prefix), the same formal parameter types, and the
470    * same return type.
471    */

472
473   static boolean equals (final String JavaDoc m, final Method JavaDoc n) {
474     String JavaDoc ms = m.substring(SUPER.length());
475     String JavaDoc ns = n.getName() + Type.getMethodDescriptor(n);
476     return ms.equals(ns);
477   }
478
479   /**
480    * A class adapter to rename methods in a mixin class.
481    */

482
483   class MixinClassAdapter extends ClassAdapter implements Constants {
484
485     /**
486      * Constructs a new {@link MixinClassGenerator.MixinClassAdapter} object.
487      *
488      * @param cv the class vistor to which this adapter must delegate calls.
489      */

490
491     public MixinClassAdapter (final ClassVisitor cv) {
492       super(cv);
493     }
494
495     public void visit (
496       final int version,
497       final int access,
498       final String JavaDoc name,
499       final String JavaDoc superName,
500       final String JavaDoc[] interfaces,
501       final String JavaDoc sourceFile)
502     {
503       // does nothing
504
}
505
506     public void visitField (
507       final int access,
508       final String JavaDoc name,
509       final String JavaDoc desc,
510       final Object JavaDoc value,
511       final Attribute attrs)
512     {
513       if (!name.startsWith(THIS) && (access & ACC_STATIC) == 0) {
514         super.visitField(access, name, desc, value, attrs);
515       }
516     }
517
518     public CodeVisitor visitMethod (
519       final int access,
520       final String JavaDoc name,
521       final String JavaDoc desc,
522       final String JavaDoc[] exceptions,
523       final Attribute attrs)
524     {
525       if ((access & (ACC_NATIVE | ACC_STATIC | ACC_ABSTRACT)) != 0) {
526         return null;
527       }
528       if (name.equals("<init>") ||
529           name.startsWith(SUPER) ||
530           name.startsWith(THIS))
531       {
532         return null;
533       }
534       int count = getCounter(name, desc);
535       CodeVisitor mv;
536       if (count == -1) {
537         mv = cv.visitMethod(access, name, desc, exceptions, attrs);
538       } else {
539         int newAccess = access & ~(ACC_PUBLIC | ACC_PROTECTED) | ACC_PRIVATE;
540         String JavaDoc newName = name + "$$" + count;
541         mv = cv.visitMethod(newAccess, newName, desc, exceptions, attrs);
542       }
543       return new MixinCodeAdapter(mv);
544     }
545   }
546
547   /**
548    * A code adapter to update method calls in methods of mixin classes.
549    */

550
551   class MixinCodeAdapter extends CodeAdapter implements Constants {
552
553     /**
554      * Constructs a new {@link MixinClassGenerator.MixinCodeAdapter} object.
555      *
556      * @param cv the code vistor to which this adapter must delegate calls.
557      */

558
559     public MixinCodeAdapter (final CodeVisitor cv) {
560       super(cv);
561     }
562
563     public void visitFieldInsn (
564       final int opcode,
565       final String JavaDoc owner,
566       final String JavaDoc name,
567       final String JavaDoc desc)
568     {
569       if (name.startsWith(THIS)) {
570         String JavaDoc normalName = name.substring(THIS.length());
571         cv.visitFieldInsn(opcode, mixedClassName, normalName, desc);
572       } else if (
573         opcode != GETSTATIC &&
574         opcode != PUTSTATIC &&
575         owner.equals(currentMixinClass))
576       {
577         cv.visitFieldInsn(opcode, mixedClassName, name, desc);
578       } else {
579         cv.visitFieldInsn(opcode, owner, name, desc);
580       }
581     }
582
583     public void visitMethodInsn (
584       final int opcode,
585       final String JavaDoc owner,
586       final String JavaDoc name,
587       final String JavaDoc desc)
588     {
589       if (name.startsWith(THIS)) {
590         String JavaDoc normalName = name.substring(THIS.length());
591         cv.visitMethodInsn(opcode, mixedClassName, normalName, desc);
592       } else if (name.startsWith(SUPER)) {
593         String JavaDoc normalName = name.substring(SUPER.length());
594         String JavaDoc superName = normalName + "$$" + (getCounter(name, desc) + 1);
595         cv.visitMethodInsn(INVOKESPECIAL, mixedClassName, superName, desc);
596       } else if (opcode != INVOKESTATIC && owner.equals(currentMixinClass)) {
597         cv.visitMethodInsn(opcode, mixedClassName, name, desc);
598       } else {
599         cv.visitMethodInsn(opcode, owner, name, desc);
600       }
601     }
602
603     public void visitLineNumber (int i, Label label) {
604       cv.visitLineNumber((currentMixin + 1) * 1000 + i, label);
605     }
606   }
607 }
608
Popular Tags