blob: 3e0b5a778b75fad80bfbdefa2bfa026880f407f4 [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.internal.storage;
import java.util.Arrays;
import java.util.List;
import java.util.Collection;
import java.util.OptionalLong;
import java.util.stream.Stream;
import org.apache.sis.feature.Features;
import org.apache.sis.storage.FeatureSet;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.DataStoreContentException;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.collection.BackingStoreException;
import org.apache.sis.internal.util.CollectionsExt;
import org.apache.sis.internal.util.UnmodifiableArrayList;
import org.apache.sis.storage.event.StoreListeners;
import org.apache.sis.storage.Query;
// Branch-dependent imports
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureType;
/**
* Exposes a sequence of {@link FeatureSet}s as a single one. A few notes:
* <ol>
* <li>The feature set concatenation must be built with a non-empty array or collection of feature set.
* It is copied verbatim in an unmodifiable list, meaning that duplicated instances won't be removed,
* and iteration order is driven by input sequence.</li>
* <li>All input feature sets must share a common type, or at least a common super-type. If you want to sequence
* sets which does not share any common parent, please pre-process them to modify their public type.</li>
* <li>There is no {@linkplain #getIdentifier() identifier} since this feature set is a computation result.</li>
* </ol>
*
* @author Alexis Manin (Geomatys)
* @author Martin Desruisseaux (Geomatys)
* @version 1.0
* @since 1.0
* @module
*/
public class ConcatenatedFeatureSet extends AggregatedFeatureSet {
/**
* The sequence of feature sets whose feature instances will be returned.
*/
private final List<FeatureSet> sources;
/**
* The most specific feature type common to all feature sets in the {@linkplain #sources} list.
*/
private final FeatureType commonType;
/**
* Creates a new concatenated feature set with the same types than the given feature set,
* but different sources.
*/
private ConcatenatedFeatureSet(final FeatureSet[] sources, final ConcatenatedFeatureSet original) {
super(original);
this.sources = UnmodifiableArrayList.wrap(sources);
commonType = original.commonType;
}
/**
* Creates a new feature set as a concatenation of the sequence of features given by the {@code sources}.
* This constructor does not verify that the given {@code sources} array contains at least two elements;
* this verification must be done by the caller. This constructor retains the given {@code sources} array
* by direct reference; clone, if desired, shall be done by the caller.
*
* @param parent listeners of the parent resource, or {@code null} if none.
* @param sources the sequence of feature sets to expose in a single set.
* Must neither be null, empty nor contain a single element only.
* @throws DataStoreException if given feature sets does not share any common type.
*/
protected ConcatenatedFeatureSet(final StoreListeners parent, final FeatureSet[] sources) throws DataStoreException {
super(parent);
for (int i=0; i<sources.length; i++) {
ArgumentChecks.ensureNonNullElement("sources", i, sources[i]);
}
this.sources = UnmodifiableArrayList.wrap(sources);
final FeatureType[] types = new FeatureType[sources.length];
for (int i=0; i<types.length; i++) {
types[i] = sources[i].getType();
}
commonType = Features.findCommonParent(Arrays.asList(types));
if (commonType == null) {
// TODO: localize.
throw new DataStoreContentException("Cannot find a common super type across all feature sets to concatenate");
}
}
/**
* Creates a new feature set as a concatenation of the sequence of features given by the {@code sources}.
* The given array shall be non-empty. If the array contains only 1 element, that element is returned.
*
* @param sources the sequence of feature sets to expose in a single set.
* @return the concatenation of given feature set.
* @throws DataStoreException if given feature sets does not share any common type.
*/
public static FeatureSet create(final FeatureSet... sources) throws DataStoreException {
ArgumentChecks.ensureNonEmpty("sources", sources);
if (sources.length == 1) {
final FeatureSet fs = sources[0];
ArgumentChecks.ensureNonNullElement("sources", 0, fs);
return fs;
} else {
return new ConcatenatedFeatureSet(null, sources.clone());
}
}
/**
* Creates a new feature set as a concatenation of the sequence of features given by the {@code sources}.
* The given collection shall be non-empty. If the collection contains only 1 element, that element is returned.
*
* @param sources the sequence of feature sets to expose in a single set.
* @return the concatenation of given feature set.
* @throws DataStoreException if given feature sets does not share any common type.
*/
public static FeatureSet create(final Collection<? extends FeatureSet> sources) throws DataStoreException {
ArgumentChecks.ensureNonNull("sources", sources);
final int size = sources.size();
switch (size) {
case 0: throw new IllegalArgumentException(Errors.format(Errors.Keys.EmptyArgument_1, "sources"));
case 1: {
final FeatureSet fs = CollectionsExt.first(sources);
ArgumentChecks.ensureNonNullElement("sources", 0, fs);
return fs;
}
default: {
return new ConcatenatedFeatureSet(null, sources.toArray(new FeatureSet[size]));
}
}
}
/**
* Returns all feature set used by this aggregation. This method is invoked for implementation of
* {@link #getEnvelope()} and {@link #createMetadata(MetadataBuilder)}.
*/
@Override
@SuppressWarnings("ReturnOfCollectionOrArrayField") // Safe because the list is unmodifiable.
final List<FeatureSet> dependencies() {
return sources;
}
/**
* Returns the most specific feature type common to all feature sets given to the constructor.
*
* @return the common type of all features returned by this set.
*/
@Override
public FeatureType getType() {
return commonType;
}
/**
* Returns an estimation of the number of features in this set, or an empty value if unknown.
* This is the sum of the estimations provided by all source sets, or empty if at least one
* source could not provide an estimation.
*
* @return estimation of the number of features.
*/
@Override
protected OptionalLong getFeatureCount() {
long sum = 0;
for (final FeatureSet fs : sources) {
if (fs instanceof AbstractFeatureSet) {
final OptionalLong count = ((AbstractFeatureSet) fs).getFeatureCount();
if (count.isPresent()) {
final long c = count.getAsLong();
if (c >= 0) { // Paranoiac check.
sum += c;
if (sum < 0) { // Integer overflow.
sum = Long.MAX_VALUE;
break;
}
continue;
}
}
}
return null; // A source can not provide estimation.
}
return OptionalLong.of(sum);
}
/**
* Returns a stream of all features contained in this concatenated dataset. If the {@code parallel} argument
* is {@code false}, then datasets are traversed in the order they were specified at construction time.
* If the {@code parallel} argument is {@code true}, then datasets are traversed in no determinist order.
* If an error occurred while reading the feature instances from a source, then the error is wrapped in a
* {@link BackingStoreException}.
*
* @param parallel {@code true} for a parallel stream, or {@code false} for a sequential stream.
* @return all features contained in this dataset.
*/
@Override
public Stream<Feature> features(final boolean parallel) {
final Stream<FeatureSet> sets = parallel ? sources.parallelStream() : sources.stream();
return sets.flatMap(set -> {
try {
return set.features(parallel);
} catch (DataStoreException e) {
throw new BackingStoreException(e);
}
});
}
/**
* Requests a subset of features and/or feature properties from this resource.
*
* @param query definition of feature and feature properties filtering applied at reading time.
* @return resulting subset of features (never {@code null}).
* @throws DataStoreException if an error occurred while processing the query.
*/
@Override
public FeatureSet subset(final Query query) throws DataStoreException {
final FeatureSet[] subsets = new FeatureSet[sources.size()];
boolean modified = false;
for (int i=0; i<subsets.length; i++) {
FeatureSet source = sources.get(i);
subsets[i] = source.subset(query);
modified |= subsets[i] != source;
}
return modified ? new ConcatenatedFeatureSet(subsets, this) : this;
}
}