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

io.deephaven.engine.table.impl.select.analyzers.SelectAndViewAnalyzer Maven / Gradle / Ivy

There is a newer version: 0.37.1
Show newest version
/**
 * Copyright (c) 2016-2022 Deephaven Data Labs and Patent Pending
 */
package io.deephaven.engine.table.impl.select.analyzers;

import io.deephaven.base.Pair;
import io.deephaven.base.log.LogOutputAppendable;
import io.deephaven.datastructures.util.CollectionUtil;
import io.deephaven.engine.liveness.LivenessNode;
import io.deephaven.engine.rowset.RowSet;
import io.deephaven.engine.rowset.RowSetFactory;
import io.deephaven.engine.rowset.TrackingRowSet;
import io.deephaven.engine.table.*;
import io.deephaven.engine.table.impl.MatchPair;
import io.deephaven.engine.table.impl.QueryTable;
import io.deephaven.engine.table.impl.select.FormulaColumn;
import io.deephaven.engine.table.impl.select.SelectColumn;
import io.deephaven.engine.table.impl.select.SourceColumn;
import io.deephaven.engine.table.impl.select.SwitchColumn;
import io.deephaven.engine.table.impl.sources.InMemoryColumnSource;
import io.deephaven.engine.table.impl.sources.SingleValueColumnSource;
import io.deephaven.engine.table.impl.sources.WritableRedirectedColumnSource;
import io.deephaven.engine.table.impl.util.InverseWrappedRowSetRowRedirection;
import io.deephaven.engine.table.impl.util.JobScheduler;
import io.deephaven.engine.table.impl.util.RowRedirection;
import io.deephaven.engine.table.impl.util.WritableRowRedirection;
import io.deephaven.engine.updategraph.UpdateGraph;
import io.deephaven.io.log.impl.LogOutputStringImpl;
import io.deephaven.util.SafeCloseable;
import io.deephaven.util.SafeCloseablePair;
import io.deephaven.vector.Vector;
import org.jetbrains.annotations.Nullable;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.stream.Stream;

public abstract class SelectAndViewAnalyzer implements LogOutputAppendable {
    private static final Consumer> NOOP = ignore -> {
    };

    public enum Mode {
        VIEW_LAZY, VIEW_EAGER, SELECT_STATIC, SELECT_REFRESHING, SELECT_REDIRECTED_REFRESHING, SELECT_REDIRECTED_STATIC
    }

    public static void initializeSelectColumns(
            final Map> parentColumnMap,
            final SelectColumn[] selectColumns) {
        final Map> targetColumnMap = new HashMap<>(parentColumnMap);
        for (SelectColumn column : selectColumns) {
            column.initDef(targetColumnMap);
            final ColumnDefinition columnDefinition =
                    ColumnDefinition.fromGenericType(column.getName(), column.getReturnedType());
            targetColumnMap.put(column.getName(), columnDefinition);
        }
    }

    public static SelectAndViewAnalyzerWrapper create(
            QueryTable sourceTable, Mode mode, Map> columnSources,
            TrackingRowSet rowSet, ModifiedColumnSet parentMcs, boolean publishTheseSources, boolean useShiftedColumns,
            SelectColumn... selectColumns) {
        return create(sourceTable, mode, columnSources, rowSet, parentMcs, publishTheseSources, useShiftedColumns,
                true, selectColumns);
    }

    public static SelectAndViewAnalyzerWrapper create(
            final QueryTable sourceTable,
            final Mode mode,
            final Map> columnSources,
            TrackingRowSet rowSet,
            final ModifiedColumnSet parentMcs,
            final boolean publishTheseSources,
            boolean useShiftedColumns,
            final boolean allowInternalFlatten,
            final SelectColumn... selectColumns) {
        final UpdateGraph updateGraph = sourceTable.getUpdateGraph();
        SelectAndViewAnalyzer analyzer = createBaseLayer(columnSources, publishTheseSources);
        final Map> columnDefinitions = new LinkedHashMap<>();
        final RowRedirection rowRedirection;
        if (mode == Mode.SELECT_REDIRECTED_STATIC) {
            rowRedirection = new InverseWrappedRowSetRowRedirection(rowSet);
        } else if (mode == Mode.SELECT_REDIRECTED_REFRESHING && rowSet.size() < Integer.MAX_VALUE) {
            final WritableRowRedirection writableRowRedirection =
                    WritableRowRedirection.FACTORY.createRowRedirection(rowSet.intSize());
            analyzer = analyzer.createRedirectionLayer(rowSet, writableRowRedirection);
            rowRedirection = writableRowRedirection;
        } else {
            rowRedirection = null;
        }

        final TrackingRowSet originalRowSet = rowSet;
        boolean flatResult = rowSet.isFlat();
        // if we preserve a column, we set this to false
        boolean flattenedResult = !flatResult
                && allowInternalFlatten
                && (columnSources.isEmpty() || !publishTheseSources)
                && mode == Mode.SELECT_STATIC;
        int numberOfInternallyFlattenedColumns = 0;

        List processedCols = new LinkedList<>();
        List remainingCols = null;
        FormulaColumn shiftColumn = null;
        boolean shiftColumnHasPositiveOffset = false;

        final HashSet resultColumns = new HashSet<>();
        final HashMap> resultAlias = new HashMap<>();
        for (final SelectColumn sc : selectColumns) {
            if (remainingCols != null) {
                remainingCols.add(sc);
                continue;
            }

            analyzer.updateColumnDefinitionsFromTopLayer(columnDefinitions);
            sc.initDef(columnDefinitions);
            sc.initInputs(rowSet, analyzer.getAllColumnSources());

            // When flattening the result, intermediate columns generate results in position space. When we discover
            // that a select column depends on an intermediate result, then we must flatten all parent columns so
            // that all dependent columns are in the same result-key space.
            if (!flatResult && flattenedResult && Stream.concat(sc.getColumns().stream(), sc.getColumnArrays().stream())
                    .anyMatch(resultColumns::contains)) {
                analyzer = analyzer.createStaticFlattenLayer(rowSet);
                rowSet = RowSetFactory.flat(rowSet.size()).toTracking();
                flatResult = true;

                // we must re-initialize the column inputs as they may have changed post-flatten
                sc.initInputs(rowSet, analyzer.getAllColumnSources());
            }

            resultColumns.add(sc.getName());
            // this shadows any known alias
            resultAlias.remove(sc.getName());

            final Stream allDependencies =
                    Stream.concat(sc.getColumns().stream(), sc.getColumnArrays().stream());
            final String[] distinctDeps = allDependencies.distinct().toArray(String[]::new);
            final ModifiedColumnSet mcsBuilder = new ModifiedColumnSet(parentMcs);

            if (useShiftedColumns && hasConstantArrayAccess(sc)) {
                remainingCols = new LinkedList<>();
                shiftColumn = sc instanceof FormulaColumn
                        ? (FormulaColumn) sc
                        : (FormulaColumn) ((SwitchColumn) sc).getRealColumn();
                shiftColumnHasPositiveOffset = hasPositiveOffsetConstantArrayAccess(sc);
                continue;
            }

            // shifted columns appear to not be safe for refresh, so we do not validate them until they are rewritten
            // using the intermediary shifted column
            if (sourceTable.isRefreshing()) {
                sc.validateSafeForRefresh(sourceTable);
            }

            processedCols.add(sc);

            if (hasConstantValue(sc)) {
                final WritableColumnSource constViewSource =
                        SingleValueColumnSource.getSingleValueColumnSource(sc.getReturnedType());
                analyzer = analyzer.createLayerForConstantView(
                        sc.getName(), sc, constViewSource, distinctDeps, mcsBuilder, flattenedResult,
                        flatResult && flattenedResult);
                continue;
            }

            final SourceColumn realColumn;
            if (sc instanceof SourceColumn) {
                realColumn = (SourceColumn) sc;
            } else if ((sc instanceof SwitchColumn) && ((SwitchColumn) sc).getRealColumn() instanceof SourceColumn) {
                realColumn = (SourceColumn) ((SwitchColumn) sc).getRealColumn();
            } else {
                realColumn = null;
            }

            if (realColumn != null && shouldPreserve(sc)) {
                boolean sourceIsNew = resultColumns.contains(realColumn.getSourceName());
                if (!sourceIsNew) {
                    if (numberOfInternallyFlattenedColumns > 0) {
                        // we must preserve this column, but have already created an analyzer for the internally
                        // flattened
                        // column, therefore must start over without permitting internal flattening
                        return create(sourceTable, mode, columnSources, originalRowSet, parentMcs, publishTheseSources,
                                useShiftedColumns, false, selectColumns);
                    } else {
                        // we can not flatten future columns because we are preserving a column that may not be flat
                        flattenedResult = false;
                    }
                }

                analyzer = analyzer.createLayerForPreserve(
                        sc.getName(), sc, sc.getDataView(), distinctDeps, mcsBuilder);

                continue;
            }

            // look for an existing alias that can be preserved instead
            if (realColumn != null) {
                final ColumnSource alias = resultAlias.get(realColumn.getSourceName());
                if (alias != null) {
                    analyzer = analyzer.createLayerForPreserve(sc.getName(), sc, alias, distinctDeps, mcsBuilder);
                    continue;
                }
            }

            // if this is a source column, then results are eligible for aliasing
            final Consumer> maybeCreateAlias = realColumn == null ? NOOP
                    : cs -> resultAlias.put(realColumn.getSourceName(), cs);

            final long targetDestinationCapacity =
                    rowSet.isEmpty() ? 0 : (flattenedResult ? rowSet.size() : rowSet.lastRowKey() + 1);
            switch (mode) {
                case VIEW_LAZY: {
                    final ColumnSource viewCs = sc.getLazyView();
                    maybeCreateAlias.accept(viewCs);
                    analyzer = analyzer.createLayerForView(sc.getName(), sc, viewCs, distinctDeps, mcsBuilder);
                    break;
                }
                case VIEW_EAGER: {
                    final ColumnSource viewCs = sc.getDataView();
                    maybeCreateAlias.accept(viewCs);
                    analyzer = analyzer.createLayerForView(sc.getName(), sc, viewCs, distinctDeps, mcsBuilder);
                    break;
                }
                case SELECT_STATIC: {
                    // We need to call newDestInstance because only newDestInstance has the knowledge to endow our
                    // created array with the proper componentType (in the case of Vectors).
                    final WritableColumnSource scs =
                            flatResult || flattenedResult ? sc.newFlatDestInstance(targetDestinationCapacity)
                                    : sc.newDestInstance(targetDestinationCapacity);
                    maybeCreateAlias.accept(scs);
                    analyzer = analyzer.createLayerForSelect(updateGraph, rowSet, sc.getName(), sc, scs, null,
                            distinctDeps, mcsBuilder, false, flattenedResult, flatResult && flattenedResult);
                    if (flattenedResult) {
                        numberOfInternallyFlattenedColumns++;
                    }
                    break;
                }
                case SELECT_REDIRECTED_STATIC: {
                    final WritableColumnSource underlyingSource = sc.newDestInstance(rowSet.size());
                    final WritableColumnSource scs = WritableRedirectedColumnSource.maybeRedirect(
                            rowRedirection, underlyingSource, rowSet.size());
                    maybeCreateAlias.accept(scs);
                    analyzer = analyzer.createLayerForSelect(updateGraph, rowSet, sc.getName(), sc, scs,
                            underlyingSource, distinctDeps, mcsBuilder, true, false, false);
                    break;
                }
                case SELECT_REDIRECTED_REFRESHING:
                case SELECT_REFRESHING: {
                    // We need to call newDestInstance because only newDestInstance has the knowledge to endow our
                    // created array with the proper componentType (in the case of Vectors).
                    // TODO(kosak): use DeltaAwareColumnSource
                    WritableColumnSource scs = sc.newDestInstance(targetDestinationCapacity);
                    WritableColumnSource underlyingSource = null;
                    if (rowRedirection != null) {
                        underlyingSource = scs;
                        scs = WritableRedirectedColumnSource.maybeRedirect(
                                rowRedirection, underlyingSource, rowSet.intSize());
                    }
                    maybeCreateAlias.accept(scs);
                    analyzer = analyzer.createLayerForSelect(updateGraph, rowSet, sc.getName(), sc, scs,
                            underlyingSource, distinctDeps, mcsBuilder, rowRedirection != null, false, false);
                    break;
                }
                default:
                    throw new UnsupportedOperationException("Unsupported case " + mode);
            }
        }
        return new SelectAndViewAnalyzerWrapper(analyzer, shiftColumn, shiftColumnHasPositiveOffset, remainingCols,
                processedCols);
    }

    private static boolean hasConstantArrayAccess(final SelectColumn sc) {
        if (sc instanceof FormulaColumn) {
            return ((FormulaColumn) sc).hasConstantArrayAccess();
        } else if (sc instanceof SwitchColumn) {
            final SelectColumn realColumn = ((SwitchColumn) sc).getRealColumn();
            if (realColumn instanceof FormulaColumn) {
                return ((FormulaColumn) realColumn).hasConstantArrayAccess();
            }
        }
        return false;
    }

    private static boolean hasPositiveOffsetConstantArrayAccess(final SelectColumn sc) {
        Pair>> shifts = null;
        if (sc instanceof FormulaColumn) {
            shifts = ((FormulaColumn) sc).getFormulaShiftColPair();
        } else if (sc instanceof SwitchColumn) {
            final SelectColumn realColumn = ((SwitchColumn) sc).getRealColumn();
            if (realColumn instanceof FormulaColumn) {
                shifts = ((FormulaColumn) realColumn).getFormulaShiftColPair();
            }
        }
        if (shifts == null) {
            throw new IllegalStateException("Column " + sc.getName() + " does not have constant array access");
        }
        return shifts.getSecond().keySet().stream().max(Long::compareTo).orElse(0L) > 0;
    }


    private static boolean hasConstantValue(final SelectColumn sc) {
        if (sc instanceof FormulaColumn) {
            return ((FormulaColumn) sc).hasConstantValue();
        } else if (sc instanceof SwitchColumn) {
            final SelectColumn realColumn = ((SwitchColumn) sc).getRealColumn();
            if (realColumn instanceof FormulaColumn) {
                return ((FormulaColumn) realColumn).hasConstantValue();
            }
        }
        return false;
    }

    private static boolean shouldPreserve(final SelectColumn sc) {
        // we already know sc is a SourceColumn or switches to a SourceColumn
        final ColumnSource sccs = sc.getDataView();
        return sccs instanceof InMemoryColumnSource && ((InMemoryColumnSource) sccs).isInMemory()
                && !Vector.class.isAssignableFrom(sc.getReturnedType());
    }

    static final int BASE_LAYER_INDEX = 0;
    static final int REDIRECTION_LAYER_INDEX = 1;

    /**
     * The layerIndex is used to identify each layer uniquely within the bitsets for completion.
     */
    private final int layerIndex;

    public SelectAndViewAnalyzer(int layerIndex) {
        this.layerIndex = layerIndex;
    }

    int getLayerIndex() {
        return layerIndex;
    }

    /**
     * Set the bits in bitset that represent the base layer and optional redirection layer. No other jobs can be
     * executed until all of these bits are set.
     *
     * @param bitset the bitset to manipulate.
     */
    abstract void setBaseBits(BitSet bitset);

    /**
     * Set the bits in bitset that represent all the new columns. This is used to identify when the select or update
     * operation is complete.
     *
     * @param bitset the bitset to manipulate.
     */
    public void setAllNewColumns(BitSet bitset) {
        getInner().setAllNewColumns(bitset);
        bitset.set(getLayerIndex());
    }

    private static SelectAndViewAnalyzer createBaseLayer(Map> sources,
            boolean publishTheseSources) {
        return new BaseLayer(sources, publishTheseSources);
    }

    private RedirectionLayer createRedirectionLayer(TrackingRowSet resultRowSet,
            WritableRowRedirection rowRedirection) {
        return new RedirectionLayer(this, resultRowSet, rowRedirection);
    }

    private StaticFlattenLayer createStaticFlattenLayer(TrackingRowSet parentRowSet) {
        return new StaticFlattenLayer(this, parentRowSet);
    }

    private SelectAndViewAnalyzer createLayerForSelect(
            UpdateGraph updateGraph, RowSet parentRowset, String name, SelectColumn sc, WritableColumnSource cs,
            WritableColumnSource underlyingSource, String[] parentColumnDependencies, ModifiedColumnSet mcsBuilder,
            boolean isRedirected, boolean flattenResult, boolean alreadyFlattened) {
        return new SelectColumnLayer(updateGraph, parentRowset, this, name, sc, cs, underlyingSource,
                parentColumnDependencies,
                mcsBuilder, isRedirected, flattenResult, alreadyFlattened);
    }

    private SelectAndViewAnalyzer createLayerForConstantView(String name, SelectColumn sc, WritableColumnSource cs,
            String[] parentColumnDependencies, ModifiedColumnSet mcsBuilder, boolean flattenResult,
            boolean alreadyFlattened) {
        return new ConstantColumnLayer(this, name, sc, cs, parentColumnDependencies, mcsBuilder, flattenResult,
                alreadyFlattened);
    }

    private SelectAndViewAnalyzer createLayerForView(String name, SelectColumn sc, ColumnSource cs,
            String[] parentColumnDependencies, ModifiedColumnSet mcsBuilder) {
        return new ViewColumnLayer(this, name, sc, cs, parentColumnDependencies, mcsBuilder);
    }

    private SelectAndViewAnalyzer createLayerForPreserve(String name, SelectColumn sc, ColumnSource cs,
            String[] parentColumnDependencies, ModifiedColumnSet mcsBuilder) {
        return new PreserveColumnLayer(this, name, sc, cs, parentColumnDependencies, mcsBuilder);
    }

    abstract void populateModifiedColumnSetRecurse(ModifiedColumnSet mcsBuilder, Set remainingDepsToSatisfy);

    enum GetMode {
        All, New, Published
    }

    public final Map> getAllColumnSources() {
        return getColumnSourcesRecurse(GetMode.All);
    }

    public final Map> getNewColumnSources() {
        return getColumnSourcesRecurse(GetMode.New);
    }

    public final Map> getPublishedColumnSources() {
        return getColumnSourcesRecurse(GetMode.Published);
    }

    abstract Map> getColumnSourcesRecurse(GetMode mode);

    public static class UpdateHelper implements SafeCloseable {
        private RowSet existingRows;
        private SafeCloseablePair shiftedWithModifies;
        private SafeCloseablePair shiftedWithoutModifies;

        private final RowSet parentRowSet;
        private final TableUpdate upstream;

        public UpdateHelper(RowSet parentRowSet, TableUpdate upstream) {
            this.parentRowSet = parentRowSet;
            this.upstream = upstream;
        }

        private RowSet getExisting() {
            if (existingRows == null) {
                existingRows = parentRowSet.minus(upstream.added());
            }
            return existingRows;
        }

        private void ensure(boolean withModifies) {
            if (withModifies && shiftedWithModifies == null) {
                shiftedWithModifies = SafeCloseablePair
                        .downcast(upstream.shifted().extractParallelShiftedRowsFromPostShiftRowSet(getExisting()));
            } else if (!withModifies && shiftedWithoutModifies == null) {
                try (final RowSet candidates = getExisting().minus(upstream.modified())) {
                    shiftedWithoutModifies = SafeCloseablePair
                            .downcast(upstream.shifted().extractParallelShiftedRowsFromPostShiftRowSet(candidates));
                }
            }
        }

        RowSet getPreShifted(boolean withModifies) {
            if (!withModifies && upstream.modified().isEmpty()) {
                return getPreShifted(true);
            }
            ensure(withModifies);
            return withModifies ? shiftedWithModifies.first : shiftedWithoutModifies.first;
        }

        RowSet getPostShifted(boolean withModifies) {
            if (!withModifies && upstream.modified().isEmpty()) {
                return getPostShifted(true);
            }
            ensure(withModifies);
            return withModifies ? shiftedWithModifies.second : shiftedWithoutModifies.second;
        }

        @Override
        public void close() {
            if (existingRows != null) {
                existingRows.close();
                existingRows = null;
            }
            if (shiftedWithModifies != null) {
                shiftedWithModifies.close();
                shiftedWithModifies = null;
            }
            if (shiftedWithoutModifies != null) {
                shiftedWithoutModifies.close();
                shiftedWithoutModifies = null;
            }
        }
    }

    /**
     * Apply this update to this SelectAndViewAnalyzer.
     *
     * @param upstream the upstream update
     * @param toClear rows that used to exist and no longer exist
     * @param helper convenience class that memoizes reusable calculations for this update
     * @param jobScheduler scheduler for parallel sub-tasks
     * @param liveResultOwner {@link LivenessNode node} to be used to manage/unmanage results that happen to be
     *        {@link io.deephaven.engine.liveness.LivenessReferent liveness referents}
     * @param onCompletion Called when an inner column is complete. The outer layer should pass the {@code onCompletion}
     */
    public abstract void applyUpdate(TableUpdate upstream, RowSet toClear, UpdateHelper helper,
            JobScheduler jobScheduler, @Nullable LivenessNode liveResultOwner,
            SelectLayerCompletionHandler onCompletion);

    /**
     * Our job here is to calculate the effects: a map from incoming column to a list of columns that it effects. We do
     * this in two stages. In the first stage we create a map from column to (set of dependent columns). In the second
     * stage we reverse that map.
     */
    public final Map calcEffects(boolean forcePublishAllResources) {
        final Map> dependsOn = calcDependsOnRecurse(forcePublishAllResources);

        // Now create effects, which is the inverse of dependsOn:
        // An entry W -> [X, Y, Z] in effects means that W affects X, Y, and Z
        final Map> effects = new HashMap<>();
        for (Map.Entry> entry : dependsOn.entrySet()) {
            final String depender = entry.getKey();
            for (final String dependee : entry.getValue()) {
                effects.computeIfAbsent(dependee, dummy -> new ArrayList<>()).add(depender);
            }
        }
        // Convert effects type into result type
        final Map result = new HashMap<>();
        for (Map.Entry> entry : effects.entrySet()) {
            final String[] value = entry.getValue().toArray(CollectionUtil.ZERO_LENGTH_STRING_ARRAY);
            result.put(entry.getKey(), value);
        }
        return result;
    }

    abstract Map> calcDependsOnRecurse(boolean forcePublishAllResources);

    public abstract SelectAndViewAnalyzer getInner();

    public abstract void updateColumnDefinitionsFromTopLayer(Map> columnDefinitions);

    public abstract void startTrackingPrev();

    /**
     * Was the result internally flattened? Only the STATIC_SELECT case flattens the result. If the result preserves any
     * columns, then flattening is not permitted. Because all the other layers cannot internally flatten, the default
     * implementation returns false.
     */
    public boolean flattenedResult() {
        return false;
    }

    /**
     * Have the column sources already been flattened? Only the STATIC_SELECT case flattens the result. A static flatten
     * layer is only added if SelectColumn depends on an intermediate result.
     */
    public boolean alreadyFlattenedSources() {
        return false;
    }

    /**
     * Return the layerIndex for a given string column.
     *
     * 

* This is executed recursively, because later columns in a select statement hide earlier columns. *

* * @param column the name of the column * * @return the layerIndex */ abstract int getLayerIndexFor(String column); /** * Can all of our columns permit parallel updates? */ abstract public boolean allowCrossColumnParallelization(); /** * A class that handles the completion of one select column. The handlers are chained together so that when a column * completes all of the downstream dependencies may execute. */ public static abstract class SelectLayerCompletionHandler { /** * Note that the completed columns are shared among the entire chain of completion handlers. */ private final BitSet completedColumns; private final SelectLayerCompletionHandler nextHandler; private final BitSet requiredColumns; private volatile boolean fired = false; /** * Create a new completion handler that calls nextHandler after its own processing. The completedColumns BitSet * is shared among all handlers. * * @param requiredColumns the columns required for this layer * @param nextHandler the next handler to call */ SelectLayerCompletionHandler(BitSet requiredColumns, SelectLayerCompletionHandler nextHandler) { this.requiredColumns = requiredColumns; this.completedColumns = nextHandler.completedColumns; this.nextHandler = nextHandler; } /** * Create the final completion handler, which has no next handler. * * @param requiredColumns the columns required for this handler to fire * @param completedColumns the set of completed columns, shared with all of the other handlers */ public SelectLayerCompletionHandler(BitSet requiredColumns, BitSet completedColumns) { this.requiredColumns = requiredColumns; this.completedColumns = completedColumns; this.nextHandler = null; } /** * Called when a single column is completed. * * If we are ready, then we call {@link #onAllRequiredColumnsCompleted()}. * * We may not be ready, but other columns downstream of us may be ready, so they are also notified (the * nextHandler). * * @param completedColumn the layerIndex of the completedColumn */ void onLayerCompleted(int completedColumn) { if (!fired) { boolean readyToFire = false; synchronized (completedColumns) { if (!fired) { completedColumns.set(completedColumn); if (requiredColumns.get(completedColumn) || requiredColumns.isEmpty()) { readyToFire = requiredColumns.stream().allMatch(completedColumns::get); if (readyToFire) { fired = true; } } } } if (readyToFire) { onAllRequiredColumnsCompleted(); } } if (nextHandler != null) { nextHandler.onLayerCompleted(completedColumn); } } protected void onError(Exception error) { if (nextHandler != null) { nextHandler.onError(error); } } /** * Called when all of the required columns are completed. */ protected abstract void onAllRequiredColumnsCompleted(); } /** * Create a completion handler that signals a future when the update is completed. * * @param waitForResult a void future indicating success or failure * * @return a completion handler that will signal the future */ public SelectLayerCompletionHandler futureCompletionHandler(CompletableFuture waitForResult) { final BitSet completedColumns = new BitSet(); final BitSet requiredColumns = new BitSet(); setAllNewColumns(requiredColumns); return new SelectLayerCompletionHandler(requiredColumns, completedColumns) { boolean errorOccurred = false; @Override public void onAllRequiredColumnsCompleted() { if (errorOccurred) { return; } waitForResult.complete(null); } @Override protected void onError(Exception error) { if (errorOccurred) { return; } errorOccurred = true; waitForResult.completeExceptionally(error); } }; } @Override public String toString() { return new LogOutputStringImpl().append(this).toString(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy