com.almworks.jira.structure.api.row.RowRetriever Maven / Gradle / Ivy
Show all versions of structure-api Show documentation
package com.almworks.jira.structure.api.row;
import com.almworks.integers.*;
import com.almworks.jira.structure.api.forest.item.ItemForest;
import com.almworks.jira.structure.api.item.CoreIdentities;
import com.almworks.jira.structure.api.item.ItemIdentity;
import com.almworks.jira.structure.api.util.Ref;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.*;
import static com.almworks.jira.structure.api.row.ItemAccessMode.ITEM_NOT_NEEDED;
import static com.almworks.jira.structure.api.row.ItemAccessMode.NORMAL_ACCESS;
/**
* {@code RowRetriever} is an abstraction of an object that can provide an instance of {@link StructureRow} by its numeric ID.
* In addition, it has bulk access methods that may provide multiple {@code StructureRow} instances more efficiently.
*
* There's a number of bulk scanning methods that are provided for convenience. The following naming convention is used:
*
* - {@link #scanRows} – basic scanning method with a {@link Predicate} callback, which allows breaking the iteration cycle.
* - {@link #scanAllRows} – "all" means that all passed rows will be scanned, and the callback is a {@link Consumer}.
* - {@link #scanAllExistingRows} – "existing" means that {@link #IGNORE_MISSING_ROWS} will be passed as the missing row collector,
* which guarantees that the method will not throw {@link MissingRowException}.
*
*
* @see RowManager
* @see ItemForest
*/
public interface RowRetriever {
/**
* Use this to indicate that missing rows should not result in throwing {@link MissingRowException}.
*
* @see #scanRows(LongIterable, boolean, LongCollector, Predicate)
*/
LongCollector IGNORE_MISSING_ROWS = LongCollector.DUMMY;
/**
* Retrieves information about a structure row by its ID.
*
* Note that it is a runtime error to request a non-existing row, because that should never happen in a
* correct code.
*
* @param rowId row ID
* @return row information
* @throws MissingRowException if the specified row ID does not exist
*/
@NotNull
default StructureRow getRow(long rowId) throws MissingRowException {
return getRow(rowId, NORMAL_ACCESS);
}
/**
* Retrieves {@code StructureRow} with additional information about how the calling code is going to use
* method {@link StructureRow#getItem}. The implementation may be optimized according to the {@code access} parameter.
*
* If row's item is invisible or does not exist, {@code StructureRow} is returned anyway, but {@link StructureRow#getItem}
* return {@code null}.
*
* @param rowId row ID
* @param access defines how item object is going to be accessed
* @return row instance
* @throws MissingRowException if the specified row ID does not exist
* @see ItemAccessMode
*/
@NotNull
StructureRow getRow(long rowId, @NotNull ItemAccessMode access) throws MissingRowException;
/**
* Loads multiple rows by their IDs and calls {@code iteratee} with a {@link StructureRow} for each row ID in the input.
* Use this method whenever you need to read a potentially large amount of rows and the order of retrieval is not important.
*
* For example, use {@code scanRows} to iterate through the whole forest to see if it has a generator row,
* or to process user input that has an array of row IDs.
*
* The order in which {@code iteratee} is called is not guaranteed to be the same as the iteration order of {@code rows}.
* Likewise, {@code missingCollector} may receive row IDs out of order.
*
* The implementation of {@code iteratee} must be reasonably fast and avoid taking locks or accessing long-running services.
* It's possible to call other {@link RowRetriever} methods inside {@code iteratee}.
*
* It's possible to pass {@link LongIterator} as {@link LongIterable} - the iterator() method is guaranteed to be called only once.
*
* Item access mode
*
* You can choose an appropriate value for the {@code} access parameter based on what you're going to do with rows in the {@code iteratee}.
* This allows the provider of {@code StructureRow} instances to optimize how {@link StructureRow#getItem} method works.
*
* Dealing with missing rows
*
* If a row ID in the input stream does not correspond to an existing row, the behavior depends on whether the {@code missingCollector}
* parameter is set. If it's {@code null}, then the method will immediately throw a {@link MissingRowException}. If it's not null, the
* collector will be called with the missing row ID.
*
* To simply ignore the missing rows, you can use {@link #IGNORE_MISSING_ROWS} collector or use one of the {@link #scanAllExistingRows} methods.
*
*
Note that if the current user does not have access to a row, but the row itself exists, it's not considered missing.
*
* Super-root
*
* Note that normally a row retriever does not handle {@link SuperRootRow} in any special way, so it will be a case of a missing row. Some
* implementations may, however, support accessing the super-root.
*
* Implementation notes
*
* The default implementation is based on calling {@link #getRow}. Specific implementations should optimize this method for retrieving
* multiple rows at once.
*
* Do not override other bulk scanning methods, unless that allows for additional optimization.
*
* @param rows row IDs of the rows to scan
* @param sorted if true, then the caller guarantees the row IDs to be sorted - this can be used for optimization
* @param access indicates how the {@code getItem()} method of the provided {@code StructureRow} is going to be used and allows to skip the access checking
* @param missingCollector a collector to receive non-existing row IDs, or {@code null}
* @param iteratee predicate to call for each resolved row; if it returns {@code false}, the iteration stops and the method returns
* @throws MissingRowException if a row was not found and {@code missingCollector} is null
* @see RowManager
* @see ItemAccessMode
*/
default void scanRows(@Nullable LongIterable rows, boolean sorted, @NotNull ItemAccessMode access, @Nullable LongCollector missingCollector,
@NotNull Predicate iteratee) throws MissingRowException
{
if (rows != null) {
for (LongIterator ii : rows) {
try {
StructureRow row = getRow(ii.value(), access);
if (!iteratee.test(row)) break;
} catch (MissingRowException e) {
if (missingCollector == null) {
throw e;
} else {
missingCollector.add(ii.value());
}
}
}
}
}
/**
* A convenience method that calls {@link #scanRows(LongIterable, boolean, ItemAccessMode, LongCollector, Predicate)} with
* the normal access mode.
*
* @param rows row IDs of the rows to scan
* @param sorted if true, then the caller guarantees the row IDs to be sorted - this can be used for optimization
* @param missingCollector a collector to receive non-existing row IDs, or {@code null}
* @param iteratee predicate to call for each resolved row; if it returns {@code false}, the iteration stops and the method returns
* @throws MissingRowException if a row was not found and {@code missingCollector} is null
*/
default void scanRows(@Nullable LongIterable rows, boolean sorted, @Nullable LongCollector missingCollector,
@NotNull Predicate iteratee) throws MissingRowException
{
scanRows(rows, sorted, NORMAL_ACCESS, missingCollector, iteratee);
}
/**
* A convenience method that calls {@link #scanRows(LongIterable, boolean, ItemAccessMode, LongCollector, Predicate)} with
* the normal access mode, and when the rows stream is not guaranteed to be sorted.
*
* @param rows row IDs of the rows to scan
* @param missingCollector a collector to receive non-existing row IDs, or {@code null}
* @param iteratee predicate to call for each resolved row; if it returns {@code false}, the iteration stops and the method returns
* @throws MissingRowException if a row was not found and {@code missingCollector} is null
*/
default void scanRows(@Nullable LongIterable rows, @Nullable LongCollector missingCollector, @NotNull Predicate iteratee)
throws MissingRowException
{
scanRows(rows, false, NORMAL_ACCESS, missingCollector, iteratee);
}
/**
* A convenience method that calls {@link #scanRows(LongIterable, boolean, ItemAccessMode, LongCollector, Predicate)} with
* the normal access mode, when the rows stream is not guaranteed to be sorted, and without a missing row collector. This method
* will throw {@link MissingRowException} if a non-existent row ID is encountered.
*
* @param rows row IDs of the rows to scan
* @param iteratee predicate to call for each resolved row; if it returns {@code false}, the iteration stops and the method returns
* @throws MissingRowException if a row was not found
*/
default void scanRows(@Nullable LongIterable rows, @NotNull Predicate iteratee) throws MissingRowException {
scanRows(rows, false, NORMAL_ACCESS, null, iteratee);
}
/**
* A convenience variation of {@link #scanRows} that always goes through all of the row IDs.
* Unlike {@code scanRows()} methods, it accepts a {@link Consumer} callback.
*
* @param rows row IDs of the rows to scan
* @param sorted if true, then the caller guarantees the row IDs to be sorted - this can be used for optimization
* @param access indicates how the {@code getItem()} method of the provided {@code StructureRow} is going to be used and allows to skip the access checking
* @param missingCollector a collector to receive non-existing row IDs, or {@code null}
* @param consumer a callback to call for every row
* @throws MissingRowException if a row was not found and {@code missingCollector} is null
*/
default void scanAllRows(@Nullable LongIterable rows, boolean sorted, @NotNull ItemAccessMode access, @Nullable LongCollector missingCollector,
@NotNull Consumer consumer) throws MissingRowException
{
scanRows(rows, sorted, access, missingCollector, row -> {
consumer.accept(row);
return true;
});
}
/**
* A convenience variation of {@link #scanRows} that always goes through all of the row IDs with a normal access mode.
*
* @param rows row IDs of the rows to scan
* @param sorted if true, then the caller guarantees the row IDs to be sorted - this can be used for optimization
* @param missingCollector a collector to receive non-existing row IDs, or {@code null}
* @param consumer a callback to call for every row
* @throws MissingRowException if a row was not found and {@code missingCollector} is null
*/
default void scanAllRows(@Nullable LongIterable rows, boolean sorted, @Nullable LongCollector missingCollector, @NotNull Consumer consumer)
throws MissingRowException
{
scanAllRows(rows, sorted, NORMAL_ACCESS, missingCollector, consumer);
}
/**
* A convenience variation of {@link #scanRows} that always goes through all of the row IDs, uses normal access mode, and assumes that
* row ID stream may be not sorted.
*
* @param rows row IDs of the rows to scan
* @param missingCollector a collector to receive non-existing row IDs, or {@code null}
* @param consumer a callback to call for every row
* @throws MissingRowException if a row was not found and {@code missingCollector} is null
*/
default void scanAllRows(@Nullable LongIterable rows, @Nullable LongCollector missingCollector, @NotNull Consumer consumer)
throws MissingRowException
{
scanAllRows(rows, false, missingCollector, consumer);
}
/**
* A convenience variation of {@link #scanRows} that always goes through all of the row IDs, uses normal access mode, assumes that
* row ID stream may be not sorted, and does not provide a missing collector. This method will throw {@link MissingRowException}
* if a non-existent row ID is encountered.
*
* @param rows row IDs of the rows to scan
* @param consumer a callback to call for every row
* @throws MissingRowException if a row was not found
*/
default void scanAllRows(@Nullable LongIterable rows, @NotNull Consumer consumer) throws MissingRowException {
scanAllRows(rows, null, consumer);
}
/**
* A convenience variation of {@link #scanRows} that always goes through all of the row IDs and ignores any missing rows.
*
* @param rows row IDs of the rows to scan
* @param sorted if true, then the caller guarantees the row IDs to be sorted - this can be used for optimization
* @param access indicates how the {@code getItem()} method of the provided {@code StructureRow} is going to be used and allows to skip the access checking
* @param consumer a callback to call for every row
*/
default void scanAllExistingRows(@Nullable LongIterable rows, boolean sorted, @NotNull ItemAccessMode access, @NotNull Consumer consumer) {
scanAllRows(rows, sorted, access, IGNORE_MISSING_ROWS, consumer);
}
/**
* A convenience variation of {@link #scanRows} that always goes through all of the row IDs with a normal access mode and ignores any missing rows.
*
* @param rows row IDs of the rows to scan
* @param sorted if true, then the caller guarantees the row IDs to be sorted - this can be used for optimization
* @param consumer a callback to call for every row
*/
default void scanAllExistingRows(@Nullable LongIterable rows, boolean sorted, @NotNull Consumer consumer) {
scanAllExistingRows(rows, sorted, NORMAL_ACCESS, consumer);
}
/**
* A convenience variation of {@link #scanRows} that always goes through all of the row IDs with a normal access mode, ignores any missing rows,
* and assumes that the row ID stream may be not sorted.
*
* @param rows row IDs of the rows to scan
* @param consumer a callback to call for every row
*/
default void scanAllExistingRows(@Nullable LongIterable rows, @NotNull Consumer consumer) {
scanAllExistingRows(rows, false, consumer);
}
/**
* Performs a reduction over a collection of rows, identified by their IDs.
*
* The reducer function must be associative and commutative, because it may be called in a random order, not corresponding to the
* iteration order of {@code rows}.
*
* @param rows row IDs of the rows to scan
* @param startingValue the initial value to start with
* @param accumulator a function that receives the next row and the accumulated value, and then produces the next accumulated value
* @param result type
* @return the last accumulated value, or {@code startingValue} if there was no iteration
* @throws MissingRowException if a row was not found
*/
@Nullable
default T reduceOverRows(@Nullable LongIterable rows, @Nullable T startingValue, @NotNull BiFunction accumulator)
throws MissingRowException
{
if (rows == null) {
return startingValue;
}
Ref r = new Ref<>(startingValue);
scanRows(rows, false, NORMAL_ACCESS, null, row -> {
r.value = accumulator.apply(row, r.value);
return true;
});
return r.value;
}
/**
* Convenience method that can be used to collect all issue IDs from given row IDs. Ignores missing rows.
*
* @param rows a collection of rows
* @param sorted if true, then rows is sorted - can be used by the optimized code
* @param issuesCollector a collection to receive issue IDs
* @param type of the collector
* @return the collector
*/
@NotNull
default C collectIssueIds(@Nullable LongIterable rows, boolean sorted, @NotNull C issuesCollector) {
if (rows == null) return issuesCollector;
scanRows(rows, sorted, ITEM_NOT_NEEDED, IGNORE_MISSING_ROWS, row -> {
ItemIdentity itemId = row.getItemId();
if (CoreIdentities.isIssue(itemId)) {
issuesCollector.add(itemId.getLongId());
}
return true;
});
return issuesCollector;
}
/**
* Convenience method that can be used to collect all issue IDs from given row IDs. Ignores missing rows.
*
* @param rows a collection of rows
* @param issuesCollector a collection to receive issue IDs
* @param type of the collector
* @return the collector
*/
@NotNull
default C collectIssueIds(@Nullable LongIterable rows, @NotNull C issuesCollector) {
return collectIssueIds(rows, false, issuesCollector);
}
/**
* Convenience method that collects item IDs from row IDs. Ignores missing rows.
*
* The returned collection is owned by the caller and may be further modified.
*
* @param rows a collection of rows
* @return a collection of item IDs found in the input rows
*/
@NotNull
Set collectItemIds(@Nullable LongIterable rows);
}