Java tutorial
/* * Copyright (c) 2012 Diamond Light Source Ltd. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.dawnsci.common.richbeans.beans; import java.beans.XMLDecoder; import java.beans.XMLEncoder; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.beanutils.BeanUtils; import org.dawnsci.common.richbeans.components.selector.ListEditor; import org.dawnsci.common.richbeans.event.ValueListener; /** * Class concerned with sending state between beans and ui. It does this through the IFieldWidget interface. Initially * the design was to have no IFieldWidget and for BeanUI to synchonize and object but this lead to a complex and * confused design of BeanUI but more importantly of RichBeanEditorPart implementations. Now a IFieldWidget must * existing for each field in the bean mapping to each widget. * * @author Matthew Gerring */ public class BeanUI { // private static Logger logger = LoggerFactory.getLogger(BeanUI.class); /** * NOTE: The order of the arguments. The first object is the bean, the second object is the uiObject which we are * going to look for getters and setters. * * @param bean * @param uiObject * @throws Exception * @throws InvocationTargetException * @throws IllegalAccessException * @throws IllegalArgumentException */ public static void beanToUI(final Object bean, final Object uiObject) throws Exception { BeanUI.notify(bean, uiObject, new BeanProcessor() { @Override public void process(String name, Object value, IFieldWidget box) throws Exception { box.setFieldName(name); box.setValue(value); } @Override public boolean requireValue() { return true; } }); } /** * Call to fire all value listeners * * @param bean * @param uiObject * @throws Exception */ public static void fireValueListeners(final Object bean, final Object uiObject) throws Exception { BeanUI.notify(bean, uiObject, new BeanProcessor() { @Override public void process(String name, Object unused, IFieldWidget box) throws Exception { box.fireValueListeners(); } }); } /** * Call to fire all value listeners * * @param bean * @param uiObject * @throws Exception */ public static void fireBoundsUpdaters(final Object bean, final Object uiObject) throws Exception { BeanUI.notify(bean, uiObject, new BeanProcessor() { @Override public void process(String name, Object unused, IFieldWidget box) throws Exception { box.fireBoundsUpdaters(); } }); } /** * NOTE: The order of the arguments. The first object is the uiobject, the second object is the bean which we are * going to set properties from the UI with. * * @param bean * @param uiObject * @throws Exception * @throws InvocationTargetException * @throws IllegalAccessException * @throws IllegalArgumentException */ public static void uiToBean(final Object uiObject, final Object bean) throws Exception { BeanUI.notify(bean, uiObject, new BeanProcessor() { @Override public void process(String name, Object unused, IFieldWidget box) throws Exception { final Object ob = box.getValue(); if (ob != null && !isNaN(ob) && !isInfinity(ob)) { setValue(bean, name, ob); } } }); } /** * Set the value of a single field specified by field name in the bean from the ui. * * @param uiObject * @param bean * @param fieldName * @throws Exception */ public static void uiToBean(final Object uiObject, final Object bean, final String fieldName) throws Exception { if (fieldName == null) throw new Exception("Null fieldName passed to uiToBean. Please set the field name."); final IFieldWidget box = BeanUI.getFieldWidget(fieldName, uiObject); if (box == null) return; // Not all properties have to be in the UI. if (!box.isActivated()) return; final Object ob = box.getValue(); if (ob != null && !isNaN(ob) && !isInfinity(ob)) { BeansFactory.setBeanValue(bean, fieldName, ob); } else { // Required to fix fields inside a list editor being edited to no value. if (ob != null) { final Method setter = bean.getClass().getMethod(BeansFactory.getSetterName(fieldName), ob.getClass()); setter.invoke(bean, ob.getClass().cast(null)); } } } private static boolean isInfinity(Object ob) { if (!(ob instanceof Double)) return false; return Double.isInfinite(((Double) ob).doubleValue()); } private static boolean isNaN(Object ob) { if (!(ob instanceof Double)) return false; return Double.isNaN(((Double) ob).doubleValue()); } public static void addValueListener(final Object bean, final Object uiObject, final ValueListener listener) throws Exception { addValueListener(bean, uiObject, listener, true); } /** * Add a value listener for the UI objects, if that method exists. * * @param bean * @param uiObject * @param listener * @throws Exception */ public static void addValueListener(final Object bean, final Object uiObject, final ValueListener listener, final boolean recursive) throws Exception { BeanUI.notify(bean, uiObject, new BeanProcessor() { @Override public void process(String name, Object unused, IFieldWidget box) throws Exception { if (!recursive && box instanceof ListEditor) return; box.addValueListener(listener); } }); } /** * Removes a value listener for the UI objects, if that method exists. * * @param bean * @param uiObject * @param listener * @throws Exception */ public static void removeValueListener(final Object bean, final Object uiObject, final ValueListener listener) throws Exception { BeanUI.notify(bean, uiObject, new BeanProcessor() { @Override public void process(String name, Object value, IFieldWidget box) throws Exception { box.removeValueListener(listener); } }); } /** * Holds the cached existing widgets for editing fields in beans. */ private static Map<String, IFieldWidget> cachedWidgets; /** * Because of Lazy initiation some fields that will exist may not exists at the point at which we wish to listen to * them. Therefore a queue of listeners is kept. These are added to the widget and removed from the queue when and * if the widget is created. */ private static Map<String, Collection<ValueListener>> waitingListeners; /** * You can record widgets associated with editing a particular bean field here. They are then available to be * listened to by other parts of the user interface. However all the field editors are recorded in a map so care * should be taken (memeory leak etc). Editors of lists like BeanListEditor have the same field to edit multiple * objects. So although you could add a listener to the field you cannot be sure which actual XML entry it is * currently dealing with. * * @param bean * @param uiObject * @throws Exception */ public static void setBeanFields(final Object bean, final Object uiObject) throws Exception { BeanUI.notify(bean, uiObject, new BeanProcessor() { @Override public void process(String name, Object unused, IFieldWidget box) throws Exception { addBeanField(bean.getClass(), name, box); } }); } /** * You can add a field associated with a bean (even if it is viewing a property and not actually editing one). All * editing fields are added through reflection with setBeanFields(...) however some are not fields and still can be * listened to. * * @param beanClazz * @param fieldName * @param box */ public static void addBeanField(Class<? extends Object> beanClazz, String fieldName, final IFieldWidget box) { fieldName = fieldName.substring(0, 1).toLowerCase(Locale.US) + fieldName.substring(1); if (cachedWidgets == null) cachedWidgets = new ConcurrentHashMap<String, IFieldWidget>(89); final String id = beanClazz.getName() + ":" + fieldName; cachedWidgets.put(id, box); if (waitingListeners != null) { final Collection<ValueListener> listeners = waitingListeners.get(id); if (listeners != null) { for (ValueListener valueListener : listeners) box.addValueListener(valueListener); waitingListeners.remove(id); } } } /** * Adds a value listener for the given class and field. Throws an exception if the class and field is not recorded * as having a UI editor at the moment. * * @param beanClass * @param fieldName * @param listener */ public static void addBeanFieldValueListener(final Class<? extends Object> beanClass, String fieldName, final ValueListener listener) { fieldName = fieldName.substring(0, 1).toLowerCase(Locale.US) + fieldName.substring(1); final String id = beanClass.getName() + ":" + fieldName; final IFieldWidget box = (cachedWidgets != null) ? cachedWidgets.get(id) : null; if (box == null) { if (waitingListeners == null) waitingListeners = new ConcurrentHashMap<String, Collection<ValueListener>>(31); Collection<ValueListener> listeners = waitingListeners.get(id); if (listeners == null) { listeners = new HashSet<ValueListener>(3); waitingListeners.put(id, listeners); } listeners.add(listener); return; } box.addValueListener(listener); } /** * Attempts to retrieve the widget for editing the given field. The widget may not have been created yet, in which * case returns null. * * @param beanClasses * @param fieldName * @return IFieldWidget */ @SafeVarargs // Safe because we only use the names of the bean classes public static IFieldWidget getBeanField(String fieldName, final Class<? extends Object>... beanClasses) { fieldName = fieldName.substring(0, 1).toLowerCase(Locale.US) + fieldName.substring(1); for (int i = 0; i < beanClasses.length; i++) { final String id = beanClasses[i].getName() + ":" + fieldName; final IFieldWidget box = (cachedWidgets != null) ? cachedWidgets.get(id) : null; if (box != null) return box; } return null; } /** * Used to switch all ui controls on or off. * * @param bean * @param uiObject * @param on * @throws Exception * @throws InvocationTargetException * @throws IllegalAccessException * @throws IllegalArgumentException */ public static void switchState(final Object bean, final Object uiObject, final boolean on) throws Exception { BeanUI.notify(bean, uiObject, new BeanProcessor() { @Override public void process(String name, Object unused, IFieldWidget box) { if (on) { box.on(); } else { box.off(); } } }); } /** * Attempts to set any IFieldWidgets available from getter methods on. * * @param uiObject * @param on */ public static void switchState(Object uiObject, boolean on) throws Exception { final Method[] methods = uiObject.getClass().getMethods(); for (int i = 0; i < methods.length; i++) { final Method m = methods[i]; if (m.getReturnType() != null && IFieldWidget.class.isAssignableFrom(m.getReturnType()) && m.getName().startsWith("get") && m.getParameterTypes().length == 0) { final Object ob = m.invoke(uiObject); if (ob instanceof IFieldWidget) { final IFieldWidget box = (IFieldWidget) ob; if (on) { box.on(); } else { box.off(); } } } } } /** * @param bean * @param uiObject * @param isEnabled * @throws Exception */ public static void setEnabled(final Object bean, final Object uiObject, final boolean isEnabled) throws Exception { BeanUI.notify(bean, uiObject, new BeanProcessor() { @Override public void process(String name, Object unused, IFieldWidget box) { box.setEnabled(isEnabled); } }); } public static void dispose(final Object bean, final Object uiObject) throws Exception { BeanUI.notify(bean, uiObject, new BeanProcessor() { @Override public void process(String name, Object unused, IFieldWidget box) { box.dispose(); } }); } public final static void notify(final Object bean, final Object uiObject, final BeanProcessor worker) throws Exception { final Map<String, String> properties = BeanUtils.describe(bean); final Iterator<String> it = properties.keySet().iterator(); final Collection<String> names = BeanUI.getEditingFields(bean, uiObject); while (it.hasNext()) { final String fieldName = it.next(); if (names.contains(fieldName)) { if (fieldName.equals("class")) continue; final IFieldWidget box = BeanUI.getFieldWidget(fieldName, uiObject); // NOTE non-IFieldWidget fields will be ignored. if (box != null) { final Object val = worker.requireValue() ? getValue(bean, fieldName) : null; worker.process(fieldName, val, box); } } } } private static Object getValue(Object bean, String fieldName) throws Exception { final String getter = BeansFactory.getGetterName(fieldName); try { return bean.getClass().getMethod(getter).invoke(bean); } catch (java.lang.NoSuchMethodException ne) { final String isser = BeansFactory.getIsserName(fieldName); return bean.getClass().getMethod(isser).invoke(bean); } } protected static void setValue(Object bean, String fieldName, Object ob) throws Exception { final String setter = BeansFactory.getSetterName(fieldName); Method method = null; try { method = bean.getClass().getMethod(setter, ob.getClass()); } catch (java.lang.NoSuchMethodException ne) { final Class<?> clazz = ob.getClass(); try { if (Double.class.isAssignableFrom(clazz)) { method = bean.getClass().getMethod(setter, new Class[] { double.class }); } else if (Float.class.isAssignableFrom(clazz)) { method = bean.getClass().getMethod(setter, new Class[] { float.class }); } else if (Long.class.isAssignableFrom(clazz)) { method = bean.getClass().getMethod(setter, new Class[] { long.class }); } else if (Integer.class.isAssignableFrom(clazz)) { method = bean.getClass().getMethod(setter, new Class[] { int.class }); } else if (Boolean.class.isAssignableFrom(clazz)) { method = bean.getClass().getMethod(setter, new Class[] { boolean.class }); } } catch (NoSuchMethodException nsm2) { method = bean.getClass().getMethod(setter, new Class[] { Number.class }); } if (method == null) { final Method[] methods = bean.getClass().getMethods(); for (Method m : methods) { if (m.getName().equals(setter) && m.getParameterTypes().length == 1) { Class<?> type = m.getParameterTypes()[0]; if (!type.isAssignableFrom(clazz)) { continue; } method = m; break; } } } } method.invoke(bean, ob); } public static abstract class BeanProcessor { public abstract void process(String name, Object value, IFieldWidget box) throws Exception; public boolean requireValue() { return false; } } /** * Get the ui field out of the object container. * * @param fieldName * @param uiObject * @return IFieldWidget or null if is not an IFieldWidget instance * @throws Exception * @throws NoSuchMethodException * @throws SecurityException * @throws InvocationTargetException * @throws IllegalAccessException * @throws IllegalArgumentException */ public static IFieldWidget getFieldWidget(final String fieldName, final Object uiObject) throws Exception { final String methodName = BeansFactory.getGetterName(fieldName); final Method getter = uiObject.getClass().getMethod(methodName); final Object box = getter.invoke(uiObject); if (box instanceof IFieldWidget) { return (IFieldWidget) box; } return null; } /** * Method name spelling was corrected to getFieldWidget(). This method with * the old name exists only to avoid breaking existing code and should be * removed once all references to the mis-spelled name have been corrected */ @Deprecated public static IFieldWidget getFieldWiget(final String fieldName, final Object uiObject) throws Exception { return getFieldWidget(fieldName, uiObject); } /** * Creates a new list of cloned beans (deep). * * @param beans * @return list of cloned beans. * @throws Exception */ public static List<?> cloneBeans(final List<?> beans) throws Exception { final List<Object> ret = new ArrayList<Object>(beans.size()); for (Object bean : beans) ret.add(BeansFactory.deepClone(bean)); return ret; } /** * Retrieves a list of fields which are both in the bean and being edited by the user. * * @param editorBean * @param editorUI * @return list of fields * @throws Exception */ public static List<String> getEditingFields(Object editorBean, Object editorUI) throws Exception { final Collection<String> fields = BeanUtils.describe(editorBean).keySet(); final List<String> expressionFields = new ArrayList<String>(fields); expressionFields.remove("class"); for (Iterator<String> it = expressionFields.iterator(); it.hasNext();) { String field = it.next(); try { final IFieldWidget wid = BeanUI.getFieldWidget(field, editorUI); if (wid == null) it.remove(); } catch (Exception ne) { it.remove(); } } return expressionFields; } /** * Bean from string using standard java serialization, useful for tables of beans with serialized strings. Used * externally to the GDA. * * @param xml * @return the bean */ public static Object getBean(final String xml, final ClassLoader loader) throws Exception { final ClassLoader original = Thread.currentThread().getContextClassLoader(); final ByteArrayInputStream stream = new ByteArrayInputStream(xml.getBytes("UTF-8")); try { Thread.currentThread().setContextClassLoader(loader); XMLDecoder d = new XMLDecoder(new BufferedInputStream(stream)); final Object bean = d.readObject(); d.close(); return bean; } finally { Thread.currentThread().setContextClassLoader(original); stream.close(); } } /** * Used externally to the GDA. * * @param bean * @return the string */ public static String getString(Object bean) throws Exception { final ByteArrayOutputStream stream = new ByteArrayOutputStream(); final ClassLoader original = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(bean.getClass().getClassLoader()); XMLEncoder e = new XMLEncoder(new BufferedOutputStream(stream)); e.writeObject(bean); e.close(); return stream.toString("UTF-8"); } finally { Thread.currentThread().setContextClassLoader(original); stream.close(); } } }