/******************************************************************************* * Copyright (c) 2007, 2014 compeople AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * compeople AG - initial API and implementation *******************************************************************************/ package org.eclipse.riena.ui.ridgets.holder; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.eclipse.core.databinding.BindingException; import org.eclipse.riena.core.util.StringUtils; import org.eclipse.riena.ui.ridgets.databinding.ConverterFactory; /** * A generic value and selection holder for combo boxes. It supports an optional * empty item. An empty item will be displayed as an empty string and selected * as {@code null}.<br> * Its intended usage is: * * <pre> * IComboRidget.bindToModel(SelectableListHolder<?> listHolder, String renderMethodName) * </pre> * * @param <T> * the generic type of the holder * * @since 4.0 */ public class SelectableListHolder<T> { /** Constant for accessing the lists items */ public static final String PROP_LIST = "items"; //$NON-NLS-1$ /** Constant for accessing the selection */ public static final String SELECTION_PROPERTY = "selection"; //$NON-NLS-1$ private final List<T> items; private Class<T> guessedType; private T selection; protected final static Object EMPTY = new Object() { @Override public String toString() { return ""; //$NON-NLS-1$ }; }; /** * Create an empty {@code SelectableListHolder}. */ public SelectableListHolder() { this(false); } /** * Create an {@code SelectableListHolder} with probably an empty item. * * @param withEmtpy * on {@code true} has an empty item */ public SelectableListHolder(final boolean withEmtpy) { this.items = init(withEmtpy, null); } /** * Create an {@code SelectableListHolder} with the given items. * * @param items * items of the list */ public SelectableListHolder(final T... items) { this(false, items); } /** * Create an {@code SelectableListHolder} with probably an empty item and * the given items. * * @param items * items of the list * @param withEmtpy * on {@code true} has an empty item */ public SelectableListHolder(final boolean withEmtpy, final T... items) { this.items = init(withEmtpy, Arrays.asList(items)); } /** * Create an {@code SelectableListHolder} with the given items. * * @param items * items of the list */ public SelectableListHolder(final List<T> items) { this(false, items); } /** * Create an {@code SelectableListHolder} with probably an empty item and * the given items. * * @param items * items of the list * @param withEmtpy * on {@code true} has an empty item */ public SelectableListHolder(final boolean withEmtpy, final List<T> items) { this.items = init(withEmtpy, items); } @SuppressWarnings("unchecked") private List<T> init(final boolean withEmpty, final List<T> items) { guessedType = (Class<T>) (items != null && items.size() > 0 ? items.get(0).getClass() : EMPTY.getClass()); final List<T> list = new ArrayList<T>(); if (withEmpty) { list.add((T) EMPTY); } if (items != null) { list.addAll(items); } return list; } /** * Get items. * * @return the items */ public List<T> getItems() { return items; } /** * Get the selected item. * * @return selected item */ public T getSelection() { return selection == EMPTY ? null : selection; } /** * Set the selected item. * * @param selection * the selected item * */ public void setSelection(final T selection) { this.selection = selection; } /** * Sort the items with the given comparator. * * @param comparator * the comparator */ public void sortItems(final Comparator<? super T> comparator) { Collections.sort(items, comparator); } /** * Get the 'guessed' type of the list holder. The type is guessed by * inspecting the lists items. * <p> * <b>Note: </b>This method is called by the ridgets bind method. * * @return the guessed type. */ public Class<?> getGuessedType() { return guessedType; } /** * Get a {@code ConverterFactory}. * <p> * <b>Note: </b>This method is called by the ridgets bind method. * * @param renderMethodName * a string that identifies a parameterless method returning a * {@code String}. * @return the {@code ConverterFactory} */ public ConverterFactory<T, String> getConverterFactory(final String renderMethodName) { final ConverterFactory<T, String> factory = new ConverterFactory<T, String>(guessedType, String.class); final Method renderer = getRenderer(renderMethodName); for (final T item : getItems()) { try { factory.add(item, item == EMPTY ? EMPTY.toString() : renderer.invoke(item).toString()); } catch (final Exception e) { throw new BindingException("Invoking render method for type " + guessedType.getName() + " failed.", e); //$NON-NLS-1$ //$NON-NLS-2$ } } return factory; } private Method getRenderer(final String renderMethodName) { try { final Method renderMethod = guessedType.getMethod((StringUtils.isGiven(renderMethodName) ? renderMethodName : "toString")); //$NON-NLS-1$ if (renderMethod.getReturnType() != String.class) { throw new BindingException("Render method for type " + guessedType.getName() //$NON-NLS-1$ + " has not String as return type."); //$NON-NLS-1$ } return renderMethod; } catch (final Exception e) { throw new BindingException("Could not find render method for type " + guessedType.getName(), e); //$NON-NLS-1$ } } }