All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.shibboleth.utilities.java.support.collection.ClassIndexedSet Maven / Gradle / Ivy

There is a newer version: 8.0.0
Show newest version
/*
 * Licensed to the University Corporation for Advanced Internet Development,
 * Inc. (UCAID) under one or more contributor license agreements.  See the
 * NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The UCAID licenses this file to You 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 net.shibboleth.utilities.java.support.collection;

import java.util.AbstractSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import net.shibboleth.utilities.java.support.logic.Constraint;

/**
 * Set implementation which provides indexed access to set members via their class, and which allows only one instance
 * of a given class to be present in the set. Null members are not allowed.
 * 
 * @param  the type of object stored by this class
 */
public class ClassIndexedSet extends AbstractSet implements Set {

    /** Storage for set members. */
    private final HashSet set;

    /** Storage for index of class -> member. */
    private final HashMap, T> index;

    /**
     * Constructor.
     * 
     */
    public ClassIndexedSet() {
        set = new HashSet<>();
        index = new HashMap<>();
    }

    /** {@inheritDoc} */
    public boolean add(@Nonnull final T o) {
        return add(o, false);
    }

    /**
     * Add member to set, optionally replacing any existing instance of the same class.
     * 
     * @param o the object to add
     * @param replace flag indicating whether to replace an existing class type
     * 
     * @return true if object was added
     */
    public boolean add(@Nonnull final T o, final boolean replace) {
        Constraint.isNotNull(o, "Null elements are not allowed");

        boolean replacing = false;
        final Class indexClass = getIndexClass(o);
        final T existing = get(indexClass);
        if (existing != null) {
            replacing = true;
            if (replace) {
                remove(existing);
            } else {
                throw new IllegalArgumentException("Set already contains a member of index class "
                        + indexClass.getName());
            }
        }
        index.put(indexClass, o);
        set.add(o);
        return replacing;
    }

    /** {@inheritDoc} */
    public void clear() {
        set.clear();
        index.clear();
    }

    /** {@inheritDoc} */
    public boolean remove(@Nullable final Object o) {
        if (o != null && set.contains(o)) {
            removeFromIndex((T) o);
            set.remove(o);
            return true;
        }
        
        return false;
    }

    /** {@inheritDoc} */
    @Nonnull public Iterator iterator() {
        return new ClassIndexedSetIterator(this, set.iterator());
    }

    /** {@inheritDoc} */
    public int size() {
        return set.size();
    }

    /**
     * Check whether set contains an instance of the specified class.
     * 
     * @param clazz the class to check
     * @return true if set contains an instance of the specified class, false otherwise
     */
    public boolean contains(@Nullable final Class clazz) {
        return get(clazz) != null;
    }

    /**
     * Get the set element specified by the class parameter.
     * 
     * @param  generic parameter which eliminates need for casting by the caller
     * @param clazz the class to whose instance is to be retrieved
     * 
     * @return the element whose class is of the type specified
     */
    @Nullable public  X get(@Nullable final Class clazz) {
        return (X) index.get(clazz);
    }

    /**
     * Get the index class of the specified object. Subclasses may override to use a class index other than the main
     * runtime class of the object.
     * 
     * @param  generic parameter which eliminates need for casting by the caller
     * @param o the object whose class index to determine
     * @return the class index value associated with the object instance
     */
    @Nonnull protected   Class getIndexClass(@Nonnull final X o) {
        Constraint.isNotNull(o, "Object can not be null");
        return (Class) o.getClass();
    }

    /**
     * Remove the specified object from the index.
     * 
     * @param o the object to remove
     */
    private void removeFromIndex(final T o) {
        index.remove(getIndexClass(o));
    }
    
    /** {@inheritDoc} */
    public String toString() {
        return set.toString();
    }
    
    /** {@inheritDoc} */
    public boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        }

        if (obj == null || this.getClass() != obj.getClass()) {
            return false;
        }

        return set.equals(((ClassIndexedSet) obj).set);
    }
    
    /** {@inheritDoc} */
    public int hashCode() {
        return set.hashCode();
    }

    /**
     * Iterator for set implementation {@link ClassIndexedSet}.
     * 
     */
    protected class ClassIndexedSetIterator implements Iterator {

        /** The set instance over which this instance is an iterator. */
        private final ClassIndexedSet set;

        /** The iterator for the owner's underlying storage. */
        private final Iterator iterator;

        /** Flag which tracks whether next() has been called at least once. */
        private boolean nextCalled;

        /** Flag which tracks whether remove can currently be called. */
        private boolean removeStateValid;;

        /**
         * The element most recently returned by next(), and the target for any subsequent remove() operation.
         */
        private T current;

        /**
         * Constructor.
         * 
         * @param parentSet the {@link ClassIndexedSet} over which this instance is an iterator
         * @param parentIterator the iterator for the parent's underlying storage
         */
        protected ClassIndexedSetIterator(final ClassIndexedSet parentSet, final Iterator parentIterator) {
            set = parentSet;
            iterator = parentIterator;
            current = null;
            nextCalled = false;
            removeStateValid = false;
        }

        /** {@inheritDoc} */
        public boolean hasNext() {
            return iterator.hasNext();
        }

        /** {@inheritDoc} */
        public T next() {
            current = iterator.next();
            nextCalled = true;
            removeStateValid = true;
            return current;
        }

        /** {@inheritDoc} */
        public void remove() {
            if (!nextCalled) {
                throw new IllegalStateException("remove() was called before calling next()");
            }
            if (!removeStateValid) {
                throw new IllegalStateException("remove() has already been called since the last call to next()");
            }
            iterator.remove();
            set.removeFromIndex(current);
            removeStateValid = false;
        }

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy