/* * Copyright Aduna (http://www.aduna-software.com/) (c) 1997-2007. * * Licensed under the Aduna BSD-style license. */ package org.openrdf.query.algebra.evaluation.iterator; import java.util.ArrayList; import java.util.Collection; 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 info.aduna.iteration.CloseableIteration; import info.aduna.iteration.CloseableIteratorIteration; import info.aduna.lang.ObjectUtil; import org.openrdf.model.Literal; import org.openrdf.model.Value; import org.openrdf.model.impl.LiteralImpl; import org.openrdf.model.vocabulary.XMLSchema; import org.openrdf.query.BindingSet; import org.openrdf.query.QueryEvaluationException; import org.openrdf.query.algebra.AggregateOperator; import org.openrdf.query.algebra.Count; import org.openrdf.query.algebra.Group; import org.openrdf.query.algebra.GroupElem; import org.openrdf.query.algebra.Max; import org.openrdf.query.algebra.Min; import org.openrdf.query.algebra.ValueExpr; import org.openrdf.query.algebra.evaluation.EvaluationStrategy; import org.openrdf.query.algebra.evaluation.QueryBindingSet; /** * @author David Huynh * @author Arjohn Kampman */ public class GroupIterator extends CloseableIteratorIteration<BindingSet, QueryEvaluationException> { /*-----------* * Constants * *-----------*/ private final EvaluationStrategy strategy; private final BindingSet parentBindings; private final Group group; /*-----------* * Variables * *-----------*/ private boolean ordered = false; /*--------------* * Constructors * *--------------*/ public GroupIterator(EvaluationStrategy strategy, Group group, BindingSet parentBindings) throws QueryEvaluationException { this.strategy = strategy; this.group = group; this.parentBindings = parentBindings; super.setIterator(createIterator()); } /*---------* * Methods * *---------*/ private Iterator<BindingSet> createIterator() throws QueryEvaluationException { Collection<BindingSet> bindingSets; Collection<Entry> entries; if (ordered) { bindingSets = new ArrayList<BindingSet>(); entries = buildOrderedEntries(); } else { bindingSets = new HashSet<BindingSet>(); entries = buildUnorderedEntries(); } for (Entry entry : entries) { QueryBindingSet sol = new QueryBindingSet(parentBindings); for (String name : group.getGroupBindingNames()) { Value value = entry.getPrototype().getValue(name); if (value != null) { // Potentially overwrites bindings from super sol.setBinding(name, value); } } for (GroupElem ge : group.getGroupElements()) { Value value = processAggregate(entry.getSolutions(), ge.getOperator()); if (value != null) { // Potentially overwrites bindings from super sol.setBinding(ge.getName(), value); } } bindingSets.add(sol); } return bindingSets.iterator(); } private Collection<Entry> buildOrderedEntries() throws QueryEvaluationException { CloseableIteration<BindingSet, QueryEvaluationException> iter = strategy.evaluate(group.getArg(), parentBindings); try { List<Entry> orderedEntries = new ArrayList<Entry>(); Map<Key, Entry> entries = new HashMap<Key, Entry>(); while (iter.hasNext()) { BindingSet bindingSet = iter.next(); Key key = new Key(bindingSet); Entry entry = entries.get(key); if (entry == null) { entry = new Entry(bindingSet); entries.put(key, entry); orderedEntries.add(entry); } entry.addSolution(bindingSet); } return orderedEntries; } finally { iter.close(); } } private Collection<Entry> buildUnorderedEntries() throws QueryEvaluationException { CloseableIteration<BindingSet, QueryEvaluationException> iter = strategy.evaluate(group.getArg(), parentBindings); try { Map<Key, Entry> entries = new HashMap<Key, Entry>(); while (iter.hasNext()) { BindingSet sol = iter.next(); Key key = new Key(sol); Entry entry = entries.get(key); if (entry == null) { entry = new Entry(sol); entries.put(key, entry); } entry.addSolution(sol); } return entries.values(); } finally { iter.close(); } } private Value processAggregate(Set<BindingSet> bindingSets, AggregateOperator operator) throws QueryEvaluationException { if (operator instanceof Count) { Count countOp = (Count)operator; ValueExpr arg = countOp.getArg(); if (arg != null) { Set<Value> values = makeValueSet(arg, bindingSets); return new LiteralImpl(Integer.toString(values.size()), XMLSchema.INTEGER); } else { return new LiteralImpl(Integer.toString(bindingSets.size()), XMLSchema.INTEGER); } } else if (operator instanceof Min) { Min minOp = (Min)operator; Set<Value> values = makeValueSet(minOp.getArg(), bindingSets); // FIXME: handle case where 'values' is empty double min = Double.POSITIVE_INFINITY; for (Value v : values) { if (v instanceof Literal) { Literal l = (Literal)v; try { min = Math.min(min, Double.parseDouble(l.getLabel())); } catch (NumberFormatException e) { // ignore } } } return new LiteralImpl(Double.toString(min), XMLSchema.DOUBLE); } else if (operator instanceof Max) { Max maxOp = (Max)operator; Set<Value> values = makeValueSet(maxOp.getArg(), bindingSets); // FIXME: handle case where 'values' is empty double max = Double.NEGATIVE_INFINITY; for (Value v : values) { if (v instanceof Literal) { Literal l = (Literal)v; try { max = Math.max(max, Double.parseDouble(l.getLabel())); } catch (NumberFormatException e) { // ignore } } } return new LiteralImpl(Double.toString(max), XMLSchema.DOUBLE); } return null; } private Set<Value> makeValueSet(ValueExpr arg, Set<BindingSet> bindingSets) throws QueryEvaluationException { Set<Value> valueSet = new HashSet<Value>(); for (BindingSet s : bindingSets) { Value value = strategy.evaluate(arg, s); if (value != null) { valueSet.add(value); } } return valueSet; } /** * A unique key for a set of existing bindings. * * @author David Huynh */ protected class Key { private BindingSet bindingSet; private int hash; public Key(BindingSet bindingSet) { this.bindingSet = bindingSet; for (String name : group.getGroupBindingNames()) { Value value = bindingSet.getValue(name); if (value != null) { this.hash ^= value.hashCode(); } } } @Override public int hashCode() { return hash; } @Override public boolean equals(Object other) { if (other instanceof Key && other.hashCode() == hash) { BindingSet otherSolution = ((Key)other).bindingSet; for (String name : group.getGroupBindingNames()) { Value v1 = bindingSet.getValue(name); Value v2 = otherSolution.getValue(name); if (!ObjectUtil.nullEquals(v1, v2)) { return false; } } return true; } return false; } } protected static class Entry { private BindingSet prototype; private Set<BindingSet> bindingSets; public Entry(BindingSet prototype) { this.prototype = prototype; this.bindingSets = new HashSet<BindingSet>(); } public BindingSet getPrototype() { return prototype; } public void addSolution(BindingSet bindingSet) { bindingSets.add(bindingSet); } public Set<BindingSet> getSolutions() { return bindingSets; } } }