001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.forms.util;
018:
019: import java.util.AbstractMap;
020: import java.util.AbstractSet;
021: import java.util.Collection;
022: import java.util.Iterator;
023: import java.util.List;
024: import java.util.Map;
025: import java.util.Set;
026:
027: import org.apache.cocoon.forms.formmodel.AbstractContainerWidget;
028: import org.apache.cocoon.forms.formmodel.ContainerWidget;
029: import org.apache.cocoon.forms.formmodel.Repeater;
030: import org.apache.cocoon.forms.formmodel.Widget;
031: import org.apache.commons.collections.iterators.AbstractIteratorDecorator;
032:
033: /**
034: * A <code>Map</code> view of a container widget, keys being children names and values either
035: * maps (for container children), objects (for terminal children) or lists (for repeaters).
036: * <p>
037: * The returned map is non-modifiable, except using the <code>put()</code> method, which much
038: * refer to an existing child widget, and <code>putAll(Map)</code> that will silently ignore keys
039: * that don't refer to existing child widgets.
040: * <p>
041: * Also, this map accepts getting and setting values for keys that correspond to value-less widgets
042: * such as {@link org.apache.cocoon.forms.formmodel.Action}. The result in that case is always
043: * <code>null</code>. This is to allow global retrieving or filling of the map values.
044: *
045: * @since 2.1.8
046: * @version $Id: ContainerWidgetAsMap.java 449149 2006-09-23 03:58:05Z crossley $
047: */
048: public class ContainerWidgetAsMap extends AbstractMap {
049: protected AbstractContainerWidget container;
050: private boolean lowerCase;
051:
052: /**
053: * Wraps a container widget in a <code>Map</code>.
054: * <p>
055: * The <code>keysToLowerCase</code> argument specifies if input keys given in <code>get()</code>,
056: * <code>put()</code> and <code>putAll()</code> should be converted to lower case before searching for
057: * the corresponding widget. This feature allows to directly feed widgets with <code>Map</code>s coming
058: * from JDBC resultset rows where keys are uppercase (see <a href="http://jdbi.codehaus.org">JDBI</a>).
059: *
060: * @param container the container to wrap
061: * @param keysToLowerCase should we convert keys to lower case?
062: */
063: public ContainerWidgetAsMap(AbstractContainerWidget container,
064: boolean keysToLowerCase) {
065: this .container = container;
066: this .lowerCase = keysToLowerCase;
067: }
068:
069: /**
070: * Same as <code>ContainerWidgetAsMap(container, false)</code>
071: */
072: public ContainerWidgetAsMap(AbstractContainerWidget container) {
073: this (container, false);
074: }
075:
076: /**
077: * Get the container widget that is wrapped by this <code>Map</code>.
078: *
079: * @return the wrapped {@link ContainerWidget}
080: */
081: public ContainerWidget getWidget() {
082: return this .container;
083: }
084:
085: /**
086: * Get a widget relative to the container wrapped by this <code>Map</code>
087: *
088: * @param path a widget lookup path
089: * @return the widget pointed to by <code>path</code> or <code>null</code> if it doesn't exist.
090: * @see Widget#lookupWidget(String)
091: */
092: public Widget getWidget(String path) {
093: return this .container.lookupWidget(path);
094: }
095:
096: /**
097: * Put a value in a child widget. The value must be compatible with the datatype
098: * expected by the child widget. In the case of repeaters and containers, this
099: * datatype is <code>Collection</code> and <code>Map</code> respectively, which
100: * will be used to fill the rows and child widgets.
101: * <p>
102: * Note also that the contract of <code>put</code> requires the previous value
103: * to be returned. In the case of repeaters and containers, the value is a live
104: * wrapper around the actual widget, meaning that it's not different from the
105: * current value.
106: */
107: public Object put(Object key, Object value) {
108: String name = (String) key;
109: if (lowerCase)
110: name = name.toLowerCase();
111:
112: Widget w = container.getChild(name);
113: if (w != null) {
114: return setValue(w, value);
115: } else {
116: throw new UnsupportedOperationException(container
117: + " has no child named '" + key + "'");
118: }
119: }
120:
121: public void putAll(Map map) {
122: Iterator iter = map.entrySet().iterator();
123: while (iter.hasNext()) {
124: Map.Entry entry = (Map.Entry) iter.next();
125: String name = (String) entry.getKey();
126: if (lowerCase)
127: name = name.toLowerCase();
128: Widget w = container.getChild(name);
129: if (w != null) {
130: setValue(w, entry.getValue());
131: }
132: }
133: }
134:
135: public Object get(Object key) {
136: String name = (String) key;
137: if (lowerCase)
138: name = name.toLowerCase();
139: Widget w = container.getChild(name);
140: return w == null ? null : asValueOrMap(w);
141: }
142:
143: public Set entrySet() {
144: return new ContainerEntrySet();
145: }
146:
147: private Object asValueOrMap(Widget w) {
148: if (w instanceof Repeater) {
149: return new RepeaterAsList((Repeater) w, lowerCase);
150: } else if (w instanceof AbstractContainerWidget) {
151: return new ContainerWidgetAsMap(
152: (AbstractContainerWidget) w, lowerCase);
153: } else {
154: try {
155: return w.getValue();
156: } catch (UnsupportedOperationException uoe) {
157: // This widget doesn't hold a value
158: return null;
159: }
160: }
161: }
162:
163: /**
164: * Set a widget's value and returns the previous value as required by put().
165: */
166: private Object setValue(Widget w, Object value) {
167: if (w instanceof Repeater) {
168: // Must be a collection
169: if (!(value instanceof Collection)) {
170: throw new IllegalArgumentException(
171: "A repeater cannot be filled with " + value);
172: }
173: List result = new RepeaterAsList((Repeater) w, lowerCase);
174: result.addAll((Collection) value);
175: return result;
176:
177: } else if (w instanceof AbstractContainerWidget) {
178: // Must be a map
179: if (!(value instanceof Map)) {
180: throw new IllegalArgumentException(
181: "A container cannot be filled with " + value);
182: }
183: Map result = new ContainerWidgetAsMap(
184: (AbstractContainerWidget) w);
185: result.putAll((Map) value);
186: return result;
187: } else {
188: try {
189: Object result = w.getValue();
190: w.setValue(value);
191: return result;
192: } catch (UnsupportedOperationException uoe) {
193: // This widget doesn't hold a value
194: return null;
195: }
196: }
197: }
198:
199: private class ContainerEntrySet extends AbstractSet {
200: public Iterator iterator() {
201: return new ContainerEntryIterator();
202: }
203:
204: public int size() {
205: return container.getSize();
206: }
207: }
208:
209: private class ContainerEntryIterator extends
210: AbstractIteratorDecorator {
211: public ContainerEntryIterator() {
212: super (container.getChildren());
213: }
214:
215: public Object next() {
216: return new ContainerEntry((Widget) super .next());
217: }
218: }
219:
220: private class ContainerEntry implements Map.Entry {
221: Widget widget;
222:
223: public ContainerEntry(Widget w) {
224: widget = w;
225: }
226:
227: public Object getKey() {
228: return widget.getName();
229: }
230:
231: public Object getValue() {
232: return asValueOrMap(widget);
233: }
234:
235: public Object setValue(Object value) {
236: Object result = asValueOrMap(widget);
237: ContainerWidgetAsMap.this.setValue(widget, value);
238: return result;
239: }
240: }
241: }
|