/* * 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; } } }