/* Glazed Lists                                                 (c) 2003-2006 */
/* http://publicobject.com/glazedlists/                      publicobject.com,*/
/*                                                     O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.hibernate;

import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.event.ListEventPublisher;
import ca.odell.glazedlists.util.concurrent.ReadWriteLock;

import org.hibernate.HibernateException;
import org.hibernate.collection.PersistentCollection;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.usertype.ParameterizedType;
import org.hibernate.usertype.UserCollectionType;

import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

/**
 * A Hibernate custom collection type for mapping and persisting a {@link BasicEventList} with the
 * help of a {@link PersistentEventList}.
 * <p>
 * To create the EventLists, an {@link EventListFactory} is used. The default factory simply
 * instantiates new {@link BasicEventList}s with unshared {@link ReadWriteLock}s and
 * {@link ListEventPublisher}s. If that doesn't suite your needs, you can either implement and set
 * your own {@link EventListFactory} implementation. Or you can use a so called
 * <em>list category</em>. By setting a list category on the
 * EventListType instance, a different list factory will be used which uses the category to determine
 * the publisher and lock to use for all EventLists it creates. This way, all EventListType instances
 * which use the same list category will produce EventLists with the same shared lock and publisher.
 * The desired list category can be set programmatically by subclassing. When Hibernate bug
 * <a href="http://opensource.atlassian.com/projects/hibernate/browse/HHH-2336">HHH-2336</a>
 * is fixed, you will be able to specify the category as collection type parameter in your
 * Hibernate mapping file.
 *
 * @see #setListFactory(EventListFactory)
 * @see #useListCategory(String)
 * @see #PROPERTYNAME_EVENTLIST_CATEGORY
 *
 * @author Bruce Alspaugh
 * @author Holger Brands
 */
public class EventListType implements UserCollectionType, ParameterizedType {

    /** Name of property for specifying an EventList category in the Hibernate mapping file. */
    public static final String PROPERTYNAME_EVENTLIST_CATEGORY = "EventList.category";

    /** Factory for EventLists. */
    private EventListFactory listFactory = EventListFactory.DEFAULT;

    /**
     * Gets the used EventListFactory.
     */
    public final EventListFactory getListFactory() {
        return listFactory;
    }

    /**
     * Sets a new EventListFactory.
     */
    public final void setListFactory(EventListFactory newListFactory) {
        if (newListFactory == null)
            throw new IllegalArgumentException("EventListFactory must not be null");
        listFactory = newListFactory;
    }

    /**
     * When Hibernate bug <a href="http://opensource.atlassian.com/projects/hibernate/browse/HHH-2336">HHH-2336</a>
     * is fixed, this method will be called by Hibernate when reading its mapping files.
     */
    public final void setParameterValues(Properties parameters) {
        if (parameters == null) return;
        final String category = parameters.getProperty(PROPERTYNAME_EVENTLIST_CATEGORY);
        if (category != null) {
            useListCategory(category);
        }
    }

    /**
     * Convenience method to specify the used list category.
     */
    protected final void useListCategory(String category) {
        setListFactory(new CategoryEventListFactory(category));
    }

    /**
     * Convenience method to specify the used list category and the associated ReadWriteLock and
     * ListEventPublisher
     */
    protected final void useListCategory(String category, ReadWriteLock lock, ListEventPublisher publisher) {
        setListFactory(new CategoryEventListFactory(category, lock, publisher));
    }

    /** {@inheritDoc} */
    public boolean contains(Object collection, Object entity) {
        return ((EventList) collection).contains(entity);
    }

    /** {@inheritDoc} */
    public Iterator getElementsIterator(Object collection) {
        return ((EventList) collection).iterator();
    }

    /** {@inheritDoc} */
    public Object indexOf(Object collection, Object obj) {
        final int index = ((EventList) collection).indexOf(obj);
        return (index < 0) ? null : new Integer(index);
    }

    /**
     * Instantiate an empty instance of the "underlying" collection (not a wrapper).
     * Kept for compatibility with older Hibernate versions.
     */
    public Object instantiate() {
        return getListFactory().createEventList();
    }

    /** {@inheritDoc} */
    public Object instantiate(int anticipatedSize) {
        final EventListFactory fac = getListFactory();
        return anticipatedSize < 0 ? fac.createEventList() : fac.createEventList(anticipatedSize);
    }

    /** {@inheritDoc} */
    public PersistentCollection instantiate(SessionImplementor session,
            CollectionPersister persister) throws HibernateException {
        return new PersistentEventList(session, getListFactory());
    }

    /** {@inheritDoc} */
    public Object replaceElements(Object original, Object target, CollectionPersister persister,
            Object owner, Map copyCache, SessionImplementor session) throws HibernateException {
        final EventList result = (EventList) target;
        final EventList source = (EventList) original;
        result.clear();
        result.addAll(source);
        return result;
    }

    /** {@inheritDoc} */
    public PersistentCollection wrap(SessionImplementor session, Object collection) {
        return new PersistentEventList(session, (EventList) collection);
    }
}