All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.almworks.jira.structure.api.query.StructureQueryConstraint Maven / Gradle / Ivy

The newest version!
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:

*
    *
  1. condition name followed by a comma-separated list of arguments in parentheses: * {@code name ( arg1 , arg2 , ... , argN ) }, where N >= 0;
  2. *
  3. condition name followed by a single argument (the argument cannot contain spaces): * {@code name arg1}.
  4. *
* *

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 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 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); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy