001: /*
002: * $Id: AttributeModifier.java 491060 2006-12-29 17:49:34Z ivaynberg $
003: * $Revision: 491060 $ $Date: 2006-12-29 18:49:34 +0100 (Fri, 29 Dec 2006) $
004: *
005: * ==============================================================================
006: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
007: * use this file except in compliance with the License. You may obtain a copy of
008: * the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
014: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
015: * License for the specific language governing permissions and limitations under
016: * the License.
017: */
018: package wicket;
019:
020: import java.io.Serializable;
021:
022: import wicket.behavior.AbstractBehavior;
023: import wicket.markup.ComponentTag;
024: import wicket.markup.parser.XmlTag;
025: import wicket.model.IModel;
026: import wicket.util.value.ValueMap;
027:
028: /**
029: * This class allows a tag attribute of a component to be modified dynamically
030: * with a value obtained from a model object. This concept can be used to
031: * programatically alter the attributes of components, overriding the values
032: * specified in the markup. The two primary uses of this class are to allow
033: * overriding of markup attributes based on business logic and to support
034: * dynamic localization. The replacement occurs as the component tag is rendered
035: * to the response.
036: * <p>
037: * The attribute whose value is to be modified must be given on construction of
038: * the instance of this class along with the model containing the value to
039: * replace with. Optionally a pattern can be supplied that is a regular
040: * expression that the existing value must match before the replacement can be
041: * carried out.
042: * <p>
043: * If an attribute is not in the markup, this modifier will add an attribute to
044: * the tag only if addAttributeIfNotPresent is true and the replacement value is
045: * not null.
046: * </p>
047: * <p>
048: * Instances of this class should be added to components via the
049: * {@link wicket.Component#add(AttributeModifier)} method after the component
050: * has been constucted.
051: * <p>
052: * It is possible to create new subclasses of AttributeModifier by overriding
053: * the newValue(String, String) method. For example, you could create an
054: * AttributeModifier subclass which appends the replacement value like this:
055: * <code>
056: * new AttributeModifier("myAttribute", model)
057: * {
058: * protected String newValue(final String currentValue, final String replacementValue)
059: * {
060: * return currentValue + replacementValue;
061: * }
062: * };
063: * </code>
064: *
065: * @author Chris Turner
066: * @author Eelco Hillenius
067: * @author Jonathan Locke
068: * @author Martijn Dashorst
069: * @author Ralf Ebert
070: */
071: public class AttributeModifier extends AbstractBehavior implements
072: Serializable {
073: /** Marker value to have an attribute without a value added. */
074: public static final String VALUELESS_ATTRIBUTE_ADD = "VA_ADD";
075:
076: /** Marker value to have an attribute without a value removed. */
077: public static final String VALUELESS_ATTRIBUTE_REMOVE = "VA_REMOVE";
078:
079: private static final long serialVersionUID = 1L;
080:
081: /** Whether to add the attribute if it is not an attribute in the markup. */
082: private final boolean addAttributeIfNotPresent;
083:
084: /** Attribute specification. */
085: private final String attribute;
086:
087: /** Modification information. */
088: private boolean enabled;
089:
090: /** The pattern. */
091: private final String pattern;
092:
093: /** The model that is to be used for the replacement. */
094: private final IModel replaceModel;
095:
096: /**
097: * Create a new attribute modifier with the given attribute name and model
098: * to replace with. The additional boolean flag specifies whether to add the
099: * attribute if it is not present.
100: *
101: * @param attribute
102: * The attribute name to replace the value for
103: * @param addAttributeIfNotPresent
104: * Whether to add the attribute if it is not present
105: * @param replaceModel
106: * The model to replace the value with
107: */
108: public AttributeModifier(final String attribute,
109: final boolean addAttributeIfNotPresent,
110: final IModel replaceModel) {
111: this (attribute, null, addAttributeIfNotPresent, replaceModel);
112: }
113:
114: /**
115: * Create a new attribute modifier with the given attribute name and model
116: * to replace with. The attribute will not be added if it is not present.
117: *
118: * @param attribute
119: * The attribute name to replace the value for
120: * @param replaceModel
121: * The model to replace the value with
122: */
123: public AttributeModifier(final String attribute,
124: final IModel replaceModel) {
125: this (attribute, null, false, replaceModel);
126: }
127:
128: /**
129: * Create a new attribute modifier with the given attribute name and
130: * expected pattern to match plus the model to replace with. A null pattern
131: * will match the attribute regardless of its value. The additional boolean
132: * flag specifies whether to add the attribute if it is not present.
133: *
134: * @param attribute
135: * The attribute name to replace the value for
136: * @param pattern
137: * The pattern of the current attribute value to match
138: * @param addAttributeIfNotPresent
139: * Whether to add the attribute if it is not present and the
140: * replacement value is not null
141: * @param replaceModel
142: * The model to replace the value with
143: */
144: public AttributeModifier(final String attribute,
145: final String pattern,
146: final boolean addAttributeIfNotPresent,
147: final IModel replaceModel) {
148: if (attribute == null) {
149: throw new IllegalArgumentException(
150: "Attribute parameter cannot be null");
151: }
152:
153: this .attribute = attribute;
154: this .pattern = pattern;
155: this .enabled = true;
156: this .addAttributeIfNotPresent = addAttributeIfNotPresent;
157: this .replaceModel = replaceModel;
158: }
159:
160: /**
161: * Create a new attribute modifier with the given attribute name and
162: * expected pattern to match plus the model to replace with. A null pattern
163: * will match the attribute regardless of its value. The attribute will not
164: * be added if it is not present.
165: *
166: * @param attribute
167: * The attribute name to replace the value for
168: * @param pattern
169: * The pattern of the current attribute value to match
170: * @param replaceModel
171: * The model to replace the value with
172: */
173: public AttributeModifier(final String attribute,
174: final String pattern, final IModel replaceModel) {
175: this (attribute, pattern, false, replaceModel);
176: }
177:
178: /**
179: * Detach the model if it was a IDetachableModel Internal method. shouldn't
180: * be called from the outside. If the attribute modifier is shared, the
181: * detach method will be called multiple times.
182: *
183: * @param component
184: * the model that initiates the detachement
185: */
186: public final void detachModel(Component component) {
187: if (replaceModel != null) {
188: replaceModel.detach();
189: }
190:
191: super .detachModel(component);
192: }
193:
194: /**
195: * Checks whether this attribute modifier is enabled or not.
196: *
197: * @return Whether enabled or not
198: */
199: public boolean isEnabled() {
200: return enabled;
201: }
202:
203: /**
204: * @see wicket.behavior.IBehavior#onComponentTag(wicket.Component,
205: * wicket.markup.ComponentTag)
206: */
207: public final void onComponentTag(Component component,
208: ComponentTag tag) {
209: if (tag.getType() != XmlTag.CLOSE) {
210: replaceAttibuteValue(component, tag);
211: }
212: }
213:
214: /**
215: * Checks the given component tag for an instance of the attribute to modify
216: * and if all criteria are met then replace the value of this attribute with
217: * the value of the contained model object.
218: *
219: * @param component
220: * The component
221: * @param tag
222: * The tag to replace the attribute value for
223: */
224: public final void replaceAttibuteValue(final Component component,
225: final ComponentTag tag) {
226: if (isEnabled()) {
227: final ValueMap attributes = tag.getAttributes();
228: final Object replacementValue = getReplacementOrNull(component);
229:
230: if (VALUELESS_ATTRIBUTE_ADD.equals(replacementValue)) {
231: attributes.put(attribute, null);
232: } else if (VALUELESS_ATTRIBUTE_REMOVE
233: .equals(replacementValue)) {
234: attributes.remove(attribute);
235: } else {
236: if (attributes.containsKey(attribute)) {
237: final String value = toStringOrNull(attributes
238: .get(attribute));
239: if (pattern == null || value.matches(pattern)) {
240: final String newValue = newValue(value,
241: toStringOrNull(replacementValue));
242: if (newValue != null) {
243: attributes.put(attribute, newValue);
244: }
245: }
246: } else if (addAttributeIfNotPresent) {
247: final String newValue = newValue(null,
248: toStringOrNull(replacementValue));
249: if (newValue != null) {
250: attributes.put(attribute, newValue);
251: }
252: }
253: }
254: }
255: }
256:
257: /**
258: * Sets whether this attribute modifier is enabled or not.
259: *
260: * @param enabled
261: * Whether enabled or not
262: */
263: public final void setEnabled(final boolean enabled) {
264: this .enabled = enabled;
265: }
266:
267: /**
268: * @see java.lang.Object#toString()
269: */
270: public String toString() {
271: return "[AttributeModifier attribute=" + attribute
272: + ", enabled=" + isEnabled() + ", pattern=" + pattern
273: + ", replacementModel=" + replaceModel + "]";
274: }
275:
276: /**
277: * Gets the replacement model. Allows subclasses access to replace model.
278: *
279: * @return the replace model of this attribute modifier
280: */
281: protected final IModel getReplaceModel() {
282: return replaceModel;
283: }
284:
285: /**
286: * Gets the value that should replace the current attribute value. This
287: * gives users the ultimate means to customize what will be used as the
288: * attribute value. For instance, you might decide to append the replacement
289: * value to the current instead of just replacing it as is Wicket's default.
290: *
291: * @param currentValue
292: * The current attribute value. This value might be null!
293: * @param replacementValue
294: * The replacement value. This value might be null!
295: * @return The value that should replace the current attribute value
296: */
297: protected String newValue(final String currentValue,
298: final String replacementValue) {
299: return replacementValue;
300: }
301:
302: /* gets replacement with null check. */
303: private Object getReplacementOrNull(final Component component) {
304: return (replaceModel != null) ? replaceModel
305: .getObject(component) : null;
306: }
307:
308: /* gets replacement as a string with null check. */
309: private String toStringOrNull(final Object replacementValue) {
310: return (replacementValue != null) ? replacementValue.toString()
311: : null;
312: }
313: }
|