de.xirp.plugin.ViewerBase.java Source code

Java tutorial

Introduction

Here is the source code for de.xirp.plugin.ViewerBase.java

Source

/** 
 * ============================================================================
 * Xirp 2: eXtendable interface for robotic purposes.
 * ============================================================================
 * 
 * Copyright (C) 2005-2007, by Authors and Contributors listed in CREDITS.txt
 * 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at:
 *
 *             http://www.opensource.org/licenses/cpl1.0.php
 *
 * ----------------------------
 * ViewerBase.java
 * ----------------------------
 *
 * Original Author:  Rabea Gransberger [rgransberger AT web.de]
 * Contributor(s):   
 *
 * Changes
 * -------
 * 14.09.2006:      Created by Rabea Gransberger.
 */
package de.xirp.plugin;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.*;

import de.xirp.ui.util.SWTUtil;
import de.xirp.ui.widgets.custom.XComposite;
import de.xirp.ui.widgets.custom.XStyledSpinner;
import de.xirp.util.Constants;
import de.xirp.util.I18n;
import de.xirp.util.Util;
import de.xirp.util.collections.MultiValueHashMap;

/**
 * Base class for UI implementations which are split from the
 * underlying data. This class handles the synchronization between UI
 * and data with different class types almost automatically.
 * 
 * @author Rabea Gransberger
 */
public class ViewerBase extends XComposite implements PropertyChangeListener {

    /**
     * log4j Logger of this Class
     */
    private static Logger logClass = Logger.getLogger(ViewerBase.class);

    /**
     * Key under which the setter method is saved in the properties of
     * the widget
     */
    private static final String SETTER_KEY = "setterMethod"; //$NON-NLS-1$

    /**
     * Prefix for a setter method ({@value})
     */
    private static final String SET_PREFIX = "set"; //$NON-NLS-1$

    /**
     * Prefix for a getter method ({@value})
     */
    private static final String GET_PREFIX = "get"; //$NON-NLS-1$
    /**
     * Prefix for a getter method ({@value})
     */
    private static final String IS_PREFIX = "is"; //$NON-NLS-1$
    /**
     * The data which the GUI should synchronize with
     */
    private AbstractData data;

    /**
     * Mapping from setter to controls
     */
    private MultiValueHashMap<String, Control> setter = new MultiValueHashMap<String, Control>();

    /**
     * Extension instance for update, selection and conversion
     */
    private ViewerBaseExtensor extensor = new ViewerBaseExtensor();

    /**
     * Constructs a new instance for synchronizing UI and data
     * 
     * @param parent
     *            the parent composite of this composite
     * @param style
     *            the style for this composite
     * @param data
     *            data to synchronize with
     */
    protected ViewerBase(Composite parent, int style, AbstractData data) {
        super(parent, style);
        this.data = data;
    }

    /**
     * Checks if a method with the given name exists in the data of
     * this instance
     * 
     * @param name
     *            the name of the method
     * @return <code>true</code> if the method exists
     */
    private boolean methodExists(String name) {
        if (data == null) {
            return false;
        }
        try {
            @SuppressWarnings("unused")
            Method method = data.getClass().getMethod(name, new Class[] {});
        } catch (SecurityException e) {
            return false;
        } catch (NoSuchMethodException e) {
            return false;
        }
        return true;
    }

    /**
     * Gets the value for a given getter from the data
     * 
     * @param getterName
     *            the name of the getter in the data
     * @return the value or <code>null</code> if the method was not
     *         found or another problem occurred.
     */
    private Object get(String getterName) {
        try {
            Method method = data.getClass().getMethod(getterName, new Class[0]);
            Object test = method.invoke(data, new Object[0]);

            return test;
        } catch (SecurityException e) {
            logClass.error("Error: " + e.getMessage() + Constants.LINE_SEPARATOR, e); //$NON-NLS-1$
        } catch (NoSuchMethodException e) {
            logClass.error("Error: " + e.getMessage() + Constants.LINE_SEPARATOR, e); //$NON-NLS-1$
        } catch (IllegalArgumentException e) {
            logClass.error("Error: " + e.getMessage() + Constants.LINE_SEPARATOR, e); //$NON-NLS-1$
        } catch (IllegalAccessException e) {
            logClass.error("Error: " + e.getMessage() + Constants.LINE_SEPARATOR, e); //$NON-NLS-1$
        } catch (InvocationTargetException e) {
            logClass.error("Error: " + e.getMessage() + Constants.LINE_SEPARATOR, e); //$NON-NLS-1$
        }

        return null;
    }

    /**
     * Synchronize the control with the named property of the data.<br/>
     * This is shorthand for<br>
     * <code>sync(control, {@value #SET_PREFIX}+propertyName, {@value #GET_PREFIX}+propertyName);</code>
     * or<br>
     * <code>sync(control, {@value #SET_PREFIX}+propertyName, {@value #IS_PREFIX}+propertyName);</code>
     * 
     * @param control
     *            the control to sync with data
     * @param propertyName
     *            the property name of a field of the data
     */
    protected void sync(Control control, String propertyName) {
        String uPropertyName = StringUtils.capitalize(propertyName);
        String setterName = SET_PREFIX + uPropertyName;
        String getterName = GET_PREFIX + uPropertyName;
        boolean exists = methodExists(getterName);
        if (!exists) {
            if (methodExists(IS_PREFIX + uPropertyName)) {
                getterName = IS_PREFIX + uPropertyName;
            }
        }

        if (data != null) {
            data.addPropertyChangeListener(propertyName, this);
        }

        setter.put(setterName, control);

        // Add the setter to the control
        control.setData(SETTER_KEY, setterName);

        // determine appropriate listener type for the control
        int listenerType = SWT.Selection;
        if (control instanceof Text) {
            listenerType = SWT.Modify;
        }

        // add the listener
        control.addListener(listenerType, new Listener() {

            public void handleEvent(Event event) {
                // only handle the event if theres no boolean
                // indicating that this event was already handled
                Object obj = event.data;
                if (obj == null || !(obj instanceof Boolean)) {
                    select(event);
                }
            }
        });

        Object value = get(getterName);

        this.propertyChange(new PropertyChangeEvent(data, propertyName, null, value));
    }

    /**
     * Handles the selection event of a control registered for
     * synchronization
     * 
     * @param event
     *            the events data
     */
    private final void select(Event event) {

        Widget widget = event.widget;
        // Get the setter name of the events widget
        String setterName = (String) widget.getData(SETTER_KEY);

        // check if this widget is handled by the extension
        // mechanism for the viewer base
        if (selectExtension(widget, data, setterName)) {
            return;
        }

        // determine the type and value of the new state
        // of the control
        Class<?> clazz = null;
        Object obj = null;

        if (widget instanceof Slider) {
            Slider slider = (Slider) widget;
            obj = slider.getSelection();
            clazz = int.class;
        } else if (widget instanceof Combo) {
            Combo combo = (Combo) widget;
            obj = combo.getText();
            clazz = String.class;
        } else if (widget instanceof Button) {
            Button comp = (Button) widget;
            // not for push, only for radio and toggle
            if (!((comp.getStyle() & SWT.PUSH) != 0)) {
                obj = comp.getSelection();
                clazz = boolean.class;
            }
        } else if (widget instanceof Spinner) {
            Spinner comp = (Spinner) widget;
            obj = comp.getSelection();
            clazz = int.class;
        } else if (widget instanceof XStyledSpinner) {
            XStyledSpinner comp = (XStyledSpinner) widget;
            if (comp.isDouble()) {
                obj = comp.getSelectionDouble();
                clazz = double.class;
            } else {
                obj = comp.getSelection();
                clazz = int.class;
            }
        } else if (widget instanceof Scale) {
            Scale comp = (Scale) widget;
            obj = comp.getSelection();
            clazz = int.class;
        } else if (widget instanceof Text) {
            Text comp = (Text) widget;
            obj = comp.getText();
            clazz = String.class;
        }

        // if the control is supported we should have a value now
        if (obj != null) {
            try {
                // get the setter and invoke with the value
                Method method = data.getClass().getMethod(setterName, clazz);
                method.invoke(data, new Object[] { obj });
            } catch (Exception e) {
                logClass.error("Error: " + e.getMessage() //$NON-NLS-1$
                        + Constants.LINE_SEPARATOR, e);
            }
        }

    }

    /**
     * Converts an object of any type to the given type.</br> The
     * conversion is first tried with the extension, and if this fails
     * with the original method of this class.
     * 
     * @param obj
     *            the object to convert
     * @param clazz
     *            the type to convert to
     * @return the converted object or <code>null</code> if
     *         conversion failed
     */
    private Object convertObject(Object obj, Class<?> clazz) {
        Object converted = convertExtension(obj, clazz);
        if (converted == null) {
            converted = Util.convertObject(obj, clazz);
        }
        return converted;
    }

    /**
     * Sets the given value to the control, by converting it to the
     * type which the control needs
     * 
     * @param control
     *            the control to set the value on
     * @param obj
     *            the value to set
     */
    private void setValue(final Control control, Object obj) {
        if (!SWTUtil.swtAssert(control)) {
            return;
        }

        Method m = null;
        Object converted = null;
        final Event event = new Event();
        event.data = Boolean.FALSE;
        event.widget = control;
        int eventType = -1;

        try {
            m = control.getClass().getMethod("setSelection", new Class[] { int.class }); //$NON-NLS-1$
            converted = convertObject(obj, Integer.class);
            eventType = SWT.Selection;
        } catch (Exception e) {
            // logClass.debug(e.getMessage( ) +
            // Constants.LINE_SEPARATOR, e);
        }

        if (m == null) {
            try {
                m = control.getClass().getMethod("setSelection", new Class[] { boolean.class }); //$NON-NLS-1$
                converted = convertObject(obj, Boolean.class);
                eventType = SWT.Selection;
            } catch (Exception e) {
                // logClass.debug(e.getMessage()
                // + Constants.LINE_SEPARATOR, e);
            }
        }

        if (m == null) {
            try {
                m = control.getClass().getMethod("setText", new Class[] { String.class }); //$NON-NLS-1$
                converted = convertObject(obj, String.class);
                eventType = SWT.Modify;
            } catch (Exception e) {
                // logClass.warn("Warning: " + e.getMessage()
                // + Constants.LINE_SEPARATOR, e);
            }
        }

        if (m != null && converted != null) {
            final Method setter = m;
            final Object value = converted;

            final int type = eventType;

            Display.getDefault().syncExec(new Runnable() {

                public void run() {
                    try {
                        setter.invoke(control, new Object[] { value });
                        control.notifyListeners(type, event);
                    } catch (Exception e) {
                        logClass.error("Error: " + e.getMessage() //$NON-NLS-1$
                                + Constants.LINE_SEPARATOR, e);
                    }
                }

            });
        } else {
            logClass.warn(I18n.getString("ViewerBase.log.noMethodFound") //$NON-NLS-1$
                    + Constants.LINE_SEPARATOR);
        }
    }

    /**
     * The synchronization may be extended through an extensor which
     * will handle the selection event, update and conversion.
     * 
     * @param extensor
     *            the extensor to use. <code>null</code> to reset
     *            the extension to the default (none)
     */
    public void setExtension(ViewerBaseExtensor extensor) {
        if (extensor == null) {
            this.extensor = new ViewerBaseExtensor();
        } else {
            this.extensor = extensor;
        }

    }

    /**
     * Converts an object of any type to the given type.
     * 
     * @param obj
     *            the object to convert
     * @param clazz
     *            the type to convert to
     * @return the converted object or <code>null</code> if
     *         conversion failed
     */
    private Object convertExtension(final Object obj, final Class<?> clazz) {
        return extensor.convertExtension(obj, clazz);
    }

    /**
     * Handles the selection event of a widget and updates the data
     * accordingly.
     * 
     * @param widget
     *            the widget which changed
     * @param data
     *            the data to update
     * @param setterName
     *            the name of the setter to update the data with
     * @return <code>true</code> if the extension updated the data,
     *         <code>false</code> if it failed and would like the
     *         original method to do the job
     */
    private boolean selectExtension(final Widget widget, final AbstractData data, final String setterName) {
        return extensor.selectExtension(widget, data, setterName);
    }

    /**
     * If a property has changed the controls which are referenced by
     * this property are determined and the value of the property is
     * set to them. Controls which are already disposed are ignored.
     * 
     * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
     */
    @Override
    public void propertyChange(PropertyChangeEvent event) {
        String propName = event.getPropertyName();
        String setterName = SET_PREFIX + StringUtils.capitalize(propName);

        List<Control> controls = setter.get(setterName);
        List<Control> delete = new ArrayList<Control>(controls.size());
        for (Control control : controls) {
            if (SWTUtil.swtAssert(control)) {
                Object value = event.getNewValue();
                // if the method returned something
                // update the ui with it
                if (value != null) {
                    setValue(control, value);
                }
            } else {
                // mark disposed controls for deletion
                delete.add(control);
            }
        }
        // Delete disposed controls
        for (Control control : delete) {
            setter.remove(setterName, control);
        }
    }
}