blob: dfbcd51f1385aa34dc911131ef35cc385c4d89ef [file] [log] [blame]
/*-
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sling.query;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.apache.sling.query.api.SearchStrategy;
import org.apache.sling.query.api.internal.IteratorToIteratorFunction;
import org.apache.sling.query.api.internal.Option;
import org.apache.sling.query.api.internal.TreeProvider;
import org.apache.sling.query.function.AddFunction;
import org.apache.sling.query.function.ChildrenFunction;
import org.apache.sling.query.function.ClosestFunction;
import org.apache.sling.query.function.CompositeFunction;
import org.apache.sling.query.function.DescendantFunction;
import org.apache.sling.query.function.FilterFunction;
import org.apache.sling.query.function.FindFunction;
import org.apache.sling.query.function.HasFunction;
import org.apache.sling.query.function.IdentityFunction;
import org.apache.sling.query.function.LastFunction;
import org.apache.sling.query.function.NextFunction;
import org.apache.sling.query.function.NotFunction;
import org.apache.sling.query.function.ParentFunction;
import org.apache.sling.query.function.ParentsFunction;
import org.apache.sling.query.function.PrevFunction;
import org.apache.sling.query.function.SiblingsFunction;
import org.apache.sling.query.function.SliceFunction;
import org.apache.sling.query.function.UniqueFunction;
import org.apache.sling.query.iterator.EmptyElementFilter;
import org.apache.sling.query.iterator.OptionDecoratingIterator;
import org.apache.sling.query.iterator.OptionStrippingIterator;
import org.apache.sling.query.predicate.IterableContainsPredicate;
import org.apache.sling.query.predicate.RejectingPredicate;
import org.apache.sling.query.selector.SelectorFunction;
import org.apache.sling.query.util.LazyList;
import org.osgi.annotation.versioning.ProviderType;
@ProviderType
public abstract class AbstractQuery<T, Q extends AbstractQuery<T, Q>> implements Iterable<T> {
protected final List<Function<?, ?>> functions = new ArrayList<>();
private final List<T> initialCollection;
private final SearchStrategy searchStrategy;
private final TreeProvider<T> provider;
AbstractQuery(TreeProvider<T> provider, T[] initialCollection, SearchStrategy strategy) {
this.provider = provider;
this.initialCollection = new ArrayList<>(Arrays.asList(initialCollection));
this.searchStrategy = strategy;
}
protected AbstractQuery(AbstractQuery<T, Q> original, SearchStrategy searchStrategy) {
this.functions.addAll(original.functions);
this.initialCollection = new ArrayList<>(original.initialCollection);
this.searchStrategy = searchStrategy;
this.provider = original.provider;
}
/**
* {@inheritDoc}
*/
@Override
public Iterator<T> iterator() {
IteratorToIteratorFunction<T> f = new CompositeFunction<>(functions);
Iterator<Option<T>> iterator = f.apply(new OptionDecoratingIterator<>(initialCollection.iterator()));
iterator = new EmptyElementFilter<>(iterator);
return new OptionStrippingIterator<>(iterator);
}
public Stream<T> stream() {
return StreamSupport.stream(this.spliterator(), false);
}
/**
* Include resources to the collection.
*
* @param resources Resources to include
* @return new SlingQuery object transformed by this operation
*/
public Q add(T... resources) {
return function(new AddFunction<>(Arrays.asList(resources)));
}
/**
* Include resources to the collection.
*
* @param iterable Resources to include
* @return new SlingQuery object transformed by this operation
*/
public Q add(Iterable<T> iterable) {
return function(new AddFunction<>(iterable));
}
/**
* Transform SlingQuery collection into a lazy list.
*
* @return List containing all elements from the collection.
*/
public List<T> asList() {
return new LazyList<>(iterator());
}
/**
* Get list of the children for each Resource in the collection.
*
* @return new SlingQuery object transformed by this operation
*/
public Q children() {
return function(new ChildrenFunction<>(provider));
}
/**
* Get list of the children for each Resource in the collection.
*
* @param filter Children filter
* @return new SlingQuery object transformed by this operation
*/
public Q children(String filter) {
return function(new ChildrenFunction<>(provider), filter);
}
/**
* Get list of the children for each Resource in the collection.
*
* @param filter Children filter
* @return new SlingQuery object transformed by this operation
*/
public Q children(Predicate<T> filter) {
return function(new ChildrenFunction<>(provider), filter);
}
/**
* Get list of the children for each Resource in the collection.
*
* @param filter Children filter
* @return new SlingQuery object transformed by this operation
*/
public Q children(Iterable<T> filter) {
return function(new ChildrenFunction<>(provider), filter);
}
/**
* For each Resource in the collection, return the first element matching the selector testing the
* Resource itself and traversing up its ancestors.
*
* @param selector Ancestor filter
* @return new SlingQuery object transformed by this operation
*/
public Q closest(String selector) {
return closest(parse(selector));
}
/**
* For each Resource in the collection, return the first element matching the selector testing the
* Resource itself and traversing up its ancestors.
*
* @param iterable Ancestor filter
* @return new SlingQuery object transformed by this operation
*/
public Q closest(Iterable<T> iterable) {
return closest(new IterableContainsPredicate<>(iterable, provider));
}
/**
* For each Resource in the collection, return the first element matching the selector testing the
* Resource itself and traversing up its ancestors.
*
* @param predicate Ancestor filter
* @return new SlingQuery object transformed by this operation
*/
public Q closest(Predicate<T> predicate) {
return function(new ClosestFunction<>(predicate, provider));
}
/**
* Reduce Resource collection to the one Resource at the given 0-based index.
*
* @param index 0-based index
* @return new SlingQuery object transformed by this operation
*/
public Q eq(int index) {
return slice(index, index);
}
/**
* Filter Resource collection using given selector.
*
* @param selector Selector
* @return new SlingQuery object transformed by this operation
*/
public Q filter(String selector) {
return function(new IdentityFunction<T>(), selector);
}
/**
* Filter Resource collection using given predicate object.
*
* @param predicate Collection filter
* @return new SlingQuery object transformed by this operation
*/
public Q filter(Predicate<T> predicate) {
return function(new FilterFunction<>(predicate));
}
/**
* Filter Resource collection using given iterable.
*
* @param iterable Collection filter
* @return new SlingQuery object transformed by this operation
*/
public Q filter(Iterable<T> iterable) {
return function(new FilterFunction<>(new IterableContainsPredicate<>(iterable, provider)));
}
/**
* For each Resource in collection use depth-first search to return all its descendants. Please notice
* that invoking this method on a Resource being a root of a large subtree may and will cause performance
* problems.
*
* @return new SlingQuery object transformed by this operation
*/
public Q find() {
return function(new FindFunction<>(searchStrategy, provider, ""));
}
/**
* For each Resource in collection use breadth-first search to return all its descendants. Please notice
* that invoking this method on a Resource being a root of a large subtree may and will cause performance
* problems.
*
* @param selector descendants filter
* @return new SlingQuery object transformed by this operation
*/
public Q find(String selector) {
return function(new FindFunction<>(searchStrategy, provider, selector), selector);
}
/**
* For each Resource in collection use breadth-first search to return all its descendants. Please notice
* that invoking this method on a Resource being a root of a large subtree may and will cause performance
* problems.
*
* @param predicate descendants filter
* @return new SlingQuery object transformed by this operation
*/
public Q find(Predicate<T> predicate) {
return function(new FindFunction<>(searchStrategy, provider, ""), predicate);
}
/**
* For each Resource in collection use breadth-first search to return all its descendants. Please notice
* that invoking this method on a Resource being a root of a large subtree may and will cause performance
* problems.
*
* @param iterable descendants filter
* @return new SlingQuery object transformed by this operation
*/
public Q find(Iterable<T> iterable) {
return function(new DescendantFunction<>(new LazyList<>(iterable.iterator()), provider));
}
/**
* Filter Resource collection to the first element. Equivalent to {@code eq(0)} or {@code slice(0, 0)}.
*
* @return new SlingQuery object transformed by this operation
*/
public Q first() {
return eq(0);
}
/**
* Pick such Resources from the collection that have descendant matching the selector.
*
* @param selector Descendant selector
* @return new SlingQuery object transformed by this operation
*/
public Q has(String selector) {
return function(new HasFunction<>(selector, searchStrategy, provider));
}
/**
* Pick such Resources from the collection that have descendant matching the selector.
*
* @param predicate Descendant selector
* @return new SlingQuery object transformed by this operation
*/
public Q has(Predicate<T> predicate) {
return function(new HasFunction<>(predicate, searchStrategy, provider));
}
/**
* Pick such Resources from the collection that have descendant matching the selector.
*
* @param iterable Descendant selector
* @return new SlingQuery object transformed by this operation
*/
public Q has(Iterable<T> iterable) {
return function(new HasFunction<>(iterable, provider));
}
/**
* Filter Resource collection to the last element.
*
* @return new SlingQuery object transformed by this operation
*/
public Q last() {
return function(new LastFunction<T>());
}
/**
* Return the next sibling for each Resource in the collection.
*
* @return new SlingQuery object transformed by this operation
*/
public Q next() {
return function(new NextFunction<>(provider));
}
/**
* Return the next sibling for each Resource in the collection and filter it by a selector. If the next
* sibling doesn't match it, empty collection will be returned.
*
* @param selector Next sibling filter
* @return new SlingQuery object transformed by this operation
*/
public Q next(String selector) {
return function(new NextFunction<>(provider), selector);
}
/**
* Return the next sibling for each Resource in the collection and filter it by a selector. If the next
* sibling doesn't match it, empty collection will be returned.
*
* @param predicate Next sibling filter
* @return new SlingQuery object transformed by this operation
*/
public Q next(Predicate<T> predicate) {
return function(new NextFunction<>(provider), predicate);
}
/**
* Return the next sibling for each Resource in the collection and filter it by a selector. If the next
* sibling doesn't match it, empty collection will be returned.
*
* @param iterable Next sibling filter
* @return new SlingQuery object transformed by this operation
*/
public Q next(Iterable<T> iterable) {
return function(new NextFunction<>(provider), iterable);
}
/**
* Return all following siblings for each Resource in the collection.
*
* @return new SlingQuery object transformed by this operation
*/
public Q nextAll() {
return function(new NextFunction<>(new RejectingPredicate<>(), provider));
}
/**
* Return all following siblings for each Resource in the collection, filtering them by a selector.
*
* @param selector Following siblings filter
* @return new SlingQuery object transformed by this operation
*/
public Q nextAll(String selector) {
return function(new NextFunction<>(new RejectingPredicate<>(), provider), selector);
}
/**
* Return all following siblings for each Resource in the collection, filtering them by a selector.
*
* @param predicate Following siblings filter
* @return new SlingQuery object transformed by this operation
*/
public Q nextAll(Predicate<T> predicate) {
return function(new NextFunction<>(new RejectingPredicate<>(), provider), predicate);
}
/**
* Return all following siblings for each Resource in the collection, filtering them by a selector.
*
* @param iterable Following siblings filter
* @return new SlingQuery object transformed by this operation
*/
public Q nextAll(Iterable<T> iterable) {
return function(new NextFunction<>(new RejectingPredicate<>(), provider), iterable);
}
/**
* Return all following siblings for each Resource in the collection up to, but not including, Resource
* matched by a selector.
*
* @param until Selector marking when the operation should stop
* @return new SlingQuery object transformed by this operation
*/
public Q nextUntil(String until) {
return function(new NextFunction<>(parse(until), provider));
}
/**
* Return all following siblings for each Resource in the collection up to, but not including, Resource
* matched by a selector.
*
* @param predicate Selector marking when the operation should stop
* @return new SlingQuery object transformed by this operation
*/
public Q nextUntil(Predicate<T> predicate) {
return function(new NextFunction<>(predicate, provider));
}
/**
* Return all following siblings for each Resource in the collection up to, but not including, Resource
* matched by a selector.
*
* @param iterable Selector marking when the operation should stop
* @return new SlingQuery object transformed by this operation
*/
public Q nextUntil(Iterable<T> iterable) {
return nextUntil(new IterableContainsPredicate<>(iterable, provider));
}
/**
* Remove elements from the collection.
*
* @param selector Selector used to remove Resources
* @return new SlingQuery object transformed by this operation
*/
public Q not(String selector) {
return function(new NotFunction<>(parse(selector)));
}
/**
* Remove elements from the collection.
*
* @param predicate Selector used to remove Resources
* @return new SlingQuery object transformed by this operation
*/
public Q not(Predicate<T> predicate) {
return function(new FilterFunction<>(new RejectingPredicate<>(predicate)));
}
/**
* Remove elements from the collection.
*
* @param iterable Selector used to remove Resources
* @return new SlingQuery object transformed by this operation
*/
public Q not(Iterable<T> iterable) {
return not(new IterableContainsPredicate<>(iterable, provider));
}
/**
* Replace each element in the collection with its parent.
*
* @return new SlingQuery object transformed by this operation
*/
public Q parent() {
return function(new ParentFunction<>(provider));
}
/**
* For each element in the collection find its all ancestor.
*
* @return new SlingQuery object transformed by this operation
*/
public Q parents() {
return function(new ParentsFunction<>(new RejectingPredicate<>(), provider));
}
/**
* For each element in the collection find its all ancestor, filtered by a selector.
*
* @param selector Parents filter
* @return new SlingQuery object transformed by this operation
*/
public Q parents(String selector) {
return function(new ParentsFunction<>(new RejectingPredicate<>(), provider), selector);
}
/**
* For each element in the collection find its all ancestor, filtered by a selector.
*
* @param predicate Parents filter
* @return new SlingQuery object transformed by this operation
*/
public Q parents(Predicate<T> predicate) {
return function(new ParentsFunction<>(new RejectingPredicate<>(), provider), predicate);
}
/**
* For each element in the collection find its all ancestor, filtered by a selector.
*
* @param iterable Parents filter
* @return new SlingQuery object transformed by this operation
*/
public Q parents(Iterable<T> iterable) {
return function(new ParentsFunction<>(new RejectingPredicate<>(), provider), iterable);
}
/**
* For each element in the collection find all of its ancestors until the predicate is met.
*
* @param until Selector marking when the operation should stop
* @return new SlingQuery object transformed by this operation
*/
public Q parentsUntil(String until) {
return function(new ParentsFunction<>(parse(until), provider));
}
/**
* For each element in the collection find all of its ancestors until the predicate is met.
*
* @param predicate Selector marking when the operation should stop
* @return new SlingQuery object transformed by this operation
*/
public Q parentsUntil(Predicate<T> predicate) {
return function(new ParentsFunction<>(predicate, provider));
}
/**
* For each element in the collection find all of its ancestors until the predicate is met.
*
* @param iterable Selector marking when the operation should stop
* @return new SlingQuery object transformed by this operation
*/
public Q parentsUntil(Iterable<T> iterable) {
return parentsUntil(new IterableContainsPredicate<>(iterable, provider));
}
/**
* Return the previous sibling for each Resource in the collection.
*
* @return new SlingQuery object transformed by this operation
*/
public Q prev() {
return function(new PrevFunction<>(provider));
}
/**
* Return the previous sibling for each Resource in the collection and filter it by a selector. If the
* previous sibling doesn't match it, empty collection will be returned.
*
* @param selector Previous sibling filter
* @return new SlingQuery object transformed by this operation
*/
public Q prev(String selector) {
return function(new PrevFunction<>(null, provider), selector);
}
/**
* Return the previous sibling for each Resource in the collection and filter it by a selector. If the
* previous sibling doesn't match it, empty collection will be returned.
*
* @param predicate Previous sibling filter
* @return new SlingQuery object transformed by this operation
*/
public Q prev(Predicate<T> predicate) {
return function(new PrevFunction<>(null, provider), predicate);
}
/**
* Return the previous sibling for each Resource in the collection and filter it by a selector. If the
* previous sibling doesn't match it, empty collection will be returned.
*
* @param iterable Previous sibling filter
* @return new SlingQuery object transformed by this operation
*/
public Q prev(Iterable<T> iterable) {
return function(new PrevFunction<>(null, provider), iterable);
}
/**
* Return all previous siblings for each Resource in the collection.
*
* @return new SlingQuery object transformed by this operation
*/
public Q prevAll() {
return function(new PrevFunction<>(new RejectingPredicate<>(), provider));
}
/**
* Return all previous siblings for each Resource in the collection, filtering them by a selector.
*
* @param selector Previous siblings filter
* @return new SlingQuery object transformed by this operation
*/
public Q prevAll(String selector) {
return function(new PrevFunction<>(new RejectingPredicate<>(), provider), selector);
}
/**
* Return all previous siblings for each Resource in the collection, filtering them by a selector.
*
* @param predicate Previous siblings filter
* @return new SlingQuery object transformed by this operation
*/
public Q prevAll(Predicate<T> predicate) {
return function(new PrevFunction<>(new RejectingPredicate<>(), provider), predicate);
}
/**
* Return all previous siblings for each Resource in the collection, filtering them by a selector.
*
* @param iterable Previous siblings filter
* @return new SlingQuery object transformed by this operation
*/
public Q prevAll(Iterable<T> iterable) {
return function(new PrevFunction<>(new RejectingPredicate<>(), provider), iterable);
}
/**
* Return all previous siblings for each Resource in the collection up to, but not including, Resource
* matched by a selector.
*
* @param until Selector marking when the operation should stop
* @return new SlingQuery object transformed by this operation
*/
public Q prevUntil(String until) {
return function(new PrevFunction<>(parse(until), provider));
}
/**
* Return all previous siblings for each Resource in the collection up to, but not including, Resource
* matched by a selector.
*
* @param predicate Selector marking when the operation should stop
* @return new SlingQuery object transformed by this operation
*/
public Q prevUntil(Predicate<T> predicate) {
return function(new PrevFunction<>(predicate, provider));
}
/**
* Return all previous siblings for each Resource in the collection up to, but not including, Resource
* matched by a selector.
*
* @param iterable Selector marking when the operation should stop
* @return new SlingQuery object transformed by this operation
*/
public Q prevUntil(Iterable<T> iterable) {
return prevUntil(new IterableContainsPredicate<>(iterable, provider));
}
/**
* Set new search strategy, which will be used in {@link AbstractQuery#find()} and
* {@link AbstractQuery#has(String)} functions.
*
* @param strategy Search strategy type
* @return new SlingQuery object transformed by this operation
*/
public Q searchStrategy(SearchStrategy strategy) {
return clone(this, strategy);
}
/**
* Return siblings for the given Ts.
*
* @return new SlingQuery object transformed by this operation
*/
public Q siblings() {
return siblings("");
}
/**
* Return siblings for the given Resources filtered by a selector.
*
* @param selector Siblings filter
* @return new SlingQuery object transformed by this operation
*/
public Q siblings(String selector) {
return function(new SiblingsFunction<>(provider), selector);
}
/**
* Return siblings for the given Resources filtered by a selector.
*
* @param predicate Siblings filter
* @return new SlingQuery object transformed by this operation
*/
public Q siblings(Predicate<T> predicate) {
return function(new SiblingsFunction<>(provider), predicate);
}
/**
* Return siblings for the given Resources filtered by a selector.
*
* @param iterable Siblings filter
* @return new SlingQuery object transformed by this operation
*/
public Q siblings(Iterable<T> iterable) {
return function(new SiblingsFunction<>(provider), iterable);
}
/**
* Filter out first {@code from} Resources from the collection.
*
* @param from How many Resources to cut out
* @return new SlingQuery object transformed by this operation
*/
public Q slice(int from) {
if (from < 0) {
throw new IndexOutOfBoundsException();
}
return function(new SliceFunction<T>(from));
}
/**
* Reduce the collection to a subcollection specified by a given range. Both from and to are inclusive,
* 0-based indices.
*
* @param from Low endpoint (inclusive) of the subcollection
* @param to High endpoint (inclusive) of the subcollection
* @return new SlingQuery object transformed by this operation
*/
public Q slice(int from, int to) {
if (from < 0) {
throw new IndexOutOfBoundsException();
}
if (from > to) {
throw new IllegalArgumentException();
}
return function(new SliceFunction<T>(from, to));
}
/**
* Filter out duplicated resources in a stream, this iterator is stateful during
* the iteration process.
*
* @return new SlingQuery object transformed by this operation
*/
public Q unique() {
return function(new UniqueFunction<>());
}
private Q function(Function<?, ?> function, Iterable<T> iterable) {
Q newQuery = clone(this, this.searchStrategy);
newQuery.functions.add(function);
newQuery.functions.add(new FilterFunction<>(new IterableContainsPredicate<>(iterable, provider)));
return newQuery;
}
private Q function(Function<?, ?> function, Predicate<T> predicate) {
Q newQuery = clone(this, this.searchStrategy);
newQuery.functions.add(function);
newQuery.functions.add(new FilterFunction<>(predicate));
return newQuery;
}
private Q function(Function<?, ?> function, String selector) {
Q newQuery = clone(this, this.searchStrategy);
newQuery.functions.add(function);
newQuery.functions.add(new SelectorFunction<>(selector, provider, searchStrategy));
return newQuery;
}
private Q function(Function<?, ?> function) {
Q newQuery = clone(this, this.searchStrategy);
newQuery.functions.add(function);
return newQuery;
}
private SelectorFunction<T> parse(String selector) {
return new SelectorFunction<>(selector, provider, searchStrategy);
}
protected abstract Q clone(AbstractQuery<T, Q> original, SearchStrategy strategy);
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("$(");
Iterator<T> iterator = this.iterator();
while (iterator.hasNext()) {
builder.append('[');
builder.append(iterator.next());
builder.append(']');
if (iterator.hasNext()) {
builder.append(", ");
}
}
builder.append(")");
return builder.toString();
}
}