001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.uif.component;
017
018import java.io.Serializable;
019import java.util.ArrayList;
020import java.util.List;
021import java.util.Map;
022
023/**
024 * Configuration for replacing a property value based on a condition
025 *
026 * <p>
027 * A <code>Component</code> may be configured with one or more <code>PropertyReplacer</code> instances. Each defines
028 * a condition to evaluate during the apply model phase, and if that condition succeeds the property on the component
029 * given by {@link #getPropertyName()}, will be replaced with the value given by {@link #getReplacement()}. Conditions
030 * are defined using an expression language and may reference any variables available in the component's context.
031 * </p>
032 *
033 * <p>
034 * Property replacers can be used to change out an entire Component or List/Map of Components. For example, based on a
035 * condition you might want to display a <code>TextControl</code> or <code>RadioControl</code> for an
036 * <code>InputField</code>. You can define the field with a text control, then include a property replacer as
037 * follows:
038 * <pre>
039        <bean parent="PropertyReplacer" p:propertyName="control"
040              p:condition="field1 eq '10985'" p:replacement-ref="RadioControl"/>
041 *
042 * </pre>
043 *
044 * Note <code>Component</code> contains a <code>List</code> or property replacers which will be evaluated in the order
045 * contained within the list. So in the above example if we wanted to now add a further condition which sets the control
046 * to a checkbox, we would just add another property replacer bean.
047 * <pre>
048 *   <property name="propertyReplacers">
049       <list>
050        <bean parent="PropertyReplacer" p:propertyName="control"
051              p:condition="field1 eq '10985'" p:replacement-ref="RadioControl"/>
052        <bean parent="PropertyReplacer" p:propertyName="control"
053              p:condition="field1 eq '11456'" p:replacement-ref="CheckboxControl"/>
054 *     </list>
055 *   </property>
056 * </pre>
057 *
058 * Property replacers may be used to substitute primitive properties as well, such as Strings
059 * </p>
060 *
061 * @author Kuali Rice Team (rice.collab@kuali.org)
062 */
063public class PropertyReplacer extends ConfigurableBase implements Serializable {
064    private static final long serialVersionUID = -8405429643299461398L;
065
066    private String propertyName;
067    private String condition;
068    private Object replacement;
069
070    public PropertyReplacer() {
071        super();
072    }
073
074    /**
075     * Returns a list of nested components
076     *
077     * <p>
078     * All nested components will be returned in the list. Current assumption is that
079     * <code>PropertyReplacer</code> can only contain a <code>Component</code>, <code>List</code> or
080     * <code>Map</code> for nested components
081     * </p>
082     *
083     * @return List<Component> nested components
084     */
085    public List<Component> getNestedComponents() {
086        ArrayList<Component> nestedComponents = new ArrayList<Component>();
087        if (replacement instanceof Component) {
088            nestedComponents.add(((Component) replacement));
089        } else if (replacement instanceof List) {
090            for (Object replacementItem : (List<?>) replacement) {
091                if (replacementItem instanceof Component) {
092                    nestedComponents.add((Component) replacementItem);
093                }
094            }
095        } else if (replacement instanceof Map) {
096            for (Object replacementItem : ((Map<?, ?>) replacement).values()) {
097                if (replacementItem instanceof Component) {
098                    nestedComponents.add((Component) replacementItem);
099                }
100            }
101        }
102
103        return nestedComponents;
104    }
105
106    /**
107     * Name of the property on the Component the property replacer is associated with that
108     * will be set when the condition for the replacer succeeds
109     *
110     * <p>
111     * Note the property name must be readable/writable on the component. The property name may
112     * be nested, and include Map or List references.
113     * </p>
114     *
115     * @return String property name to set
116     */
117    public String getPropertyName() {
118        return this.propertyName;
119    }
120
121    /**
122     * Setter for the property name that will be set
123     *
124     * @param propertyName
125     */
126    public void setPropertyName(String propertyName) {
127        this.propertyName = propertyName;
128    }
129
130    /**
131     * Gives the expression that should be evaluated to determine whether or not
132     * the property replacement should be made
133     *
134     * <p>
135     * Expression follows SPEL and may access any model data along with any variables
136     * available in the context for the Component. The expression should evaluate to
137     * a boolean. If the resulting boolean is true, the object given by {@link #getReplacement()}
138     * will be set as the value for the associated property on the component. If the resulting
139     * boolean is false, no action will take place
140     * </p>
141     *
142     * <p>
143     * Note the value does not need to contain the expression placeholder @{}
144     * </p>
145     *
146     * @return String expression that should be evaluated
147     * @see org.kuali.rice.krad.uif.service.ExpressionEvaluatorService
148     * @see org.kuali.rice.krad.uif.UifConstants.ContextVariableNames
149     */
150    public String getCondition() {
151        return this.condition;
152    }
153
154    /**
155     * Setter for the replacement condition
156     *
157     * @param condition
158     */
159    public void setCondition(String condition) {
160        this.condition = condition;
161    }
162
163    /**
164     * Gives the Object that should be used to set the property value if the replacers condition
165     * evaluates to true
166     *
167     * <p>
168     * Note the configured Object must be valid for the type given by the property on the Component. Standard
169     * property editors will be used for setting the property value
170     * </p>
171     *
172     * @return Object instance to set
173     */
174    public Object getReplacement() {
175        return this.replacement;
176    }
177
178    /**
179     * Setter for the replacement Object
180     *
181     * @param replacement
182     */
183    public void setReplacement(Object replacement) {
184        this.replacement = replacement;
185    }
186
187}