/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2012, 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.memory; import java.util.NoSuchElementException; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.logging.Level; import org.geotoolkit.data.FeatureCollection; import org.geotoolkit.data.FeatureIterator; import org.geotoolkit.data.FeatureReader; import org.geotoolkit.data.FeatureStoreRuntimeException; import org.geotoolkit.data.FeatureWriter; import org.geotoolkit.factory.Hints; import org.apache.sis.util.Classes; import org.geotoolkit.factory.HintsPending; import org.apache.sis.util.logging.Logging; import org.opengis.feature.Feature; import org.opengis.feature.FeatureType; /** * Wrap a feature iterator and precache the given number of values. * A separate thread is created to load the buffer. * * @author Johann Sorel (Geomatys) */ public class GenericCachedFeatureIterator implements FeatureIterator { //TODO : wait for martin, there should already be a thread pool for global tasks somewhere. public static final Executor POOL = Executors.newCachedThreadPool(); private final Object QUEUELOCK = new Object(); private final Object FINISHLOCK = new Object(); // used by the main thread to notify collector thread to stop retrieving features. private volatile boolean closed = false; // used by the collector thread to notify main thread that sub iterator can be closed. private volatile boolean canCloseSub = false; private final ArrayBlockingQueue queue = new ArrayBlockingQueue(2); protected FeatureIterator iterator; protected final int cacheSize; private Feature[] buffer = null; private int bufferIndex = 0; protected Feature next = null; private FeatureStoreRuntimeException subException = null; /** * Creates a new instance of GenericCacheFeatureIterator * * @param iterator FeatureReader to limit * @param cacheSize cacheSize */ private GenericCachedFeatureIterator(final FeatureIterator iterator, final int cacheSize) { this.iterator = iterator; this.cacheSize = cacheSize; POOL.execute(new Collector()); } /** * {@inheritDoc } */ @Override public Feature next() throws FeatureStoreRuntimeException { if(subException != null){ //forward sub exception final FeatureStoreRuntimeException d = subException; subException = null; throw d; } findNext(); final Feature c = next; next = null; if(c == null){ throw new NoSuchElementException("No such Feature exists"); } return c; } /** * {@inheritDoc } */ @Override public void close() throws FeatureStoreRuntimeException { closed = true; //notify collector thread, may be waiting synchronized(QUEUELOCK){ QUEUELOCK.notify(); } //sub iterator might already be closed if(iterator != null){ synchronized(FINISHLOCK){ iterator.close(); iterator = null; } } } /** * {@inheritDoc } */ @Override public boolean hasNext() throws FeatureStoreRuntimeException { if(subException != null){ //forward sub exception final FeatureStoreRuntimeException d = subException; subException = null; throw d; } findNext(); return next != null; } private void findNext() throws FeatureStoreRuntimeException { if(next != null || closed) return; if(buffer == null){ //collector thread might have finish reading before iteration //is over. we can release the sub iterator sooner to release resources. if(iterator!=null && canCloseSub){ iterator.close(); iterator = null; } try { buffer = (Feature[]) queue.take(); } catch (InterruptedException ex) { Logging.getLogger("org.geotoolkit.data.memory").log(Level.WARNING, ex.getMessage(), ex); } bufferIndex = 0; //notify collector thread some space is available synchronized(QUEUELOCK){ QUEUELOCK.notify(); } } next = buffer[bufferIndex]; bufferIndex++; if(bufferIndex >= cacheSize){ //we have finish reading this buffer. //next iteration will get a new one. buffer = null; } if(next == null){ //no more records closed = true; } } /** * {@inheritDoc } */ @Override public void remove() { throw new FeatureStoreRuntimeException("Cached iterator does not support remove operation."); } @Override public String toString() { final StringBuilder sb = new StringBuilder(Classes.getShortClassName(this)); sb.append("[CacheSize=").append(cacheSize).append("]\n"); String subIterator = "\u2514\u2500\u2500" + iterator.toString(); //move text to the right subIterator = subIterator.replaceAll("\n", "\n\u00A0\u00A0\u00A0"); //move text to the right sb.append(subIterator); return sb.toString(); } /** * Wrap a FeatureReader with a cache size. * * @param <T> extends FeatureType * @param <F> extends Feature * @param <R> extends FeatureReader<T,F> */ private static final class GenericCachedFeatureReader extends GenericCachedFeatureIterator implements FeatureReader{ private final FeatureType ft; private GenericCachedFeatureReader(final FeatureReader reader, final int cacheSize){ super(reader,cacheSize); ft = reader.getFeatureType(); } @Override public FeatureType getFeatureType() { return ft; } } private static final class GenericCachedFeatureCollection extends WrapFeatureCollection{ private final int cacheSize; private GenericCachedFeatureCollection(final FeatureCollection original, final int cacheSize){ super(original); this.cacheSize = cacheSize; } @Override public FeatureIterator iterator(Hints hints) throws FeatureStoreRuntimeException { if(hints!= null){ hints = new Hints(hints); hints.put(HintsPending.FEATURE_DETACHED,Boolean.TRUE); } return wrap(getOriginalFeatureCollection().iterator(hints), cacheSize); } @Override protected Feature modify(Feature original) throws FeatureStoreRuntimeException { throw new UnsupportedOperationException("should not have been called."); } } /** * Wrap a FeatureIterator with a cache size. */ public static FeatureIterator wrap(final FeatureIterator reader, final int cacheSize){ if(reader instanceof FeatureReader){ return wrap((FeatureReader)reader,cacheSize); }else if(reader instanceof FeatureWriter){ return wrap((FeatureWriter)reader,cacheSize); }else{ return new GenericCachedFeatureIterator(reader, cacheSize); } } /** * Wrap a FeatureReader with a cache size. */ public static FeatureReader wrap(final FeatureReader reader, final int cacheSize){ return new GenericCachedFeatureIterator.GenericCachedFeatureReader(reader, cacheSize); } /** * Create an caching FeatureCollection wrapping the given collection. */ public static FeatureCollection wrap(final FeatureCollection original, final int cacheSize){ return new GenericCachedFeatureIterator.GenericCachedFeatureCollection(original, cacheSize); } private final class Collector implements Runnable{ @Override public void run() { boolean finish = false; synchronized(FINISHLOCK){ mainLoop: while(!closed && !finish){ //create and fill buffer final Feature[] buffer = new Feature[cacheSize]; try{ for(int i=0;i<buffer.length;i++){ if(iterator.hasNext()){ buffer[i] = iterator.next(); }else{ finish = true; break; } } }catch(FeatureStoreRuntimeException ex){ subException = ex; Logging.getLogger("org.geotoolkit.data.memory").log(Level.WARNING, subException.getMessage(),ex); break; } boolean success; do{ success = queue.offer(buffer); if(!success){ try { //no space left, go to sleep until iterator wake it up synchronized(QUEUELOCK){ if(closed) break mainLoop; QUEUELOCK.wait(); } } catch (InterruptedException ex) { Logging.getLogger("org.geotoolkit.data.memory").log(Level.WARNING, ex.getMessage(), ex); } } }while(!success); } } canCloseSub = true; } } }