Search

Dark theme | Light theme

August 18, 2009

Groovy Goodness: Bound and Constrained Properties in GroovyBeans

In a previous post we learned some basic GroovyBeans syntax. With less code we could write a complete JavaBean. In this post we see how we can implement bound and constrained properties as defined in the JavaBeans specification. A bound property is a bean property for which a change to the property results in a notification being sent to some other bean. A constrained property is a bean property for which a change to the property results in validation by another bean. The other bean may reject the change if it is not appropriate.

Implementing these properties is of course easy in Groovy! Groovy supports the @Bindable and @Vetoable annotations (extra info on Groovy site) to implement bound and constrained properties. The following code shows a simple bean:

import groovy.beans.*

class Car {
   int numberOfDoors
   @Vetoable String model
   @Vetoable String brand
   boolean automatic
   @Bindable double price
 
   String toString() {
     "[Car details => brand: '${brand}', model: '${model}', #doors: '${numberOfDoors}', automatic: '${automatic}', price: '${price}']"
   }
}

And that is it! When we compile the Car class Groovy adds all necessary addXXXListener methods with implementations. Just for excercise we see the equivalent in Java:

import java.beans.*;

public class Car {
 private int numberOfDoors;
 private String model;
 private String brand;
 private boolean automatic;
 private double price;
 
 private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
 private final VetoableChangeSupport vcs = new VetoableChangeSupport(this);

 public void addPropertyChangeListener(PropertyChangeListener listener) {
  pcs.addPropertyChangeListener(listener);
 }

 public void removePropertyChangeListener(PropertyChangeListener listener) {
  pcs.removePropertyChangeListener(listener);
 }
 
 public void addVetoableChangeListener(VetoableChangeListener listener) {
  vcs.addVetoableChangeListener(listener);
 }

 public void removeVetoableChangeListener(VetoableChangeListener listener) {
  vcs.removeVetoableChangeListener(listener);
 }

 public void setPrice(double price) {
  double oldPrice = this.price;
  this.price = price;
  pcs.firePropertyChange("price", oldPrice, price);
    }
 
 public double getPrice() {
  return this.price;
 }
 
 public void setModel(String model) throws PropertyVetoException {
  String oldModel = this.model;
  vcs.fireVetoableChange("model", oldModel, model);
  this.model = model;
  pcs.firePropertyChange("model", oldModel, model);
 }
 
 public String getModel() {
  return this.model;
 }
 
 public void setBrand(String model) throws PropertyVetoException {
  String oldBrand = this.brand;
  vcs.fireVetoableChange("model", oldBrand, brand);
  this.brand = brand;
  pcs.firePropertyChange("model", oldBrand, brand);
 }
 
 public String getBrand() {
  return this.brand;
 }
 
 public void setNumberOfDoors(int numberOfDoors) {
  this.numberOfDoors = numberOfDoors;
 }
 
 public int getNumberOfDoors() {
  return numberOfDoors;
 }
 
 public void setAutomatic(boolean automatic) {
  this.automatic = automatic;
 }
 
 public boolean isAutomatic() {
  return this.automatic;
 }
 
 public String toString() {
  final StringBuilder builder = new StringBuilder();
  builder.append("[Car details => brand: '");
  builder.append(brand);
  builder.append("', model: '");
  builder.append(model);
  builder.append("', #doors: '");
  builder.append(numberOfDoors);
  builder.append("', automatic: '");
  builder.append(automatic);
  builder.append("', price: '");
  builder.append(price);
  builder.append("']");
  return builder.toString();
 }
}

Well I think we see the difference in lines of code...

Let's write a simple Groovy script to listen for the propertychange and vetoablechange events. We can also write less code than the Java equivalent here. In Groovy we can use closures to implement listener interfaces. The following Groovy script listens for property changes and rejects certain values:

import groovy.beans.*
import java.beans.*

def toyota = new Car(brand: 'Toyota', model: 'Verso', price: 28919, numberOfDoors: 5)
toyota.propertyChange = {
 if (it.propertyName == 'price') {
  println "The price has changed. Inform sales the new price is '${it.newValue}'." 
 }
}
toyota.vetoableChange = { PropertyChangeEvent pce ->
 if (pce.propertyName == "brand") {
  if (!(pce.newValue in ['Toyota', 'Lexus'])) {
   throw new PropertyVetoException('New value is not Toyota or Lexus', pce)
  }
 }
 if (pce.propertyName == "model") {
  if (pce.newValue ==~ /.*\d+.*/) {
   throw new PropertyVetoException('No numbers in model names allowed.', pce)
  }
 }
}

toyota.price = 30995
assert 30995 == toyota.price

toyota.brand = 'Lexus'
assert 'Lexus' == toyota.brand

try {
 toyota.brand = 'AUDI'
 assert false: 'We should not be able to set this value.'
} catch (PropertyVetoException e) {
 assert true
}

try {
 toyota.model = 'A5'
 assert false: 'We should not be able to set this value.'
} catch (PropertyVetoException e) {
 assert true
}

Our Groovy Car class is compiled to Java byte code. So we must be able to use this class in our normal Java applications. So here is a Java application which implements the appropriate listener interface. Notice we must compile this Java application with groovyc and we cannot use anonymous inner classes as interface implementations.

import java.beans.*;
import java.util.regex.*;

public class CarApp implements PropertyChangeListener, VetoableChangeListener {
    public static void main(String[] args) {
        Car toyota = new Car();
        toyota.setModel("Verso");
        toyota.setBrand("Toyota");
        toyota.setNumberOfDoors(5);
        toyota.setPrice(28919);

        CarApp app = new CarApp();
        toyota.addPropertyChangeListener(app);
        toyota.addVetoableChangeListener(app);

        toyota.setPrice(30995);
        toyota.setBrand("Lexus");
        try {
            toyota.setBrand("AUDI");
        } catch (PropertyVetoException e) {
            System.out.println("Brand is not changed.");
        }
        try {
            toyota.setModel("A5");
        } catch (PropertyVetoException e) {
            System.out.println("Model is not changed.");
        }
    }

    public void propertyChange(PropertyChangeEvent evt) {
       if (evt.getPropertyName().equals("price")) {
           System.out.println("The price has changed. Inform sales the new price is '" + evt.getNewValue() + "'.");
       }
    }

    public void vetoableChange(PropertyChangeEvent evt) {
       if (evt.getPropertyName().equals("brand")) {
           if (!isValidBrand(evt.getNewValue())) {
               throw new PropertyVetoException("New value is not Toyota or Lexus", evt)
           }
       }
       if (evt.getPropertyName().equals("model")) {
           if (!isValidModel(evt.getNewValue())) {
               throw new PropertyVetoException("No numbers in model names allowed.", evt)       
           }
       }
    }

    private boolean isValidBrand(String newValue) {
        final String[] names = new String[2];
        names[0] = "Toyota";
        names[1] = "Lexus";
        for (String name: names) {
            if (newValue.equals(name)) {
                return true;
            }
        }
        return false;
    }
 
    private boolean isValidModel(String model) {
        return !Pattern.matches(".*\\d+.*", model);
    }
}

The @Bindable and @Vetoable really make for readable Groovy code and still implement all the functionality we expect.