/* * Copyright (c) 2001-2007, Inversoft Inc., All Rights Reserved * * Licensed 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.primeframework.mvc.control.form; import java.lang.reflect.Array; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; import org.primeframework.mvc.message.l10n.MessageProvider; import org.primeframework.mvc.message.l10n.MissingMessageException; import org.primeframework.mvc.parameter.el.ExpressionEvaluator; import com.google.inject.Inject; /** * This class is an abstract input that is used for any input that uses a list or items. This includes select boxes and * checkbox groups. * * @author Brian Pontarelli */ public abstract class AbstractListInput extends AbstractInput { protected ExpressionEvaluator expressionEvaluator; protected AbstractListInput(boolean labeled) { super(labeled); } @Inject public void setExpressionEvaluator(ExpressionEvaluator expressionEvaluator) { this.expressionEvaluator = expressionEvaluator; } /** * Handles the items and value. Here's the skinny: * <p> * <ul> <li>If items is null, just inserts an empty Map in the attributes under <code>options</code></li> <li>If * items * is a Collection, loops over it and creates options. The selected state of the options are based on whether or not * the value is a Collection or an array or null or just a plain Object. In the collection/array case, if the current * items value is in the collection the option is selected. In the plain object case, if the current items value is * equal it is selected. Otherwise, it isn't selected. Also, this handles the text and key using the expression * attributes or the current items value.</li> <li>If items is a Map, loops over it and creates options. The selected * state of the options are based on whether or not the value is a Collection or an array or null or just a plain * Object. In the collection/array case, if the current items value is in the collection the option is selected. In * the plain object case, if the current items value is equal it is selected. Otherwise, it isn't selected. Also, * this * handles the text and key using the key from the items Map, the expression attributes or the current items * value.</li> </ul> */ @Override protected Map<String, Object> makeParameters() { Map<String, Object> parameters = super.makeParameters(); Map<String, Option> options = new LinkedHashMap<>(); // Handle the header option String headerValue = (String) attributes.remove("headerValue"); String headerL10n = (String) attributes.remove("headerL10n"); if (headerValue != null) { String message = ""; if (headerL10n != null) { message = messageProvider.getMessage(headerL10n); } options.put(headerValue, new Option(message, false, null)); } // Grab the value Object beanValue = currentAction() != null ? expressionEvaluator.getValue((String) attributes.get("name"), currentAction()) : null; // Next, let's handle the items here. I'll create a Map that contains a simple inner class // that determines if the option is selected or not. This will allow me to get the text // as well String valueExpr = (String) attributes.remove("valueExpr"); String textExpr = (String) attributes.remove("textExpr"); String l10nExpr = (String) attributes.remove("l10nExpr"); Object items = attributes.remove("items"); if (items != null) { if (items instanceof Collection) { Collection<?> c = (Collection<?>) items; for (Object o : c) { Object value = makeValue(o, null, valueExpr); options.put(value.toString(), makeOption(o, value, beanValue, textExpr, l10nExpr)); } } else if (items instanceof Map) { Map<?, ?> m = (Map<?, ?>) items; for (Map.Entry entry : m.entrySet()) { Object value = makeValue(entry.getValue(), entry.getKey(), valueExpr); Option option = makeOption(entry.getValue(), value, beanValue, textExpr, l10nExpr); options.put(value.toString(), option); } } else if (items.getClass().isArray()) { int length = Array.getLength(items); for (int i = 0; i < length; i++) { Object itemsValue = Array.get(items, i); Object value = makeValue(itemsValue, null, valueExpr); Option option = makeOption(itemsValue, value, beanValue, textExpr, l10nExpr); options.put(value.toString(), option); } } } parameters.put("options", options); return parameters; } /** * Makes an option. If the attributes contains a <code>l10nExpr</code>, it is used with the Object to get a message * from the {@link MessageProvider}. If that doesn't exist and a <code>textExpr</code> does, it is used to get the * text for the option from the object. Otherwise, the object is converted to a String for the text. Also, if the * object exists in the given Collection the option is set to selected. * * @param item The current value from the items collection/array/map. * @param value The value of the option. This could have been from the items Map or the valueExpr evaluation. * @param beanValue The value from the bean, used to determine selected state. * @param textExpr The textExpr attribute. * @param l10nExpr The l10nExpr attribute. * @return The option and never null. */ private Option makeOption(Object item, Object value, Object beanValue, String textExpr, String l10nExpr) { if (item == null) { return new Option("", false, null); } String text = null; if (l10nExpr != null) { Object l10nKey = expressionEvaluator.getValue(l10nExpr, item); if (l10nKey != null) { text = messageProvider.getMessage(l10nKey.toString()); } } if (text == null && textExpr != null) { text = expressionEvaluator.getValue(textExpr, item).toString(); } if (text == null && item instanceof Enum) { try { text = messageProvider.getMessage(item.toString()); } catch (MissingMessageException e) { // Smother this and continue to use the toString() for the text } } if (text == null) { text = item.toString(); } if (beanValue == null) { return new Option(text, false, item); } if (beanValue instanceof Collection) { return new Option(text, ((Collection<?>) beanValue).contains(value), item); } if (beanValue.getClass().isArray()) { int length = Array.getLength(beanValue); for (int i = 0; i < length; i++) { Object arrayValue = Array.get(beanValue, i); if (arrayValue != null && arrayValue.equals(value)) { return new Option(text, true, item); } } } // FreeMarker wraps all maps into Map<String, Object> and does it incorrectly at that. // This is a hack to ensure that when keys are compared, it works for Maps. Most of the time // the class of the value from the bean and the value for the ListInput will be the same, // such as Integer. boolean equal; if (value != null) { if (beanValue.getClass().isInstance(value)) { equal = beanValue.equals(value); } else { equal = beanValue.toString().equals(value.toString()); } } else { if (beanValue.getClass().isInstance(item)) { equal = beanValue.equals(item); } else { equal = beanValue.toString().equals(item.toString()); } } return new Option(text, equal, item); } /** * Determines the key. If the attribute contains a <code>keyExpr</code>, it is used against the object to get the * key. * Otherwise, the object is just converted to a String. * * @param itemsValue The current value from the items collection/array/map used to determine the key. * @param key The key from a items Map or null if items is not a Map. * @param valueExpr The valueExpr attribute. * @return The key and never null. If the Object is null, this returns an empty String. */ private Object makeValue(Object itemsValue, Object key, String valueExpr) { if (itemsValue == null) { return ""; } if (valueExpr != null) { Object value = expressionEvaluator.getValue(valueExpr, itemsValue); if (value != null) { return value; } } if (key != null) { return key; } return itemsValue; } public static class Option { private final Object object; private final boolean selected; private final String text; public Option(String text, boolean selected, Object object) { this.text = text; this.selected = selected; this.object = object; } public Object getObject() { return object; } public String getText() { return text; } public boolean isSelected() { return selected; } } }