/* * Copyright 2009-2011 the 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 org.jdal.swing.bind; import java.beans.PropertyDescriptor; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jdal.swing.ModelHolder; import org.jdal.ui.Binder; import org.jdal.ui.bind.ConfigurableControlAccessorFactory; import org.jdal.ui.bind.ControlAccessor; import org.jdal.ui.bind.ControlAccessorFactory; import org.springframework.beans.BeanWrapper; import org.springframework.beans.ConfigurablePropertyAccessor; import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.PropertyAccessException; import org.springframework.beans.PropertyAccessorFactory; import org.springframework.validation.BeanPropertyBindingResult; import org.springframework.validation.BindingErrorProcessor; import org.springframework.validation.BindingResult; import org.springframework.validation.DefaultBindingErrorProcessor; /** * Do an automatic binding of a view using reflection. Bind * controls with name equals to property names in model. * * @author Jose Luis Martin * @see org.jdal.ui.Binder * @since 1.1 */ public class AutoBinder<T> implements Binder<T> { /** Log */ private static final Log log = LogFactory.getLog(AutoBinder.class); /** View to bind on */ private Object view; /** Control accessor factory to use for create control accessors */ private ControlAccessorFactory controlAccessorFactory; /** PropertyAccessor for access view fields */ private ConfigurablePropertyAccessor viewPropertyAccessor; /** PropertyAccessor for access model properties */ private BeanWrapper modelPropertyAccessor; /** Set with property names to ingnore on binding commands */ private Set<String> ignoredProperties = new HashSet<String>(); /** Binded model */ private T model; /** Command to execute on refresh */ private RefreshCommand refreshCommand = new RefreshCommand(); /** Command to execute on update */ private UpdateCommand updateCommand = new UpdateCommand(); /** Process binding errors */ private BindingErrorProcessor errorProcessor = new DefaultBindingErrorProcessor(); /** binding errors */ private BindingResult bindingResult; /** Hold property name -> ControlAccessor mapping */ private Map<String, ControlAccessor> controlAccessorMap = new HashMap<String, ControlAccessor>(); /** * Create an AutoBinder for a View * @param view View to bind. */ public AutoBinder(ModelHolder<T> view) { this(view, view.getModel()); } /** * Create a Binder for view and model * @param view * @param model */ public AutoBinder(Object view, T model) { this.view = view; this.model = model; viewPropertyAccessor = new DirectFieldAccessor(this.view); bindingResult = new BeanPropertyBindingResult(model, "model"); } public void bind(String viewField, String propertyName) throws UndefinedAccessorException { Object control = viewPropertyAccessor.getPropertyValue(propertyName); if (control != null) { ControlAccessor accessor = controlAccessorFactory.getControlAccessor(control); if (accessor != null) { controlAccessorMap.put(propertyName, accessor); } else { String msg = "Not found ControlAcessor for control class [" + control.getClass().getName() + "]"; throw new UndefinedAccessorException(msg); } } } /** * {@inheritDoc} */ public void refresh() { executeBinderCommand(refreshCommand); } /** * {@inheritDoc} */ public void update() { executeBinderCommand(updateCommand); } /** * Execute BinderCommand (update or refresh) for all model properties * @param command Command to execute. */ private void executeBinderCommand(BinderCommand command) { modelPropertyAccessor = PropertyAccessorFactory.forBeanPropertyAccess(model); // iterate on model properties for (PropertyDescriptor pd : modelPropertyAccessor.getPropertyDescriptors()) { String propertyName = pd.getName(); if ( !ignoredProperties.contains(propertyName)) { ControlAccessor controlAccessor = getControlAccessor(propertyName); if (controlAccessor != null) command.execute(controlAccessor, propertyName); } } } /** * Gets control accessor * @param control to get accessor * @return accessor or null if none found. */ private ControlAccessor getControlAccessor(String name) { // try map first if (controlAccessorMap.containsKey(name)) return controlAccessorMap.get(name); // try matching view property ControlAccessor accessor = null; if (viewPropertyAccessor.isReadableProperty(name)) { Object control = viewPropertyAccessor.getPropertyValue(name); if (control != null) { if (log.isDebugEnabled()) log.debug("Found control: " + control.getClass().getSimpleName() + " for property: " + name); accessor = getControlAccessorFactory().getControlAccessor(control); } } return accessor; } /** * {@inheritDoc} */ public T getModel() { return model; } /** * {@inheritDoc} */ public void setModel(T model) { this.model = model; } /** * @return the controlAccessorFactory */ public ControlAccessorFactory getControlAccessorFactory() { if (controlAccessorFactory == null) controlAccessorFactory = new ConfigurableControlAccessorFactory(); return controlAccessorFactory; } /** * @param controlAccessorFactory the controlAccessorFactory to set */ public void setControlAccessorFactory(ControlAccessorFactory controlAccessorFactory) { this.controlAccessorFactory = controlAccessorFactory; } /** * 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<String> getIgnoredProperties() { return ignoredProperties; } /** * @param ignoredProperties the ignoredProperties to set */ public void setIgnoredProperties(Set<String> 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 Binding result * @return the binding result */ public BindingResult getBindingResult() { return bindingResult; } /** * Binder Command Callback * @author Jose Luis Martin - (jlm@joseluismartin.info) */ interface BinderCommand { void execute(ControlAccessor accessor, String propertyName); } /** * Update Command */ class UpdateCommand implements BinderCommand { public void execute(ControlAccessor controlAccessor, String name) { try { modelPropertyAccessor.setPropertyValue(name, controlAccessor.getControlValue()); } catch (PropertyAccessException pae) { errorProcessor.processPropertyAccessException(pae, bindingResult); } } } /** * Refresh Command */ class RefreshCommand implements BinderCommand { public void execute(ControlAccessor controlAccessor, String name) { controlAccessor.setControlValue(modelPropertyAccessor.getPropertyValue(name)); } } }