blob: 2dd59bc193af305084eafa8ee34b3b4b76d4e2a7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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 org.apache.sis.util.collection;
import java.util.AbstractSet;
import java.util.BitSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.io.Serializable;
import java.lang.reflect.Modifier;
import org.opengis.util.CodeList;
import org.apache.sis.internal.util.CodeLists;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.NullArgumentException;
import org.apache.sis.internal.util.CheckedArrayList;
/**
* A specialized {@code Set} implementation for use with {@link CodeList} values.
* All elements in a {@code CodeListSet} are of the same {@code CodeList} class,
* which must be final. Iterators traverse the elements in the order in which the
* code list constants are declared.
*
* <h2>Implementation note</h2>
* {@code CodeListSet} is implemented internally by bit vectors for compact and efficient storage.
* All bulk operations ({@code addAll}, {@code removeAll}, {@code containsAll}) are very quick if
* their argument is also a {@code CodeListSet} instance.
*
* <h2>Usage example</h2>
* The following example creates a set of {@link org.opengis.referencing.cs.AxisDirection}s
* for a (<var>x</var>,<var>y</var>,<var>z</var>) coordinate system:
*
* {@preformat java
* CodeListSet<AxisDirection> codes = new CodeListSet<>(AxisDirection.class);
* Collections.addAll(codes, AxisDirection.EAST, AxisDirection.NORTH, AxisDirection.UP),
* }
*
* @author Martin Desruisseaux (Geomatys)
* @version 0.4
*
* @param <E> the type of code list elements in the set.
*
* @see java.util.EnumSet
*
* @since 0.3
* @module
*/
public class CodeListSet<E extends CodeList<E>> extends AbstractSet<E>
implements CheckedContainer<E>, Cloneable, Serializable
{
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = -6328082298556260980L;
/**
* A pool of code list arrays. When many {@code CodeListSet} instances are for the
* same code list type, this allows those instances to share the same arrays.
*/
@SuppressWarnings("rawtypes")
private static final WeakHashSet<CodeList[]> POOL = new WeakHashSet<>(CodeList[].class);
/**
* The type of code list elements.
*
* @see #getElementType()
*/
private final Class<E> elementType;
/**
* A bitmask of code list values present in this map.
*/
private long values;
/**
* The bit set for supplementary values beyond the {@code values} mask, or {@code null}
* if none. This is very rarely needed, but we need this field in case a code list has
* more than 64 elements.
*
* <div class="note"><b>Implementation note:</b>
* The standard {@link java.util.EnumSet} class uses different implementations depending on whether
* the enumeration contains more or less than 64 elements. We can not apply the same strategy for
* {@code CodeListSet}, because new code list elements can be created at runtime. Consequently this
* implementation needs to be able to growth its capacity.</div>
*/
private BitSet supplementary;
/**
* All possible code list elements, fetched when first needed.
* Note that this array may need to be fetched more than once,
* because code list elements can be dynamically added.
*
* @see #valueOf(int)
*/
private transient E[] codes;
/**
* Creates an initially empty set for code lists of the given type.
* The given {@code CodeList} type shall be final.
*
* @param elementType the type of code list elements to be included in this set.
* @throws IllegalArgumentException if the given class is not final.
*/
public CodeListSet(final Class<E> elementType) throws IllegalArgumentException {
if (!Modifier.isFinal(elementType.getModifiers())) {
throw new IllegalArgumentException(Errors.format(Errors.Keys.ClassNotFinal_1, elementType));
}
this.elementType = elementType;
}
/**
* Creates set for code lists of the given type. If the {@code fill} argument is {@code false},
* then the new set will be initially empty. Otherwise the new set will be filled with all code
* list elements of the given type that are known at construction time. Note that if new code
* list elements are created after the invocation of this {@code CodeListSet} constructor, then
* those new elements will <em>not</em> be in this set.
*
* @param elementType the type of code list elements to be included in this set.
* @param fill {@code true} for filling the set with all known elements of the given type,
* or {@code false} for leaving the set empty.
* @throws IllegalArgumentException if the given class is not final.
*/
public CodeListSet(final Class<E> elementType, final boolean fill) throws IllegalArgumentException {
this(elementType);
if (fill) {
codes = POOL.unique(CodeLists.values(elementType));
int n = codes.length;
if (n < Long.SIZE) {
values = (1L << n) - 1;
} else {
values = -1;
if ((n -= Long.SIZE) != 0) {
supplementary = new BitSet(n);
supplementary.set(0, n);
}
}
}
}
/**
* Returns the type of code list elements in this set.
*
* @return the type of code list elements in this set.
*/
@Override
public Class<E> getElementType() {
return elementType;
}
/**
* Returns the code list for the given ordinal value. This methods depends
* only on the code list type; it does not depend on the content of this set.
*/
final E valueOf(final int ordinal) {
E[] array = codes;
if (array == null || ordinal >= array.length) {
codes = array = POOL.unique(CodeLists.values(elementType));
}
return array[ordinal];
}
/**
* Removes all elements from this set.
*/
@Override
public void clear() {
values = 0;
final BitSet s = supplementary;
if (s != null) {
s.clear();
}
}
/**
* Returns {@code true} if this set does not contains any element.
*
* @return {@code true} if this set is empty.
*/
@Override
public boolean isEmpty() {
final BitSet s;
return values == 0 && ((s = supplementary) == null || s.isEmpty());
}
/**
* Returns the number of elements in this set.
*
* @return the number of elements in this set.
*/
@Override
public int size() {
int n = Long.bitCount(values);
final BitSet s = supplementary;
if (s != null) {
n += s.cardinality();
}
return n;
}
/**
* Adds the specified code list element in this set.
*
* @param element the code list element to add in this set.
* @return {@code true} if this set has been modified as a consequence of this method call.
*/
@Override
public boolean add(final E element) {
if (element == null) {
final String message = CheckedArrayList.illegalElement(this, element, elementType);
if (message == null) {
/*
* If a unmarshalling process is under way, silently discard null element.
* This case happen when a codeListValue attribute in a XML file is empty.
* See https://issues.apache.org/jira/browse/SIS-157
*/
return false;
}
throw new NullArgumentException(message);
}
int ordinal = element.ordinal();
if (ordinal < Long.SIZE) {
return values != (values |= (1L << ordinal));
}
/*
* The above code is sufficient in the vast majority of cases. In the rare cases where
* there is more than 64 elements, create a BitSet for storing the supplementary values.
*/
BitSet s = supplementary;
if (s == null) {
supplementary = s = new BitSet();
}
if (s.get(ordinal -= Long.SIZE)) {
return false;
}
s.set(ordinal);
return true;
}
/**
* Removes the specified code list element from this set.
* This methods does nothing if the given argument is {@code null} or is
* not an instance of the code list class specified at construction time.
*
* @param object the code list element to remove from this set.
* @return {@code true} if this set has been modified as a consequence of this method call.
*/
@Override
public boolean remove(final Object object) {
if (elementType.isInstance(object)) {
return clear(((CodeList<?>) object).ordinal());
}
return false;
}
/**
* Clears the bit at the given ordinal value. This method is invoked by
* {@link #remove(Object)} or by {@link Iter#remove()}.
*/
final boolean clear(int ordinal) {
if (ordinal < Long.SIZE) {
return values != (values &= ~(1L << ordinal));
}
// Rare cases where there is more than 64 elements.
final BitSet s = supplementary;
if (s != null && s.get(ordinal -= Long.SIZE)) {
s.clear(ordinal);
return true;
}
return false;
}
/**
* Returns {@code true} if this set contains the given element.
* This methods returns {@code false} if the given argument is {@code null} or
* is not an instance of the code list class specified at construction time.
*
* @param object the element to test for presence in this set.
* @return {@code true} if the given object is contained in this set.
*/
@Override
public boolean contains(final Object object) {
if (elementType.isInstance(object)) {
int ordinal = ((CodeList<?>) object).ordinal();
if (ordinal < Long.SIZE) {
return (values & (1L << ordinal)) != 0;
}
// Rare cases where there is more than 64 elements.
final BitSet s = supplementary;
if (s != null) {
return s.get(ordinal - Long.SIZE);
}
}
return false;
}
/**
* Returns {@code true} if this set contains all the elements of the given collection.
*
* @param c the collection to be checked for containment in this set.
* @return {@code true} if this set contains all elements of the given collection.
*/
@Override
public boolean containsAll(final Collection<?> c) {
if (c instanceof CodeListSet) {
final CodeListSet<?> o = (CodeListSet<?>) c;
if (elementType == o.elementType) {
if (values == (values | o.values)) {
/*
* Code below this point checks for the rare cases
* where there is more than 64 code list elements.
*/
final BitSet s = supplementary;
final BitSet os = o.supplementary;
if (( s == null || s.isEmpty()) &&
(os == null || os.isEmpty()))
{
return true;
}
if (s != null && os != null) {
final BitSet tmp = (BitSet) os.clone();
tmp.andNot(s);
return tmp.isEmpty();
}
}
}
return false;
}
return super.containsAll(c);
}
/**
* Adds all elements of the given collection to this set.
*
* @param c the collection containing elements to be added to this set.
* @return {@code true} if this set changed as a result of this method call.
*/
@Override
public boolean addAll(final Collection<? extends E> c) throws IllegalArgumentException {
if (c instanceof CodeListSet) {
final CodeListSet<?> o = (CodeListSet<?>) c;
/*
* Following assertion should be ensured by parameterized types.
*/
assert elementType.isAssignableFrom(o.elementType);
boolean changed = (values != (values |= o.values));
/*
* Code below this point is for the rare cases
* where there is more than 64 code list elements.
*/
final BitSet os = o.supplementary;
if (os != null) {
final BitSet s = supplementary;
if (s == null) {
if (!os.isEmpty()) {
supplementary = (BitSet) os.clone();
changed = true;
}
} else if (changed) {
// Avoid the cost of computing cardinality.
s.or(os);
} else {
final int cardinality = s.cardinality();
s.or(os);
changed = (cardinality != s.cardinality());
}
}
return changed;
}
return super.addAll(c);
}
/**
* Returns the bitmask to use for a bulk operation with an other set of code lists.
*/
private long mask(final CodeListSet<?> other) {
return (elementType == other.elementType) ? other.values : 0;
}
/**
* Adds all elements of the given collection from this set.
*
* @param c the collection containing elements to be removed from this set.
* @return {@code true} if this set changed as a result of this method call.
*/
@Override
public boolean removeAll(final Collection<?> c) {
if (c instanceof CodeListSet) {
boolean changed = (values != (values &= ~mask((CodeListSet<?>) c)));
/*
* Code below this point is for the rare cases
* where there is more than 64 code list elements.
*/
final BitSet s = supplementary;
if (s != null) {
final BitSet os = ((CodeListSet<?>) c).supplementary;
if (os != null) {
if (changed) {
// Avoid the cost of computing cardinality.
s.andNot(os);
} else {
final int cardinality = s.cardinality();
s.andNot(os);
changed = (cardinality != s.cardinality());
}
}
}
return changed;
}
return super.removeAll(c);
}
/**
* Retains only the elements of the given collection in this set.
*
* @param c the collection containing elements to retain in this set.
* @return {@code true} if this set changed as a result of this method call.
*/
@Override
public boolean retainAll(final Collection<?> c) {
if (c instanceof CodeListSet) {
boolean changed = (values != (values &= mask((CodeListSet<?>) c)));
/*
* Code below this point is for the rare cases
* where there is more than 64 code list elements.
*/
final BitSet s = supplementary;
if (s != null) {
final BitSet os = ((CodeListSet<?>) c).supplementary;
if (os == null) {
changed |= !s.isEmpty();
s.clear();
} else if (changed) {
// Avoid the cost of computing cardinality.
s.and(os);
} else {
final int cardinality = s.cardinality();
s.and(os);
changed = (cardinality != s.cardinality());
}
}
return changed;
}
return super.retainAll(c);
}
/**
* Returns an iterator over the elements in this set.
* The instance returned by this implementation will iterate over a snapshot of this
* {@code CodeListSet} content at the time this method has been invoked. Changes in
* this {@code CodeListSet} made after this method call will not affect the values
* returned by the iterator.
*
* @return an iterator over the elements in this set.
*/
@Override
public Iterator<E> iterator() {
return new Iter(values, supplementary);
}
/**
* The iterator returned by {@link CodeListSet#iterator()}.
*/
private final class Iter implements Iterator<E> {
/**
* Initialized to {@link CodeListSet#values}, then the bits are cleared as we
* progress in the iteration. This value become 0 when the iteration is done.
*/
private long remaining;
/**
* Initialized to a clone of {@link CodeListSet#supplementary}, then the bits are cleared
* as we progress in the iteration. The bit set become empty when the iteration is done.
*/
private final BitSet more;
/**
* Ordinal value of the last element returned by {@link #next()}, or -1 if none.
*/
private int last;
/**
* Creates a new iterator initialized to the given values.
*/
Iter(final long values, final BitSet supplementary) {
remaining = values;
more = (supplementary != null) ? (BitSet) supplementary.clone() : null;
last = -1;
}
/**
* Returns {@code true} if there is more elements to return.
*/
@Override
public boolean hasNext() {
return remaining != 0 || (more != null && !more.isEmpty());
}
/**
* Returns the next element.
*/
@Override
public E next() {
int ordinal = Long.numberOfTrailingZeros(remaining);
if (ordinal >= Long.SIZE) {
// Rare case when we have more than 64 elements.
if (more == null || (ordinal = more.nextSetBit(0)) < 0) {
throw new NoSuchElementException();
}
more.clear(ordinal);
ordinal += Long.SIZE;
}
last = ordinal;
remaining &= ~(1L << ordinal);
return valueOf(ordinal);
}
/**
* Removes the last element returned by this iterator.
*/
@Override
public void remove() {
if (last < 0) {
throw new IllegalStateException();
}
clear(last);
last = -1;
}
}
/**
* Returns a new set of the same class containing the same elements than this set.
*
* @return a clone of this set.
*/
@Override
@SuppressWarnings("unchecked")
public CodeListSet<E> clone() {
final CodeListSet<E> clone;
try {
clone = (CodeListSet<E>) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e); // Should never happen, since we are cloneable.
}
final BitSet s = supplementary;
if (s != null) {
clone.supplementary = (BitSet) s.clone();
}
return clone;
}
}