/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 2000-2003
 *      Sleepycat Software.  All rights reserved.
 *
 * $Id: StoredValueSet.java,v 1.16 2003/10/30 20:40:17 gburd Exp $
 */

package com.sleepycat.bdb.collection;

import com.sleepycat.bdb.bind.DataBinding;
import com.sleepycat.bdb.bind.EntityBinding;
import com.sleepycat.bdb.DataCursor;
import com.sleepycat.bdb.DataIndex;
import com.sleepycat.bdb.DataStore;
import com.sleepycat.bdb.DataView;
import com.sleepycat.db.Db;
import com.sleepycat.db.DbException;
import java.io.IOException;
import java.util.Collection;
import java.util.Set;

/**
 * The Set returned by Map.values() and Map.duplicates(), and which can also be
 * constructed directly if a Map is not needed.
 * Although this collection is a set it may contain duplicate values.  Only if
 * an entity value binding is used are all elements guaranteed to be unique.
 *
 * @author Mark Hayes
 */
public class StoredValueSet extends StoredCollection implements Set {

    // This class is also used internally for the set returned by duplicates().

    private boolean isSingleKey;

    /**
     * Creates a value set view of a {@link DataStore}.
     *
     * @param store is the DataStore underlying the new collection.
     *
     * @param valueBinding is the binding used to translate between value
     * buffers and value objects.
     *
     * @param writeAllowed is true to create a read-write collection or false
     * to create a read-only collection.
     *
     * @throws IllegalArgumentException if formats are not consistently
     * defined or a parameter is invalid.
     *
     * @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
     */
    public StoredValueSet(DataStore store,
                          DataBinding valueBinding,
                          boolean writeAllowed) {

        super(new DataView(store, null, null, valueBinding,
                          null, writeAllowed));
    }

    /**
     * Creates a value set entity view of a {@link DataStore}.
     *
     * @param store is the DataStore underlying the new collection.
     *
     * @param valueEntityBinding is the binding used to translate between
     * key/value buffers and entity value objects.
     *
     * @param writeAllowed is true to create a read-write collection or false
     * to create a read-only collection.
     *
     * @throws IllegalArgumentException if formats are not consistently
     * defined or a parameter is invalid.
     *
     * @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
     */
    public StoredValueSet(DataStore store,
                          EntityBinding valueEntityBinding,
                          boolean writeAllowed) {

        super(new DataView(store, null, null, null,
                           valueEntityBinding, writeAllowed));
    }

    /**
     * Creates a value set view of a {@link DataIndex}.
     *
     * @param index is the DataIndex underlying the new collection.
     *
     * @param valueBinding is the binding used to translate between value
     * buffers and value objects.
     *
     * @param writeAllowed is true to create a read-write collection or false
     * to create a read-only collection.
     *
     * @throws IllegalArgumentException if formats are not consistently
     * defined or a parameter is invalid.
     *
     * @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
     */
    public StoredValueSet(DataIndex index,
                          DataBinding valueBinding,
                          boolean writeAllowed) {

        super(new DataView(null, index, null, valueBinding,
                           null, writeAllowed));
    }

    /**
     * Creates a value set entity view of a {@link DataIndex}.
     *
     * @param index is the DataIndex underlying the new collection.
     *
     * @param valueEntityBinding is the binding used to translate between
     * key/value buffers and entity value objects.
     *
     * @param writeAllowed is true to create a read-write collection or false
     * to create a read-only collection.
     *
     * @throws IllegalArgumentException if formats are not consistently
     * defined or a parameter is invalid.
     *
     * @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
     */
    public StoredValueSet(DataIndex index,
                          EntityBinding valueEntityBinding,
                          boolean writeAllowed) {

        super(new DataView(null, index, null, null,
                           valueEntityBinding, writeAllowed));
    }

    StoredValueSet(DataView valueSetView) {

        super(valueSetView);
    }

    StoredValueSet(DataView valueSetView, boolean isSingleKey) {

        super(valueSetView);
        this.isSingleKey = isSingleKey;
    }

    /**
     * Adds the specified entity to this set if it is not already present
     * (optional operation).
     * This method conforms to the {@link Set#add} interface.
     *
     * @param entity is the entity to be added.
     *
     * @return true if the entity was added, that is the key-value pair
     * represented by the entity was not previously present in the collection.
     *
     * @throws UnsupportedOperationException if the collection is read-only,
     * if the collection is indexed, or if an entity binding is not used.
     *
     * @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
     */
    public boolean add(Object entity) {

        if (view.getIndex() != null) {
            throw new UnsupportedOperationException(
                "add() not allowed with index");
        } else if (isSingleKey) {
            // entity is actually just a value in this case
            boolean doAutoCommit = beginAutoCommit();
            try {
                int err = view.addValue(view.getSingleKeyThang(), entity,
                                        Db.DB_NODUPDATA);
                commitAutoCommit(doAutoCommit);
                return (err == 0);
            } catch (Exception e) {
                throw handleException(e, doAutoCommit);
            }
        } else if (view.getValueEntityBinding() == null) {
            throw new UnsupportedOperationException(
                "add() requires entity binding");
        } else {
            return add(null, entity);
        }
    }

    /* hide for now, add() will suffice
    public Object put(Object value) {

        return super.put(null, value);
    }

    public void putAll(Collection coll) {

        boolean doAutoCommit = beginAutoCommit();
	Iterator i = null;
        try {
            i = coll.iterator();
            while (i.hasNext()) {
                put(i.next());
            }
            StoredIterator.close(i);
            commitAutoCommit(doAutoCommit);
        } catch (Exception e); {
            StoredIterator.close(i);
            throw handleException(e, doAutoCommit);
        }
    }
    */

    /**
     * Returns true if this set contains the specified element.
     * This method conforms to the {@link java.util.Set#contains(Object)}
     * interface.
     *
     * @param value the value to check.
     *
     * @return whether the set contains the given value.
     */
    public boolean contains(Object value) {

        return containsValue(value);
    }

    /**
     * Removes the specified value from this set if it is present (optional
     * operation).
     * If an entity binding is used, the key-value pair represented by the
     * given entity is removed.  If an entity binding is used, the first
     * occurance of a key-value pair with the given value is removed.
     * This method conforms to the {@link Set#remove} interface.
     *
     * @throws UnsupportedOperationException if the collection is read-only.
     *
     * @throws RuntimeExceptionWrapper if a {@link DbException} is thrown.
     */
    public boolean remove(Object value) {

        return removeValue(value);
    }

    // javadoc is inherited
    public int size() {

        if (!isSingleKey) return super.size();
        DataCursor cursor = null;
        try {
            cursor = new DataCursor(view, false);
            int err = cursor.get(null, null, Db.DB_FIRST, false);
            if (err == 0) {
                return cursor.count();
            } else {
                return 0;
            }
        } catch (Exception e) {
            throw StoredContainer.convertException(e);
        } finally {
            closeCursor(cursor);
        }
    }

    Object makeIteratorData(StoredIterator iterator, DataCursor cursor)
        throws DbException, IOException {

        return cursor.getCurrentValue();
    }

    boolean hasValues() {

        return true;
    }
}
