HOW TO: Stereotyping a java Class
In this HowTo post I will show how we can stereotype a class with another class. Why is this useful?
- When there are a lot of BCI’ing happening in your project, it is not prudent to let every developer write the BCI code.
- For one, this does not abstract the BCI library used. Thus changing the library itself becomes difficult.
- BCI is complex. The probability of introducing bugs increase if every developer has to understand BCI.
- It would be better to write a framework that allows a developer to write an interface and a class which can be used for BCI’ing.
- Given that java does not support Multiple inheritance, stereotyping can be used to achieve Multiple inheritance without delegating. Check here for multiple inheritance options.
- There exists aspects of code such as profiling that need to be present only if the code is being tested. Production code is best without being splattered with debug code. Stereotyping can be used in this case by totally varying the classloader used to load the classes, one that adds the profile code and one that does not.
StereoTyping means…
Say there was an interface:
public interface PerfInterface { public void start(String nm); public void end(); public String getValue(String value); }
You write an implementation for this interface:
import java.util.Stack; public class PerfTemplate implements PerfInterface { private Stack _stats; public void start(String nm) { PerfStats stat = new PerfStats(); stat.start(nm); if (_stats == null) _stats = new Stack(); _stats.push(stat); } public void end() { try { PerfStats stat = (PerfStats)_stats.pop(); stat.end(); } catch (Exception e) { e.printStackTrace(); } } public String getValue(String val) { return "PerfTemplate:Modified:" + val; } }
You want all classes that need to implement the interface to use the above implementation without a developer actually coding it. At runtime you want the class to adapt this behavior. For eg., you want to code a class as:
public class ClassToStereoType { ..... }
Which does not implement the PerfInterface. But when running it for performance you want the class to be:
public class ClassToStereoType implements PerfInterface { ..... }
with all the functions of PerfInterface implemented. This is called stereotyping.
How to Stereotype?
Here we will use the asm library to stereotype. We will follow the same steps as in “BCI during runtime“. We will create a ClassNode object from the class from which we have to stereotype as below:
InputStream nstr = new FileInputStream("PerfTemplate.class"); ClassReader n = new ClassReader(nstr); ClassNode cn = new ClassNode(); n.accept(cn, ClassReader.EXPAND_FRAMES);
Here we read the class PerfTemplate.class and accept it into a ClassNode which now contains all the fields and methods from PerfTemplate class.
We will write a ClassVisitor that overrides the visitEnd to add the fields and methods from the ClassNode created.
public void visitEnd() { System.out.println("In visit End. Adding Fields"); for (Iterator it = _cn.fields.iterator(); it.hasNext();) { ((FieldNode) it.next()).accept(cv); } for(Iterator it = _cn.methods.iterator(); it.hasNext();) { MethodNode mn = (MethodNode) it.next(); if (!mn.name.equals("")) //ignore constructor { String[] exceptions = new String[mn.exceptions.size()]; mn.exceptions.toArray(exceptions); MethodVisitor mv = cv.visitMethod( mn.access, mn.name, mn.desc, mn.signature, exceptions); mn.instructions.resetLabels(); mn.accept(new RemappingMethodAdapter( mn.access, mn.desc, mv, new SimpleRemapper(_cn.name, _name))); } } super.visitEnd(); }
In the above code, we iterate through the fields and add it to the class that we are modifying (i.e., ClassToStereoType). When adding the methods we should be careful to ensure all references to PerfTemplate class is modified to ClassToStereoType. For this, we use the RemappingMethodAdapter which is a class provided by asm.
To add the interfaces from PerfTemplate to ClassToStereoType we override the visit method. Here we add the interfaces from the ClassNode to the current class.
public void visit (int version, int access, String name, String signature, String superName, String[] interfaces) { System.out.println("Class Name is: " + name + ":" + signature + ":" + superName); int len = 0; List ndeints = _cn.interfaces; if (interfaces != null) len = interfaces.length; String[] modinterfaces = new String[len + ndeints.size()]; int cnt = 0; for (cnt = 0; (interfaces != null) && ( cnt < interfaces.length); cnt++) { modinterfaces[cnt] = interfaces[cnt]; } for (String inter : ndeints) modinterfaces[cnt++] = inter; cv.visit(version, Opcodes.ACC_PUBLIC, name, signature, superName, modinterfaces); _name = name; }
In the above code, we append all the interfaces from the ClassNode got by calling _cn.interfaces to the interfaces of ClassToStereoType. We use this modified list of interfaces to visit the class. This ensures that the interfaces are implemented in the loaded class.
Now the ClassVisitor implemented with these changes can be used to modify the bytes in the class loader to stereotype the class.
The code for this HOWTO can be found here. Run the commands in compiletst.sh to try the sample out.