| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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.apache.cocoon.forms.util; |
| |
| import java.util.AbstractMap; |
| import java.util.AbstractSet; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.cocoon.forms.formmodel.AbstractContainerWidget; |
| import org.apache.cocoon.forms.formmodel.ContainerWidget; |
| import org.apache.cocoon.forms.formmodel.Repeater; |
| import org.apache.cocoon.forms.formmodel.Widget; |
| import org.apache.commons.collections.iterators.AbstractIteratorDecorator; |
| |
| /** |
| * A <code>Map</code> view of a container widget, keys being children names and values either |
| * maps (for container children), objects (for terminal children) or lists (for repeaters). |
| * <p> |
| * The returned map is non-modifiable, except using the <code>put()</code> method, which much |
| * refer to an existing child widget, and <code>putAll(Map)</code> that will silently ignore keys |
| * that don't refer to existing child widgets. |
| * <p> |
| * Also, this map accepts getting and setting values for keys that correspond to value-less widgets |
| * such as {@link org.apache.cocoon.forms.formmodel.Action}. The result in that case is always |
| * <code>null</code>. This is to allow global retrieving or filling of the map values. |
| * |
| * @since 2.1.8 |
| * @version $Id$ |
| */ |
| public class ContainerWidgetAsMap extends AbstractMap { |
| protected AbstractContainerWidget container; |
| private boolean lowerCase; |
| |
| /** |
| * Wraps a container widget in a <code>Map</code>. |
| * <p> |
| * The <code>keysToLowerCase</code> argument specifies if input keys given in <code>get()</code>, |
| * <code>put()</code> and <code>putAll()</code> should be converted to lower case before searching for |
| * the corresponding widget. This feature allows to directly feed widgets with <code>Map</code>s coming |
| * from JDBC resultset rows where keys are uppercase (see <a href="http://jdbi.codehaus.org">JDBI</a>). |
| * |
| * @param container the container to wrap |
| * @param keysToLowerCase should we convert keys to lower case? |
| */ |
| public ContainerWidgetAsMap(AbstractContainerWidget container, boolean keysToLowerCase) { |
| this.container = container; |
| this.lowerCase = keysToLowerCase; |
| } |
| |
| /** |
| * Same as <code>ContainerWidgetAsMap(container, false)</code> |
| */ |
| public ContainerWidgetAsMap(AbstractContainerWidget container) { |
| this(container, false); |
| } |
| |
| /** |
| * Get the container widget that is wrapped by this <code>Map</code>. |
| * |
| * @return the wrapped {@link ContainerWidget} |
| */ |
| public ContainerWidget getWidget() { |
| return this.container; |
| } |
| |
| /** |
| * Get a widget relative to the container wrapped by this <code>Map</code> |
| * |
| * @param path a widget lookup path |
| * @return the widget pointed to by <code>path</code> or <code>null</code> if it doesn't exist. |
| * @see Widget#lookupWidget(String) |
| */ |
| public Widget getWidget(String path) { |
| return this.container.lookupWidget(path); |
| } |
| |
| /** |
| * Put a value in a child widget. The value must be compatible with the datatype |
| * expected by the child widget. In the case of repeaters and containers, this |
| * datatype is <code>Collection</code> and <code>Map</code> respectively, which |
| * will be used to fill the rows and child widgets. |
| * <p> |
| * Note also that the contract of <code>put</code> requires the previous value |
| * to be returned. In the case of repeaters and containers, the value is a live |
| * wrapper around the actual widget, meaning that it's not different from the |
| * current value. |
| */ |
| public Object put(Object key, Object value) { |
| String name = (String)key; |
| if (lowerCase) name = name.toLowerCase(); |
| |
| Widget w = container.getChild(name); |
| if (w != null) { |
| return setValue(w, value); |
| } else { |
| throw new UnsupportedOperationException(container + " has no child named '" + key + "'"); |
| } |
| } |
| |
| public void putAll(Map map) { |
| Iterator iter = map.entrySet().iterator(); |
| while(iter.hasNext()) { |
| Map.Entry entry = (Map.Entry)iter.next(); |
| String name = (String)entry.getKey(); |
| if (lowerCase) name = name.toLowerCase(); |
| Widget w = container.getChild(name); |
| if (w != null) { |
| setValue(w, entry.getValue()); |
| } |
| } |
| } |
| |
| public Object get(Object key) { |
| String name = (String)key; |
| if (lowerCase) name = name.toLowerCase(); |
| Widget w = container.getChild(name); |
| return w == null ? null : asValueOrMap(w); |
| } |
| |
| public Set entrySet() { |
| return new ContainerEntrySet(); |
| } |
| |
| private Object asValueOrMap(Widget w) { |
| if (w instanceof Repeater) { |
| return new RepeaterAsList((Repeater)w, lowerCase); |
| } else if (w instanceof AbstractContainerWidget) { |
| return new ContainerWidgetAsMap((AbstractContainerWidget)w, lowerCase); |
| } else { |
| try { |
| return w.getValue(); |
| } catch (UnsupportedOperationException uoe) { |
| // This widget doesn't hold a value |
| return null; |
| } |
| } |
| } |
| |
| /** |
| * Set a widget's value and returns the previous value as required by put(). |
| */ |
| private Object setValue(Widget w, Object value) { |
| if (w instanceof Repeater) { |
| // Must be a collection |
| if (!(value instanceof Collection)) { |
| throw new IllegalArgumentException("A repeater cannot be filled with " + value); |
| } |
| List result = new RepeaterAsList((Repeater)w, lowerCase); |
| result.addAll((Collection)value); |
| return result; |
| |
| } else if (w instanceof AbstractContainerWidget) { |
| // Must be a map |
| if (!(value instanceof Map)) { |
| throw new IllegalArgumentException("A container cannot be filled with " + value); |
| } |
| Map result = new ContainerWidgetAsMap((AbstractContainerWidget)w); |
| result.putAll((Map)value); |
| return result; |
| } else { |
| try { |
| Object result = w.getValue(); |
| w.setValue(value); |
| return result; |
| } catch (UnsupportedOperationException uoe) { |
| // This widget doesn't hold a value |
| return null; |
| } |
| } |
| } |
| |
| private class ContainerEntrySet extends AbstractSet { |
| public Iterator iterator() { |
| return new ContainerEntryIterator(); |
| } |
| |
| public int size() { |
| return container.getSize(); |
| } |
| } |
| |
| private class ContainerEntryIterator extends AbstractIteratorDecorator { |
| public ContainerEntryIterator() { |
| super(container.getChildren()); |
| } |
| |
| public Object next() { |
| return new ContainerEntry((Widget)super.next()); |
| } |
| } |
| |
| private class ContainerEntry implements Map.Entry { |
| Widget widget; |
| public ContainerEntry(Widget w) { |
| widget = w; |
| } |
| public Object getKey() { |
| return widget.getName(); |
| } |
| public Object getValue() { |
| return asValueOrMap(widget); |
| } |
| public Object setValue(Object value) { |
| Object result = asValueOrMap(widget); |
| ContainerWidgetAsMap.this.setValue(widget, value); |
| return result; |
| } |
| } |
| } |