Gesticulations and Other Geekery » Page 'Metawidget BeanUtils auto-update binding'

Metawidget BeanUtils auto-update binding

I have been using Metawidget (which is a really cool library by the way) on a project lately. I wanted to avoid the need for a save button by having changes automatically propagated to the beans when changes are made to the widgets. However I am using BeanUtils (from apache) which does not support binding internally.

I did try BeanBinding but it was really slow (I don’t know why and I didn’t have time to debug it). So I created an extension to the BeanUtilsBinding class (from Metawidget) that adds auto-save support. It’s a really ugly hackish implementation but it works reliably (though I think there may be corner cases where the save does not trigger when it should). It is also quite fast. It doesn’t run the save until all the messages currently in the swing queue are processed (using SwingUtilities.invokeLater) and then it only runs it once so even if a lot of changes happen at the same time only one save will be done.

The implementation is below. Feel free to use it. It is copyright Arthur Peters under the GNU GPL v3.


import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.metawidget.swing.SwingMetawidget;
import org.metawidget.swing.propertybinding.beanutils.BeanUtilsBinding;

public class BeanUtilsAutoUpdateBinding extends BeanUtilsBinding {
	@SuppressWarnings("unused")
	private static final Logger log = Logger
			.getLogger("BeanUtilsAutoUpdateBinding");

	protected static final class ListenToRecord {
		private final Component component;
		private final String propertyName;

		public ListenToRecord(Component component, String propertyName) {
			this.component = component;
			this.propertyName = propertyName;
		}

		public Component getComponent() {
			return component;
		}

		public String getPropertyName() {
			return propertyName;
		}
	}

	private List listeningTo = new ArrayList();

	private ChangeListener changeListener = new ChangeListener() {
		@Override
		public void stateChanged(ChangeEvent e) {
			doSave();
		}
	};

	private ActionListener actionListener = new ActionListener() {
		@Override
		public void actionPerformed(ActionEvent e) {
			doSave();
		}
	};
	/*private DocumentListener documentListener = new DocumentListener() {
		@Override
		public void changedUpdate(DocumentEvent e) {
			doSave();
		}

		@Override
		public void insertUpdate(DocumentEvent e) {
			doSave();
		}

		@Override
		public void removeUpdate(DocumentEvent e) {
			doSave();
		}
	};
	*/
	private KeyListener keyListener = new KeyListener() {
		@Override
		public void keyPressed(KeyEvent e) {
			doSave();
		}

		@Override
		public void keyReleased(KeyEvent e) {
			doSave();
		}

		@Override
		public void keyTyped(KeyEvent e) {
			doSave();
		}

	};
	private MouseListener mouseListener = new MouseListener() {
		@Override
		public void mouseClicked(MouseEvent e) {
			doSave();
		}

		@Override
		public void mouseEntered(MouseEvent e) {
			// Ignore
		}

		@Override
		public void mouseExited(MouseEvent e) {
			// Ignore
		}

		@Override
		public void mousePressed(MouseEvent e) {
			// Ignore
		}

		@Override
		public void mouseReleased(MouseEvent e) {
			doSave();
		}
	};
	private FocusListener focusListener = new FocusListener() {
		@Override
		public void focusGained(FocusEvent e) {
			// Ignore
		}

		@Override
		public void focusLost(FocusEvent e) {
			doSave();
		}
	};

	private boolean saveAlreadyInEventQueue;

	public BeanUtilsAutoUpdateBinding(SwingMetawidget metawidget) {
		super(metawidget);
	}

	@Override
	public void bindProperty(Component component,
			Map attributes, String path) {
		super.bindProperty(component, attributes, path);

		String valueProperty = getMetawidget().getValueProperty(component);
		// component.addPropertyChangeListener(valueProperty, this);
		attemptToCall(component, "addChangeListener", ChangeListener.class,
				changeListener);
		attemptToCall(component, "addActionListener", ActionListener.class,
				actionListener);
		// attemptToCall(component, "addDocumentListener",
		// DocumentListener.class, documentListener);
		component.addKeyListener(keyListener);
		component.addMouseListener(mouseListener);
		component.addFocusListener(focusListener);
		listeningTo.add(new ListenToRecord(component, valueProperty));
	}

	private void attemptToCall(Component component, String methodName,
			Class eventListenerCls,
			EventListener eventListener) {

		try {
			Class cls = component.getClass();
			Method method = null;
			while (method == null && cls != Object.class) {
				try {
					method = cls.getMethod(methodName, eventListenerCls);
				} catch (NoSuchMethodException e) {
					// Ignore
				}
				cls = cls.getSuperclass();
			}
			method.invoke(component, eventListener);
		} catch (Exception e) {
			// Ignore it and return. This function is an attempt.
			log.log(Level.FINE, "Failed to call " + methodName + " on "
					+ component, e);
		}
	}

	@Override
	public void unbindProperties() {
		super.unbindProperties();

		for (ListenToRecord listenToRecord : listeningTo) {
			Component component = listenToRecord.getComponent();
			// component.removePropertyChangeListener(listenToRecord.getPropertyName(),
			// this);
			component.removeKeyListener(keyListener);
			component.removeMouseListener(mouseListener);
			attemptToCall(component, "removeChangeListener",
					ChangeListener.class, changeListener);
			attemptToCall(component, "removeActionListener",
					ActionListener.class, actionListener);
			// attemptToCall(component, "removeDocumentListener",
			// DocumentListener.class, documentListener);
		}
		listeningTo.clear();
	}

	protected void doSave() {
		synchronized (this) {
			if (!saveAlreadyInEventQueue) {
				saveAlreadyInEventQueue = true;
				SwingUtilities.invokeLater(new Runnable() {
					@Override
					public void run() {
						getMetawidget().save();
						synchronized (BeanUtilsAutoUpdateBinding.this) {
							saveAlreadyInEventQueue = false;
						}
					}
				});
			}
		}
	}
}
Posted in Java, Programming

Leave a comment