/** * Copyright (C) 2015 Valkyrie RCP * * 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.valkyriercp.form.binding.swing; import org.springframework.util.Assert; import org.valkyriercp.binding.form.FormModel; import org.valkyriercp.form.binding.Binding; import org.valkyriercp.form.binding.support.AbstractBinder; import org.valkyriercp.rules.closure.Closure; import org.valkyriercp.rules.constraint.Constraint; import javax.swing.*; import java.util.Comparator; import java.util.Map; /** * Abstract base class for list bindings. * <p> * The property <code>selectableItems</code> is used as a source of list values. the value of * <code>selectableItems</code> is converted to {@link javax.swing.ListModel} by using the conversion service * <p> * The items of <code>selectableItems</code> can be filtered by defining a constraint for <code>filter</code> and * sorted by defining <code>comparator</code>. Dynamic filtering and sorting is supported if the values of * <code>filter</code> or <code>comparator</code> implements {@link java.util.Observable} * <p> * Every value for <code>selectableItems</code>, <code>filter</code> and <code>comparator</code> can be defined * for all created binding instances or by using a context map where the keys <code>SELECTABLE_ITEMS_KEY</code>, * <code>COMPARATOR_KEY</code> and <code>FILTER_KEY</code> can be used for specifying the context dependent values. * <p> * If the context values of <code>SELECTABLE_ITEMS_KEY</code>, <code>COMPARATOR_KEY</code> and * <code>FILTER_KEY</code> implement {@link Closure} it will be used to create the instance for the value by passing * the value of <code>selectableItems</code>, <code>filter</code> or <code>comparator</code> as the argument.<br/> * Please take into account that the argument for the closure can be null if the property is not set. This feature is * usefull to chain filter constraints. * <p> * Subclasses must implement {@link #createListBinding(javax.swing.JComponent, FormModel, String)} which creates the instance for * {@link AbstractListBinding}. {@link #applyContext(AbstractListBinding, java.util.Map)} can be overwritten to apply additional * context values * * @author Mathias Broekelmann * */ public abstract class AbstractListBinder extends AbstractBinder { /** * key for defining a context dependent selectableItems value. See class description for details. */ public static final String SELECTABLE_ITEMS_KEY = "selectableItems"; /** * key for defining a context dependent comparator value that is used to sort the elements of selectableItems. * Values must be instances of {@link java.util.Comparator}. See class description for more details. */ public static final String COMPARATOR_KEY = "comparator"; /** * key for defining a context dependent filter constraint value that is used to filter the elements of * selectableItems. Values must be instances of {@link Constraint}. See class description for more details. */ public static final String FILTER_KEY = "filter"; private Object selectableItems; private Comparator comparator; private Constraint filter; /** * Creates a new instance by defining a type for the form fields and using the default context keys * <code>SELECTABLE_ITEMS_KEY</code>, <code>COMPARATOR_KEY</code> and <code>FILTER_KEY</code> * * @param requiredSourceClass * the type of the form fields to bind, if null form fields can have any type */ public AbstractListBinder(Class requiredSourceClass) { this(requiredSourceClass, new String[] { SELECTABLE_ITEMS_KEY, COMPARATOR_KEY, FILTER_KEY }); } /** * Creates a new instance by defining a type for the form fields and using the given context keys * * @param requiredSourceClass * the type of the form fields to bind, if null form fields can have any type * @param supportedContextKeys * the keys which can be defined as context values * * @throws NullPointerException * if supportedContextKeys is null */ public AbstractListBinder(Class requiredSourceClass, String[] supportedContextKeys) { super(requiredSourceClass, supportedContextKeys); } /** * Returns the {@link Comparator} which is used for bindings. The value can be overwritten with a context value for * <code>COMPARATOR_KEY</code> * * @return the comparator. If null no comparator is defined */ public Comparator getComparator() { return comparator; } /** * Defines the {@link Comparator} which is used for bindings. The value can be overwritten with a context value for * <code>COMPARATOR_KEY</code> * * @param comparator * the comparator. If null no comparator will be used */ public void setComparator(Comparator comparator) { this.comparator = comparator; } /** * Returns the {@link Constraint} which is used as a filter for the selectable items. The value can be overwritten * with a context value for <code>FILTER_KEY</code> * * @return the filter. If null no filter is defined */ public Constraint getFilter() { return filter; } /** * Defines the {@link Constraint} which is used as a filter for the selectable items. The value can be overwritten * with a context value for <code>FILTER_KEY</code> * * @param filter * the filter constraint. If null no filter will be used */ public void setFilter(Constraint filter) { this.filter = filter; } /** * Returns the selectable items which where used as a source for the selectable items. The value can be overwritten * with a context value for <code>SELECTABLE_ITEMS_KEY</code> * * @return the selectable items. If null no selectable items will be used */ public Object getSelectableItems() { return selectableItems; } /** * Defines the selectable items which where used as a source for the selectable items. The value can be overwritten * with a context value for <code>SELECTABLE_ITEMS_KEY</code> * * @param selectableItems * the selectable items. If null no selectable items will be used */ public void setSelectableItems(Object selectableItems) { this.selectableItems = selectableItems; } /** * Creates the binding and applies any context values */ protected final Binding doBind(JComponent control, FormModel formModel, String formPropertyPath, Map context) { AbstractListBinding binding = createListBinding(control, formModel, formPropertyPath); Assert.notNull(binding); applyContext(binding, context); return binding; } /** * Called to create the binding instance * * @param control * the control to bind * @param formModel * the formmodel with the value of the <code>formPropertyPath</code> field * @param formPropertyPath * the field path to bind * @return the binding instance. Must not be null */ protected abstract AbstractListBinding createListBinding(JComponent control, FormModel formModel, String formPropertyPath); /** * Applies any context or preset value. * * @param binding * the binding to apply the values * @param context * contains context dependent values */ protected void applyContext(AbstractListBinding binding, Map context) { if (context.containsKey(SELECTABLE_ITEMS_KEY)) { binding.setSelectableItems(decorate(context.get(SELECTABLE_ITEMS_KEY), selectableItems)); } else if (selectableItems != null) { binding.setSelectableItems(selectableItems); } if (context.containsKey(COMPARATOR_KEY)) { binding.setComparator((Comparator) decorate(context.get(COMPARATOR_KEY), comparator)); } else if (comparator != null) { binding.setComparator(comparator); } if (context.containsKey(FILTER_KEY)) { binding.setFilter((Constraint) decorate(context.get(FILTER_KEY), filter)); } else if (filter != null) { binding.setFilter(filter); } } /** * Decorates an object instance if the <code>closure</code> value is an instance of {@link Closure}. * * @param closure * the closure which is used to decorate the object. * @param object * the value to decorate * @return the decorated instance if <code>closure</code> implements {@link Closure}, otherwise the value of * <code>object</code> */ protected Object decorate(Object closure, Object object) { if (closure instanceof Closure) { return ((Closure) closure).call(object); } return closure; } }