Difference between revisions of "JFace Data Binding/PojoBindable"

From Eclipsepedia

Jump to: navigation, search
(with Java code by implementing BindableStrategyProvider)
(Configure Pojo Bindable Java Agent with -Dbindable.packages)
Line 292: Line 292:
 
To fill JVM Parameters that with Eclipse IDE, click on Run->Run Configurations. Select "PojoPersonBindableTest" launch (from Java Application node) and click on Arguments tabs. Fill VM Arguments textarea with this content :  
 
To fill JVM Parameters that with Eclipse IDE, click on Run->Run Configurations. Select "PojoPersonBindableTest" launch (from Java Application node) and click on Arguments tabs. Fill VM Arguments textarea with this content :  
  
<source lang="text" >-javaagent:lib/org.eclipse.core.databinding.pojo.bindable_1.0.0.jar -Dbindable.packages=org.eclipse.core.examples.databinding.pojo.bindable.model</source>
+
<source lang="text" >-javaagent:lib/org.eclipse.core.databinding.pojo.bindable_1.0.0.jar  
 +
-Dbindable.packages=org.eclipse.core.examples.databinding.pojo.bindable.model</source>
  
 
[[Image:PojoBindable_VMParameters.png]]
 
[[Image:PojoBindable_VMParameters.png]]

Revision as of 04:50, 16 April 2010

JFace Data Binding
Home
How to Contribute
FAQ
Snippets
Concepts
Binding
Converter
Observable
Realm

Contents

Target

Work is underway to support binding with Pojo by using BeansObservable WITHOUT coding firePropertyChange (into setter method)/addPropertyChangeListener/removePropertyChangeListener. For the last version of Pojo Bindable, plese see Pojo Bindable SVN section otherwise, see (for the moment) Bug 307417- you can find 7 plug-in projects that provide Pojo Bindable :

  • org.eclipse.core.databinding.pojo.bindable : Pojo Bindable source.
  • org.eclipse.core.examples.databinding.pojo.bindable : Pojo Bindable samples.
  • org.eclipse.core.tests.databinding.beans.bindable : Pojo Bindable tests.
  • bindable-pojo-test : basic sample with Pojo Bindable explained into the section With Pojo Bindable
  • com.springsource.org.objectweb.asm : ASM bundle "org.objectweb.asm" version of 3.2.0 getted from SpringSource Enterprise Bundle Repository.
  • com.springsource.org.objectweb.asm.commons : ASM Commons bundle "org.objectweb.asm" version of 3.2.0 getted from SpringSource Enterprise Bundle Repository.
  • com.springsource.org.objectweb.asm.tree : ASM Tree bundle "org.objectweb.asm" version of 3.2.0 getted from SpringSource Enterprise Bundle Repository. This bundle is not required for Pojo Bindable but just required for com.springsource.org.objectweb.asm.commons bundle.

Pojo Bindable in Action

Before explaining how use Pojo Bindable with JFace Databinding, take a simple example with Pojo. You can find sources of this project into the bindable-pojo-test project that you can download on Bug 307417. In this section we will observe a property change of a Pojo. To do that we will do :

Without Pojo Bindable

Create a Java Project bindable-pojo-test and create a basic Pojo PojoPerson into package org.eclipse.core.examples.databinding.pojo.bindable.model :

package org.eclipse.core.examples.databinding.pojo.bindable.model;
 
public class PojoPerson {
 
	String name = "HelloWorld";
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
}

Create PojoPersonBindableTest class into package org.eclipse.core.examples.databinding.pojo.bindable :

package org.eclipse.core.examples.databinding.pojo.bindable;
 
import org.eclipse.core.examples.databinding.pojo.bindable.model.PojoPerson;
 
public class PojoPersonBindableTest {
 
	public static void main(String[] args) {
		PojoPerson person = new PojoPerson();
		person.setName("New name");		
	}
}

Now we wish observe change of "name" property of the PojoPerson. To do that we must add java.beans.PropertyChangeSupport in our PojoPerson like this :

package org.eclipse.core.examples.databinding.pojo.bindable.model;
 
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
 
public class PojoPerson {
 
	private transient PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(
			this);
 
	String name = "HelloWorld";
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		Object oldValue = getName();
		Object newValue = name;
		this.name = name;
		firePropertyChange("name", oldValue, newValue);
	}
 
	public void addPropertyChangeListener(String propertyName,
			PropertyChangeListener listener) {
		propertyChangeSupport.addPropertyChangeListener(propertyName,
				listener);
	}
 
 
	public void removePropertyChangeListener(String propertyName,
			PropertyChangeListener listener) {
		propertyChangeSupport.removePropertyChangeListener(propertyName,
				listener);
	}
 
	protected void firePropertyChange(String propertyName, Object oldValue,
			Object newValue) {
		propertyChangeSupport.firePropertyChange(propertyName, oldValue,
				newValue);
	}
}

You can notice that we do like this :

public void setName(String name) {
  Object oldValue = getName();
  ...
}

instead of doing :

public void setName(String name) {
  Object oldValue = this.name;
  ...
}

Indead, Experience has shown that sometimes getter methods do not simply return the field value especially in JPA entities. Further, field names could be totally different than the setter/getter method names. This is the reason that getter method is used to get the old value, rather than accessing the field.

By adding java.beans.PropertyChangeSupport in our Pojo, we can observe change of the "name" property of our Pojo (wich become a JavaBean). Update PojoPersonBindableTest code like this  :

package org.eclipse.core.examples.databinding.pojo.bindable;
 
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
 
import org.eclipse.core.examples.databinding.pojo.bindable.model.PojoPerson;
 
public class PojoPersonBindableTest {
 
	public static void main(String[] args) {
 
		// Create listener
		PropertyChangeListener listener = new PropertyChangeListener() {
			public void propertyChange(PropertyChangeEvent event) {
				System.out.println("---------- Property PojoPerson changed --------");
				System.out.println("  PropertyName=" + event.getPropertyName());
				System.out.println("  OldValue=" + event.getOldValue());
				System.out.println("  NewValue=" + event.getNewValue());
				System.out
						.println("------------------------------------------");
			}
		};
 
		// Create Pojo instance
		PojoPerson person = new PojoPerson();
		// Add listener
		person.addPropertyChangeListener("name", listener);
		// Here Change "name" property of PojoPerson is tracked.
		person.setName("New name");
		// Remove listener
		person.removePropertyChangeListener("name", listener);
		// Here Change "name" property of PojoPerson is not tracked.
		person.setName("New name2");		
	}
}

When you launch PojoPersonBindableTest, console display :

---------- Property PojoPerson changed --------
  PropertyName=name
  OldValue=HelloWorld
  NewValue=New name
------------------------------------------

This basic sample show you that you must write in your Pojo code, the PropertyChangeSupport to track the change of the property.

With Pojo Bindable

Pojo Bindable give you the capability to track property change with pur Pojo, without coding PropertyChangeSupport. It means that you can use this Pojo content :

package org.eclipse.core.examples.databinding.pojo.bindable.model;
 
public class PojoPerson {
 
	String name = "HelloWorld";
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
}

Change class PojoPerson with this previous code (your project doesn't compile because addPropertyChangeListener/removePropertyChangeListener doesn' exist. For the moment comments code line wich call addPropertyChangeListener/removePropertyChangeListener to compile PojoPersonBindableTest Class).

Pojo Bindable Java Agent

Pojo Bindable is a Java Agent (works only with java.version>=5) which change bytecode of the Pojo to add PropertyChangeSupport. Just for your Information, you can find the Bindable Java Agent into class org.eclipse.core.databinding.pojo.bindable.agent.BindableAgent (which define premain method) and where this class is filled into MANIFEST.MF :

 Premain-Class: org.eclipse.core.databinding.pojo.bindable.agent.Binda
 bleAgent

To use Pojo Bindable Java Agent (if you are not into OSGi context), you must launch your program with the JVM parameter :

-javaagent:<your path>/org.eclipse.core.databinding.pojo.bindable_1.0.0.jar

This parameter means that you execute the BindableAgent before executing your program. The BindableAgent add a ClassFileTransformer into Instrumentation instance to update class bytecode.

Configure Pojo Bindable Java Agent

Pojo Bindable Java Agent must be configured. The required configuration is to set the property bindable.packages which defines the packages where Pojo are stored in order to Bindable Java Agent change the bytecode only for the Pojo Class. You can configure Pojo Bindable with 3 means :

with JVM parameter -Dbindable.packages

You can configure Bindable Java Agent with JVM parameter -Dbindable.packages with value org.eclipse.core.examples.databinding.pojo.bindable.model like this :

-javaagent:lib/org.eclipse.core.databinding.pojo.bindable_1.0.0.jar 
-Dbindable.packages=org.eclipse.core.examples.databinding.pojo.bindable.model
with Java code by implementing BindableStrategyProvider

You can configure Bindable Java Agent with Java code BindableStrategyProvider with value org.eclipse.core.examples.databinding.pojo.bindable.model by implementing org.eclipse.core.databinding.pojo.bindable.BindableStrategyProvider interface like this :

package org.eclipse.core.examples.databinding.pojo.bindable.provider;
 
import org.eclipse.core.databinding.pojo.bindable.BindableStrategy;
import org.eclipse.core.databinding.pojo.bindable.BindableStrategyProvider;
import org.eclipse.core.databinding.pojo.bindable.DefaultBindableStrategy;
 
public class MyBindableStrategyProvider implements BindableStrategyProvider {
 
	private DefaultBindableStrategy bindableStrategy = null;
 
	public MyBindableStrategyProvider() {
 
		bindableStrategy = new DefaultBindableStrategy(null);
		bindableStrategy
				.addPackageName("org.eclipse.core.examples.databinding.pojo.bindable.model");	
	}
 
	public BindableStrategy getBindableStrategy() {
		return bindableStrategy;
	}
 
}

After you must launch your program with -Dbindable.strategy_provider like this :

-javaagent:lib/org.eclipse.core.databinding.pojo.bindable_1.0.0.jar
-Dbindable.strategy_provider=org.eclipse.core.examples.databinding.pojo.bindable.provider.MyBindableStrategyProvider
with bindable.properties file
Configuration properties of Pojo Bindable
bindable.packages=<your model package>

BindableAgent requires the property "bindable.packages" to know which Class owned "bindable.packages", must be transformed to add PropertyChangeSupport. If you have several packages you can use ';' character as delimiter (ex : Dbindable.packages=com.example.package1;com.example.package2).

bindable.use_annotation=<true|false>

BindableAgent use "bindable.use_annotation" property to see if @Bindable annotation must be used or not. This property is not required and by default the value is false.

bindable.strategy_provider=<provider>

Set an implementation of BindableStrategyProvider to configure BindableStrategy with Java code.

bindable.gen_basedir=<path>

Set the path of base dir if you wish generate the result of transformed class into a file.

bindable.debug=<true|false>

If true, display information about BindableAgent and Class Transformation.

Configure Pojo Bindable Java Agent with -Dbindable.packages

Create a lib folder and copy/paste Pojo Bindable Java Agent org.eclipse.core.databinding.pojo.bindable_1.0.0.jar (that you can find into org.eclipse.core.tests.databinding.pojo.bindable/lib folder).

We must define 2 JVM Parametres with our example :

  • -javaagent:lib/org.eclipse.core.databinding.pojo.bindable_1.0.0.jar : our BindableAgent is stored into lib/org.eclipse.core.databinding.pojo.bindable_1.0.0.jar.
  • -Dbindable.packages=org.eclipse.core.examples.databinding.pojo.bindable.model : our Pojo package is org.eclipse.core.examples.databinding.pojo.bindable.model.

To fill JVM Parameters that with Eclipse IDE, click on Run->Run Configurations. Select "PojoPersonBindableTest" launch (from Java Application node) and click on Arguments tabs. Fill VM Arguments textarea with this content :

-javaagent:lib/org.eclipse.core.databinding.pojo.bindable_1.0.0.jar 
-Dbindable.packages=org.eclipse.core.examples.databinding.pojo.bindable.model

PojoBindable VMParameters.png

Add ASM in your ClassPath

Click on Run and you will have this error :

Caused by: java.lang.NoClassDefFoundError: org/objectweb/asm/Opcodes
...
	at org.eclipse.core.databinding.pojo.bindable.agent.BindableAgent.premain(BindableAgent.java:67)FATAL ERROR in native method: processing of -javaagent failed
...

This error means that you must add ObjectWeb ASM in your ClassPath. Indead Pojo Bindable use ObjectWeb ASM 3.2.0 to change bytecode. You can download ASM here or use ASM bundle com.springsource.org.objectweb.asm ASM bundle com.springsource.org.objectweb.asm.commons (ASM OSGified) that you can find into the Pojo Bindable projects. For our example you can copy/paste ASM Jars (that you can find on bindable-pojo-test) com.springsource.org.objectweb.asm-3.2.0.jar and com.springsource.org.objectweb.asm.commons-3.2.0.jar into lib folder and add it into Class Path project.

Add Eclipse/Core/Runtime in your ClassPath

Run it and error will display :

Caused by: java.lang.NoClassDefFoundError: org/eclipse/core/runtime/IStatus
	at org.eclipse.core.databinding.pojo.bindable.agent.BindableAgent.getBindableStrategyFromPackages(BindableAgent.java:160)
...

This erro means that you must add Eclipse/Core/Runtime into your ClassPath because Pojo Bindable use org.eclipse.core.runtime.IStatus to log messages (like ILogger from JFace Databinding). Add org.eclipse.equinox.common_*.jar into lib and add it into Class Path project (you can find this Jar org.eclipse.equinox.common_3.4.0.v20080421-2006.jar on bindable-pojo-test).

Run it and error must disappear.

Pojo Transformed with Pojo Bindable

At this time, when PojoPerson Class is loaded, BindableAgent transform bytecode with this content :

package org.eclipse.core.examples.databinding.pojo.bindable.model;
 
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
 
import org.eclipse.core.databinding.pojo.bindable.BindableAware;
 
public class PojoPerson implements BindableAware {
 
	private String name = "HelloWorld";
	private transient PropertyChangeSupport _bindable_propertyChangeSupport;
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		String s = getName();
		this.name = name;
		_bindable_getPropertyChangeSupport().firePropertyChange("name", s,
				getName());
	}
 
	private PropertyChangeSupport _bindable_getPropertyChangeSupport() {
		if (_bindable_propertyChangeSupport == null)
			_bindable_propertyChangeSupport = new PropertyChangeSupport(this);
		return _bindable_propertyChangeSupport;
	}
 
	public void addPropertyChangeListener(String s,
			PropertyChangeListener propertychangelistener) {
		_bindable_getPropertyChangeSupport().addPropertyChangeListener(s,
				propertychangelistener);
	}
 
	public void addPropertyChangeListener(
			PropertyChangeListener propertychangelistener) {
		_bindable_getPropertyChangeSupport().addPropertyChangeListener(
				propertychangelistener);
	}
 
	public void removePropertyChangeListener(String s,
			PropertyChangeListener propertychangelistener) {
		_bindable_getPropertyChangeSupport().removePropertyChangeListener(s,
				propertychangelistener);
	}
 
	public void removePropertyChangeListener(
			PropertyChangeListener propertychangelistener) {
		_bindable_getPropertyChangeSupport().removePropertyChangeListener(
				propertychangelistener);
	}
 
 
}

As you can see, Bindable Agent add PropertyChangeSupport capability and update bytecode of PojoPerson#setName(String name) to call PropertyChangeSupport#firePropertyChange(String propertyName, Object oldValue, Object newValue).

getter getXXX()/isXXX() required

Here code of setName :

public void setName(String name) {
  String s = getName();
  this.name = name;
  _bindable_getPropertyChangeSupport().firePropertyChange("name", s, getName());
}

As you can notice, getName() is used into setName to get the old value. It's a restriction to use Pojo Bindable. For boolean primtive type isXXX is required.

public void setBooleanValue(boolean value) {
  Boolean oldValue = Boolean.valueOf(isBooleanValue());
  this.value = value;
  _bindable_getPropertyChangeSupport().firePropertyChange("booleanValue", oldValue , Boolean.valueOf(isBooleanValue()));
}
setter with primitive type

When primitive type is used into setter, The old/new value is converted to Java Object (boolean to java.lang.Boolean, short to java.lang.Short....). Here sampel with short value :

public void setShortValue(short value) {
  Short oldValue = Short.valueOf(getShortValue());
  this.value = value;
  _bindable_getPropertyChangeSupport().firePropertyChange("shortValue", oldValue , Short.valueOf(getShortValue()));
}


Implements BindableAware

You can notice that PojoPerson implements org.eclipse.core.databinding.pojo.bindable.BindableAware interface. Here te content of this BindableAware interface :

package org.eclipse.core.databinding.pojo.bindable;
 
import java.beans.PropertyChangeListener;
 
public interface BindableAware {
 
	void addPropertyChangeListener(String propertyName,
			PropertyChangeListener listener);
 
	void addPropertyChangeListener(PropertyChangeListener listener);
 
	void removePropertyChangeListener(String propertyName,
			PropertyChangeListener listener);
 
	void removePropertyChangeListener(PropertyChangeListener listener);
}

Add/Remove PropertyChangeListener with Reflection

BindableAgent must transform our Pojo bytecode with PropertyChangeSupport on Class loading of our PojoPerson. Method PojoPerson#addPropertyChangeListener and PojoPerson#removePropertyChangeListener are not available when PojoPersonBindableTest is coding. So we can use Reflection to

  • add PropertyChangeListener, we can do that :
    try {
      Method m = person.getClass().getMethod("addPropertyChangeListener", String.class, PropertyChangeListener.class);
      m.invoke(person, "name", listener);
    } catch (Exception e) {
      e.printStackTrace();
    }
  • remove PropertyChangeListener, we can do that :
    try {
      Method m = person.getClass().getMethod("removePropertyChangeListener", String.class, PropertyChangeListener.class);
      m.invoke(person, "name", listener);
    } catch (Exception e) {
      e.printStackTrace();
    }

Update PojoPersonBindableTest like this :

package org.eclipse.core.examples.databinding.pojo.bindable;
 
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.Method;
 
import org.eclipse.core.examples.databinding.pojo.bindable.model.PojoPerson;
 
public class PojoPersonBindableTest {
 
	public static void main(String[] args) {
 
		// Create listener
		PropertyChangeListener listener = new PropertyChangeListener() {
			public void propertyChange(PropertyChangeEvent event) {
				System.out
						.println("---------- Property PojoPerson changed --------");
				System.out.println("  PropertyName=" + event.getPropertyName());
				System.out.println("  OldValue=" + event.getOldValue());
				System.out.println("  NewValue=" + event.getNewValue());
				System.out
						.println("------------------------------------------");
			}
		};
 
		// Create Pojo instance
		PojoPerson person = new PojoPerson();
		// Add listener
		// person.addPropertyChangeListener("name", listener);
		try {
			Method m = person.getClass().getMethod("addPropertyChangeListener",
					String.class, PropertyChangeListener.class);
			m.invoke(person, "name", listener);
		} catch (Exception e) {
			e.printStackTrace();
		}
		// Here Change "name" property of PojoPerson is tracked.
		person.setName("New name");
		// Remove listener
		// person.removePropertyChangeListener("name", listener);
		try {
			Method m = person.getClass().getMethod("removePropertyChangeListener",
					String.class, PropertyChangeListener.class);
			m.invoke(person, "name", listener);
		} catch (Exception e) {
			e.printStackTrace();
		}
		// Here Change "name" property of PojoPerson is not tracked.
		person.setName("New name2");
	}
}

Run the program and you will notice that we can track change of "name" property. If you have this error :

java.lang.NoSuchMethodException: org.eclipse.core.examples.databinding.pojo.bindable.model.PojoPerson.addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
	at java.lang.Class.getMethod(Unknown Source)
...

It means that BindableAgent doesn't execute transformation. Please check your parameter bindable.packages and if you execute your programm with -javaagent.

Add/Remove PropertyChangeListener with BindableAware

Reflection works very well but the code is awfull. Into section Pojo Transformed with Pojo Bindable we can see that our PojoPerson implements org.eclipse.core.databinding.pojo.bindable.BindableAware interface. To avoid using Reflection, we can cast our PojoPerson with this interface. To use this interface you must add Pojo Bindable Agent in your ClassPath. Add org.eclipse.core.databinding.pojo.bindable_1.0.0.jar library in your ClassPath.

To add Listener you can cast person instance into BindableAware :

PojoPerson person = new PojoPerson();
// Add listener
((BindableAware) person).addPropertyChangeListener("name", listener);

To avoid having ClassCastException (if you launch your program without javaagent), it's better to test if PojoPerson implements BindableAware :

PojoPerson person = new PojoPerson();
// Add listener
if (person instanceof BindableAware) {
  ((BindableAware) person).addPropertyChangeListener("name", listener);
}

Pojo Bindable provides an Helper which do that. You can write :

if (BindableHelper.isBindableAware(person)) {
// Add listener
  ((BindableAware) person).addPropertyChangeListener("name", listener);
}

Update PojoPersonBindableTest like this :

package org.eclipse.core.examples.databinding.pojo.bindable;
 
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
 
import org.eclipse.core.databinding.pojo.bindable.BindableAware;
import org.eclipse.core.databinding.pojo.bindable.BindableHelper;
import org.eclipse.core.examples.databinding.pojo.bindable.model.PojoPerson;
 
public class PojoPersonBindableTest {
 
	public static void main(String[] args) {
 
		// Create listener
		PropertyChangeListener listener = new PropertyChangeListener() {
			public void propertyChange(PropertyChangeEvent event) {
				System.out
						.println("---------- Property PojoPerson changed --------");
				System.out.println("  PropertyName=" + event.getPropertyName());
				System.out.println("  OldValue=" + event.getOldValue());
				System.out.println("  NewValue=" + event.getNewValue());
				System.out
						.println("------------------------------------------");
			}
		};
 
		// Create Pojo instance
		PojoPerson person = new PojoPerson();
		if (BindableHelper.isBindableAware(person)) {
			// Add listener
			((BindableAware) person)
					.addPropertyChangeListener("name", listener);
		}
		// Here Change "name" property of PojoPerson is tracked.
		person.setName("New name");
		if (BindableHelper.isBindableAware(person)) {
			// Remove listener
			((BindableAware) person).removePropertyChangeListener("name",
					listener);
		}
		// Here Change "name" property of PojoPerson is not tracked.
		person.setName("New name2");
	}
}

Run it to check that property "name" of pur Pojo PojoPerson can be tracked.

-Dbindable.debug=true

To debug BindableAgent and check your Pojo class is transformed, you can add the parameter into JVM parameters :

-Dbindable.debug=true

If you lanch the previous example with this parameter, console will display :

Status INFO: NO Transformation for class: org/eclipse/core/internal/runtime/LocalizationUtils
Status INFO: NO Transformation for class: org/eclipse/core/internal/runtime/CommonMessages
Status INFO: Java Bindable Agent called, with configuration: 
bindable.packages=org/eclipse/core/examples/databinding/pojo/bindable/model
bindable.use_annotation=false
bindable.debug=true
bindable.gen_basedir=D://tmp/
bindable.strategy_provider=null
Status INFO: NO Transformation for class: org/eclipse/core/examples/databinding/pojo/bindable/PojoPersonBindableTest
Status INFO: NO Transformation for class: org/eclipse/core/examples/databinding/pojo/bindable/PojoPersonBindableTest$1
Status INFO: Transform class: org/eclipse/core/examples/databinding/pojo/bindable/model/PojoPerson
Status INFO: NO Transformation for class: org/eclipse/core/databinding/pojo/bindable/BindableAware
---------- Property PojoPerson changed --------
  PropertyName=name
  OldValue=HelloWorld
  NewValue=New name
------------------------------------------

You can notice that console display each Class wich must be loaded. The message

Status INFO: NO Transformation for class: ...

means that Class is not transformed.

Status INFO: Transform class: org/eclipse/core/examples/databinding/pojo/bindable/model/PojoPerson

means that our PojoPerson class is transformed.

You can notice on start of teh console teh configuration of Pojo Bindable :

Status INFO: Java Bindable Agent called, with configuration: 
bindable.packages=org/eclipse/core/examples/databinding/pojo/bindable/model
bindable.use_annotation=false
bindable.debug=true
bindable.gen_basedir=D://tmp/
bindable.strategy_provider=null

-Dbindable.gen_basedir=D://tmp/

You can if you wish, generate the result of class transformed into file. To do that you must add the parameter into JVM parameters :

-Dbindable.gen_basedir=D://tmp/

Here, result of class transformed will be generate into folder D://tmp/. If you lanch the previous example with this parameter, console will display :

...
Status INFO: Transform class: org/eclipse/core/examples/databinding/pojo/bindable/model/PojoPerson
Status INFO: Generate transformed class: org/eclipse/core/examples/databinding/pojo/bindable/model/PojoPerson into file: D://tmp/org/eclipse/core/examples/databinding/pojo/bindable/model/PojoPerson.class
Status INFO: Transformed class output written to: D://tmp/org/eclipse/core/examples/databinding/pojo/bindable/model/PojoPerson.class
...

You can check that you have D://tmp/org/eclipse/core/examples/databinding/pojo/bindable/model/PojoPerson.class file generated. You can decompile it to check that this class implements BindableAware interface.

-Dbindable.strategy_provider=org...MyBindableStrategyProvider

We have configured BindableAgent with several JVM parameters (-Dbindable.packages, -Dbindable.debug...), but it's possible to configure it with Java code. To do that create a class wich implements org.eclipse.core.databinding.pojo.bindable.BindableStrategyProvider interface. You can found sample org.eclipse.core.examples.databinding.pojo.bindable.provider.MyBindableStrategyProvider into org.eclipse.core.examples.databinding.pojo.bindable bundle.

package org.eclipse.core.examples.databinding.pojo.bindable.provider;
 
import org.eclipse.core.databinding.pojo.bindable.BindableStrategy;
import org.eclipse.core.databinding.pojo.bindable.BindableStrategyProvider;
import org.eclipse.core.databinding.pojo.bindable.DefaultBindableStrategy;
 
public class MyBindableStrategyProvider implements BindableStrategyProvider {
 
	private DefaultBindableStrategy bindableStrategy = null;
 
	public MyBindableStrategyProvider() {
 
		bindableStrategy = new DefaultBindableStrategy(null);
		bindableStrategy
				.addPackageName("org.eclipse.core.examples.databinding.pojo.bindable.model");
		bindableStrategy.setDebugMode(true);
		// bindableStrategy.setGenBaseDir("D://tmp");
		bindableStrategy.setUseAnnotation(false);
	}
 
	public BindableStrategy getBindableStrategy() {
		return bindableStrategy;
	}
 
}

Launch Bindable Agent with -Dbindable.strategy_provider like this :

-javaagent:lib/org.eclipse.core.databinding.pojo.bindable_1.0.0.jar 
-Dbindable.strategy_provider=org.eclipse.core.examples.databinding.pojo.bindable.provider.MyBindableStrategyProvider

You can find that into the launch/HelloWorldWithBeansObservablesWithBindableAgentProvider.launch

Pojo Bindable & JFace Databinding

In this section we will use Pojo Bindable with JFace Databinding. It explain examples of the project org.eclipse.core.examples.databinding.pojo.bindable. In this project you can find 3 samples into org.eclipse.core.examples.databinding.pojo.bindable.snippets package :

  • HelloWorldWithPojoObservables : bind the property "name" of pur Pojo Person with SWT UI Text by using PojoObservables. Binding UI -> Pojo model is only available, on other words when Pojo model change, UI is not updated.
  • HelloWorldWithBeansObservables : bind the property "name" of JavaBean Person with SWT UI Text by using BeansObservables. Binding UI <-> Pojo model is available, on other words when JavaBean model change, UI is too updated.
  • HelloWorldWithBeansObservablesWithBindableAgent : bind the property "name" of pur Pojo PojoPerson with SWT UI Text by using BeansObservables. Binding UI <-> Pojo model is available, on other words when Pojo model change, UI is too updated. This sampel must be launched with Pojo Bindable Agent.

HelloWorldWithPojoObservables

This sample use pur Pojo :

package org.eclipse.core.examples.databinding.pojo.bindable.model;
 
public class PojoPerson {
 
	String name = "HelloWorld";
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
}

In this sample, we bind the property "name" of pur Pojo Person with SWT UI Text by using PojoObservables. Here the code of HelloWorldWithPojoObservables :

package org.eclipse.core.examples.databinding.pojo.bindable.snippets;
 
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.beans.PojoObservables;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.core.examples.databinding.pojo.bindable.model.PojoPerson;
import org.eclipse.jface.databinding.swt.SWTObservables;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
 
/**
 * Hello, databinding. Bind POJO with {@link PojoObservables}. Model change
 * doesn't update UI.
 */
public class HelloWorldWithPojoObservables {
	public static void main(String[] args) {
		Display display = new Display();
		final ViewModel viewModel = new ViewModel();
 
		Realm.runWithDefault(SWTObservables.getRealm(display), new Runnable() {
			public void run() {
				final Shell shell = new View(viewModel).createShell();
				// The SWT event loop
				Display display = Display.getCurrent();
				while (!shell.isDisposed()) {
					if (!display.readAndDispatch()) {
						display.sleep();
					}
				}
			}
		});
		// Print the results
		System.out.println("person.getName() = "
				+ viewModel.getPerson().getName());
	}
 
	static class ViewModel {
		// The model to bind
		private PojoPerson person = new PojoPerson();
 
		public PojoPerson getPerson() {
			return person;
		}
	}
 
	// The GUI view
	static class View {
		private ViewModel viewModel;
		private Text name;
 
		public View(ViewModel viewModel) {
			this.viewModel = viewModel;
		}
 
		public Shell createShell() {
			// Build a UI
			Display display = Display.getDefault();
			Shell shell = new Shell(display);
			shell.setLayout(new RowLayout(SWT.VERTICAL));
			name = new Text(shell, SWT.BORDER);
 
			Button changeModelButton = new Button(shell, SWT.BORDER);
			changeModelButton.setText("Change model");
 
			// Bind it
			DataBindingContext bindingContext = new DataBindingContext();
			PojoPerson person = viewModel.getPerson();
 
			bindingContext.bindValue(SWTObservables.observeText(name,
					SWT.Modify), PojoObservables.observeValue(person, "name"));
 
			changeModelButton.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					PojoPerson person = viewModel.getPerson();
					person.setName("HelloWorld");
				}
			});
 
			// Open and return the Shell
			shell.pack();
			shell.open();
			return shell;
		}
	}
 
}

When you launch this code, you can see this window :

PojoBindable HelloWord.png

Binding between SWT Text and Pojo model is done with PojoObservables :

bindingContext.bindValue(SWTObservables.observeText(name, SWT.Modify), BeansObservables.observeValue(person, "name"));

SWT Text is filled with "HelloWord" wich come from of the initial value of the PojoPerson. When UI is changed, property "name" of the Pojo is updated. To check that, change UI Text with "a" value. and close the window, console display the value of "name" property of the Pojo :

person.getName() = a

In this sample there is "Change model" button which change "name" property of Pojo model with "HelloWorld" value :

changeModelButton.addSelectionListener(new SelectionAdapter() {
  @Override
  public void widgetSelected(SelectionEvent e) {
    PojoPerson person = viewModel.getPerson();
      person.setName("HelloWorld");
    }
});

If you update the UI with "a" value and you click on "Change model" button, UI is not updated with new value of the model Pojo.

HelloWorldWithBeansObservables

We have seen that with PojoObservables we can manage only UI -> Model but not UI <- Model (UI is initialized with Model but when Model change, UI is not updated). To manage UI <- Model, we must use BeansObservables. Copy paste HelloWorldWithPojoObservables and rename it with HelloWorldWithBeansObservables. Update PojoObservables with BeansObservables :

bindingContext.bindValue(SWTObservables.observeText(name, SWT.Modify), BeansObservables.observeValue(person, "name"));

Run the HelloWorldWithBeansObservables and you will have this error :

Status WARNING: org.eclipse.core.databinding code=0 Could not attach listener to org.eclipse.core.examples.databinding.pojo.bindable.model.PojoPerson@743399 java.lang.NoSuchMethodException: org.eclipse.core.examples.databinding.pojo.bindable.model.PojoPerson.addPropertyChangeListener(java.beans.PropertyChangeListener)
java.lang.NoSuchMethodException: org.eclipse.core.examples.databinding.pojo.bindable.model.PojoPerson.addPropertyChangeListener(java.beans.PropertyChangeListener)
...

This exception show you that you must define in the model, methods addPropertyChangeListener and removePropertyChangeListener. PojoPerson become JavaBeanPerson :

package org.eclipse.core.examples.databinding.pojo.bindable.model;
 
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
 
public class JavaBeanPerson {
 
	// A property...
	String name = "HelloWorld";
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		Object oldValue = this.name;
		Object newValue = name;
		this.name = name;
		firePropertyChange("name", oldValue, newValue);
	}
 
	private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(
			this);
 
 
	public void addPropertyChangeListener(String propertyName,
			PropertyChangeListener listener) {
		propertyChangeSupport.addPropertyChangeListener(propertyName,
				listener);
	}
 
 
	public void removePropertyChangeListener(String propertyName,
			PropertyChangeListener listener) {
		propertyChangeSupport.removePropertyChangeListener(propertyName,
				listener);
	}
 
	protected void firePropertyChange(String propertyName, Object oldValue,
			Object newValue) {
		propertyChangeSupport.firePropertyChange(propertyName, oldValue,
				newValue);
	}
}

Use JavaBeanPerson inseatd of PojoPerson and launch HelloWorldWithBeansObservables. Error will disappear, and when you wil click on "Change Model", SWT UI will be updated with "HelloWorld" value comminf from the Model.

HelloWorldWithBeansObservablesWithBindableAgent

Here we will use Pojo Bindable to use pur Pojo with BeansObservables. Class HelloWorldWithBeansObservablesWithBindableAgent use BeansObservables although our model is a pur Pojo (without addPropertyChangeListener/removeropertyChangeListener methods).

PojoPerson person = ...
...
bindingContext.bindValue(SWTObservables.observeText(name,
SWT.Modify), BeansObservables.observeValue(person, "name"));

HelloWorldWithBeansObservablesWithBindableAgent must launch with Bindable Java Agent. To do that, you must :

  • Add Bindable Java Agent org.eclipse.core.databinding.pojo.bindable_1.0.0.jar into lib project.
  • Add SM in your ClassPath
  • configure Bindable Java Agent into 2 JVM parameters :
    • -javaagent:lib/org.eclipse.core.databinding.pojo.bindable_1.0.0.jar : our BindableAgent is stored into lib/org.eclipse.core.databinding.pojo.bindable_1.0.0.jar.
    • -Dbindable.packages=org.eclipse.core.examples.databinding.pojo.bindable.model : our Pojo PojoPerson package is org.eclipse.core.examples.databinding.pojo.bindable.model.

Lanch the program with Bindable JavaAgent filled into JVM Parameters :

-javaagent:lib/org.eclipse.core.databinding.pojo.bindable_1.0.0.jar -Dbindable.packages=org.eclipse.core.examples.databinding.pojo.bindable.model

You can find this launch into \launch\HelloWorldWithBeansObservablesWithBindableAgent.launch. Launch HelloWorldWithBeansObservablesWithBindableAgent and you will notice that UI<->Model is available by using pur Pojo.

@Bindable annotation

Sometimes you have "set" method into your model Pojo and you will NOT transform setXXX method by calling PropertyChangeSupport. To do that you can use @Bindable annotation. For instance :

At first you must available @Bindable annotation with JVM parameters bindable.use_annotation=true.

@Bindable#value

After you can write this to transform setName method and avoid transforming setLogin method :

package org.eclipse.core.tests.databinding.pojo.bindable.domain.annotations;
 
import org.eclipse.core.databinding.pojo.bindable.Bindable;
 
public class UserBindable {
 
	private String name;
 
	private String login;
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
	public String getLogin() {
		return login;
	}
 
	@Bindable(value = false)
	// This method must not be transformed with firePropertyChange.
	public void setLogin(String login) {
		this.login = login;
	}
 
}

You can set @Bindable before class declaration to disable transformation for setXXX method. In this sample setName is not transformed and setLogin is transformed :

package org.eclipse.core.tests.databinding.pojo.bindable.domain.annotations;
 
import org.eclipse.core.databinding.pojo.bindable.Bindable;
 
@Bindable(value = false)
// All methods must not be transformed with firePropertyChange.
public class UserNotBindable {
 
	private String name;
 
	private String login;
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
	public String getLogin() {
		return login;
	}
 
	@Bindable(value = true)
	// This method MUST ne transformed with firePropertyChange.
	public void setLogin(String login) {
		this.login = login;
	}
}

Project org.eclipse.core.tests.databinding.pojo.bindable show you usecase with org.eclipse.core.databinding.pojo.bindable.Bindable annotation.

@Bindable#dependsOn

If you wish manage computed values, you can use @Bindable#dependsOn. You can find TestCase into ComputedValueTest which use model ComputedValueTestEntity

Imagine you have this Pojo :

package org.eclipse.core.tests.databinding.pojo.bindable.domain.annotations.computed;
 
import java.beans.PropertyChangeEvent;
 
import org.eclipse.core.databinding.pojo.bindable.annotation.Bindable;
 
public class ComputedValueTestEntity {
 
	private double sellingPrice;
	private double buyingPrice;
 
	public ComputedValueTestEntity(double sellingPrice, double buyingPrice) {
		this.sellingPrice = sellingPrice;
		this.buyingPrice = buyingPrice;
	}
 
	public double getSellingPrice() {
		return sellingPrice;
	}
 
	public void setSellingPrice(double sellingPrice) {
		this.sellingPrice = sellingPrice;
	}
 
	public double getBuyingPrice() {
		return buyingPrice;
	}
 
	public void setBuyingPrice(double buyingPrice) {
		this.buyingPrice = buyingPrice;
	}
 
	public double getRatio() {
		return sellingPrice / buyingPrice;
	}
 
	public boolean isCheapBuyingPrice() {
		return buyingPrice < 100;
	}
 
}

Now you want observe changed of the "ratio" property (ComputedValueTestEntity#getRatio()). As you can notice, the method ComputedValueTestEntity#setRatio(double ratio) doesn't exist, because "ratio" property depends on "sellingPrice" and "buyingPrice" properties. So "ratio" change when "sellingPrice" and "buyingPrice" change, on other words when methods setSellingPrice/setBuyingPrice are called.

Imagine you want bind this "ratio" property with a SWT Label. An event PropertyChangeEvent with property name "ratio" must be fired when setSellingPrice/setBuyingPrice methods are called. It means that "ratio" property, depends on call of setSellingPrice and setBuyingPrice methods. You can manage that with Pojo Bindable with @Bindable#dependsOn declared into (ComputedValueTestEntity#getRatio()) getter method like this :

@Bindable(dependsOn = { "sellingPrice", "buyingPrice" })
public double getRatio() {
    return sellingPrice / buyingPrice;
}


After you can observe "ratio" property. Here a sample code to observe it :

PropertyChangeListener listener = new PropertyChangeListener() {
	public void propertyChange(PropertyChangeEvent event) {
		System.out
				.println("---------- Property ComputedValueTestEntity changed --------");
		System.out.println("  PropertyName=" + event.getPropertyName());
		System.out.println("  OldValue=" + event.getOldValue());
		System.out.println("  NewValue=" + event.getNewValue());
		System.out
				.println("------------------------------------------");		
	}
};
 
// Create ComputedValueTestEntity model
ComputedValueTestEntity p = new ComputedValueTestEntity(4, 2);
 
// addPropertyChangeListener to observe ratio (javagent must be used to transform bytecode!!!) 
((BindableAware)p).addPropertyChangeListener("ratio", listener);
 
// Change buyingPrice, event is fired with 'ratio' property 
p.setBuyingPrice(100);
// Change sellingPrice, event is fired with 'ratio' property
p.setSellingPrice(100);


If you launch this sample with Pojo Bindable Java Agent :

-javaagent:lib/org.eclipse.core.databinding.pojo.bindable_1.0.0.jar -Dbindable.packages=org.eclipse.core.tests.databinding.pojo.bindable.domain.annotations.computed -Dbindable.use_annotation=true

You will see on console :

---------- Property ComputedValueTestEntity changed --------
  PropertyName=ratio
  OldValue=2.0
  NewValue=0.04
------------------------------------------
---------- Property ComputedValueTestEntity changed --------
  PropertyName=ratio
  OldValue=0.04
  NewValue=1.0
------------------------------------------

For information Pojo Bindable change the bytecode of the Pojo like this :

package org.eclipse.core.tests.databinding.pojo.bindable.domain.annotations.computed;
 
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import org.eclipse.core.databinding.pojo.bindable.BindableAware;
 
public class ComputedValueTestEntity
    implements BindableAware
{
 
    private double sellingPrice;
    private double buyingPrice;
    private transient PropertyChangeSupport _bindable_propertyChangeSupport;
    private transient double _bindable_setSellingPrice_ratio;
    private transient double _bindable_setBuyingPrice_ratio;
 
    public ComputedValueTestEntity(double sellingPrice, double buyingPrice)
    {
        this.sellingPrice = sellingPrice;
        this.buyingPrice = buyingPrice;
    }
 
    public double getSellingPrice()
    {
        return sellingPrice;
    }
 
    public void setSellingPrice(double sellingPrice)
    {
        _bindable_beforeDependsOn_setSellingPrice();
        Double double1 = Double.valueOf(getSellingPrice());
        this.sellingPrice = sellingPrice;
        _bindable_getPropertyChangeSupport().firePropertyChange("sellingPrice", double1, Double.valueOf(getSellingPrice()));
        _bindable_afterDependsOn_setSellingPrice();
    }
 
    public double getBuyingPrice()
    {
        return buyingPrice;
    }
 
    public void setBuyingPrice(double buyingPrice)
    {
        _bindable_beforeDependsOn_setBuyingPrice();
        Double double1 = Double.valueOf(getBuyingPrice());
        this.buyingPrice = buyingPrice;
        _bindable_getPropertyChangeSupport().firePropertyChange("buyingPrice", double1, Double.valueOf(getBuyingPrice()));
        _bindable_afterDependsOn_setBuyingPrice();
    }
 
    public double getRatio()
    {
        return sellingPrice / buyingPrice;
    }
 
    private PropertyChangeSupport _bindable_getPropertyChangeSupport()
    {
        if(_bindable_propertyChangeSupport == null)
            _bindable_propertyChangeSupport = new PropertyChangeSupport(this);
        return _bindable_propertyChangeSupport;
    }
 
    public void addPropertyChangeListener(String s, PropertyChangeListener propertychangelistener)
    {
        _bindable_getPropertyChangeSupport().addPropertyChangeListener(s, propertychangelistener);
    }
 
    public void addPropertyChangeListener(PropertyChangeListener propertychangelistener)
    {
        _bindable_getPropertyChangeSupport().addPropertyChangeListener(propertychangelistener);
    }
 
    public void removePropertyChangeListener(String s, PropertyChangeListener propertychangelistener)
    {
        _bindable_getPropertyChangeSupport().removePropertyChangeListener(s, propertychangelistener);
    }
 
    public void removePropertyChangeListener(PropertyChangeListener propertychangelistener)
    {
        _bindable_getPropertyChangeSupport().removePropertyChangeListener(propertychangelistener);
    }
 
    private void _bindable_beforeDependsOn_setSellingPrice()
    {
        _bindable_setSellingPrice_ratio = getRatio();
    }
 
    private void _bindable_afterDependsOn_setSellingPrice()
    {
        _bindable_getPropertyChangeSupport().firePropertyChange("ratio", Double.valueOf(_bindable_setSellingPrice_ratio), Double.valueOf(getRatio()));
    }
 
    private void _bindable_beforeDependsOn_setBuyingPrice()
    {
        _bindable_setBuyingPrice_ratio = getRatio();
    }
 
    private void _bindable_afterDependsOn_setBuyingPrice()
    {
        _bindable_getPropertyChangeSupport().firePropertyChange("ratio", Double.valueOf(_bindable_setBuyingPrice_ratio), Double.valueOf(getRatio()));
    }
 
}

Use another Java Agent

Only one Java Agent can be filled into JVM parameter -javaagent. If you wish using another Java Agent (like spring-agent.jar), the only thing to do is to find a mean to get the instance of Instrumentation and initialize Pojo Bindable BEFORE loading Class model wich must be transformed.

// Initialize Bindable.
static {
	// Get Instrumentation instance from 
	Instrumentation instrumentation = ???
    // Add it Bindable
	// ClassFileTransformer for Domain POJO.
	BindableStrategy bindableStrategy = new DefaultBindableStrategy( new String[] { "org.eclipse.core.tests.databinding.pojo.bindable.domain" });
	BindableHelper.initialize(bindableStrategy,	instrumentation);
}

Spring-agent provides for instance InstrumentationSavingAgent :

package org.springframework.instrument;
 
import java.lang.instrument.Instrumentation;
 
public class InstrumentationSavingAgent {
 
	private static volatile Instrumentation instrumentation;
 
	/**
	 * Save the {@link Instrumentation} interface exposed by the JVM.
	 */
	public static void premain(String agentArgs, Instrumentation inst) {
		instrumentation = inst;
	}
 
	public static Instrumentation getInstrumentation() {
		return instrumentation;
	}
 
}

Pojo Bindable include this class, and Instrumentation instance can be getted with the code :

Instrumentation instrumentation = InstrumentationSavingAgent.getInstrumentation();

Once you have the instance you can do :

// Initialize Bindable.
static {
	// Get Instrumentation instance from 
	Instrumentation instrumentation = InstrumentationSavingAgent.getInstrumentation();
    // Add it Bindable
	// ClassFileTransformer for Domain POJO.
	BindableStrategy bindableStrategy = new DefaultBindableStrategy( new String[] { "org.eclipse.core.tests.databinding.pojo.bindable.domain" });
	BindableHelper.initialize(bindableStrategy,	instrumentation);
}

You can use too SpringInstrumentationProvider from Pojo Bindable like this :

// Initialize Bindable.
static {
	// Get Instrumentation instance from spring-agent and add it Bindable
	// ClassFileTransformer for Domain POJO.
	BindableStrategy bindableStrategy = new DefaultBindableStrategy( new String[] { "org.eclipse.core.tests.databinding.pojo.bindable.domain" });
	BindableHelper.initialize(bindableStrategy,	SpringInstrumentationProvider.INSTANCE);
}

OSGi Context

To manage Pojo Bindable into OSGi context, please see Pojo Bindable SVN. It Following the same idea that EclipseLink/Examples/OSGi/Equinox Byte Code Weaving.