package de.unioninvestment.eai.portal.portlet.crud.mvp.views.ui;
import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.vaadin.data.Container;
import com.vaadin.data.Container.Filterable;
import com.vaadin.data.Container.Indexed;
import com.vaadin.data.Container.ItemSetChangeNotifier;
import com.vaadin.data.Item;
import com.vaadin.data.Property;
import com.vaadin.data.util.AbstractContainer;
import com.vaadin.data.util.ObjectProperty;
import com.vaadin.data.util.filter.UnsupportedFilterException;
import com.vaadin.ui.ComboBox;
import de.unioninvestment.eai.portal.portlet.crud.domain.events.OptionListChangeEvent;
import de.unioninvestment.eai.portal.portlet.crud.domain.events.OptionListChangeEventHandler;
import de.unioninvestment.eai.portal.portlet.crud.domain.model.OptionList;
import de.unioninvestment.eai.portal.portlet.crud.domain.model.SelectionContext;
/**
* Container for Option Lists. This class allows lazy initialization of the
* backing {@link OptionList} by the vaadin framework. If lazy loading is
* activated, the list is only loaded after the first filtering action (key
* input on Vaadin {@link ComboBox}). Up to then, the container behaves like
* empty.
*
* @author carsten.mjartan
*/
public class OptionListContainer extends AbstractContainer implements
Container, Filterable, Indexed, ItemSetChangeNotifier,
OptionListChangeEventHandler {
private static final long serialVersionUID = 1L;
private static final List<String> PROPERTY_IDS = asList("title");
private final Object lock = new Object();
private final OptionList optionList;
private SelectionContext context;
private LinkedHashMap<String, Option> options = new LinkedHashMap<String, Option>();
private ArrayList<Option> optionsAsList = new ArrayList<Option>();
private List<Filter> filters = new LinkedList<Filter>();
volatile boolean initialized = false;
volatile boolean contentChanged = false;
/**
* One Option {@link Item} of the list.
*
* @author carsten.mjartan
*/
public static class Option implements Item {
private static final long serialVersionUID = 1L;
private final String key;
private final String title;
private final int index;
/**
* Constructor.
*
* @param key
* the option key
* @param title
* the display text
* @param index
* the position inside the optionlist
*/
public Option(String key, String title, int index) {
this.key = key;
this.title = title;
this.index = index;
}
/**
* @return the option key
*/
String getKey() {
return key;
}
/**
* @return the display text
*/
String getTitle() {
return title;
}
/**
* @return the position of the option
*/
int getIndex() {
return index;
}
@Override
public String toString() {
return title;
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public Property getItemProperty(Object id) {
if (id.equals("title")) {
return new ObjectProperty(title, String.class);
} else {
return null;
}
}
@Override
public Collection<?> getItemPropertyIds() {
return PROPERTY_IDS;
}
@Override
public boolean addItemProperty(Object id, @SuppressWarnings("rawtypes") Property property)
throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
@Override
public boolean removeItemProperty(Object id)
throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
}
/**
* Constructor. If lazy loading is disabled, the Optionlist is fetched
* immediately.
*
* @param optionList
* the backing OptionList
* @param context
* the current usage context
*/
public OptionListContainer(OptionList optionList, SelectionContext context) {
this.optionList = optionList;
this.context = context;
this.optionList.addChangeListener(this);
if (!optionList.isLazy() || optionList.isInitialized()) {
createFilteredOptionList();
initialized = true;
}
}
@Override
public Item getItem(Object itemId) {
refreshOptionListIfNeeded();
String key = (String) itemId;
return options.get(key);
}
@Override
public Collection<?> getContainerPropertyIds() {
return PROPERTY_IDS;
}
@Override
public Collection<?> getItemIds() {
refreshOptionListIfNeeded();
return options.keySet();
}
@Override
public List<?> getItemIds(int startIndex, int numberOfItems) {
int realCount = optionsAsList.size() - startIndex;
if (realCount < 0) {
throw new IndexOutOfBoundsException();
}
if (realCount > numberOfItems) {
realCount = numberOfItems;
}
List<String> results = new ArrayList<String>(realCount);
List<Option> subList = optionsAsList.subList(startIndex, startIndex
+ realCount);
for (Option option : subList) {
results.add(option.key);
}
return results;
}
@Override
public Property<?> getContainerProperty(Object itemId, Object propertyId) {
refreshOptionListIfNeeded();
Option option = options.get(itemId);
if (option == null) {
return null;
} else {
return option.getItemProperty(propertyId);
}
}
@Override
public Class<?> getType(Object propertyId) {
return String.class;
}
@Override
public int size() {
refreshOptionListIfNeeded();
return options.size();
}
@Override
public boolean containsId(Object itemId) {
refreshOptionListIfNeeded();
return options.containsKey(itemId);
}
private void refreshOptionListIfNeeded() {
if ((!initialized && (!optionList.isLazy())) || contentChanged) {
contentChanged = false;
createFilteredOptionList();
if (!initialized) {
initialized = true;
}
}
}
private boolean doesFilter() {
return filters.size() > 0;
}
private void createFilteredOptionList() {
// this has to be outside the lock to prevent a deadlock situation
Map<String, String> newOptions = optionList.getOptions(context);
synchronized (lock) {
boolean unfiltered = !doesFilter();
int index = 0;
options = new LinkedHashMap<String, Option>();
for (Entry<String, String> entry : newOptions.entrySet()) {
String key = entry.getKey();
Option item = new Option(key, entry.getValue(), index++);
if (unfiltered || passesFilter(key, item)) {
options.put(key, item);
}
}
optionsAsList = new ArrayList<Option>(options.values());
}
}
private boolean passesFilter(String key, Option item) {
for (Filter filter : filters) {
if (!filter.passesFilter(key, item)) {
return false;
}
}
return true;
}
@Override
public void addContainerFilter(Filter filter)
throws UnsupportedFilterException {
this.filters.add(filter);
fireItemSetChange();
}
@Override
public void removeContainerFilter(Filter filter) {
for (Iterator<Filter> it = filters.iterator(); it.hasNext();) {
Filter next = it.next();
if (next.equals(filter)) {
it.remove();
fireItemSetChange();
return;
}
}
}
@Override
public Collection<Filter> getContainerFilters() {
return unmodifiableList(filters);
}
@Override
protected void fireItemSetChange() {
contentChanged = true;
super.fireItemSetChange();
}
@Override
public void removeAllContainerFilters() {
if (filters.size() > 0) {
filters.clear();
fireItemSetChange();
}
}
@Override
public Object nextItemId(Object itemId) {
refreshOptionListIfNeeded();
int index = options.get(itemId).getIndex();
if (index + 1 >= optionsAsList.size()) {
return null;
} else {
return optionsAsList.get(index + 1).getKey();
}
}
@Override
public Object prevItemId(Object itemId) {
refreshOptionListIfNeeded();
int index = options.get(itemId).getIndex();
return index == 0 ? null : optionsAsList.get(index - 1).getKey();
}
@Override
public Object firstItemId() {
refreshOptionListIfNeeded();
return optionsAsList.size() > 0 ? optionsAsList.get(0).getKey() : null;
}
@Override
public Object lastItemId() {
refreshOptionListIfNeeded();
return optionsAsList.size() > 0 ? optionsAsList.get(
optionsAsList.size() - 1).getKey() : null;
}
@Override
public boolean isFirstId(Object itemId) {
refreshOptionListIfNeeded();
return itemId.equals(firstItemId());
}
@Override
public boolean isLastId(Object itemId) {
refreshOptionListIfNeeded();
return itemId.equals(lastItemId());
}
@Override
public int indexOfId(Object itemId) {
refreshOptionListIfNeeded();
Option option = options.get(itemId);
return option == null ? -1 : option.getIndex();
}
@Override
public Object getIdByIndex(int index) {
refreshOptionListIfNeeded();
return optionsAsList.get(index).getKey();
}
@Override
public Item addItem(Object itemId) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
@Override
public Object addItem() throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
@Override
public boolean removeItem(Object itemId)
throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
@Override
public boolean addContainerProperty(Object propertyId, Class<?> type,
Object defaultValue) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
@Override
public boolean removeContainerProperty(Object propertyId)
throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
@Override
public boolean removeAllItems() throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
@Override
public Object addItemAfter(Object previousItemId)
throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
@Override
public Item addItemAfter(Object previousItemId, Object newItemId)
throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
@Override
public Object addItemAt(int index) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
@Override
public Item addItemAt(int index, Object newItemId)
throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
@Override
public void addListener(ItemSetChangeListener listener) {
super.addItemSetChangeListener(listener);
}
@Override
public void removeListener(ItemSetChangeListener listener) {
super.removeItemSetChangeListener(listener);
}
@Override
public void onOptionListChange(OptionListChangeEvent event) {
contentChanged = true;
if (event.isInitialized()) {
fireItemSetChange();
}
}
@Override
public void addItemSetChangeListener(ItemSetChangeListener listener) {
super.addItemSetChangeListener(listener);
}
@Override
public void removeItemSetChangeListener(ItemSetChangeListener listener) {
super.removeItemSetChangeListener(listener);
}
/**
* Unregister from {@link OptionList} events.
*/
public void close() {
optionList.removeChangeListener(this);
options = null;
optionsAsList = null;
}
}