blob: 8b3b30a57393d61743f3df03f6e5cef182bfeb71 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2009 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* EclipseSource - ongoing development
*******************************************************************************/
package org.eclipse.equinox.internal.provisional.p2.metadata.query;
import java.util.*;
import org.eclipse.equinox.p2.metadata.query.IQuery;
/**
* A query that combines a group of sub-queries.<P>
*
* In a CompoundQuery each sub-query is executed and the results are combined using
* either logical AND or logical OR operations. <P>
*
* Clients are expected to call {@link CompoundQuery#createCompoundQuery(IQuery[], boolean)}
* to create a concrete instance of a CompoundQuery. If all Queries are instances of
* {@link IMatchQuery} then the resulting compound query will be a MatchCompoundQuery, otherwise the
* resulting compound query will be a {@link ContextQuery}.
*
* @noextend This class is not intended to be subclassed by clients.
*/
public abstract class CompoundQuery implements IQuery, ICompositeQuery {
protected IQuery[] queries;
protected boolean and;
/**
* Creates a compound query that combines the given queries. The queries
* will be performed by the compound query in the given order. This method
* might not perform all queries if it can determine the result of the compound
* expression without doing so.
*
* If all the queries are instances of {@link IMatchQuery} then the resulting
* compound query will be an instance of IMatchQuery, otherwise the resulting
* compound query will be a context query.
*
* @param queries The queries to perform
* @param and <code>true</code> if this query represents a logical 'and', and
* <code>false</code> if this query represents a logical 'or'.
*/
public static CompoundQuery createCompoundQuery(IQuery[] queries, boolean and) {
if (isMatchQueries(queries)) {
return new CompoundQuery.MatchCompoundQuery(queries, and);
}
return new CompoundQuery.ContextCompoundQuery(queries, and);
}
/**
* Returns the queries that make up this compound query
*/
public IQuery[] getQueries() {
return queries;
}
/**
* Returns whether this compound query combines its queries with a logical
* 'and' or 'or'.
* @return <code>true</code> if this query represents a logical 'and', and
* <code>false</code> if this query represents a logical 'or'.
*/
public boolean isAnd() {
return and;
}
protected CompoundQuery(IQuery[] queries, boolean and) {
this.queries = queries;
this.and = and;
}
/**
* @param queries
*/
private static boolean isMatchQueries(IQuery[] queries) {
for (int i = 0; i < queries.length; i++) {
if (!(queries[i] instanceof IMatchQuery)) {
return false;
}
}
return true;
}
/**
* Gets the ID for this Query.
*/
public String getId() {
return QueryHelpers.getId(this);
}
/**
* Gets a particular property of the query.
* @param property The property to retrieve
*/
public Object getProperty(String property) {
return QueryHelpers.getProperty(this, property);
}
/**
* The compound query instantiated when all queries are Match Queries.
*/
private static class MatchCompoundQuery extends CompoundQuery implements IMatchQuery {
protected MatchCompoundQuery(IQuery[] queries, boolean and) {
super(queries, and);
}
public boolean isMatch(Object candidate) {
for (int i = 0; i < queries.length; i++) {
boolean valid = ((IMatchQuery) queries[i]).isMatch(candidate);
// if we are OR'ing then the first time we find a requirement that is met, return success
if (valid && !and)
return true;
// if we are AND'ing then the first time we find a requirement that is NOT met, return failure
if (!valid && and)
return false;
}
// if we get past the requirements check and we are AND'ing then return true
// since all requirements must have been met. If we are OR'ing then return false
// since none of the requirements were met.
return and;
}
/**
* Performs this query on the given iterator, passing all objects in the iterator
* that match the criteria of this query to the given result.
*/
public final Collector perform(Iterator iterator, Collector result) {
prePerform();
try {
while (iterator.hasNext()) {
Object candidate = iterator.next();
if (isMatch(candidate))
if (!result.accept(candidate))
break;
}
} finally {
postPerform();
}
return result;
}
public void prePerform() {
for (int i = 0; i < queries.length; i++) {
((IMatchQuery) queries[i]).prePerform();
}
}
public void postPerform() {
for (int i = 0; i < queries.length; i++) {
((IMatchQuery) queries[i]).postPerform();
}
}
}
/**
* The compound query instantiated when any of the queries are not
* match queries.
*/
private static class ContextCompoundQuery extends CompoundQuery {
protected ContextCompoundQuery(IQuery[] queries, boolean and) {
super(queries, and);
}
/*
* A collector that takes the set to puts the elements in.
*/
class SetCollector extends Collector {
Set s = null;
public SetCollector(Set s) {
this.s = s;
}
public boolean accept(Object object) {
s.add(object);
return true;
}
}
public Collector perform(Iterator iterator, Collector result) {
if (queries.length < 1)
return result;
Collection data = new LinkedList();
while (iterator.hasNext()) {
data.add(iterator.next());
}
Set[] resultSets = new Set[queries.length];
for (int i = 0; i < queries.length; i++) {
resultSets[i] = new HashSet();
queries[i].perform(data.iterator(), new SetCollector(resultSets[i]));
}
Set set = resultSets[0];
for (int i = 1; i < resultSets.length; i++) {
if (isAnd())
set.retainAll(resultSets[i]);
else
set.addAll(resultSets[i]);
}
Iterator resultIterator = set.iterator();
boolean gatherResults = true;
while (resultIterator.hasNext() && gatherResults)
gatherResults = result.accept(resultIterator.next());
return result;
}
}
}