blob: 8f0f53c8ecb96c26ddc509d372e26697fe60526f [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.Set;
import java.util.Iterator;
import java.io.Serializable;
import org.apache.sis.math.FunctionProperty;
import org.apache.sis.util.ObjectConverter;
import org.apache.sis.util.UnconvertibleObjectException;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.internal.util.SetOfUnknownSize;
/**
* A set whose values are derived <cite>on-the-fly</cite> from an other set.
* Conversions are performed when needed by two converters:
*
* <ul>
* <li>The {@linkplain #iterator() iterator} obtain the derived values using the {@linkplain #converter}.</li>
* <li>Queries ({@link #contains contains}) and write operations ({@link #add add}, {@link #remove remove})
* obtain the storage values using the {@link Invertible#inverse} converter.</li>
* </ul>
*
* <h2>Constraints</h2>
* <ul>
* <li>This set does not support {@code null} values, since {@code null} is used as a
* sentinel value when no mapping from {@linkplain #storage} to {@code this} exists.</li>
* <li>Instances of this class are serializable if their underlying {@linkplain #storage} set
* and the {@linkplain #converter} are serializable.</li>
* <li>This class performs no synchronization by itself. Nevertheless instances of this class
* may be thread-safe (depending on the sub-class implementation) if the underlying
* {@linkplain #storage} set (including its iterator) and the {@linkplain #converter}
* are thread-safe.</li>
* </ul>
*
* <h2>Performance considerations</h2>
* This class does not cache any value, since the {@linkplain #storage} set is presumed modifiable.
* If the storage set is known to be immutable, then sub-classes may consider to cache some values,
* especially the result of the {@link #size()} method.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 1.0
*
* @param <S> the type of elements in the storage set.
* @param <E> the type of elements in this set.
*
* @since 0.3
* @module
*/
class DerivedSet<S,E> extends SetOfUnknownSize<E> implements CheckedContainer<E>, Serializable {
/**
* Serial number for inter-operability with different versions.
*/
private static final long serialVersionUID = 6309535868745970619L;
/**
* The storage set whose values are derived from.
*/
protected final Set<S> storage;
/**
* The converter from the storage to the derived type.
*/
protected final ObjectConverter<S,E> converter;
/**
* Creates a new derived set from the specified storage set.
*
* @param storage the set which actually store the elements.
* @param converter the converter from the type in the storage set to the type in the derived set.
*/
static <S,E> Set<E> create(final Set<S> storage, final ObjectConverter<S,E> converter) {
final Set<FunctionProperty> properties = converter.properties();
if (properties.contains(FunctionProperty.INVERTIBLE)) {
if (FunctionProperty.isBijective(properties)) {
return new Bijective<>(storage, converter);
}
return new Invertible<>(storage, converter);
}
return new DerivedSet<>(storage, converter);
}
/**
* Creates a new derived set from the specified storage set.
*
* @param storage the set which actually store the elements.
* @param converter the converter from the storage to the derived type.
*/
private DerivedSet(final Set<S> storage, final ObjectConverter<S,E> converter) {
this.storage = storage;
this.converter = converter;
}
/**
* Returns the derived element type.
*/
@Override
public final Class<E> getElementType() {
return converter.getTargetClass();
}
/**
* Returns an iterator over the elements contained in this set.
* The iterator will invoke the {@link ObjectConverter#apply(Object)} method for each element.
*
* @return an iterator over the elements contained in this set.
*/
@Override
public final Iterator<E> iterator() {
return new DerivedIterator<>(storage.iterator(), converter);
}
/**
* Returns {@code true} if this set contains no elements.
*
* @return {@code true} if this set contains no elements.
*/
@Override
public boolean isEmpty() {
return storage.isEmpty() || !iterator().hasNext();
}
/**
* Ensures that this set contains the specified element.
* This method first checks if the given element is non-null,
* then delegates to the {@link #storage} set like below:
*
* {@preformat java
* return storage.add(inverse.apply(element));
* }
*
* @param element element whose presence in this set is to be ensured.
* @return {@code true} if the set changed as a result of the call.
* @throws UnsupportedOperationException if the {@linkplain #storage} set doesn't
* supports the {@code add} operation.
*/
@Override
public boolean add(final E element) throws UnsupportedOperationException {
return add(element, converter.inverse().apply(element));
}
/**
* Implementation of the {@link #add(Object)} method adding the given converted value
* to the storage set. The {@code original} value is used only for formatting an error
* message in case of failure.
*/
final boolean add(final E original, final S element) {
if (element == null) {
throw new UnconvertibleObjectException(Errors.format(
Errors.Keys.IllegalArgumentValue_2, "element", original));
}
return storage.add(element);
}
/**
* A {@link DerivedSet} for invertible converters. Availability of the inverse conversion
* allows us to delegate the {@link #contains(Object)} and {@linkplain #remove(Object)}
* operations to the {@linkplain #storage} set instead than iterating over all elements.
*
* @param <S> the type of elements in the storage set.
* @param <E> the type of elements in this set.
*/
private static class Invertible<S,E> extends DerivedSet<S,E> {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = -5336633027232952482L;
/**
* The converter from the derived to the storage type.
*/
private final ObjectConverter<E,S> inverse;
/**
* Creates a new derived set from the specified storage set.
*
* @param storage the set which actually store the elements.
* @param converter the type of elements in this derived set.
*/
Invertible(final Set<S> storage, final ObjectConverter<S,E> converter) {
super(storage, converter);
inverse = converter.inverse();
}
/**
* {@inheritDoc}
*/
@Override
public final boolean add(final E element) throws UnsupportedOperationException {
return add(element, inverse.apply(element));
}
/**
* Returns {@code true} if this set contains the specified element.
* This method first checks if the given element is an instance of {@link #getElementType()},
* then delegates to the {@link #storage} set like below:
*
* {@preformat java
* return storage.contains(inverse.apply(element));
* }
*
* @param element object to be checked for containment in this set.
* @return {@code true} if this set contains the specified element.
*/
@Override
public final boolean contains(final Object element) {
final Class<E> type = getElementType();
return type.isInstance(element) && storage.contains(inverse.apply(type.cast(element)));
}
/**
* Removes a single instance of the specified element from this set.
* This method first checks if the given element is an instance of {@link #getElementType},
* then delegates to the {@link #storage} set like below:
*
* {@preformat java
* return storage.remove(inverse.apply(element));
* }
*
* @param element element to be removed from this set, if present.
* @return {@code true} if the set contained the specified element.
* @throws UnsupportedOperationException if the {@linkplain #storage} set doesn't
* supports the {@code remove} operation.
*/
@Override
public final boolean remove(final Object element) throws UnsupportedOperationException {
final Class<E> type = getElementType();
return type.isInstance(element) && storage.remove(inverse.apply(type.cast(element)));
}
}
/**
* A {@link DerivedSet} for converters that are both invertible and bijective.
* The bijection allows us to query the {@linkplain #storage} set size directly
* instead than iterating over all elements.
*
* @param <S> the type of elements in the storage set.
* @param <E> the type of elements in this set.
*/
private static final class Bijective<S,E> extends Invertible<S,E> {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = -7601944988804380342L;
/**
* Creates a new derived set from the specified storage set.
*
* @param storage the set which actually store the elements.
* @param converter the type of elements in this derived set.
*/
Bijective(final Set<S> storage, final ObjectConverter<S,E> converter) {
super(storage, converter);
}
/**
* {@inheritDoc}
*/
@Override
public int size() {
return storage.size();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isEmpty() {
return storage.isEmpty();
}
}
}