info.joseluismartin.gui.AbstractView Maven / Gradle / Ivy
/*
* Copyright 2009-2011 original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package info.joseluismartin.gui;
import info.joseluismartin.gui.bind.BinderFactory;
import info.joseluismartin.gui.bind.CompositeBinder;
import info.joseluismartin.gui.bind.ConfigurableControlAccessorFactory;
import info.joseluismartin.gui.bind.ControlAccessor;
import info.joseluismartin.gui.bind.ControlAccessorBinderFactory;
import info.joseluismartin.gui.bind.ControlAccessorFactory;
import info.joseluismartin.gui.bind.ControlChangeListener;
import info.joseluismartin.gui.bind.ControlEvent;
import info.joseluismartin.gui.bind.DirectFieldAccessor;
import info.joseluismartin.gui.bind.PropertyBinder;
import info.joseluismartin.gui.validation.ErrorProcessor;
import java.awt.Component;
import java.beans.PropertyDescriptor;
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.Set;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessor;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.validation.Validator;
/**
* Template class that simplifies {@link View} implementation.
*
* The central method is buildPanel
that builds the JComponent
* that hold the view controls. You may use custom binding of the view overwriting the methods
* doUpdate
and doRefresh
.
*
*
For common binding code, you usually use autobinding facitility, that is, using
* the same name to the field control and model property name, and setting autobinding to true.
* When using autobinding, you may exclude model properties from binding using some
* of ignoreProperty
methods.
*
*
Manual binding is also supported via bind
methods. When binding a control, a
* StateChangeListener
is added to the control for setting dirty property on control
* changes.
*
*
Only org.springframework.util.validation.Validator
validators are supported
* The validateView
method calls configured
* ErrorProcessors
to process errors found in validation.
*
* @author Jose Luis Martin - ([email protected])
* @since 1.0
* @see BinderFactory
* @see ControlAccessorFactory
* @see ErrorProcessor
*/
public abstract class AbstractView implements View, ControlChangeListener {
public final static String DEFAULT_BINDER_FACTORY_NAME = "binderFactory";
/** log */
private static final Log log = LogFactory.getLog(AbstractView.class);
/** view name */
private String name;
/** binder factory to make property binders */
private BinderFactory binderFactory;
/** control accessor factory */
private ControlAccessorFactory controlAccessorFactory = new ConfigurableControlAccessorFactory();
/** hold binders */
private CompositeBinder binder = new CompositeBinder();
/** if true, do an automatic binding using property names */
private boolean autobinding = false;
/** Set with property names to ingnore on binding commands */
private Set ignoredProperties = new HashSet();
/** data model */
private T model;
/** JComponent that hold controls */
private JComponent panel;
/** subviews list */
private List> subViews = new ArrayList>();
/** validator to check binding and model values */
private Validator validator;
/** message source for internationalization */
@Autowired
protected MessageSource messageSource;
/** List of error handlers */
private List errorProcessors = new ArrayList();
/** Validation Errors */
protected BindingResult errors;
/** dirty state */
boolean dirty = false;
protected int width = 0;
protected int height = 0;
/**
* Default ctor
*/
public AbstractView() {
}
/**
* Create the view and set the model
* @param model model to set
*/
public AbstractView(T model) {
setModel(model);
}
/**
* add a binding for control and model property name
* @param comoponent control
* @param propertyName the model property path to bind
* @param readOnly if true, binding only do refresh()
*/
public void bind(Object component, String propertyName, boolean readOnly) {
binder.bind(component, propertyName, readOnly);
listen(component);
}
/**
* add a binding for control and model property name
* @param comoponent control
* @param propertyName the model property path to bind
*/
public void bind(Object component, String propertyName) {
bind(component, propertyName, false);
}
/**
* {@inheritDoc}
*/
public JComponent getPanel() {
if (panel == null) {
panel = buildPanel();
if (width != 0 && height != 0)
panel.setSize(width, height);
}
return panel;
}
/**
* Build the JComponent that hold controls.
* @return a JCompoent
*/
protected abstract JComponent buildPanel();
/**
* {@inheritDoc}
*/
public T getModel() {
return model;
}
/**
* {@inheritDoc}
*/
public final void setModel(T model) {
this.model = model;
binder.setModel(model);
// refresh subviews
for (View v : subViews)
v.setModel(model);
onSetModel(model);
}
/**
* Callback method to handle model changes
* @param model the new model
*/
protected void onSetModel(T model) {
}
/**
* {@inheritDoc}
*/
public final void update() {
clearErrors();
// do custom update
doUpdate();
// update binder
binder.update();
if (errors != null && binder.getBindingResult() != null &&
errors.getObjectName().equals(binder.getBindingResult().getObjectName()))
errors.addAllErrors(binder.getBindingResult());
// update subviews
for (View v : subViews) {
v.update();
if (errors != null && v.getBindingResult() != null &&
errors.getObjectName().equals(v.getBindingResult().getObjectName()))
errors.addAllErrors(v.getBindingResult());
}
}
/**
* Clear validation erros
*/
private void clearErrors() {
if (getModel() != null)
errors = new BeanPropertyBindingResult(getModel(), getModel().getClass().getSimpleName(), true);
resetErrorProcessors();
}
/**
* Callback method on update()
*/
protected void doUpdate() {
}
/**
* Add a subview, the subview is refreshed, updated and hold the same model
* that this view, for adding views with other models, use bind()
* @param view
*/
public void addView(View view) {
subViews.add(view);
view.setModel(model);
}
/**
* {@inheritDoc}
*/
public final void refresh() {
clearErrors();
doRefresh();
binder.refresh();
// refresh subviews
for (View v : subViews)
v.refresh();
}
/**
* Callback method for refresh()
*/
protected void doRefresh() {
}
/**
* Listen control for changes.
*/
public void listen(Object control) {
ControlAccessor c = controlAccessorFactory.getControlAccessor(control);
if (c != null) {
c.addControlChangeListener(this);
}
}
/**
* {@inheritDoc}
*/
public void controlChange(ControlEvent e) {
setDirty(true);
}
/**
* Gets the binder factory
* @return the binder factory
*/
public BinderFactory getBinderFactory() {
return binderFactory;
}
/**
* Sets the binder factory, propagate it to composite binder.
* @param binderFactory to set
*/
public void setBinderFactory(BinderFactory binderFactory) {
this.binderFactory = binderFactory;
binder.setBinderFactory(binderFactory);
}
/**
* {@inheritDoc}
*/
public boolean validateView() {
if (validator == null && !errors.hasErrors())
return true;
if (validator != null)
validator.validate(getModel(), errors);
if (errors.hasErrors()) {
String errorMessage = getErrorMessage(errors);
JOptionPane.showMessageDialog(getPanel(),errorMessage, "Error", JOptionPane.ERROR_MESSAGE);
for (FieldError error : errors.getFieldErrors()) {
for (ErrorProcessor ep : errorProcessors )
ep.processError(binder.getBinder(error.getField()), error);
}
return false;
}
return true;
}
private void resetErrorProcessors() {
for (ErrorProcessor ep : errorProcessors) {
ep.reset();
}
}
/**
* Build a error message with all errors.
* @param errors erros to use
* @return String with error message
*/
protected String getErrorMessage(Errors errors) {
StringBuilder sb = new StringBuilder();
if (errors.hasErrors()) {
sb.append("\n");
Iterator iter = errors.getAllErrors().iterator();
while (iter.hasNext()) {
ObjectError oe = (ObjectError) iter.next();
sb.append("- ");
sb.append(messageSource.getMessage(oe, null));
sb.append("\n");
}
}
sb.append("\n");
return sb.toString();
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public void clear() {
T model = getModel();
if (model != null) {
try {
setModel((T) model.getClass().newInstance());
refresh();
} catch (InstantiationException e) {
log.error(e);
} catch (IllegalAccessException e) {
log.error(e);
}
}
}
/**
* {@inheritDoc}
*/
public void enableView(boolean enabled) {
for (Binder> b : binder.getPropertyBinders()) {
Object control = ((PropertyBinder) b).getComponent();
if (control instanceof Component)
((Component) control).setEnabled(enabled);
else if (control instanceof View>)
((View>) control).enableView(enabled);
for (View> v : subViews)
v.enableView(enabled);
}
}
/**
* Bind Controls with the same name that a property in the model.
*/
protected void autobind() {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(getModel());
PropertyAccessor viewPropertyAccessor = new DirectFieldAccessor(this);
// iterate on model properties
for (PropertyDescriptor pd : bw.getPropertyDescriptors()) {
String propertyName = pd.getName();
if ( !ignoredProperties.contains(propertyName) && viewPropertyAccessor.isReadableProperty(propertyName)) {
Object control = viewPropertyAccessor.getPropertyValue(propertyName);
if (control != null) {
if (log.isDebugEnabled())
log.debug("Found control: " + control.getClass().getSimpleName() +
" for property: " + propertyName);
// do bind
bind(control, propertyName);
}
}
}
}
/**
* I18n Support
* @param code message code
* @return message or code if none defined
*/
protected String getMessage(String code) {
return messageSource == null ?
code : messageSource.getMessage(name, null, Locale.getDefault());
}
/**
* Add a property name to ignore on binding.
* @param propertyName property name to ignore
*/
public void ignoreProperty(String propertyName) {
ignoredProperties.add(propertyName);
}
/**
* @return the ignoredProperties
*/
public Set getIgnoredProperties() {
return ignoredProperties;
}
/**
* @param ignoredProperties the ignoredProperties to set
*/
public void setIgnoredProperties(Set ignoredProperties) {
this.ignoredProperties = ignoredProperties;
}
/**
* Add a Collection of property names to ignore on binding
* @param c Collection of property names.
*/
public void ignoreProperties(Collection extends String> c) {
ignoredProperties.addAll(c);
}
/**
* @return the validator
*/
public Validator getValidator() {
return validator;
}
/**
* @param validator the validator to set
*/
public void setValidator(Validator validator) {
this.validator = validator;
}
/**
* @return the messageSource
*/
public MessageSource getMessageSource() {
return messageSource;
}
/**
* @param messageSource the messageSource to set
*/
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
/**
* @return the width
*/
public int getWidth() {
return width;
}
/**
* @param width the width to set
*/
public void setWidth(int width) {
this.width = width;
}
/**
* @return the height
*/
public int getHeight() {
return height;
}
/**
* @param height the height to set
*/
public void setHeight(int height) {
this.height = height;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the errorProcessors
*/
public List getErrorProcessors() {
return errorProcessors;
}
/**
* @param errorProcessors the errorProcessors to set
*/
public void setErrorProcessors(List errorProcessors) {
this.errorProcessors = errorProcessors;
}
/**
* @return the dirty
*/
public boolean isDirty() {
return dirty;
}
/**
* @param dirty the dirty to set
*/
public void setDirty(boolean dirty) {
this.dirty = dirty;
}
/**
* @return the controlAccessorFactory
*/
public ControlAccessorFactory getControlAccessorFactory() {
return controlAccessorFactory;
}
/**
* @param controlAccessorFactory the controlAccessorFactory to set
*/
public void setControlAccessorFactory(ControlAccessorFactory controlAccessorFactory) {
this.controlAccessorFactory = controlAccessorFactory;
if (binderFactory == null)
setBinderFactory(new ControlAccessorBinderFactory(controlAccessorFactory));
}
/**
* @return the autobinding
*/
public boolean isAutobinding() {
return autobinding;
}
/**
* @param autobinding the autobinding to set
*/
public void setAutobinding(boolean autobinding) {
this.autobinding = autobinding;
}
public BindingResult getBindingResult() {
return errors;
}
}