BinaryMap.java Source code

Java tutorial

Introduction

Here is the source code for BinaryMap.java

Source

/*
 * LingPipe v. 3.9
 * Copyright (C) 2003-2010 Alias-i
 *
 * This program is licensed under the Alias-i Royalty Free License
 * Version 1 WITHOUT ANY WARRANTY, without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the Alias-i
 * Royalty Free License Version 1 for more details.
 *
 * You should have received a copy of the Alias-i Royalty Free License
 * Version 1 along with this program; if not, visit
 * http://alias-i.com/lingpipe/licenses/lingpipe-license-1.txt or contact
 * Alias-i, Inc. at 181 North 11th Street, Suite 401, Brooklyn, NY 11211,
 * +1 (718) 290-9170.
ectends */

//package com.aliasi.util;

import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;

import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

/**
 * The <code>BinaryMap</code> class implements a map from objects to
 * integer objects where the only value is the integer with value 1.
 * Instances of this class are typically returned by boolean feature
 * extractors, who return a map with only 1 values, because the 0
 * values are implicit.
 *
 * <p>Binary maps are based on a set of keys that map to 1.  Thus
 * they are more space efficient than Java's utility maps such
 * as tree maps or hash maps.  The underlying set implementation is
 * pluggable, but must be mutable if the resulting binary map
 * is to be mutable.
 *
 * <p>Modifiability through the entry set, key set, and values
 * collection is fully supported through their respective iterators
 * and through the collections themselves.  The map entries making
 * up the entry set may not have their values modified.
 *
 * <h3>Equality and Hash Codes</h3>
 *
 * Binary maps satisfy the requirements laid out for hash codes
 * and object equality specified in {@link Map}.
 * 
 * <h3>Thread Safety</h3>
 *
 * Binary maps have the same thread safety as their underlying
 * sets.  
 *
 * <h3>Serialization</h3>
 *
 * A binary map may be serialized if its underlying positive
 * set is serializable.
 *
 * @author  Bob Carpenter
 * @version 3.9.1
 * @since   LingPipe3.9.1
 * @param <E> the type of keys in the map
 */
public class BinaryMap<E> extends AbstractMap<E, Integer> implements Serializable {

    static final long serialVersionUID = -6965494782866980478L;

    private final Set<E> mPositiveSet;

    /**
     * Construct a binary map based on the specified set of positive
     * values.  The set is stored as is rather than copied.  The
     * set must be mutable if the constructed map is to be mutable.
     *
     * @param positiveSet
     */
    public BinaryMap(Set<E> positiveSet) {
        mPositiveSet = positiveSet;
    }

    /**
     * Adds the specified element to the map with value 1.
     *
     * <p>Note that this method is not part of the {@link Map}
     * interface.
     *
     * @param e Element added to the map with value 1.
     * @return {@code true} if the map didn't already contain the
     * element.
     * @throws UnsupportedOperationException If the underlying set of
     * positive elements does not support {@code Collection.add}.
     */
    public boolean add(E e) {
        return mPositiveSet.add(e);
    }

    /**
     * Returns the set of keys mapping to 1.  This set is backed by
     * this map, so changes to the set are reflected in the map and
     * vice-versa.  
     *
     * <p>If the underlying set is not modifiable, attempts to
     * modify the key set will raise unsupported operation exceptions.
     *
     * <p>Note that results are undefined in the middle of an
     * iterator, which will likely throw concurrent modification, null
     * pointer, or array index out of bounds exceptions.  Therefore,
     * access to the key set must be synchronized in the same way
     * access to the underlying set.
     *
     * <p>Further note that unlike the specification in {@link Map},
     * the returned keyset supports the {@link Collection#add(Object)} and
     * {@link Collection#addAll(Collection)}.  Adding elements to the key set
     * is the same as adding them through the {@link #add(Object)}
     * method of this class.
     *
     * @return The set of keys mapping to one.
     */
    public Set<E> keySet() {
        return mPositiveSet;
    }

    /**
     * Return a set view of the mappings in this map.  This set is
     * backed by this map, so changes to the return set affect
     * this set and vice-versa.  The resulting set supports deletions
     * through {@code Iterator.remove}, {@code Collection.remove},
     * {@code Collection.removeAll}, {@code Collection.retainAll},
     * and {@code Collection.clear} operations, but does not support
     * the add operations.
     *
     * <p>If the underlying set of positive elements does not
     * support these modification operations, they will throw an
     * unsupported operation exception.
     *
     * <p><i>Implementation Note:</i> The {@code Map.Entry} elements
     * are created as necessary by the entry set using a relatively
     * efficient implementation of entries that only stores the key.
     * Accessing the key set is more efficient.
     *
     * @return The set of mappings for this map. 
     */
    @Override
    public Set<Map.Entry<E, Integer>> entrySet() {
        return new EntrySet();
    }

    /**
     * Returns the {@code Integer} with value 1 if the specified
     * argument is mapped to 1 by this map, and returns null
     * otherwise.
     *
     * <p>The constant {@link #ONE} is used for the return
     * value.
     *
     * @param key The element whose value is returned.
     * @return 1 if the element is mapped to 1, and {@code null}
     * otherwise.
     */
    @Override
    public Integer get(Object key) {
        return mPositiveSet.contains(key) ? ONE : null;
    }

    /**
     * Remove the mapping with the specified key if it is present, returning
     * the previous value, or {@code null} if it was previously undefined.
     *
     * @param key Key of mapping to remove.
     * @return The value 1 if the key is already present, and {@code
     * null} otherwise.
     * @throws UnsupportedOperationException If the underlying set for
     * this map does not support the {@link Set#remove(Object)} operation.
     */
    @Override
    public Integer remove(Object key) {
        return mPositiveSet.remove(key) ? ONE : null;
    }

    /**
     * Returns the size of this mapping.
     *
     * @return Size of this mapping.
     */
    @Override
    public int size() {
        return mPositiveSet.size();
    }

    /**
     * Returns an immutable collection view of the values for this
     * map.  The resulting collecton will be empty if the map is
     * entry, or contain the single value {@link #ONE} if the map is
     * not empty.
     */
    @Override
    public Collection<Integer> values() {
        return new Values();
    }

    /**
     * Removes all of the mappings from this map.
     *
     * <p>The implementation just delegates the clear operation to the
     * contained set.
     *
     * @throws UnsupportedOperationException If the clear operation
     * is not supported by the contained set.
     */
    @Override
    public void clear() {
        mPositiveSet.clear();
    }

    /**
     * Returns {@code true} if this mapping contains a mapping
     * for the specified object.
     *
     * <p>This method delegates to the underlying positive set's
     * {@code Collection.contains} method.
     *
     * @param o Object to teset.
     * @return {@code true} if it is mapped by this mapping.
     * @throws ClassCastException If the underlying set throws
     * a class cast when checking the specified object for
     * membership.
     */
    @Override
    public boolean containsKey(Object o) {
        return mPositiveSet.contains(o);
    }

    /**
     * Returns {@code true} if this map contains a mapping
     * from some key to the specified value.  
     *
     * <p>Note that the only object for which this map may
     * return true is the {@code Integer} with value 1.
     *
     * @param o Object to test.
     * @return {@code true} if this map contains a mapping
     * from some object to this value.
     */
    @Override
    public boolean containsValue(Object o) {
        return ONE.equals(o) && !isEmpty();
    }

    /**
     * Returns {@code true} if this mapping is empty.
     *
     * @return {@code true} if this mapping is empty.
     */
    @Override
    public boolean isEmpty() {
        return mPositiveSet.isEmpty();
    }

    /**
     * Adds the mapping of the specified object to the specified
     * value if the specified number is the {@code Integer} with
     * value 1.
     *
     * @param e Key for the mapping.
     * @param n Value for the mapping.
     * @throws IllegalArgumentException If the specified integer
     * does not have value 1.
     */
    @Override
    public Integer put(E e, Integer n) {
        if (!ONE.equals(n))
            throw new IllegalArgumentException();
        return mPositiveSet.add(e) ? null : ONE;
    }

    class Values extends AbstractCollection<Integer> {
        @Override
        public Iterator<Integer> iterator() {
            return new ValuesIterator();
        }

        @Override
        public int size() {
            return isEmpty() ? 0 : 1;
        }

        @Override
        public void clear() {
            mPositiveSet.clear();
        }

        @Override
        public boolean contains(Object o) {
            return ONE.equals(o) && !isEmpty();
        }

        @Override
        public boolean isEmpty() {
            return mPositiveSet.isEmpty();
        }

        @Override
        public boolean remove(Object o) {
            if (!ONE.equals(o))
                return false;
            boolean removedSomething = !isEmpty();
            mPositiveSet.clear();
            return removedSomething;
        }

        @Override
        public boolean removeAll(Collection<?> c) {
            if (!c.contains(ONE))
                return false;
            boolean removedSomething = !isEmpty();
            mPositiveSet.clear();
            return removedSomething;
        }

        @Override
        public boolean retainAll(Collection<?> c) {
            if (isEmpty())
                return false;
            if (c.contains(ONE))
                return false;
            mPositiveSet.clear();
            return true;
        }

        @Override
        public Object[] toArray() {
            return isEmpty() ? EMPTY_OBJECT_ARRAY : new Object[] { ONE };
        }
    }

    class ValuesIterator implements Iterator<Integer> {
        boolean finished = mPositiveSet.isEmpty();
        boolean mayRemove = false;

        public boolean hasNext() {
            return !finished;
        }

        public Integer next() {
            if (!hasNext())
                throw new NoSuchElementException();
            finished = true;
            mayRemove = true;
            return ONE;
        }

        public void remove() {
            if (!mayRemove)
                throw new IllegalStateException();
            mayRemove = false;
            mPositiveSet.clear();
        }
    }

    class EntrySet extends AbstractSet<Map.Entry<E, Integer>> {
        @Override
        public int size() {
            return mPositiveSet.size();
        }

        @Override
        public Iterator<Map.Entry<E, Integer>> iterator() {
            return new EntrySetIterator();
        }

        @Override
        public void clear() {
            mPositiveSet.clear();
        }

        @Override
        public boolean contains(Object o) {
            if (!(o instanceof Map.Entry<?, ?>))
                return false;
            @SuppressWarnings("unchecked") // checked above
            Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o;
            return ONE.equals(entry.getValue()) && mPositiveSet.contains(entry.getKey());
        }

        @Override
        public boolean isEmpty() {
            return mPositiveSet.isEmpty();
        }

        @Override
        public boolean remove(Object o) {
            if (!(o instanceof Map.Entry<?, ?>))
                return false;
            @SuppressWarnings("unchecked") // checked above
            Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o;
            return ONE.equals(entry.getValue()) && mPositiveSet.remove(entry.getKey());
        }
    }

    class EntrySetIterator implements Iterator<Map.Entry<E, Integer>> {
        private final Iterator<E> mIterator = mPositiveSet.iterator();;

        public boolean hasNext() {
            return mIterator.hasNext();
        }

        public Map.Entry<E, Integer> next() {
            return new PositiveMapEntry();
        }

        public void remove() {
            mIterator.remove();
        }

        class PositiveMapEntry implements Map.Entry<E, Integer> {
            private final E mE = mIterator.next();

            public E getKey() {
                return mE;
            }

            public Integer getValue() {
                return ONE;
            }

            public Integer setValue(Integer value) {
                throw new UnsupportedOperationException();
            }

            public boolean equals(Object that) {
                if (!(that instanceof Map.Entry<?, ?>))
                    return false;
                @SuppressWarnings("unchecked") // checked above
                Map.Entry<?, ?> e1 = (Map.Entry<?, ?>) that;
                Map.Entry<?, ?> e2 = this;
                return (e1.getKey() == null ? e2.getKey() == null : e1.getKey().equals(e2.getKey()))
                        && (e1.getValue() == null ? e2.getValue() == null : e1.getValue().equals(e2.getValue()));
            }

            public int hashCode() {
                return (getKey() == null ? 0 : getKey().hashCode())
                        ^ (getValue() == null ? 0 : getValue().hashCode());
            }
        }
    }

    /**
     * The constant used for the {@code Integer} with value 1.  The
     * value is defined by {@code Integer.valueOf(1)}, so may be the
     * same object as returned by other calls to {@code valueOf()}.
     */
    public static final Integer ONE = Integer.valueOf(1);

    static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];

}