/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2009-2010, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.data;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.sis.feature.FeatureExt;
import org.apache.sis.feature.FeatureTypeExt;
import org.apache.sis.feature.ReprojectFeatureType;
import org.apache.sis.feature.TransformFeatureType;
import org.apache.sis.feature.ViewFeatureType;
import org.apache.sis.storage.DataStoreException;
import org.geotoolkit.data.memory.GenericEmptyFeatureIterator;
import org.geotoolkit.data.memory.GenericFilterFeatureIterator;
import org.geotoolkit.data.memory.GenericMaxFeatureIterator;
import org.geotoolkit.data.memory.GenericSortByFeatureIterator;
import org.geotoolkit.data.memory.GenericStartIndexFeatureIterator;
import org.geotoolkit.data.query.Query;
import org.geotoolkit.data.query.QueryUtilities;
import org.geotoolkit.data.query.Selector;
import org.geotoolkit.data.query.Source;
import org.geotoolkit.data.query.TextStatement;
import org.geotoolkit.data.session.Session;
import org.geotoolkit.factory.FactoryFinder;
import org.geotoolkit.factory.Hints;
import org.geotoolkit.factory.HintsPending;
import org.geotoolkit.geometry.jts.transform.GeometryScaleTransformer;
import static org.apache.sis.util.ArgumentChecks.*;
import org.geotoolkit.data.memory.GenericDecoratedFeatureIterator;
import org.geotoolkit.storage.StorageListener;
import org.geotoolkit.util.collection.CloseableIterator;
import org.opengis.feature.AttributeType;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureType;
import org.opengis.feature.MismatchedFeatureException;
import org.opengis.feature.PropertyType;
import org.opengis.util.GenericName;
import org.opengis.filter.Filter;
import org.opengis.filter.Id;
import org.opengis.filter.identity.FeatureId;
import org.opengis.filter.sort.SortBy;
import org.opengis.geometry.Envelope;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
/**
*
* @author Johann Sorel (Geomatys)
* @module
*/
public abstract class AbstractFeatureCollection extends AbstractCollection<Feature>
implements FeatureCollection, FeatureStoreListener{
private final Set<StorageListener> listeners = new HashSet<StorageListener>();
private final FeatureStoreListener.Weak weakListener = new Weak(this);
protected String id;
protected final Source source;
public AbstractFeatureCollection(final String id, final Source source){
ensureNonNull("feature collection id", id);
ensureNonNull("feature collection source", source);
this.id = id;
this.source = source;
final Collection<Session> sessions = QueryUtilities.getSessions(source, null);
for (Session s : sessions) {
weakListener.registerSource(s);
}
}
public void setId(final String id) {
this.id = id;
}
/**
* {@inheritDoc }
*/
@Override
public String getID() {
return id;
}
/**
* {@inheritDoc }
*/
@Override
public Session getSession() {
if(source instanceof Selector){
return ((Selector)source).getSession();
}else if(source instanceof TextStatement){
return ((TextStatement)source).getSession();
}else{
return null;
}
}
/**
* {@inheritDoc }
*/
@Override
public Source getSource() {
return source;
}
/**
* {@inheritDoc }
*/
@Override
public boolean isWritable() throws FeatureStoreRuntimeException {
try {
return QueryUtilities.isWritable(source);
} catch (DataStoreException ex) {
throw new FeatureStoreRuntimeException(ex);
}
}
/**
* {@inheritDoc }
*/
@Override
public final FeatureIterator iterator(){
return iterator(null);
}
/**
* {@inheritDoc }
*/
@Override
public Envelope getEnvelope() throws DataStoreException{
try{
return FeatureStoreUtilities.calculateEnvelope(iterator());
}catch(FeatureStoreRuntimeException ex){
throw new DataStoreException(ex);
}
}
/**
* {@inheritDoc }
*/
@Override
public int size() throws FeatureStoreRuntimeException{
return (int) FeatureStoreUtilities.calculateCount(iterator());
}
@Override
public boolean contains(Object o) {
final FeatureIterator e = iterator();
try{
if (o==null) {
while (e.hasNext())
if (e.next()==null)
return true;
} else {
while (e.hasNext())
if (o.equals(e.next()))
return true;
}
return false;
}finally{
e.close();
}
}
@Override
public boolean containsAll(Collection<?> c) {
final Iterator<?> e = c.iterator();
try{
while (e.hasNext())
if (!contains(e.next()))
return false;
return true;
}finally{
if(e instanceof CloseableIterator){
((CloseableIterator)e).close();
}
}
}
@Override
public boolean retainAll(Collection<?> c) {
boolean modified = false;
final FeatureIterator e = iterator();
try{
while (e.hasNext()) {
if (!c.contains(e.next())) {
e.remove();
modified = true;
}
}
return modified;
}finally{
e.close();
}
}
@Override
public void clear() {
FeatureIterator e = iterator();
try{
while (e.hasNext()) {
e.next();
e.remove();
}
}finally{
e.close();
}
}
@Override
public String toString() {
return "FeatureCollection\n"+String.valueOf(getFeatureType());
}
@Override
public void update(Feature feature) throws DataStoreException {
if(feature == null) return;
FeatureId fid = FeatureExt.getId(feature);
final Filter filter = FactoryFinder.getFilterFactory(null).id(Collections.singleton(fid));
final Map<String,Object> map = new HashMap<>();
for(PropertyType pt : feature.getType().getProperties(true)){
if(pt instanceof AttributeType){
map.put(pt.getName().toString(), feature.getPropertyValue(pt.getName().toString()));
}
}
update(filter, map);
}
@Override
public FeatureCollection subCollection(final Query remainingParameters) throws DataStoreException {
FeatureCollection result = this;
final Integer start = remainingParameters.getStartIndex();
final Integer max = remainingParameters.getMaxFeatures();
final Filter filter = remainingParameters.getFilter();
final String[] properties = remainingParameters.getPropertyNames();
final SortBy[] sorts = remainingParameters.getSortBy();
final double[] resampling = remainingParameters.getResolution();
final CoordinateReferenceSystem crs = remainingParameters.getCoordinateSystemReproject();
final Hints hints = remainingParameters.getHints();
//we should take care of wrapping the reader in a correct order to avoid
//unnecessary calculations. fast and reducing number wrapper should be placed first.
//but we must not take misunderstanding assumptions neither.
//exemple : filter is slow than startIndex and MaxFeature but must be placed before
// otherwise the result will be illogic.
//wrap sort by ---------------------------------------------------------
//This can be really expensive, and force the us to read the full iterator.
//that may cause out of memory errors.
if(sorts != null && sorts.length != 0){
result = GenericSortByFeatureIterator.wrap(result, sorts);
}
//wrap filter ----------------------------------------------------------
//we must keep the filter first since it impacts the start index and max feature
if(filter != null && filter != Filter.INCLUDE){
if(filter == Filter.EXCLUDE){
//filter that exclude everything, use optimzed reader
result = GenericEmptyFeatureIterator.wrap(result);
}else{
result = GenericFilterFeatureIterator.wrap(result, filter);
}
}
//wrap start index -----------------------------------------------------
if(start != null && start > 0){
result = GenericStartIndexFeatureIterator.wrap(result, start);
}
//wrap max -------------------------------------------------------------
if(max != null){
if(max == 0){
//use an optimized reader
result = GenericEmptyFeatureIterator.wrap(result);
}else{
result = GenericMaxFeatureIterator.wrap(result, max);
}
}
//wrap properties --------------------
final FeatureType original = result.getFeatureType();
FeatureType mask = original;
if(properties!=null && FeatureTypeExt.isAllProperties(original, properties)) {
try {
result = GenericDecoratedFeatureIterator.wrap(result, new ViewFeatureType(mask, properties));
} catch (MismatchedFeatureException | IllegalStateException ex) {
throw new DataStoreException(ex);
}
}
//wrap resampling ------------------------------------------------------
if(resampling != null){
final GeometryScaleTransformer trs = new GeometryScaleTransformer(resampling[0], resampling[1]);
final TransformFeatureType ttype = new TransformFeatureType(result.getFeatureType(), trs);
result = GenericDecoratedFeatureIterator.wrap(result, ttype);
}
//wrap reprojection ----------------------------------------------------
if(crs != null){
result = GenericDecoratedFeatureIterator.wrap(result, new ReprojectFeatureType(result.getFeatureType(), crs));
}
return result;
}
// fix toArray methods to forced separate features
@Override
public Object[] toArray() {
final List<Object> datas = new ArrayList<>();
final Hints hints = new Hints();
hints.put(HintsPending.FEATURE_DETACHED, Boolean.TRUE);
final FeatureIterator ite = iterator(hints);
try{
while(ite.hasNext()){
datas.add(ite.next());
}
}finally{
ite.close();
}
return datas.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
final List<Object> datas = new ArrayList<>();
final Hints hints = new Hints();
hints.put(HintsPending.FEATURE_DETACHED, Boolean.TRUE);
final FeatureIterator ite = iterator(hints);
try{
while(ite.hasNext()){
datas.add(ite.next());
}
}finally{
ite.close();
}
return datas.toArray(a);
}
////////////////////////////////////////////////////////////////////////////
// listeners methods ///////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
/**
* Forward event to listeners by changing source.
*/
@Override
public void structureChanged(FeatureStoreManagementEvent event){
final FeatureType currentType = getFeatureType();
//forward events only if the collection is typed and match the type name
if(currentType != null && currentType.getName().equals(event.getFeatureTypeName())){
event = event.copy(this);
final FeatureStoreListener[] lst;
synchronized (listeners) {
lst = listeners.toArray(new FeatureStoreListener[listeners.size()]);
}
for (final FeatureStoreListener listener : lst) {
listener.structureChanged(event);
}
}
}
/**
* Forward event to listeners by changing source.
*/
@Override
public void contentChanged(final FeatureStoreContentEvent event){
final FeatureType currentType = getFeatureType();
//forward events only if the collection is typed and match the type name
if(currentType != null && currentType.getName().equals(event.getFeatureTypeName())){
sendEvent(event.copy(this));
}
}
/**
* {@inheritDoc }
*/
@Override
public void addStorageListener(final StorageListener listener) {
synchronized (listeners) {
listeners.add(listener);
}
}
/**
* {@inheritDoc }
*/
@Override
public void removeStorageListener(final StorageListener listener) {
synchronized (listeners) {
listeners.remove(listener);
}
}
/**
* Fires a features add event.
*
* @param name of the schema where features where added.
* @param ids modified feature ids.
*/
protected void fireFeaturesAdded(final GenericName name, final Id ids){
sendEvent(FeatureStoreContentEvent.createAddEvent(this, name,ids));
}
/**
* Fires a features update event.
*
* @param name of the schema where features where updated.
* @param ids modified feature ids.
*/
protected void fireFeaturesUpdated(final GenericName name, final Id ids){
sendEvent(FeatureStoreContentEvent.createUpdateEvent(this, name, ids));
}
/**
* Fires a features delete event.
*
* @param name of the schema where features where deleted
* @param ids modified feature ids.
*/
protected void fireFeaturesDeleted(final GenericName name, final Id ids){
sendEvent(FeatureStoreContentEvent.createDeleteEvent(this, name, ids));
}
/**
* Forward a features event to all listeners.
* @param event , event to send to listeners.
*/
protected void sendEvent(final FeatureStoreContentEvent event) {
final StorageListener[] lst;
synchronized (listeners) {
lst = listeners.toArray(new StorageListener[listeners.size()]);
}
for (final StorageListener listener : lst) {
listener.contentChanged(event);
}
}
}