/* * Copyright (c) 2012 Data Harmonisation Panel * * All rights reserved. This program and the accompanying materials are made * available under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution. If not, see <http://www.gnu.org/licenses/>. * * Contributors: * HUMBOLDT EU Integrated Project #030962 * Data Harmonisation Panel <http://www.dhpanel.eu> */ package eu.esdihumboldt.hale.common.instance.model.impl; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.NoSuchElementException; import com.google.common.base.Function; import com.google.common.collect.Maps; import eu.esdihumboldt.hale.common.instance.model.ContextAwareFilter; import eu.esdihumboldt.hale.common.instance.model.Filter; import eu.esdihumboldt.hale.common.instance.model.Instance; import eu.esdihumboldt.hale.common.instance.model.InstanceCollection; import eu.esdihumboldt.hale.common.instance.model.ResourceIterator; import eu.esdihumboldt.hale.common.instance.model.TypeFilter; import eu.esdihumboldt.hale.common.instance.model.ext.InstanceCollection2; import eu.esdihumboldt.hale.common.instance.model.ext.InstanceIterator; import eu.esdihumboldt.hale.common.instance.model.ext.helper.EmptyInstanceCollection; import eu.esdihumboldt.hale.common.instance.model.ext.helper.InstanceCollectionDecorator; import eu.esdihumboldt.hale.common.schema.model.TypeDefinition; /** * Instance collection that wraps an instance collection and represents a * selection that contains the instances matching a given {@link Filter}. * * @author Simon Templer */ public class FilteredInstanceCollection extends InstanceCollectionDecorator { /** * Create an instance collection that applies a filter to the given instance * collection. * * @param instances the instance collection to filter * @param filter the filter * @return the filtered instance collection */ public static InstanceCollection applyFilter(InstanceCollection instances, Filter filter) { if (filter instanceof TypeFilter && instances instanceof InstanceCollection2) { /* * For type filters check if we can make use of fan-out. */ InstanceCollection2 instances2 = (InstanceCollection2) instances; if (instances2.supportsFanout()) { TypeDefinition type = ((TypeFilter) filter).getType(); InstanceCollection result = instances2.fanout().get(type); if (result == null) { result = EmptyInstanceCollection.INSTANCE; } return result; } } // create a filtered collection return new FilteredInstanceCollection(instances, filter); } /** * Filtered resource iterator. */ public class FilteredIterator implements ResourceIterator<Instance> { private final ResourceIterator<Instance> decoratee; /** * The next matching instance */ private Instance preview; /** * States if the value in {@link #preview} represents a valid element */ private boolean previewPresent; /** * States if {@link #preview}/{@link #previewPresent} must be updated */ private boolean updatePreview = true; /** * Iteration context for filters. */ private final Map<Object, Object> context; /** * Create a filtered resource iterator. * * @param decoratee the original iterator */ public FilteredIterator(ResourceIterator<Instance> decoratee) { this.decoratee = decoratee; if (filter instanceof ContextAwareFilter) { context = Collections.synchronizedMap(new HashMap<>()); } else { context = null; } } @Override public boolean hasNext() { update(); // ensure previewPresent/preview are set return previewPresent; } @Override public Instance next() { update(); // ensure previewPresent/preview are set if (!previewPresent) { throw new NoSuchElementException(); } updatePreview = true; // next time, update the preview return preview; } /** * Move {@link #preview} to the next match if possible, update * {@link #previewPresent}. */ private void update() { if (updatePreview) { previewPresent = false; // find first instance matching the filter while (!previewPresent && decoratee.hasNext()) { Instance instance = decoratee.next(); boolean match; if (context != null) { match = ((ContextAwareFilter) filter).match(instance, context); } else { match = filter.match(instance); } if (match) { previewPresent = true; preview = instance; } } if (!previewPresent) { preview = null; } updatePreview = false; } } @Override public void remove() { throw new UnsupportedOperationException( "Removing instances not supported on filtered collections"); } @Override public void close() { decoratee.close(); // in case the iterator is kept around, clear the context if (context != null) { context.clear(); } } } private final Filter filter; /** * Create a filtered instance collection. * * @param decoratee the instance collection to perform the selection on * @param filter the filter representing the selection */ private FilteredInstanceCollection(InstanceCollection decoratee, Filter filter) { super(decoratee); this.filter = filter; } @Override public ResourceIterator<Instance> iterator() { ResourceIterator<Instance> it = decoratee.iterator(); if (filter instanceof TypeFilter && it instanceof InstanceIterator && ((InstanceIterator) it).supportsTypePeek()) { // make use of type peek if possible return new TypeFilteredIterator(it, ((TypeFilter) filter).getType()); } return new FilteredIterator(it); } @Override public boolean hasSize() { // the size cannot be pre-determined return false; } @Override public int size() { return UNKNOWN_SIZE; } @Override public boolean isEmpty() { ResourceIterator<Instance> it = iterator(); try { return !it.hasNext(); } finally { it.close(); } } @Override public InstanceCollection select(Filter filter) { return new FilteredInstanceCollection(this, filter); } @Override public Map<TypeDefinition, InstanceCollection> fanout() { Map<TypeDefinition, InstanceCollection> fanout = super.fanout(); if (fanout != null) { return Maps.transformValues(fanout, new Function<InstanceCollection, InstanceCollection>() { @Override public InstanceCollection apply(InstanceCollection from) { return new FilteredInstanceCollection(from, filter); } }); } return null; } }