Adding a statement using Javassist

1.3k views Asked by At
System.out.println("A read operation on a field is encountered "); 

How can I add a statement, lets say , the above statement , whenever a read operation has been performed on a non-local field ? and also I need to know the details of the field which is read and the set of details should correspond to the uniqueness of the field

Example (to remove abstraction in the question):

public class Greet{
   int knowncount;
   public Greet()
   {
      System.out.println("Hello");
      knowncount++;
   }   
   public Greet(String language)
   {
     if(String.equals("ENGLISH"))  {System.out.println("Hello"); knowncount++; }
     else  if(String.equals("SPANISH")) {System.out.println("Hola"); knowncount++;}
     else  System.out.println("Language not recognized");
   }

   public void showCount() 
   {
     System.out.println("count : "+knowncount);
   }

}

and the user class test is:

class test{
  public static void main(String[] args){
    Greet g("SPANISH");
    g.showCount();

  }

}

in the above example after using javassist our code should output :

A read operation on a field is encountered
1
1

There are 1 answers

2
pabrantes On BEST ANSWER

You can do what you ask by using Javassist's ExprEditor. ExprEditor allows you to edit what is done in a FieldAccess, to implement your request you can create an injector that does the following:

ClassPool classPool = ClassPool.getDefault();
CtClass greetCtClass = classPool.get(Greet.class.getName());

greetCtClass.instrument(new ExprEditor() {
        @Override
        public void edit(FieldAccess fieldAccess)
                throws CannotCompileException {
            if (fieldAccess.getFieldName().equals("knowncount")) {
                fieldAccess
                        .replace(" { System.out.println(\"A read operation on a field is encountered \"); $_ = $proceed($$); } ");
            }
        }
    });

    greetCtClass
            .writeFile("<ROOT DIRECTORY WHERE THE CLASSES ARE>");

Best way to explain parameter is with an example, imagine that Greet class (that happens to be in the greatPackage) is in the following path /home/user/dev/proj1/build/greetPackage/Greet.class. In this scenario your root directory will be /home/user/dev/proj1/build/.

The line of interest in all the boiler plate above is the following:

{ System.out.println(\"A read operation on a field is encountered \"); $_ = $proceed($$); }

What is happening here?

  • First notice that all code is between curly brackets, if you know your way in javassist this is something trivial to you, if not it might make you loose a few minutes trying to understand what is wrong.
  • Then you have the System.out you requested
  • Finally you have a magic line: $_ = $proceed($$);. You can find more information about this in the javassist tutorial (search for FieldAccess in that section, since they don't have a direct anchor for it, sorry!) but basically what this line is saying is that the resulting value of the field access is the the value of the virtual method that's being invoked for the field access, so in other words the actual value of the field.

Keep in mind that you have to rewrite your Greet class with the injector in a separated JVM process and only afterwards you'll be able to use the class with the injected behavior, unless you do some tricks with classloading to make sure you load the modified version. I'm not going into those topics because it's out of the scope, but if you need help with that as well please say so. I'll gladly help you out.