com.almworks.jira.structure.api.query.StructureQueryConstraint Maven / Gradle / Ivy
Show all versions of structure-api Show documentation
package com.almworks.jira.structure.api.query;
import com.almworks.integers.*;
import com.almworks.jira.structure.api.forest.raw.Forest;
import com.atlassian.annotations.PublicApi;
import com.atlassian.annotations.PublicSpi;
import com.atlassian.jira.util.MessageSet;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* This is an extension point in the S-JQL, through which you can add custom basic constraints on forest rows to
* {@link StructureQuery}.
*
* The constraint is always executed in the context of some forest (e.g., when {@link StructureQuery#execute(Forest)}
* is called). It is given a sequence of indices into the forest, which it filters according to the desired criteria.
*
* Syntactically, this corresponds to one of the following constructs:
*
* - condition name followed by a comma-separated list of arguments in parentheses:
* {@code name ( arg1 , arg2 , ... , argN ) }, where N >= 0;
* - condition name followed by a single argument (the argument cannot contain spaces):
* {@code name arg1}.
*
*
* An instance of a query constraint is registered in the {@code atlassian-plugin.xml} file of your plugin using
* {@code structure-query-constraint} module type.
*
* Query constraint name must not coincide with one of the existing S-JQL keywords. However, it is possible to
* register two constraints with the same names. The conflict is resolved using the {@code order} attribute of the
* {@code structure-query-constraint} element in {@code atlassian-plugin.xml}: lower order wins -
* constraint with lower order always overrides constraint with higher order. In case of tie, the order is unspecified.
*
* @see S-JQL reference
* @see StructureQuery#execute(Forest)
* */
@PublicSpi
public interface StructureQueryConstraint {
/**
* Validates the list of arguments. This method is called when {@link StructureQuery#validate()} is called.
*
* Number of arguments, as well as semantic validity of arguments can be checked.
* If any of the tests fail, the results should be included in the resulting message set through
* {@link MessageSet#addErrorMessage(String)}. Warnings can be added as well, but note that both JIRA and Structure
* ignore them.
*
* See {@link StructureQueryConstraints#validateArgumentCount} for a standard method of verifying the number of
* arguments.
*
* @param arguments list of arguments passed to this constraint in some {@code StructureQuery}: not {@code null},
* its elements are not {@code null}
* @return validation results (as error messages)
*
* @see StructureQueryConstraints#validateArgumentCount(List, int, int, MessageSet)
* */
MessageSet validate(@NotNull List*@NotNull*/ String> arguments);
/**
* Filters the specified indices in the forest being searched according to some criteria.
*
* Use {@link QueryContext} to access the forest being searched.
*
* The incoming indices are sorted in the increasing order. The implementation must return indices also in increasing order,
* without repetition. Moreover, it must return only those indices that were returned from {@code indices} iterator.
* An example where the latter is violated is a "pass-all" constraint that always returns all indices in the range
* {@code [0..context.size())} without consulting {@code indices}. To implement this correctly, such constraint should
* read indices from {@code indices} and return all of them.
*
* Note that this method may be called without a prior call to {@link #validate}. The implementation's behaviour is
* undefined in this case. However, such call may only originate from inside Structure plugin, since all methods
* in {@code StructureQuery} that execute the query validate it first, and don't run it if reports any errors.
*
* @param indices increasing indices into the forest being searched; the implementation should test them and return
* those of them that pass the constraint
* @param arguments list of arguments passed to this constraint in some {@code StructureQuery}: not {@code null},
* its elements are not {@code null}
* @param context contains forest being searched and some row resolution methods
* @return a sequence of matching indices in the increasing order
* */
Sequence filter(@NotNull IntIterator indices, @NotNull List*@NotNull*/ String> arguments, @NotNull QueryContext context);
/**
* Allows to implement a sequence of numbers in a
* generator-like fashion.
* Namely, object of this class represents a function that, when called, may produce 0, 1 or more results, or can
* indicate that it cannot produce any more results. {@code Acceptor} is the place where the results are placed.
* Callers of this function will construct an object of a class implementing {@code Acceptor} and then call this function.
* They might call it until it produces at least one value.
* Then they might use the produced values immediately and either stop the computation, or call the function again.
* It is recommended to produce values lazily. Ideally, the code would look like this:
*
* class MySequence implements Sequence {
* private IntIterator myInput;
*
* @Override public boolean advance(Acceptor acceptor) {
* if (!myInput.hasNext()) return false;
* if (matches(myInput.nextValue()) {
* acceptor.accept(myInput.value());
* }
* return true;
* }
* }
*
* For convenience, we provide default implementations: {@link SimpleFilter} does the same as the code sample above,
* so that you only have to define the {@code matches()} method. {@link BulkFilter} accounts for cases where you
* need to process indices in bulk, such as compute attributes via {@code StructureAttributeService}.
*
* */
@PublicSpi
interface Sequence {
/**
* Attempts to advance in this sequence by 0, 1 or more positions
* (i.e., attempts to find the next 0, 1 or more matching indices).
* Each position should be passed to {@code acceptor} through calls to {@link Acceptor#accept(int) acceptor.accept(index)}
* or {@link Acceptor#accept(IntIterable) acceptor.accept(indices)}.
* Return value indicates whether this sequence is capable of producing values: {@code false} indicates the end of this sequence.
* @param acceptor accepts members of this sequence
* @return false iff this sequence does not have any more values
* */
boolean advance(Acceptor acceptor);
/**
* A sequence that has no values.
* */
Sequence EMPTY = new EmptySequence();
}
/**
* Represents the consumer of {@link Sequence} values - i.e., the consumer of matching indices.
* */
@PublicApi
interface Acceptor {
/**
* Report one matching index.
* @param index matching index
* */
void accept(int index);
/**
* Report several matching indices.
* @param indices matching indices (increasing)
* */
void accept(IntIterable indices);
/**
* Optimization: if a {@code Sequence} knows that it is going to accept {@code n} indices, it might communicate this knowledge
* to this {@code Acceptor} through this method, so that the latter can preallocate internal buffers.
* Important: if this method is called, it must be followed by calls to {@link #accept(int)}
* or {@link #accept(IntIterable)} that provide at least {@code n} indices. Do not call this method if you are unsure
* that you will provide that many values.
* */
void willAccept(int n);
}
/**
* A default implementation of an empty sequence.
* */
class EmptySequence implements Sequence {
@Override
public boolean advance(Acceptor acceptor) {
return false;
}
}
/**
* A base implementation for a simple constraint that looks at one row at a time.
* */
abstract class SimpleFilter implements Sequence {
private final IntIterator myInput;
/**
* @param input the incoming indices to be filtered (from {@link StructureQueryConstraint#filter(IntIterator, List, QueryContext)}
* */
protected SimpleFilter(IntIterator input) {
myInput = input;
}
/**
* Returns {@code true} if the row at the specified index matches the criteria.
* @param index index into the forest being searched
* */
public abstract boolean matches(int index);
@Override
public boolean advance(Acceptor acceptor) {
if (!myInput.hasNext()) return false;
if (matches(myInput.nextValue())) {
acceptor.accept(myInput.value());
}
return true;
}
}
/**
* A base implementation for a constraint that looks at a bunch of rows at a time.
*
* This class represents a "middle ground" between looking at one row at a time and at looking at all rows being
* filtered at once. This works best when the caller is not interested in all the results: it can stop at any moment,
* and the constraint will not process the rest of the rows.
* */
abstract class BulkFilter implements Sequence {
private final IntIterator myInput;
private final IntArray myCurrentInput;
protected final int myBulkSize;
/**
* @param input the incoming indices to be filtered (from {@link StructureQueryConstraint#filter(IntIterator, List, QueryContext)}
* @param bulkSize the size of the bulk that is processed
* */
protected BulkFilter(IntIterator input, int bulkSize) {
myInput = input;
myCurrentInput = new IntArray(bulkSize);
myBulkSize = bulkSize;
}
@Override
public boolean advance(Acceptor acceptor) {
myCurrentInput.clear();
myCurrentInput.addAllNotMore(myInput, myBulkSize);
if (myCurrentInput.isEmpty()) return false;
bulkFilter(myCurrentInput, acceptor);
return true;
}
/**
* Processes a bunch of indices and passes the matching ones to the acceptor. Indices are sorted in the increasing order,
* and the indices are all greater than the indices from the previous calls to this method.
* @param input sorted indices to filter
* @param acceptor accepts the results
* */
protected abstract void bulkFilter(IntList input, Acceptor acceptor);
}
}