/* * @(#)BeansBinding.java * * Copyright (c) 2013 The authors and contributors of JHotDraw. * * You may not use, copy or modify this file, except in compliance with the * license agreement you entered into with the copyright holders. For details * see accompanying license terms. */ package org.jhotdraw.beans; import javax.annotation.Nullable; import java.beans.IntrospectionException; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; /** * Can bind a property of a JavaBean to a property of another JavaBean. <p> The * binding can be unidirectional or bidirectional. * * * @author Werner Randelshofer * @version 1.0 2013-06-13 Created. */ public class BeansBinding { private @Nullable String sourceProperty; private @Nullable String targetProperty; private @Nullable Object source; private @Nullable Object target; private boolean bidirectional; private @Nullable Method targetWriteMethod; private @Nullable Method sourceWriteMethod; private @Nullable Method sourceReadMethod; private class Handler implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getSource() == source) { if (sourceProperty.equals(evt.getPropertyName())) { if (target != null) { try { getTargetWriteMethod().invoke(target, evt.getNewValue()); } catch (Exception ex) { InternalError ie = new InternalError("Could not set property \"" + targetProperty + "\" on " + target); ie.initCause(ex); throw ie; } } } } else if (bidirectional && evt.getSource() == target) { if (targetProperty.equals(evt.getPropertyName())) { if (source != null) { try { getSourceWriteMethod().invoke(source, evt.getNewValue()); } catch (Exception ex) { InternalError ie = new InternalError("Could not set property \"" + targetProperty + "\" on " + target); ie.initCause(ex); throw ie; } } } } } } private Handler handler = new Handler(); /** * Creates a bidirectional binding from a source bean to a target bean. * Updates the value of the target bean. * * @param source The source bean. * @param sourceProperty The name of the source property. * @param target The target bean. * @param targetProperty The name of the target property. */ public void bind(@Nullable Object source, String sourceProperty, @Nullable Object target, String targetProperty) { setSource(source, sourceProperty); setTarget(target, targetProperty); bidirectional = true; updateTarget(); } /** Removes the binding. */ public void unbind() { setSource(null, sourceProperty); setTarget(null, targetProperty); } private void addPropertyChangeListener(Object bean, PropertyChangeListener listener) { try { Method m = bean.getClass().getMethod("addPropertyChangeListener", PropertyChangeListener.class); m.invoke(bean, listener); } catch (Exception ex) { InternalError ie = new InternalError("Could not add property change listener to " + bean); ie.initCause(ex); throw ie; } } private void removePropertyChangeListener(Object bean, PropertyChangeListener listener) { try { Method m = bean.getClass().getMethod("removePropertyChangeListener", PropertyChangeListener.class); m.invoke(bean, listener); } catch (Exception ex) { InternalError ie = new InternalError("Could not remove property change listener from " + bean); ie.initCause(ex); throw ie; } } /** * Sets the source bean. * * @param source * @param sourceProperty */ private void setSource(@Nullable Object source, String sourceProperty) { if (this.source != null) { removePropertyChangeListener(this.source, handler); } this.source = source; this.sourceProperty = sourceProperty; sourceWriteMethod = null; sourceReadMethod = null; if (this.source != null) { addPropertyChangeListener(this.source, handler); } } private void setTarget(@Nullable Object target, String targetProperty) { if (this.target != null) { removePropertyChangeListener(this.target, handler); } this.target = target; this.targetProperty = targetProperty; targetWriteMethod = null; if (this.target != null) { addPropertyChangeListener(this.target, handler); } } public void updateTarget() { try { Object value = getSourceReadMethod().invoke(source); getTargetWriteMethod().invoke(target, value); } catch (Exception ex) { InternalError ie = new InternalError("Could not update target from source."); ie.initCause(ex); throw ie; } } private Method getTargetWriteMethod() { if (targetWriteMethod == null) { try { PropertyDescriptor pd = new PropertyDescriptor(targetProperty, target.getClass()); targetWriteMethod = pd.getWriteMethod(); } catch (IntrospectionException ex) { InternalError ie = new InternalError("Could not create target property descriptor for " + target); ie.initCause(ex); throw ie; } } return targetWriteMethod; } private Method getSourceWriteMethod() { if (sourceWriteMethod == null) { try { PropertyDescriptor pd = new PropertyDescriptor(sourceProperty, source.getClass()); sourceWriteMethod = pd.getWriteMethod(); } catch (IntrospectionException ex) { InternalError ie = new InternalError("Could not create source property descriptor for " + source); ie.initCause(ex); throw ie; } } return sourceWriteMethod; } private Method getSourceReadMethod() { if (sourceReadMethod == null) { try { PropertyDescriptor pd = new PropertyDescriptor(sourceProperty, source.getClass()); sourceReadMethod = pd.getReadMethod(); } catch (IntrospectionException ex) { InternalError ie = new InternalError("Could not create source property descriptor for " + source); ie.initCause(ex); throw ie; } } return sourceReadMethod; } }