org.vaadin.viritin.ListContainer.java Source code

Java tutorial

Introduction

Here is the source code for org.vaadin.viritin.ListContainer.java

Source

/*
 * Copyright 2014 Matti Tahvonen.
 *
 * 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.vaadin.viritin;

import com.vaadin.data.Container;
import com.vaadin.data.Container.ItemSetChangeNotifier;
import com.vaadin.data.Item;
import com.vaadin.data.Property;
import com.vaadin.data.util.AbstractContainer;
import org.apache.commons.beanutils.*;
import org.apache.commons.beanutils.expression.DefaultResolver;
import org.apache.commons.beanutils.expression.Resolver;
import org.apache.commons.collections.comparators.ComparableComparator;
import org.apache.commons.collections.comparators.NullComparator;
import org.apache.commons.collections.comparators.ReverseComparator;
import org.apache.commons.lang3.ClassUtils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A replacement for BeanItemContainer from the core
 * <p>
 * The ListContainer is rather similar to the cores BeanItemContainer, but has
 * better typed API, much smaller memory overhead (practically no overhead if
 * data is given as List) and also otherwise better performance.
 *
 * @param <T> the type of beans in the backed list
 */
public class ListContainer<T> extends AbstractContainer
        implements Container.Indexed, Container.Sortable, ItemSetChangeNotifier {

    private static final long serialVersionUID = -6709228455051205922L;

    private List<T> backingList;
    private List<String> properties;

    public ListContainer(Collection<? extends T> backingList) {
        setCollection(backingList);
    }

    public ListContainer(Class<? extends T> type, Collection<? extends T> backingList) {
        if (type != null) {
            dynaClass = WrapDynaClass.createDynaClass(type);
        }
        setCollection(backingList);
    }

    public final void setCollection(Collection<? extends T> backingList1) {
        if (backingList1 instanceof List<?>) {
            this.backingList = (List<T>) backingList1;
        } else {
            this.backingList = new ArrayList<T>(backingList1); // Type parameter to keep NB happy
        }

        fireItemSetChange();
    }

    public ListContainer(Class<? extends T> type) {
        backingList = new ArrayList<>();
        dynaClass = WrapDynaClass.createDynaClass(type);
    }

    public ListContainer(Class<? extends T> type, String... properties) {
        this(type);
        setContainerPropertyIds(properties);
    }

    protected List<T> getBackingList() {
        return backingList;
    }

    private transient WrapDynaClass dynaClass;

    private WrapDynaClass getDynaClass() {
        if (dynaClass == null && !backingList.isEmpty()) {
            return getDynaClass(backingList.get(0));
        }
        return dynaClass;
    }

    private WrapDynaClass getDynaClass(Object reference) {
        if (dynaClass == null && reference != null) {
            dynaClass = WrapDynaClass.createDynaClass(reference.getClass());
        }
        return dynaClass;
    }

    @Override
    public int indexOfId(Object itemId) {
        return getBackingList().indexOf(itemId);
    }

    public int indexOf(T bean) {
        return indexOfId(bean);
    }

    @Override
    public T getIdByIndex(int index) {
        return getBackingList().get(index);
    }

    @Override
    public List<T> getItemIds(int startIndex, int numberOfItems) {
        // Whooo!? Vaadin calls this method with numberOfItems == -1
        if (numberOfItems < 0) {
            throw new IllegalArgumentException();
        }

        return getBackingList().subList(startIndex, startIndex + numberOfItems);
    }

    @Override
    public Object addItemAt(int index) throws UnsupportedOperationException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public Item addItemAt(int index, Object newItemId) throws UnsupportedOperationException {
        backingList.add(index, (T) newItemId);
        fireItemSetChange();
        return getItem(newItemId);
    }

    @Override
    public T nextItemId(Object itemId) {
        int i = getBackingList().indexOf(itemId) + 1;
        if (getBackingList().size() == i) {
            return null;
        }
        return getBackingList().get(i);
    }

    @Override
    public T prevItemId(Object itemId) {
        int i = getBackingList().indexOf(itemId) - 1;
        if (i < 0) {
            return null;
        }
        return getBackingList().get(i);
    }

    @Override
    public T firstItemId() {
        return (getBackingList().isEmpty()) ? null : getBackingList().get(0);
    }

    @Override
    public T lastItemId() {
        return getBackingList().isEmpty() ? null : getBackingList().get(getBackingList().size() - 1);
    }

    @Override
    public boolean isFirstId(Object itemId) {
        return itemId.equals(firstItemId());
    }

    @Override
    public boolean isLastId(Object itemId) {
        return itemId.equals(lastItemId());
    }

    @Override
    public Object addItemAfter(Object previousItemId) throws UnsupportedOperationException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public Item addItemAfter(Object previousItemId, Object newItemId) throws UnsupportedOperationException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public Item getItem(Object itemId) {
        if (itemId == null) {
            return null;
        }
        return new DynaBeanItem<>((T) itemId);
    }

    @Override
    public Collection<String> getContainerPropertyIds() {
        if (properties == null) {
            if (getDynaClass() != null) {
                ArrayList<String> props = new ArrayList<>();
                for (DynaProperty db : getDynaClass().getDynaProperties()) {
                    if (db.getType() != null) {
                        props.add(db.getName());
                    } else {
                        // type may be null in some cases
                        Logger.getLogger(ListContainer.class.getName()).log(Level.FINE,
                                "Type not detected for property {0}", db.getName());
                    }
                }
                props.remove("class");
                properties = props;
            } else {
                return Collections.EMPTY_LIST;
            }
        }
        return properties;

    }

    @Override
    public Collection<?> getItemIds() {
        return getBackingList();
    }

    @Override
    public Property getContainerProperty(Object itemId, Object propertyId) {
        final Item i = getItem(itemId);
        return (i != null) ? i.getItemProperty(propertyId) : null;
    }

    @Override
    public Class<?> getType(Object propertyId) {
        final String pName = propertyId.toString();
        try {
            final DynaProperty dynaProperty = getDynaClass().getDynaProperty(pName);
            final Class<?> type = dynaProperty.getType();
            if (type.isPrimitive()) {
                // Vaadin can't handle primitive types in _all_ places, so use
                // wrappers instead. FieldGroup works, but e.g. Table in _editable_
                // mode fails for some reason
                return ClassUtils.primitiveToWrapper(type);
            }
            return type;
        } catch (Exception e) {
            // If type can't be found via dynaClass, it is most likely 
            // nested/indexed/mapped property
            try {
                return getNestedPropertyType(getDynaClass(), pName);
            } catch (final IllegalAccessException | InvocationTargetException | NoSuchMethodException
                    | ClassNotFoundException | NoSuchFieldException ex) {
                throw new RuntimeException(ex);
            }
        }
    }

    private static Resolver resolver = new DefaultResolver();

    public static Class<?> getNestedPropertyType(DynaClass bean, String name) throws IllegalAccessException,
            InvocationTargetException, NoSuchMethodException, ClassNotFoundException, NoSuchFieldException {
        if (bean == null) {
            // The type is not properly initilized yet, just leave it as generic
            // Object
            return Object.class;
        }
        // Resolve nested references
        while (resolver.hasNested(name)) {
            String next = resolver.next(name);
            if (resolver.isIndexed(next) || resolver.isMapped(next)) {
                String property = resolver.getProperty(next);
                Class<?> clazz = Class.forName(bean.getName());
                Class<?> detectTypeParameter = detectTypeParameter(clazz, property,
                        resolver.isIndexed(name) ? 0 : 1);
                bean = WrapDynaClass.createDynaClass(detectTypeParameter);
                return getNestedPropertyType(bean, resolver.remove(name));
            }
            DynaProperty db = bean.getDynaProperty(next);
            bean = WrapDynaClass.createDynaClass(db.getType());
            name = resolver.remove(name);
        }
        if (resolver.isMapped(name) || resolver.isIndexed(name)) {
            String property = resolver.getProperty(name);
            Class<?> clazz = Class.forName(bean.getName());
            return detectTypeParameter(clazz, property, resolver.isIndexed(name) ? 0 : 1);
        }
        Class<?> type;
        DynaProperty dynaProperty = bean.getDynaProperty(name);
        if (dynaProperty != null) {
            type = dynaProperty.getType();
        } else { //happens for default methods
            Method method = obtainGetterOfProperty(bean, name);
            type = method.getReturnType();
        }
        if (type.isPrimitive() == true) {
            // Vaadin can't handle primitive types in _all_ places, so use
            // wrappers instead. FieldGroup works, but e.g. Table in _editable_
            // mode fails for some reason
            return ClassUtils.primitiveToWrapper(type);
        }
        return type;
    }

    /**
     * Explicitly resolves the getter bypassing commons.beanutils
     * @param beanClass
     * @param propertyName
     * @return getter method of the property
     * @throws ClassNotFoundException if class forName fails
     * @throws NoSuchMethodException if there is no getter
     * @throws SecurityException if there are constraints by the security manager
     */
    private static Method obtainGetterOfProperty(DynaClass beanClass, String propertyName)
            throws ClassNotFoundException, NoSuchMethodException, SecurityException {
        Class<?> clazz = Class.forName(beanClass.getName());
        return clazz.getMethod("get" + firstLetterUppercase(propertyName));
    }

    private static String firstLetterUppercase(String s) {
        return Character.toUpperCase(s.charAt(0)) + (s.length() > 1 ? s.substring(1) : "");
    }

    private static Class<?> detectTypeParameter(Class clazz, String name, int idx) throws NoSuchFieldException {
        Field declaredField = clazz.getDeclaredField(name);
        Type genericType = declaredField.getGenericType();
        if (genericType instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) genericType;
            return (Class<?>) parameterizedType.getActualTypeArguments()[idx];
        }
        return Object.class;
    }

    @Override
    public int size() {
        return getBackingList().size();
    }

    @Override
    public boolean containsId(Object itemId) {
        return getBackingList().contains(itemId);
    }

    @Override
    public Item addItem(Object itemId) throws UnsupportedOperationException {
        backingList.add((T) itemId);
        fireItemSetChange();
        return getItem(itemId);
    }

    @Override
    public Object addItem() throws UnsupportedOperationException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public boolean removeItem(Object itemId) throws UnsupportedOperationException {
        final boolean remove = backingList.remove(itemId);
        if (remove) {
            fireItemSetChange();
        }
        return remove;
    }

    @Override
    public boolean addContainerProperty(Object propertyId, Class<?> type, Object defaultValue)
            throws UnsupportedOperationException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public boolean removeContainerProperty(Object propertyId) throws UnsupportedOperationException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public boolean removeAllItems() throws UnsupportedOperationException {
        backingList.clear();
        fireItemSetChange();
        return true;
    }

    public ListContainer addAll(Collection<T> beans) {
        backingList.addAll(beans);
        fireItemSetChange();
        return this;
    }

    @Override
    public void sort(Object[] propertyId, boolean[] ascending) {

        // Grid in 7.4 may call this method with empty sorting instructions...
        if (propertyId.length > 0) {
            if (backingList instanceof SortableLazyList) {
                // using with MGrid may end up here
                SortableLazyList sll = (SortableLazyList) backingList;
                String[] stringProperties = new String[propertyId.length];
                System.arraycopy(propertyId, 0, stringProperties, 0, propertyId.length);
                sll.setSortProperty(stringProperties);
                sll.setSortAscending(ascending);
                sll.reset();
            } else {
                Comparator<T> comparator = new PropertyComparator(propertyId, ascending);

                Collections.sort(backingList, comparator);
            }
            fireItemSetChange();
        }
    }

    @Override
    public Collection<?> getSortableContainerPropertyIds() {
        if (backingList instanceof SortableLazyList) {
            // Assume SortableLazyList can sort by any Comparable property
        } else if (backingList instanceof LazyList) {
            // When using LazyList, don't support sorting by default
            // as the sorting should most probably be done at backend call level
            return Collections.emptySet();
        }
        final ArrayList<String> props = new ArrayList<>();
        for (Object a : getContainerPropertyIds()) {
            String propName = a.toString();
            try {
                Class<?> propType = getType(propName);
                if (propType != null && (propType.isPrimitive() || Comparable.class.isAssignableFrom(propType))) {
                    props.add(propName);
                }
            } catch (Exception e) {
            }
        }
        return props;
    }

    @Override
    public void addItemSetChangeListener(Container.ItemSetChangeListener listener) {
        super.addItemSetChangeListener(listener);
    }

    @Override
    public void removeItemSetChangeListener(Container.ItemSetChangeListener listener) {
        super.removeItemSetChangeListener(listener);
    }

    @Override
    public void addListener(Container.ItemSetChangeListener listener) {
        super.addListener(listener);
    }

    @Override
    public void removeListener(Container.ItemSetChangeListener listener) {
        super.removeListener(listener);
    }

    /**
     *
     * Override point. Allows user to use custom comparators based on
     * properties.
     *
     * @param property the property whose comparator is requested
     * @return Comparator that will compare two objects based on a property
     */
    protected Comparator<T> getUnderlyingComparator(Object property) {
        return new NullComparator();
    }

    /**
     * Explicitly sets the property ids that should be reported by this
     * container. Allows e.g. usage of "nested properties".
     *
     * @param properties the properties reported by this container
     */
    public void setContainerPropertyIds(String... properties) {
        this.properties = Arrays.asList(properties);
    }

    @Override
    public void fireItemSetChange() {
        super.fireItemSetChange();
    }

    private class PropertyComparator implements Comparator<T> {

        private final Object[] propertyId;
        private final boolean[] ascending;

        private PropertyComparator(Object[] propertyId, boolean[] ascending) {
            this.propertyId = propertyId;
            this.ascending = ascending;
        }

        @Override
        public int compare(T o1, T o2) {
            for (int i = 0; i < propertyId.length; i++) {
                Comparator underlyingComparator = getUnderlyingComparator(propertyId[i]);
                Comparator currentComparator = underlyingComparator != null ? underlyingComparator
                        : ComparableComparator.getInstance();

                if (!ascending[i]) {
                    currentComparator = new ReverseComparator(currentComparator);
                }

                final Property o1Prop = getContainerProperty(o1, propertyId[i]);
                Object o1Value = (o1Prop != null) ? o1Prop.getValue() : null;

                final Property o2Prop = getContainerProperty(o2, propertyId[i]);
                Object o2Value = (o2Prop != null) ? o2Prop.getValue() : null;

                return currentComparator.compare(o1Value, o2Value);
            }

            return 0;
        }
    }

    public class DynaBeanItem<T> implements Item {

        private static final long serialVersionUID = 39911097876284908L;

        private final Map<Object, DynaProperty> propertyIdToProperty = new HashMap<>();

        private class DynaProperty implements Property {

            private static final long serialVersionUID = -6887983770952190177L;

            private final String propertyName;

            DynaProperty(String property) {
                propertyName = property;
            }

            @Override
            public Object getValue() {
                DynaBean dynaBean = getDynaBean();
                try {
                    return dynaBean.get(propertyName);
                } catch (final Exception e) {
                    try {
                        return PropertyUtils.getProperty(bean, propertyName);
                    } catch (final NestedNullException | java.lang.IndexOutOfBoundsException ex) {
                        return null;
                    } catch (final IllegalAccessException | InvocationTargetException ex) {
                        throw new RuntimeException(ex);
                    } catch (final NoSuchMethodException ex) {
                        Logger.getLogger(ListContainer.class.getName()).log(Level.FINE,
                                "Trying default method fallback for property {0}", propertyName);
                    }
                }

                //fallback for default methods:
                Method method;
                try {
                    method = obtainGetterOfProperty(dynaBean.getDynaClass(), propertyName);
                    return method.invoke(bean);
                } catch (final ReflectiveOperationException | SecurityException | IllegalArgumentException e) {
                    throw new RuntimeException(e);
                }
            }

            @Override
            public void setValue(Object newValue) throws Property.ReadOnlyException {
                try {
                    PropertyUtils.setProperty(bean, propertyName, newValue);
                } catch (final IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) {
                    throw new RuntimeException(ex);
                }
            }

            @Override
            public Class getType() {
                return ListContainer.this.getType(propertyName);
            }

            @Override
            public boolean isReadOnly() {
                return getDynaClass().getPropertyDescriptor(propertyName).getWriteMethod() == null;
            }

            @Override
            public void setReadOnly(boolean newStatus) {
                throw new UnsupportedOperationException("Not supported yet.");
            }

        }

        private final T bean;

        private transient DynaBean db;

        public DynaBeanItem(T bean) {
            this.bean = bean;
        }

        public T getBean() {
            return bean;
        }

        private DynaBean getDynaBean() {
            if (db == null) {
                try {
                    db = new WrapDynaBean(bean, getDynaClass(bean));
                } catch (Throwable e) {
                    // Older version of beanutils is somehow available by the 
                    // classloader! Probably tomee
                    db = new WrapDynaBean(bean);
                }
            }
            return db;
        }

        @Override
        public Property getItemProperty(Object id) {
            DynaProperty prop = propertyIdToProperty.get(id);
            if (prop == null) {
                prop = new DynaProperty(id.toString());
                propertyIdToProperty.put(id, prop);
            }
            return prop;
        }

        @Override
        public Collection<String> getItemPropertyIds() {
            return ListContainer.this.getContainerPropertyIds();
        }

        @Override
        public boolean addItemProperty(Object id, Property property) throws UnsupportedOperationException {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public boolean removeItemProperty(Object id) throws UnsupportedOperationException {
            throw new UnsupportedOperationException("Not supported yet.");
        }
    }
}