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

io.deephaven.engine.table.impl.util.HashSetBackedTableFactory 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.util;

import io.deephaven.base.verify.Assert;
import io.deephaven.engine.context.ExecutionContext;
import io.deephaven.engine.rowset.*;
import io.deephaven.engine.rowset.RowSetFactory;
import io.deephaven.engine.table.Table;
import io.deephaven.engine.table.impl.QueryTable;
import io.deephaven.engine.table.impl.AbstractColumnSource;
import io.deephaven.engine.table.ColumnSource;
import io.deephaven.engine.table.impl.MutableColumnSourceGetDefaults;
import gnu.trove.iterator.TObjectLongIterator;
import gnu.trove.list.array.TLongArrayList;
import gnu.trove.map.TLongLongMap;
import gnu.trove.map.TLongObjectMap;
import gnu.trove.map.TObjectLongMap;
import gnu.trove.map.hash.TLongLongHashMap;
import gnu.trove.map.hash.TLongObjectHashMap;
import gnu.trove.map.hash.TObjectLongHashMap;
import io.deephaven.engine.updategraph.UpdateGraph;
import io.deephaven.tuple.ArrayTuple;

import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Supplier;

/**
 * An abstract table that represents a hash set of array-backed tuples. Since we are representing a set, there we are
 * not defining an order to our output. Whatever order the table happens to end up in, is fine.
 *
 * The table will run by regenerating the full hash set (using the setGenerator Function passed in); and then comparing
 * that to the existing hash set.
 */
public class HashSetBackedTableFactory {

    private final Supplier> setGenerator;
    private final int refreshIntervalMs;
    private long nextRefresh;
    private final Map> columns;

    private final UpdateGraph updateGraph;

    private final TObjectLongMap valueToIndexMap = new TObjectLongHashMap<>();
    private final TLongObjectMap indexToValueMap = new TLongObjectHashMap<>();

    private final TLongObjectMap indexToPreviousMap = new TLongObjectHashMap<>();
    private final TLongLongMap indexToPreviousClock = new TLongLongHashMap();
    private long lastIndex = 0;
    private final TLongArrayList freeSet = new TLongArrayList();
    private TrackingWritableRowSet rowSet;

    private HashSetBackedTableFactory(Supplier> setGenerator, int refreshIntervalMs,
            String... colNames) {
        this.setGenerator = setGenerator;
        this.refreshIntervalMs = refreshIntervalMs;
        nextRefresh = System.currentTimeMillis() + this.refreshIntervalMs;

        columns = new LinkedHashMap<>();

        for (int ii = 0; ii < colNames.length; ++ii) {
            columns.put(colNames[ii], new ArrayTupleWrapperColumnSource(ii));
        }

        updateGraph = ExecutionContext.getContext().getUpdateGraph();
    }

    /**
     * Create a ticking table based on a setGenerator.
     *
     * @param setGenerator a function that returns a HashSet of ArrayTuples, each ArrayTuple is a row in the output.
     * @param refreshIntervalMs how often to run the table, if less than or equal to 0 the table does not tick.
     * @param colNames the column names for the output table, must match the number of elements in each ArrayTuple.
     * @return a table representing the Set returned by the setGenerator
     */
    public static Table create(Supplier> setGenerator, int refreshIntervalMs,
            String... colNames) {
        HashSetBackedTableFactory factory = new HashSetBackedTableFactory(setGenerator, refreshIntervalMs, colNames);

        RowSetBuilderRandom addedBuilder = RowSetFactory.builderRandom();
        RowSetBuilderRandom removedBuilder = RowSetFactory.builderRandom();

        factory.updateValueSet(addedBuilder, removedBuilder);

        WritableRowSet added = addedBuilder.build();
        RowSet removed = removedBuilder.build();

        factory.rowSet = added.toTracking();
        Assert.assertion(removed.size() == 0, "removed.size() == 0");

        return factory.getTable();
    }

    private Table getTable() {
        return new HashSetBackedTable(rowSet, columns);
    }

    private void updateValueSet(RowSetBuilderRandom addedBuilder, RowSetBuilderRandom removedBuilder) {
        HashSet valueSet = setGenerator.get();

        synchronized (this) {
            for (TObjectLongIterator it = valueToIndexMap.iterator(); it.hasNext();) {
                it.advance();
                ArrayTuple key = it.key();
                if (!valueSet.contains(key)) {
                    removeValue(it, removedBuilder);
                }
            }

            for (ArrayTuple value : valueSet) {
                if (!valueToIndexMap.containsKey(value)) {
                    addValue(value, addedBuilder);
                }
            }
        }
    }

    private void removeValue(TObjectLongIterator vtiIt, RowSetBuilderRandom removedBuilder) {
        long index = vtiIt.value();

        // record the old value for get prev
        indexToPreviousMap.put(index, vtiIt.key());
        vtiIt.remove();

        indexToPreviousClock.put(index, updateGraph.clock().currentStep());

        indexToValueMap.remove(index);
        removedBuilder.addKey(index);
        freeSet.add(index);
    }

    private void addValue(ArrayTuple value, RowSetBuilderRandom addedBuilder) {
        long newIndex;
        if (freeSet.isEmpty()) {
            newIndex = lastIndex++;
        } else {
            newIndex = freeSet.get(freeSet.size() - 1);
            freeSet.remove(freeSet.size() - 1, 1);
        }
        addedBuilder.addKey(newIndex);
        valueToIndexMap.put(value, newIndex);
        indexToValueMap.put(newIndex, value);

        if (indexToPreviousClock.get(newIndex) != updateGraph.clock().currentStep()) {
            indexToPreviousClock.put(newIndex, updateGraph.clock().currentStep());
            indexToPreviousMap.put(newIndex, null);
        }
    }

    /**
     * @implNote The constructor publishes {@code this} to the {@link UpdateGraph} and cannot be subclassed.
     */
    private final class HashSetBackedTable extends QueryTable implements Runnable {
        HashSetBackedTable(TrackingRowSet rowSet, Map> columns) {
            super(rowSet, columns);
            if (refreshIntervalMs >= 0) {
                setRefreshing(true);
                updateGraph.addSource(this);
            }
        }

        @Override
        public void run() {
            if (System.currentTimeMillis() < nextRefresh) {
                return;
            }
            nextRefresh = System.currentTimeMillis() + refreshIntervalMs;

            RowSetBuilderRandom addedBuilder = RowSetFactory.builderRandom();
            RowSetBuilderRandom removedBuilder = RowSetFactory.builderRandom();

            updateValueSet(addedBuilder, removedBuilder);

            final WritableRowSet added = addedBuilder.build();
            final WritableRowSet removed = removedBuilder.build();

            if (added.size() > 0 || removed.size() > 0) {
                final RowSet modified = added.intersect(removed);
                added.remove(modified);
                removed.remove(modified);

                rowSet.update(added, removed);
                notifyListeners(added, removed, modified);
            }
        }

        @Override
        public void destroy() {
            super.destroy();
            if (refreshIntervalMs >= 0) {
                updateGraph.removeSource(this);
            }
        }
    }

    private class ArrayTupleWrapperColumnSource extends AbstractColumnSource
            implements MutableColumnSourceGetDefaults.ForObject {

        private final int columnIndex;

        public ArrayTupleWrapperColumnSource(int columnIndex) {
            super(String.class, null);
            this.columnIndex = columnIndex;
        }

        @Override
        public String get(long rowKey) {
            synchronized (HashSetBackedTableFactory.this) {
                ArrayTuple row = indexToValueMap.get(rowKey);
                if (row == null)
                    return null;
                return row.getElement(columnIndex);
            }
        }

        @Override
        public String getPrev(long rowKey) {
            synchronized (HashSetBackedTableFactory.this) {
                if (indexToPreviousClock.get(rowKey) == updateGraph.clock().currentStep()) {
                    ArrayTuple row = indexToPreviousMap.get(rowKey);
                    if (row == null)
                        return null;
                    return row.getElement(columnIndex);
                } else {
                    return get(rowKey);
                }
            }
        }

        @Override
        public void startTrackingPrevValues() {
            // Do nothing.
        }

        @Override
        public boolean isImmutable() {
            return false;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy