JavaFX Programmatic POJO Expression Bindings (Part I)

UPDATE: SEE PART II

 

The Ties That Bind

A common feature found in most UI frameworks is the ability to bind an application’s domain model to a UI control. JavaFX uses the JavaBeans component architecture as a basis to handle bindings. JavaFX bindings provide an abstraction layer that allows properties to be bidirectionally bound. When either one of the property’s value changes the other property will reflect the change. Like many presentation layer implementations, JavaFX provides a means in FXML to bind your own properties to a JavaFX control properties to ensure the property values are the same (similar in syntax to the Expression Language used in JSP).

The Problem

While the JavaBeans API/design pattern provides a clean separation from other business specific data it does however inherit some additional coding requirements in order to provide a relationship between it and an application’s POJOs. Say, for instance, we had a Person POJO that contained multiple fields for contact information.

public class Person {
	private String firstName;
	private String lastName;
	public String getFirstName() {
		return firstName;
	}
	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}
	public String getLastName() {
		return lastName;
	}
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
}

In order to use an instance of a Person and follow the design patterns set forth by JavaFX properties we would need to create another class that would translate each of the Person fields into JavaFX recognizable Properties.

public class PersonProperties {
	private final Person person;
	private StringProperty firstName = new SimpleStringProperty() {
		@Override
		public void set(String v) {
			super.set(v);
			person.setFirstName(v);
		}
	};
	private StringProperty lastName = new SimpleStringProperty() {
		@Override
		public void set(String v) {
			super.set(v);
			person.setLastName(v);
		}
	};
	public PersonProperties(Person person) {
		this.person = person;
	}
	public final String getFirstName() {
		return firstName.get();
	}
	public final void setFirstName(String value) {
		firstName.set(value);
	}
	public StringProperty firstNameProperty() {
		return firstName;
	}
	public final String getLastName() {
		return lastName.get();
	}
	public final void setLastName(String value) {
		lastName.set(value);
	}
	public StringProperty lastNameProperty() {
		return lastName;
	}
}

The Solution

As you can see from the prior Person example the amount of extra code that would need to be generated could easily become very large and difficult to maintain. As mentioned before FXML allows for expression bindings between different properties. For example, we could take a personProperties.firstName property and bind it to a JavaFX control…

<label></label>

If we take a similar approach that FXML uses for expression bindings and apply it in a different manner we could potentially abstract the Person properties into a generic pattern that would walk an expression. Unfortunately, the plumbing FXML uses to accomplish the expression evaluation isn’t completely exposed publicly. However, we can accomplish the same thing using Java 7′s new java.lang.invoke.MethodHandle (we could use the java.lang.reflect API, but this could possibly be much slower).

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Arrays;
import javafx.beans.property.ObjectPropertyBase;

public class PathProperty extends ObjectPropertyBase {
	private final String fieldPath;
	private PropertyMethodHandles propMethHandles;
	private final B bean;

	public PathProperty(final B bean, final String fieldPath, final Class type) {
		super();
		this.bean = bean;
		this.fieldPath = fieldPath;
		try {
			this.propMethHandles = PropertyMethodHandles.build(getBean(), getName());
		} catch (final Throwable t) {
			throw new RuntimeException(String.format(
					"Unable to instantiate expression %1$s on %2$s",
					getBean(), getName()), t);
		}
	}
	@Override
	public void set(T v) {
		try {
			getPropMethHandles().getSetter().invoke(v);
			super.set(v);
		} catch (final Throwable t) {
			throw new RuntimeException("Unable to set value: " + v, t);
		}
	};
	@Override
	public T get() {
		try {
			// TODO : here we are lazily loading the property which will prevent any property listeners
			// from receiving notice of a direct model field change until the next time the get method
			// is called on the PathProperty
			final T prop = (T) getPropMethHandles().getAccessor().invoke();
			if (!super.get().equals(prop)) {
				super.set(prop);
			}
			return super.get();
		} catch (final Throwable t) {
			throw new RuntimeException("Unable to get value", t);
		}
	}
	@Override
	public B getBean() {
		return bean;
	}
	@Override
	public String getName() {
		return fieldPath;
	}
	public PropertyMethodHandles getPropMethHandles() {
		return propMethHandles;
	}

	public static class PropertyMethodHandles {
		private final String fieldName;
		private final MethodHandle accessor;
		private final MethodHandle setter;
		private Object setterArgument;

		protected PropertyMethodHandles(final Object target, final String fieldName,
				final boolean insertSetterArgument) throws NoSuchMethodException {
			this.fieldName = fieldName;
			this.accessor = buildGetter(target, fieldName);
			this.setter = buildSetter(getAccessor(), target, fieldName, insertSetterArgument);
		}
		public static PropertyMethodHandles build(final Object initialTarget,
				final String expString) throws NoSuchMethodException, IllegalStateException {
			final String[] expStr = expString.split("\\.");
			Object target = initialTarget;
			PropertyMethodHandles pmh = null;
			for (int i = 0; i < expStr.length; i++) {
				pmh = new PropertyMethodHandles(target, expStr[i], i < (expStr.length - 1));
				target = pmh.getSetterArgument();
			}
			return pmh;
		}
		protected MethodHandle buildGetter(final Object target, final String fieldName)
						throws NoSuchMethodException {
			final MethodHandle mh = buildAccessor(target, fieldName, "get", "is", "has");
			if (mh == null) {
				throw new NoSuchMethodException(fieldName);
			}
			return mh;
		}
		protected MethodHandle buildSetter(final MethodHandle accessor,
				final Object target, final String fieldName,
				final boolean insertSetterArgument) {
			if (insertSetterArgument) {
				try {
					this.setterArgument = accessor.invoke();
				} catch (final Throwable t) {
					this.setterArgument = null;
				}
				if (getSetterArgument() == null) {
					try {
						this.setterArgument = accessor.type().returnType().newInstance();
					} catch (final Exception e) {
						throw new IllegalArgumentException(
								String.format("Unable to build setter expression for %1$s using %2$s.",
										fieldName, accessor.type().returnType()));
					}
				}
			}
			try {
				final MethodHandle mh1 = MethodHandles.lookup().findVirtual(target.getClass(),
						buildMethodName("set", fieldName),
						MethodType.methodType(void.class,
								accessor.type().returnType())).bindTo(target);
				if (getSetterArgument() != null) {
					mh1.invoke(getSetterArgument());
				}
				return mh1;
			} catch (final Throwable t) {
				throw new IllegalArgumentException("Unable to resolve setter "
						+ fieldName, t);
			}
		}
		protected static MethodHandle buildAccessor(final Object target,
				final String fieldName, final String... fieldNamePrefix) {
			final String accessorName = buildMethodName(fieldNamePrefix[0], fieldName);
			try {
				return MethodHandles.lookup().findVirtual(target.getClass(), accessorName,
						MethodType.methodType(
								target.getClass().getMethod(
										accessorName).getReturnType())).bindTo(target);
			} catch (final NoSuchMethodException e) {
				return fieldNamePrefix.length 					buildAccessor(target, fieldName,
						Arrays.copyOfRange(fieldNamePrefix, 1,
								fieldNamePrefix.length));
			} catch (final Throwable t) {
				throw new IllegalArgumentException(
						"Unable to resolve accessor " + accessorName, t);
			}
		}
		public static String buildMethodName(final String prefix,
				final String fieldName) {
			return (fieldName.startsWith(prefix) ? fieldName : prefix +
				fieldName.substring(0, 1).toUpperCase() +
					fieldName.substring(1));
		}
		public String getFieldName() {
			return fieldName;
		}
		public MethodHandle getAccessor() {
			return accessor;
		}
		public MethodHandle getSetter() {
			return setter;
		}
		public Object getSetterArgument() {
			return setterArgument;
		}
	}
}

Now with the above PathProperty we create bindings on the fly by passing a path expression string into it’s constructor. The path will be traversed until the last field is reached. It supports the instantiation of intermediate objects found while walking the path expression (as long as the class type of that object contains a no-argument constructor). So, revisiting the Person class, if we add another field called address that takes an Address class with an accessible getStreetName method we can do the following:

Person person = new Person();
PathProperty prop = new PathProperty(
				person, "address.streetName", String.class);
// bind it to a JavaFX control
Bindings.bindBidirectional(prop, myTextField.textProperty());

// apply value to the JavaFX property and verify the same value exists in the model
prop.set("123 1st Street");
System.out.println("Address Property: " + prop.get());
System.out.println("Address POJO: " + person.getAddress().getStreetName());
// apply value to the POJO field and verify the same value exists in the JavaFX property
person.getAddress().setStreetName("456 2nd Street");
System.out.println("Address Property: " + prop.get());
System.out.println("Address POJO: " + person.getAddress().getStreetName());

In A Bind

As you can see the values are virtually bound bidirectionally between the JavaFX property and the POJO. Any calls to PathProperty#set(T v) will dispatch the required events that allow JavaFX to reflect those changes in the bound property of the control. However, there are some drawbacks to this approach. You may have noticed the TODO in the PathProperty#get() method shown above. Due to Java’s lack of native dependency injection instrumentation without a Java agent or byte-code manipulation any listeners that are listening to a PathProperty will not be notified of changes made directly to the POJO until the next time the PathProperty#get() method is called. Lazy loading of property values is really less than adequate for most applications where domain POJOs are being altered outside the scope of JavaFX.

About these ads

About ugate
UGate is a fully open source solution for a do-it-yourself configurable indoor/outdoor security system. It's built with Arduino Uno32 (ChipKIT) + JavaFX and a number of other leading software/hardware technologies. Our goal is to provide individuals with access to the most popular hardware sensor technology used in industrial today. Our attempt is to bridge the gap between the hardware and software in an extendable and intuitive GUI interface that is free and open to the public. Visit our code page at: http://ugate.org

14 Responses to JavaFX Programmatic POJO Expression Bindings (Part I)

  1. Pingback: JavaFX Programmatic POJO Expression Bindings (Part II) « UGate

  2. tbeernot says:

    Looks very good! In order to prevent not compiler checked path-strings all over the place, a good step would be to put those string in one place, but that could be expanded to a wrapped class that takes a person and sets up a bunch of those PathProperty methods, e.g. “streetNameProperty()”, which would mimick JavaFX more closely. Or should one go for final public variables, e.g. “streetNameProperty” because it involves less scaffolding?

    • ugate says:

      What I ended up doing is creating an enum for each top level class so the path strings would at least be in one central location. I actually went a step further and abstracted the PathProperty portion in part 2.

      public enum ActorType implements IModelType<Actor> {
              USERNAME("login"),
              PASSWORD("pwd"),
              USE_METRIC("host.useMetric"),
              HOST_COM_ADDY("host.comAddress"),
              HOST_COM_PORT("host.comPort"),
              HOST_BAUD_RATE("host.comBaud"),
              MAIL_RECIPIENTS("host.mailRecipients"),
              MAIL_SMTP_HOST("host.mailSmtpHost"),
              MAIL_SMTP_PORT("host.mailSmtpPort"),
              MAIL_IMAP_HOST("host.mailImapHost"),
              MAIL_IMAP_PORT("host.mailImapPort"),
              MAIL_INBOX_NAME("host.mailInboxName"),
              MAIL_USERNAME("host.mailUserName"),
              MAIL_PASSWORD("host.mailPassword"),
              WEB_HOST("host.webHost"),
              WEB_PORT("host.webPort");
      
              public final String key;
      
              private ActorType(final String key) {
                      this.key = key;
              }
      
              public String getKey() {
                      return this.key;
              }
      }
      
  3. Hi, how would you go about binding a Collection (List) of POJOs?

    • ugate says:

      Part 2 has a more robust version of POJO binding that may answer your question? If you have a list of POJOs you can create as many BeanPathAdapter instances for each of the POJOs you need to display at one time. You can also reuse a BeanPathAdapter if all you need is to display fields of one POJO at a time, but would like to switch between a list of POJOs (see example in part 2).

      • Sorry for not being concise enough. I was thinking more on binding the TableView’s or ListView’s items property to a List. For example, if a Person can have zero or more Phones (0:n cardinality, represented by an List in the Person POJO), and I want to bind JavaFX ListView to those Phones bidirectionally? Would it be possible for me to do this:
        personPA.bindBidirectional("phones", phonesListView.itemsProperty());

      • ugate says:

        Currently, there isn’t a way to bind collection properties to POJO fields. I’m working on a solution that will allow for BeanPathAdapter#bindBidirectional(String, Property<ObservableList>, Class), BeanPathAdapter#bindBidirectional(String, Property<ObservableSet>, Class), and BeanPathAdapter#bindBidirectional(String, Property<ObservableMap>, Class, Class). I’ll post the results once completed.

      • ugate says:

        There’s now a way to bind collections/maps… see Part III

      • Thanks, I was just checking it out! :) I’ll make sure to give some feedback when I’m done.

      • OK, thank you very much, what you’ve done so far is excellent. BTW, are you aware of the javafx.beans.property.adapter package in JavaFX 2.1?

      • ugate says:

        I chose not to use javafx.beans.property.adapter so I could take advantage of Java 7′s MethodHandles which are much faster and less memory intensive than the reflection API that is used in javafx.beans.property.adapter.

  4. Pingback: JavaFX Programmatic POJO Expression Bindings (Part III) « UGate

  5. Niklas Skeppstedt says:

    Hi, I have had great use of the BeanPathAdapter but I have a question. Is there someway of using the BeanPathAdapter for uni-directional binding for read only properties? When we try to bind to a pojo that lacks a setter for the bound property I get an error that there is no setter method for the property. Is there something I have missed here?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: