Search code examples
javaaopeclipse-pluginbcel

Call a method when object state changes


I am providing an annotation @validateName which anyone can include in their code. Suppose some one coded

class Person {
    @validateName
    private String name;
    ....
}

Then they can call NameValidator.validate(personObject) or some similar method to validate the field.

I want to make sure that the name field is always in a valid state i.e. I want to call the validate() method automatically whenever an annotated variable changes (no matter where it changes whether inside or outside the class).
I am willing to write a plugin that hooks into Eclipse and gets invoked during compilation phase. Please provide some pointers where I can start looking for solutions.
(I guess I have to implement some sort of AOP or should modify the bytecode using BCEL or something. I am not sure as I haven't tried both.)


Solution

  • With AspectJ you can do this:

    Annotation

    package de.scrum_master.aop.app;
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ValidateName {}
    

    Driver class

    package de.scrum_master.aop.app;
    
    public class Application {
        private int id;
        @ValidateName
        private String firstName;
        @ValidateName
        private String lastName;
        private String placeOfBirth;
    
        public Application(int id, String firstName, String lastName, String placeOfBirth) {
            this.id = id;
            this.firstName = firstName;
            this.lastName = lastName;
            this.placeOfBirth = placeOfBirth;
        }
    
        @Override
        public String toString() {
            return "Application [id=" + id + ", firstName=" + firstName
                    + ", lastName=" + lastName + ", placeOfBirth=" + placeOfBirth
                    + "]";
        }
    
        public static void main(String[] args) {
            System.out.println(new Application(1, "Galileo", "Galilei", "Pisa, Italy"));
            System.out.println(new Application(2, "Isaac", "Newton", "Woolsthorpe-by-Colsterworth, United Kingdom"));
            System.out.println(new Application(3, "Albert", "Einstein", "Ulm, Germany"));
            System.out.println(new Application(4, "Werner", "Heisenberg", "Würzburg, Germany"));
        }
    }
    

    Validator aspect

    package de.scrum_master.aop.aspect;
    
    import java.util.Random;
    import de.scrum_master.aop.app.ValidateName;
    
    public aspect NameValidator {
        void validate(String name) {
            if (new Random().nextBoolean())
                throw new RuntimeException("Invalid name " + name);
        }
    
        void around(String name) : set(@ValidateName * *.*) && args(name) {
            //System.out.println(thisJoinPointStaticPart);
            System.out.print("Validating name " + name);
            try {
                validate(name);
                System.out.println(" -> OK");
                proceed(name);
            }
            catch (Exception e) {
                name = name.toUpperCase();
                System.out.println(" -> " + e.getMessage() + " -> replaced by " + name);
                proceed(name);
            }
        }
    }
    

    As you can see, my validator just randomly fails in ca. 50% of all cases based on a pseudo-random value. When it does, it just replaces the "invalid" name by a capitalised version. The output looks like a variation of this:

    Validating name Galileo -> OK
    Validating name Galilei -> Invalid name Galilei -> replaced by GALILEI
    Application [id=1, firstName=Galileo, lastName=GALILEI, placeOfBirth=Pisa, Italy]
    Validating name Isaac -> Invalid name Isaac -> replaced by ISAAC
    Validating name Newton -> Invalid name Newton -> replaced by NEWTON
    Application [id=2, firstName=ISAAC, lastName=NEWTON, placeOfBirth=Woolsthorpe-by-Colsterworth, United Kingdom]
    Validating name Albert -> OK
    Validating name Einstein -> Invalid name Einstein -> replaced by EINSTEIN
    Application [id=3, firstName=Albert, lastName=EINSTEIN, placeOfBirth=Ulm, Germany]
    Validating name Werner -> OK
    Validating name Heisenberg -> Invalid name Heisenberg -> replaced by HEISENBERG
    Application [id=4, firstName=Werner, lastName=HEISENBERG, placeOfBirth=Würzburg, Germany]