blob: 3c68c1635b266951b556d5dd0a233c33770fd4b4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2013 Cloudsmith Inc. 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:
* Cloudsmith Inc. - initial API and implementation
* IBM Corporation - ongoing development
*******************************************************************************/
package org.eclipse.equinox.p2.query;
import java.util.ArrayList;
import java.util.Collection;
import org.eclipse.equinox.internal.p2.metadata.InstallableUnit;
import org.eclipse.equinox.internal.p2.metadata.expression.*;
import org.eclipse.equinox.internal.p2.metadata.expression.Expression.VariableFinder;
import org.eclipse.equinox.p2.metadata.*;
import org.eclipse.equinox.p2.metadata.expression.*;
/**
* Helper class for query related tasks.
* @since 2.0
*/
public class QueryUtil {
public static final IQuery<IInstallableUnit> ALL_UNITS = QueryUtil.createMatchQuery(ExpressionUtil.TRUE_EXPRESSION);
public static final String ANY = "*"; //$NON-NLS-1$
public static final IQuery<IInstallableUnit> NO_UNITS = QueryUtil.createQuery("limit(0)"); //$NON-NLS-1$
public static final String PROP_TYPE_CATEGORY = "org.eclipse.equinox.p2.type.category"; //$NON-NLS-1$
public static final String PROP_TYPE_GROUP = "org.eclipse.equinox.p2.type.group"; //$NON-NLS-1$
public static final String PROP_TYPE_PATCH = "org.eclipse.equinox.p2.type.patch"; //$NON-NLS-1$
private static final IExpression matchesRequirementsExpression = ExpressionUtil.parse("$0.exists(r | this ~= r)"); //$NON-NLS-1$
private static final IExpression matchIU_ID = ExpressionUtil.parse("id == $0"); //$NON-NLS-1$
private static final IExpression matchIU_IDAndRange = ExpressionUtil.parse("id == $0 && version ~= $1"); //$NON-NLS-1$
private static final IExpression matchIU_IDAndVersion = ExpressionUtil.parse("id == $0 && version == $1"); //$NON-NLS-1$
private static final IExpression matchIU_Range = ExpressionUtil.parse("version ~= $0"); //$NON-NLS-1$
private static final IExpression matchIU_Version = ExpressionUtil.parse("version == $0"); //$NON-NLS-1$
private static final IExpression matchIU_propAny = ExpressionUtil.parse("properties[$0] != null"); //$NON-NLS-1$
private static final IExpression matchIU_propNull = ExpressionUtil.parse("properties[$0] == null"); //$NON-NLS-1$
private static final IExpression matchIU_propTrue = ExpressionUtil.parse("properties[$0] == true"); //$NON-NLS-1$
private static final IExpression matchIU_propValue = ExpressionUtil.parse("properties[$0] == $1"); //$NON-NLS-1$
/**
* Creates a queryable that combines the given collection of input queryables
*
* @param queryables The collection of queryables to be combined
*/
public static <T> IQueryable<T> compoundQueryable(Collection<? extends IQueryable<T>> queryables) {
// don't suppress the warning as it will cause warnings in the official build
// see bug 423628. Write this without unchecked conversion.
return new CompoundQueryable<T>(queryables.toArray(new IQueryable[queryables.size()]));
}
/**
* Creates a queryable that combines the two provided input queryables
*
* @param query1 The first queryable
* @param query2 The second queryable
*/
@SuppressWarnings("unchecked")
public static <T> IQueryable<T> compoundQueryable(IQueryable<T> query1, IQueryable<T> query2) {
return new CompoundQueryable<T>(new IQueryable[] {query1, query2});
}
/**
* Creates a compound query that combines the given queries. If all queries
* are candidate match queries, then the queries will be concatenated as a
* boolean 'and' or a boolean 'or' expression depending on the <code>and</code>
* flag. If at least one query is a full query, all queries will instead be evaluated
* using intersection when <code>and</code> is <code>true</code> or a union.
*
* @param queries The queries to perform
* @param and <code>true</code> if this query represents an intersection or a
* logical 'and', and <code>false</code> if this query represents a union or
* a logical 'or'.
* @return A compound query
*/
@SuppressWarnings("unchecked")
public static <T> IQuery<T> createCompoundQuery(Collection<? extends IQuery<? extends T>> queries, boolean and) {
IExpressionFactory factory = ExpressionUtil.getFactory();
int top = queries.size();
if (top == 1)
return (IQuery<T>) queries.iterator().next();
Class<? extends T> elementClass = (Class<T>) Object.class;
if (top == 0)
return QueryUtil.<T> createMatchQuery(elementClass, ExpressionUtil.TRUE_EXPRESSION);
IExpression[] expressions = new IExpression[top];
boolean justBooleans = true;
boolean justContexts = true;
int idx = 0;
for (IQuery<? extends T> query : queries) {
if (query instanceof IMatchQuery<?>)
justContexts = false;
else
justBooleans = false;
IExpression expr = query.getExpression();
if (expr == null)
expr = factory.toExpression(query);
Class<? extends T> ec = ExpressionQuery.getElementClass(query);
if (elementClass == null)
elementClass = ec;
else if (elementClass != ec) {
if (elementClass.isAssignableFrom(ec)) {
if (and)
// Use most restrictive class
elementClass = ec;
} else if (ec.isAssignableFrom(elementClass)) {
if (!and)
// Use least restrictive class
elementClass = ec;
}
}
expressions[idx++] = expr;
}
if (justBooleans) {
IExpression compound = and ? factory.and(expressions) : factory.or(expressions);
return QueryUtil.<T> createMatchQuery(elementClass, compound);
}
if (!justContexts) {
// Mix of boolean queries and context queries. All must be converted into context then.
for (idx = 0; idx < expressions.length; ++idx)
expressions[idx] = makeContextExpression(factory, expressions[idx]);
}
IExpression compound = expressions[0];
for (idx = 1; idx < expressions.length; ++idx)
compound = and ? factory.intersect(compound, expressions[idx]) : factory.union(compound, expressions[idx]);
return QueryUtil.<T> createQuery(elementClass, compound);
}
/**
* Creates a compound query that combines the two queries. If both queries
* are candidate match queries, then the queries will be concatenated as a
* boolean 'and' or a boolean 'or' expression depending on the <code>and</code>
* flag. If at least one query is a full query, all queries will instead be evaluated
* using intersection when <code>and</code> is <code>true</code> or a union.
*
* @param query1 the first query
* @param query2 the second query
* @param and <code>true</code> if this query represents an intersection or a
* logical 'and', and <code>false</code> if this query represents a union or
* a logical 'or'.
* @return A compound query
*/
public static <T> IQuery<T> createCompoundQuery(IQuery<? extends T> query1, IQuery<T> query2, boolean and) {
ArrayList<IQuery<? extends T>> queries = new ArrayList<IQuery<? extends T>>(2);
queries.add(query1);
queries.add(query2);
return createCompoundQuery(queries, and);
}
/**
* Returns a query that matches all {@link InstallableUnit} elements
*/
public static IQuery<IInstallableUnit> createIUAnyQuery() {
return ALL_UNITS;
}
/**
* Creates a new query that will return the members of the
* given <code>category</code>. If the specified {@link IInstallableUnit}
* is not a category, then no installable unit will satisfy the query.
*
* @param category The category
* @return A query that returns category members
*/
public static IQuery<IInstallableUnit> createIUCategoryMemberQuery(IInstallableUnit category) {
if (QueryUtil.isCategory(category))
return QueryUtil.createMatchQuery(matchesRequirementsExpression, category.getRequirements());
return NO_UNITS;
}
/**
* Creates a query matching every {@link IInstallableUnit} that is a category.
* @return The query that matches categories
* @since 2.0
*/
public static IQuery<IInstallableUnit> createIUCategoryQuery() {
return createIUPropertyQuery(QueryUtil.PROP_TYPE_CATEGORY, Boolean.TRUE.toString());
}
/**
* Creates a query matching every {@link IInstallableUnit} that is a group.
* @return a query that matches all groups
*/
public static IQuery<IInstallableUnit> createIUGroupQuery() {
return createIUPropertyQuery(PROP_TYPE_GROUP, Boolean.TRUE.toString());
}
/**
* Creates an {@link IInstallableUnit} that will match all patches.
* @return The created query
*/
public static IQuery<IInstallableUnit> createIUPatchQuery() {
return createIUPropertyQuery(QueryUtil.PROP_TYPE_PATCH, Boolean.TRUE.toString());
}
/**
* Creates an {@link IInstallableUnit} that will match all products.
* @return The created query
* @since 2.2
*/
public static IQuery<IInstallableUnit> createIUProductQuery() {
return createIUPropertyQuery(MetadataFactory.InstallableUnitDescription.PROP_TYPE_PRODUCT, Boolean.TRUE.toString());
}
/**
* Creates a query that searches for {@link IInstallableUnit} instances that have
* a property whose value matches the provided value. If no property name is
* specified, then all {@link IInstallableUnit} instances are accepted.
* @param propertyName The key of the property to match or <code>null</code> to match all
* @param propertyValue The value of the property. Can be {@link #ANY} to match all values
* except <code>null</code>
* @return The query matching properties
*/
public static IQuery<IInstallableUnit> createIUPropertyQuery(String propertyName, String propertyValue) {
if (propertyName == null)
return QueryUtil.createMatchQuery(ExpressionUtil.TRUE_EXPRESSION);
if (propertyValue == null)
return QueryUtil.createMatchQuery(matchIU_propNull, propertyName);
if (ANY.equals(propertyValue))
return QueryUtil.createMatchQuery(matchIU_propAny, propertyName);
if (Boolean.valueOf(propertyValue).booleanValue())
return QueryUtil.createMatchQuery(matchIU_propTrue, propertyName);
return QueryUtil.createMatchQuery(matchIU_propValue, propertyName, propertyValue);
}
/**
* Creates a query that will match any {@link IInstallableUnit} with the given
* id and version.
*
* @param versionedId The precise id/version combination that a matching unit must have
* @return a query that matches IU's by id and version
*/
public static IQuery<IInstallableUnit> createIUQuery(IVersionedId versionedId) {
return createIUQuery(versionedId.getId(), versionedId.getVersion());
}
/**
* Creates a query that will match any {@link IInstallableUnit} with the given
* id, regardless of version.
*
* @param id The installable unit id to match, or <code>null</code> to match any id
* @return a query that matches IU's by id
*/
public static IQuery<IInstallableUnit> createIUQuery(String id) {
return id == null ? ALL_UNITS : QueryUtil.createMatchQuery(matchIU_ID, id);
}
/**
* Creates a query that will match any {@link IInstallableUnit} with the given
* id and version.
*
* @param id The installable unit id to match, or <code>null</code> to match any id
* @param version The precise version that a matching unit must have or <code>null</code>
* to match any version
* @return a query that matches IU's by id and version
*/
public static IQuery<IInstallableUnit> createIUQuery(String id, Version version) {
if (version == null || version.equals(Version.emptyVersion))
return createIUQuery(id);
if (id == null)
return QueryUtil.createMatchQuery(matchIU_Version, version);
return QueryUtil.createMatchQuery(matchIU_IDAndVersion, id, version);
}
/**
* Creates a query that will match any {@link IInstallableUnit} with the given
* id, and whose version falls in the provided range.
*
* @param id The installable unit id to match, or <code>null</code> to match any id
* @param range The version range to match or <code>null</code> to match any range.
* @return a query that matches IU's by id and range
*/
public static IQuery<IInstallableUnit> createIUQuery(String id, VersionRange range) {
if (range == null || range.equals(VersionRange.emptyRange))
return createIUQuery(id);
if (id == null)
return QueryUtil.createMatchQuery(matchIU_Range, range);
return QueryUtil.createMatchQuery(matchIU_IDAndRange, id, range);
}
/**
* Creates a query that returns the latest version for each unique id of an {@link IVersionedId}.
* All other elements are discarded.
* @return A query matching the latest version for each id.
*/
public static IQuery<IInstallableUnit> createLatestIUQuery() {
return QueryUtil.createQuery(ExpressionUtil.getFactory().latest(ExpressionFactory.EVERYTHING));
}
/**
* Creates a query that returns the latest version for each unique id of an {@link IVersionedId}
* from the collection produced by <code>query</code>.
* All other elements are discarded.
* @param query The query that precedes the latest query when evaluating.
* @return A query matching the latest version for each id.
*/
public static <T extends IVersionedId> IQuery<T> createLatestQuery(IQuery<T> query) {
IContextExpression<T> ctxExpr = ExpressionQuery.createExpression(query);
IExpressionFactory factory = ExpressionUtil.getFactory();
@SuppressWarnings("unchecked")
Class<T> elementClass = (Class<T>) IVersionedId.class;
return QueryUtil.createQuery(elementClass, factory.latest(((ContextExpression<?>) ctxExpr).operand), ctxExpr.getParameters());
}
/**
* Creates a limit query that can be used to limit the number of query results returned. Once
* the limit is reached, the query is terminated.
* @param query The query that should be limited
* @param limit A positive integer denoting the limit
* @return A limited query
* @since 2.0
*/
public static <T> IQuery<T> createLimitQuery(IQuery<T> query, int limit) {
IContextExpression<T> ctxExpr = ExpressionQuery.createExpression(query);
IExpressionFactory factory = ExpressionUtil.getFactory();
return QueryUtil.createQuery(ExpressionQuery.getElementClass(query), factory.limit(((ContextExpression<T>) ctxExpr).operand, limit), ctxExpr.getParameters());
}
/**
* Creates an {@link IInstallableUnit} query that will iterate over all candidates and discriminate by
* applying the boolean <code>matchExpression</code> on each candidate.
* @param matchExpression The boolean expression used for filtering one candidate
* @param parameters Values for parameter substitution
* @return The created query
*/
public static IQuery<IInstallableUnit> createMatchQuery(IExpression matchExpression, Object... parameters) {
return new ExpressionMatchQuery<IInstallableUnit>(IInstallableUnit.class, matchExpression, parameters);
}
/**
* Parses the <code>matchExpression</code> and creates an {@link IInstallableUnit} query that will
* iterate over all candidates and discriminate by applying the boolean <code>matchExpression</code>
* on each candidate.
* @param matchExpression The boolean expression used for filtering one candidate
* @param parameters Values for parameter substitution
* @return The created query
*/
public static IQuery<IInstallableUnit> createMatchQuery(String matchExpression, Object... parameters) {
return new ExpressionMatchQuery<IInstallableUnit>(IInstallableUnit.class, matchExpression, parameters);
}
/**
* Creates an query that will iterate over all candidates and discriminate all
* candidates that are not instances of <code>matchinClass></code> or for which
* the boolean <code>matchExpression</code> returns false.
* @param matchingClass The class that matching candidates must be an instance of
* @param matchExpression The boolean expression used for filtering one candidate
* @param parameters Values for parameter substitution
* @return The created query
*/
public static <T> IQuery<T> createMatchQuery(Class<? extends T> matchingClass, IExpression matchExpression, Object... parameters) {
return new ExpressionMatchQuery<T>(matchingClass, matchExpression, parameters);
}
/**
* Parses the <code>matchExpression</code> and creates an query that will iterate over
* all candidates and discriminate all candidates that are not instances of
* <code>matchinClass></code> or for which the boolean <code>matchExpression</code>
* returns false.
* @param matchingClass The class that matching candidates must be an instance of
* @param matchExpression The boolean expression used for filtering one candidate
* @param parameters Values for parameter substitution
* @return The created query
*/
public static <T> IQuery<T> createMatchQuery(Class<? extends T> matchingClass, String matchExpression, Object... parameters) {
return new ExpressionMatchQuery<T>(matchingClass, matchExpression, parameters);
}
/**
* <p>Creates a piped query based on the provided input queries.</p>
* <p>A pipe is a composite query in which each sub-query is executed in succession.
* The results from the ith sub-query are piped as input into the i+1th sub-query. The
* query will short-circuit if any query returns an empty result set.</p>
*
* @param queries the ordered list of queries to perform
* @return A query pipe
*/
@SuppressWarnings("unchecked")
public static <T> IQuery<T> createPipeQuery(Collection<? extends IQuery<? extends T>> queries) {
IExpressionFactory factory = ExpressionUtil.getFactory();
int top = queries.size();
IExpression[] expressions = new IExpression[top];
int idx = 0;
for (IQuery<? extends T> query : queries) {
IExpression expr = query.getExpression();
if (expr == null)
expr = factory.toExpression(query);
expressions[idx++] = expr;
}
IExpression pipe = factory.pipe(expressions);
VariableFinder finder = new VariableFinder(ExpressionFactory.EVERYTHING);
pipe.accept(finder);
return finder.isFound() ? QueryUtil.<T> createQuery((Class<T>) Object.class, pipe) : QueryUtil.<T> createMatchQuery((Class<T>) Object.class, pipe);
}
/**
* <p>Creates a piped query based on the provided input queries.</p>
* <p>A pipe is a composite query in which each sub-query is executed in succession.
* The results from the ith sub-query are piped as input into the i+1th sub-query. The
* query will short-circuit if any query returns an empty result set.</p>
*
* @param query1 the first query
* @param query2 the second query
* @return A query pipe
*/
public static <T> IQuery<T> createPipeQuery(IQuery<? extends T> query1, IQuery<? extends T> query2) {
ArrayList<IQuery<? extends T>> queries = new ArrayList<IQuery<? extends T>>(2);
queries.add(query1);
queries.add(query2);
return createPipeQuery(queries);
}
/**
* Creates an {@link IInstallableUnit} query based on an <code>expression</code> that
* uses all candidates as input.
* @param expression The query expression
* @param parameters Values for parameter substitution
* @return The created query
*/
public static IQuery<IInstallableUnit> createQuery(IExpression expression, Object... parameters) {
return new ExpressionQuery<IInstallableUnit>(IInstallableUnit.class, expression, parameters);
}
/**
* Parses the <code>expression</code> and creates an {@link IInstallableUnit} query. The
* <code>expression</code> is expected to use all candidates as input.
* @param expression The query expression
* @param parameters Values for parameter substitution
* @return The created query
*/
public static IQuery<IInstallableUnit> createQuery(String expression, Object... parameters) {
return new ExpressionQuery<IInstallableUnit>(IInstallableUnit.class, expression, parameters);
}
/**
* Creates a query that will limit the result to instances of the <code>matchinClass</code>. The
* <code>expression</code> is expected to use all candidates as input.
* @param matchingClass The class used as discriminator for the result
* @param expression The query expression
* @param parameters Values for parameter substitution
* @return The created query
*/
public static <T> IQuery<T> createQuery(Class<? extends T> matchingClass, IExpression expression, Object... parameters) {
return new ExpressionQuery<T>(matchingClass, expression, parameters);
}
/**
* Parses the <code>expression</code> and creates a query that will limit the result
* to instances of the <code>matchinClass</code>. The <code>expression</code> is expected
* to use all candidates as input.
* @param matchingClass The class used as discriminator for the result
* @param expression The query expression
* @param parameters Values for parameter substitution
* @return The created query
*/
public static <T> IQuery<T> createQuery(Class<? extends T> matchingClass, String expression, Object... parameters) {
return new ExpressionQuery<T>(matchingClass, expression, parameters);
}
/**
* Test if the {@link IInstallableUnit} is a category.
* @param iu the element being tested.
* @return <tt>true</tt> if the parameter is a category.
*/
public static boolean isCategory(IInstallableUnit iu) {
String value = iu.getProperty(PROP_TYPE_CATEGORY);
if (value != null && (value.equals(Boolean.TRUE.toString())))
return true;
return false;
}
/**
* Test if the {@link IInstallableUnit} is a fragment.
* @param iu the element being tested.
* @return <tt>true</tt> if the parameter is a fragment.
*/
public static boolean isFragment(IInstallableUnit iu) {
return iu instanceof IInstallableUnitFragment;
}
/**
* Test if the {@link IInstallableUnit} is a group.
* @param iu the element being tested.
* @return <tt>true</tt> if the parameter is a group.
*/
public static boolean isGroup(IInstallableUnit iu) {
String value = iu.getProperty(PROP_TYPE_GROUP);
if (value != null && (value.equals(Boolean.TRUE.toString())))
return true;
return false;
}
/**
* Test if the {@link IInstallableUnit} is a product.
* @param iu the element being tested.
* @return <tt>true</tt> if the parameter is a group.
* @since 2.2
*/
public static boolean isProduct(IInstallableUnit iu) {
String value = iu.getProperty(MetadataFactory.InstallableUnitDescription.PROP_TYPE_PRODUCT);
if (value != null && (value.equals(Boolean.TRUE.toString())))
return true;
return false;
}
/**
* Test if the {@link IInstallableUnit} is a patch.
* @param iu the element being tested.
* @return <tt>true</tt> if the parameter is a patch.
*/
public static boolean isPatch(IInstallableUnit iu) {
String value = iu.getProperty(PROP_TYPE_PATCH);
if (value != null && (value.equals(Boolean.TRUE.toString())))
return true;
return false;
}
private static IExpression makeContextExpression(IExpressionFactory factory, IExpression expr) {
VariableFinder finder = new VariableFinder(ExpressionFactory.EVERYTHING);
expr.accept(finder);
if (!finder.isFound())
expr = factory.select(ExpressionFactory.EVERYTHING, factory.lambda(ExpressionFactory.THIS, expr));
return expr;
}
}