All Downloads are FREE. Search and download functionalities are using the official Maven repository.

no.tornado.databinding.Binding Maven / Gradle / Ivy

Go to download

Swing Data Binding is a powerful, fast, light and simple data binding framework for Java.

The newest version!
package no.tornado.databinding;

import no.tornado.databinding.converter.ConversionException;
import no.tornado.databinding.converter.Converter;
import no.tornado.databinding.converter.ConverterRegistry;
import no.tornado.databinding.status.StatusMonitor;
import no.tornado.databinding.uibridge.UIBridge;
import no.tornado.databinding.uibridge.UIBridgeRegistry;
import no.tornado.databinding.validator.RequiredValidator;
import no.tornado.databinding.validator.ValidationResult;
import no.tornado.databinding.validator.ValidationStrategy;
import no.tornado.databinding.validator.Validator;
import org.apache.commons.beanutils.BeanUtilsBean2;
import org.apache.commons.beanutils.PropertyUtilsBean;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

public class Binding {
    public static final PropertyUtilsBean propertyUtils = BeanUtilsBean2.getInstance().getPropertyUtils();

    private String propertyExpression;
    private UI_TYPE ui;
    private Object model;
    private UIBridge bridge;
    private StatusMonitor statusMonitor;
    private boolean valid = true;

    private BindingStrategy bindingStrategy = BindingStrategy.ONFLUSH;
    private ValidationStrategy validationStrategy = ValidationStrategy.ONCHANGE;

    private List validators = new ArrayList();
    private Converter converter;
    private Class uiValueType;
    private Class modelValueType;
    private BindingGroup bindingGroup;
    private List focusListeners = new ArrayList();
    private List changeListeners = new ArrayList();
    private boolean bound = false;
    private boolean usePropertyChangeSupport = false;
    private PropertyChangeListener modelPropertyChangeListener;

    public Binding(UI_TYPE ui, Object model, String propertyExpression) {
        this.ui = ui;
        this.model = model;
        this.propertyExpression = propertyExpression;
    }

    @SuppressWarnings({"unchecked"})
    public static Binding create(JComponent ui, Object model, String propertyExpression) {
        return new Binding(ui, model, propertyExpression);
    }

    public static Binding create(JComponent ui, String propertyExpression) {
        return new Binding(ui, null, propertyExpression);
    }

    public void flushModelToUI() {
        try {
            bridge.setUIValue(ui, convertForward(getModelValue()));
            // Load "full" object from ui listmodel into model
            if (ui.getClass().equals(JComboBox.class)) {
                JComboBox c = (JComboBox) ui;
                int idx = c.getSelectedIndex();
                if (idx > -1) {
                    Object o = c.getModel().getElementAt(idx);
                    propertyUtils.setNestedProperty(model, propertyExpression, o);
                }
            }
        } catch (Exception ignored) {
        }
    }

    public void flushUIToModel() {
        try {
            doValidate();
            setModelValue(convertReverse(bridge.getUIValue(ui)));
            handleConversionSuccess();
        } catch (ConversionException ex) {
            handleConversionFailed(ex);
        }
    }

    private void setValid(boolean valid) {
        this.valid = valid;
        if (valid)
            bindingGroup.bindingValid(this);
        else
            bindingGroup.bindingInvalid(this);
    }

    private void setModelValue(MODEL_VALUE_TYPE value) throws ConversionException {
        try {
            propertyUtils.setNestedProperty(model, propertyExpression, value);
        } catch (Exception e) {
            throw new ConversionException("Failed to retrieve property from object", e);
        }
    }

    @SuppressWarnings({"unchecked"})
    private MODEL_VALUE_TYPE getModelValue() throws ConversionException {
        try {
            return (MODEL_VALUE_TYPE) propertyUtils.getNestedProperty(model, propertyExpression);
        } catch (Exception e) {
            throw new ConversionException("Failed to retrieve property from object", e);
        }
    }

    public Binding bind() {
        if (bound)
            return this;

        bound = true;

        if (model == null)
            throw new RuntimeException("Missing model!");

        if (propertyExpression == null)
            throw new RuntimeException("No propertyexpression set on binding");

        if (ui == null)
            throw new RuntimeException("No ui component set when when connecting " + propertyExpression);

        bridge = UIBridgeRegistry.getBridge(ui.getClass());

        if (bridge == null)
            throw new RuntimeException("No UIBridge found for " + ui.getClass() + " when connecting " + propertyExpression);

        try {
            this.modelValueType = propertyUtils.getPropertyType(model, propertyExpression);
        } catch (Exception e) {
            throw new RuntimeException("Cannot get model type");
        }

        this.uiValueType = bridge.getUIValueType();

        flushModelToUI();

        configureBindingStrategy();
        configureValidationStrategy();
        configurePropertyChangeSupport();

        return this;
    }

    private void configurePropertyChangeSupport() {
        if (isUsePropertyChangeSupport() && model != null) {
            try {
                modelPropertyChangeListener = new PropertyChangeListener() {
                    public void propertyChange(PropertyChangeEvent evt) {
                        if (evt.getPropertyName().equals(propertyExpression))
                            flushModelToUI();
                    }
                };
                Method addListenerMethod = model.getClass().getMethod("addPropertyChangeListener", PropertyChangeListener.class);
                addListenerMethod.invoke(model, modelPropertyChangeListener);
            } catch (Exception ignored) {

            }
        }
    }

    private void configureValidationStrategy() {
        // Skip validation if conversion happens onchange or onblur, because validation happens in the conversion step as well
        if (bindingStrategy.equals(BindingStrategy.ONFLUSH)) {
            switch (validationStrategy) {
                case ONCHANGE:
                    ChangeListener ch = new ChangeListener() {
                        public void stateChanged(ChangeEvent e) {
                            validate();
                        }
                    };
                    changeListeners.add(bridge.addValueChangeListener(ui, ch));
                    break;

                case ONBLUR:
                    FocusAdapter focus = new FocusAdapter() {
                        public void focusLost(FocusEvent e) {
                            validate();
                        }
                    };
                    focusListeners.add(focus);
                    ui.addFocusListener(focus);
                    break;
            }
        }
    }

    public void validate() {
        try {
            doValidate();
        } catch (ConversionException ex) {
            handleConversionFailed(ex);
        }
    }

    private void handleConversionSuccess() {
        setValid(true);
        if (statusMonitor != null)
            statusMonitor.setStatus(null);
    }

    private void handleConversionFailed(ConversionException ex) {
        setValid(false);
        if (statusMonitor != null)
            statusMonitor.setStatus(ex.getMessage());
    }

    /**
     * Validate UI content, but don't flush the data to the model object.
     * Trigger conversion to trap any exceptions there as well
     *
     * @throws ConversionException If the conversion fails
     */
    private void doValidate() throws ConversionException {
        if (validators.isEmpty()) {
            convertReverse(bridge.getUIValue(ui));
        } else {
            MODEL_VALUE_TYPE convertedValue = convertReverse(bridge.getUIValue(ui));
            for (Validator validator : validators) {
                ValidationResult result = validator.validate(convertedValue);
                if (!result.isOk())
                    throw new ConversionException(result);
            }
        }
        handleConversionSuccess();
    }

    private UI_VALUE_TYPE convertForward(MODEL_VALUE_TYPE modelValue) throws ConversionException {
        if (modelValue == null) return null;
        if (converter != null) return converter.convertForward(modelValue);
        return ConverterRegistry.convert(modelValue, uiValueType);
    }

    private MODEL_VALUE_TYPE convertReverse(UI_VALUE_TYPE uiValue) throws ConversionException {
        if (uiValue == null) return null;
        if (converter != null) return converter.convertReverse(uiValue);
        return ConverterRegistry.convert(uiValue, modelValueType);
    }

    public Binding required() {
        validators.add(RequiredValidator.INSTANCE);
        return this;
    }

    private void configureBindingStrategy() {
        switch (bindingStrategy) {
            case ONCHANGE:
                ChangeListener ch = new ChangeListener() {
                    public void stateChanged(ChangeEvent e) {
                        flushUIToModel();
                    }
                };
                changeListeners.add(bridge.addValueChangeListener(ui, ch));
                break;

            case ONBLUR:
                FocusAdapter focus = new FocusAdapter() {
                    public void focusLost(FocusEvent e) {
                        flushUIToModel();
                    }
                };
                focusListeners.add(focus);
                ui.addFocusListener(focus);
                break;
        }
    }

    public Binding unbind() {
        if (!bound)
            return this;

        for (UI_LISTENER_TYPE listener : changeListeners)
            bridge.removeValueChangelistener(ui, listener);

        for (FocusListener focus : focusListeners)
            ui.removeFocusListener(focus);

        focusListeners.clear();
        changeListeners.clear();

        removePropertyChangeSupport();
        bound = false;
        return this;
    }

    private void removePropertyChangeSupport() {
        if (modelPropertyChangeListener != null) {
            try {
                Method removeListenerMethod = model.getClass().getMethod("removePropertyChangeListener", PropertyChangeListener.class);
                removeListenerMethod.invoke(model, modelPropertyChangeListener);
                modelPropertyChangeListener = null;
            } catch (Exception ignored) {

            }
        }
    }

    public Binding validator(Validator validator) {
        validators.add(validator);
        return this;
    }

    public Binding bindingStrategy(BindingStrategy bindingStrategy) {
        this.bindingStrategy = bindingStrategy;
        return this;
    }

    public Binding validationStrategy(ValidationStrategy validationStrategy) {
        this.validationStrategy = validationStrategy;
        return this;
    }

    public String getPropertyExpression() {
        return propertyExpression;
    }

    public void setPropertyExpression(String propertyExpression) {
        this.propertyExpression = propertyExpression;
    }

    public UI_TYPE getUi() {
        return ui;
    }

    public void setUi(UI_TYPE ui) {
        this.ui = ui;
    }

    public Object getModel() {
        return model;
    }

    public void setModel(Object model) {
        this.model = model;
    }

    public BindingStrategy getBindingStrategy() {
        return bindingStrategy;
    }

    public void setBindingStrategy(BindingStrategy bindingStrategy) {
        this.bindingStrategy = bindingStrategy;
    }

    public ValidationStrategy getValidationStrategy() {
        return validationStrategy;
    }

    public void setValidationStrategy(ValidationStrategy validationStrategy) {
        this.validationStrategy = validationStrategy;
    }

    public List getValidators() {
        return validators;
    }

    public void setValidators(List validators) {
        this.validators = validators;
    }

    public Converter getConverter() {
        return converter;
    }

    public void setConverter(Converter converter) {
        this.converter = converter;
    }

    public StatusMonitor getStatusMonitor() {
        return statusMonitor;
    }

    public void setStatusMonitor(StatusMonitor statusMonitor) {
        this.statusMonitor = statusMonitor;
    }

    public Binding statusMonitor(StatusMonitor statusMonitor) {
        this.statusMonitor = statusMonitor;
        return this;
    }

    public boolean isValid() {
        return valid;
    }

    public BindingGroup getBindingGroup() {
        return bindingGroup;
    }

    public void setBindingGroup(BindingGroup bindingGroup) {
        this.bindingGroup = bindingGroup;
    }

    public boolean isUsePropertyChangeSupport() {
        return usePropertyChangeSupport;
    }

    public void setUsePropertyChangeSupport(boolean usePropertyChangeSupport) {
        this.usePropertyChangeSupport = usePropertyChangeSupport;
    }

    public Binding usePropertyChangeSupport(boolean usePropertyChangeSupport) {
        setUsePropertyChangeSupport(usePropertyChangeSupport);
        return this;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy