blob: a65f28761af5981f325d61bd2ea45e9544fc53fe [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2013, 2022 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.rj.services.util.dataaccess;
import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.statet.jcommons.lang.NonNull;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.jcommons.lang.ObjectUtils.ToStringBuilder;
import org.eclipse.statet.jcommons.status.ProgressMonitor;
import org.eclipse.statet.jcommons.status.Status;
/**
*
*
* @param <V> type of R object fragments
* @since 2.0 (provisional)
*/
@NonNullByDefault
public class LazyRStore<V> {
public final static int DEFAULT_FRAGMENT_SIZE= 2500;
public final static class Fragment<W> extends RDataSubset {
private static final byte SCHEDULED= (byte) 1 << 0;
private static final byte SET= (byte) 1 << 1;
/** sequential number of fragments, first by fragment col, then by fragment row */
private final long number;
private @Nullable W rObject;
private Fragment<W> newer;
private Fragment<W> older;
private byte state;
Fragment(final long number,
final long rowBeginIdx, final long rowCount,
final long columnBeginIdx, final long columnCount) {
super(rowBeginIdx, rowCount, columnBeginIdx, columnCount);
this.number= number;
}
/**
* Returns the R object for this fragment.
*
* @return the R object for this fragment or <code>null</code>, if not available.
*/
public @Nullable W getRObject() {
return this.rObject;
}
@Override
public String toString() {
final ToStringBuilder sb= new ToStringBuilder("LazyRStore$Fragment"); //$NON-NLS-1$
sb.append(" " + this.number);
sb.addProp("rows", "[%1$s, %2$s)", getRowBeginIdx(), getRowEndIdx()); //$NON-NLS-1$ //$NON-NLS-2$
sb.addProp("columns", "[%1$s, %2$s)", getColumnBeginIdx(), getColumnEndIdx()); //$NON-NLS-1$ //$NON-NLS-2$
return sb.toString();
}
}
public static final int FORCE_SYNC= 1 << 0;
public static interface Updater<T> {
void scheduleUpdate(final LazyRStore<T> store,
final @Nullable RDataAssignment assignment, final @Nullable Fragment<T> fragment,
final int flags, final ProgressMonitor m);
}
private final long columnCount;
private long rowCount;
private final int maxFragmentCount;
private @Nullable Fragment<V>[] fragments;
private int currentFragmentCount= 0;
private final Fragment<V> topFragment= new Fragment<>(-1, 0, 0, 0, 0);
private final Fragment<V> bottomFragment= new Fragment<>(-1, 0, 0, 0, 0);
private final int fragmentRowCount;
private final int fragmentColCount;
private final long fragmentCountInRow;
private final List<RDataAssignment> assignments= new ArrayList<>();
private int scheduledCount;
private @Nullable Fragment<V> scheduleNext;
private Updater<V> updater;
public LazyRStore(final long rowCount, final long columnCount,
final int maxFragmentCount,
final Updater<V> updater) {
this(rowCount, columnCount, maxFragmentCount, DEFAULT_FRAGMENT_SIZE, updater);
}
public LazyRStore(final long rowCount, final long columnCount,
final int maxFragmentCount, final int fragmentSize,
final Updater<V> updater) {
this.columnCount= columnCount;
this.maxFragmentCount= maxFragmentCount;
this.fragmentColCount= (int) Math.min(columnCount, 25);
this.fragmentCountInRow= (columnCount - 1) / this.fragmentColCount + 1;
this.fragmentRowCount= fragmentSize / this.fragmentColCount;
this.updater= updater;
init(rowCount);
}
public LazyRStore(final long rowCount, final long columnCount,
final int maxFragmentCount,
final int fragmentRowCount, final int fragmentColCount,
final Updater<V> updater) {
this.columnCount= columnCount;
this.maxFragmentCount= maxFragmentCount;
this.fragmentColCount= fragmentColCount;
this.fragmentCountInRow= (columnCount - 1) / fragmentColCount + 1;
this.fragmentRowCount= fragmentRowCount;
this.updater= updater;
init(rowCount);
}
private void init(final long rowCount) {
this.fragments= new Fragment[Math.min(16, this.maxFragmentCount)];
clear(rowCount);
}
private long getNumber(final long rowIdx, final long columnIdx) {
return (rowIdx / this.fragmentRowCount) * this.fragmentCountInRow +
(columnIdx / this.fragmentColCount);
}
public LazyRStore. @Nullable Fragment<V> getFragment(final long rowIdx, final long columnIdx,
final int flags, final ProgressMonitor m) {
if (rowIdx >= this.rowCount) {
return null;
}
final long number= getNumber(rowIdx, columnIdx);
final Fragment<V> fragment= (@NonNull Fragment<V>) getFragment(number, true);
if ((fragment.state & Fragment.SET) != 0) {
return fragment;
}
final boolean scheduleUpdate= (this.scheduledCount == 0);
this.scheduleNext= this.topFragment;
if ((fragment.state & Fragment.SCHEDULED) == 0) {
fragment.state= Fragment.SCHEDULED;
this.scheduledCount++;
}
if (scheduleUpdate || (flags & FORCE_SYNC) != 0) {
this.updater.scheduleUpdate(this, null, fragment, flags, m);
if ((fragment.state & Fragment.SET) != 0) {
return fragment;
}
}
return null;
}
public void setFragment(final long rowIdx, final long columnIdx, final V rObject) {
final long number= getNumber(rowIdx, columnIdx);
final Fragment<V> fragment= (@NonNull Fragment<V>) getFragment(number, true);
updateFragment(fragment, rObject);
}
public LazyRStore.@Nullable Fragment<V> getLoadedFragment(final long rowIdx, final long columnIdx) {
if (rowIdx >= this.rowCount) {
return null;
}
final long number= getNumber(rowIdx, columnIdx);
final Fragment<V> fragment= getFragment(number, false);
if (fragment != null && (fragment.state & Fragment.SET) != 0) {
return fragment;
}
return null;
}
public LazyRStore. @Nullable Fragment<V> getLoadedFragmentAny() {
Fragment<V> fragment= this.topFragment;
while (fragment != null) {
if ((fragment.state & Fragment.SET) != 0) {
return fragment;
}
fragment= fragment.older;
}
return null;
}
public void set(final RDataAssignment assignment,
final int flags, final ProgressMonitor m) {
final Fragment<V> fragment= clear(assignment);
this.assignments.add(assignment);
final boolean scheduleUpdate= (this.scheduledCount == 0);
this.scheduledCount++; // assignment
if (fragment != null) {
fragment.state= Fragment.SCHEDULED;
this.scheduledCount++;
}
if (scheduleUpdate) {
this.updater.scheduleUpdate(this, assignment, fragment, flags, m);
}
}
public @Nullable Fragment<V> clear(final RDataSubset subset) {
Fragment<V> firstFragment= null;
long columnBeginIdx= (subset.getColumnBeginIdx() / this.fragmentColCount) * this.fragmentColCount;
while (columnBeginIdx < subset.getColumnEndIdx()) {
long rowBeginIdx= (subset.getRowBeginIdx() / this.fragmentRowCount) * this.fragmentRowCount;
while (rowBeginIdx < subset.getRowEndIdx()) {
final long number= getNumber(rowBeginIdx, columnBeginIdx);
final int idx= indexOf(number);
if (idx >= 0) {
final Fragment<V> fragment= clearFragment(idx);
if (firstFragment == null) {
firstFragment= fragment;
}
}
rowBeginIdx= Math.min(rowBeginIdx + this.fragmentRowCount, this.rowCount);
}
columnBeginIdx= Math.min(columnBeginIdx + this.fragmentColCount, this.columnCount);
}
return firstFragment;
}
private int indexOf(final long number) {
final Fragment<V>[] array= this.fragments; // < this.currentFragmentCount
int low= 0;
int high= this.currentFragmentCount - 1;
while (low <= high) {
final int mid= (low + high) >>> 1;
final Fragment<V> fragment= array[mid];
if (fragment.number < number) {
low= mid + 1;
}
else if (fragment.number > number) {
high= mid - 1;
}
else {
return mid;
}
}
return - (low + 1);
}
private @Nullable Fragment<V> getFragment(final long number, final boolean create) {
if (this.topFragment.older.number == number) {
return this.topFragment.older;
}
final Fragment<V>[] array= this.fragments; // < this.currentFragmentCount
int low= 0;
{ int high= this.currentFragmentCount - 1;
while (low <= high) {
final int mid= (low + high) >>> 1;
final Fragment<V> fragment= array[mid];
if (fragment.number < number) {
low= mid + 1;
}
else if (fragment.number > number) {
high= mid - 1;
}
else {
fragment.newer.older= fragment.older;
fragment.older.newer= fragment.newer;
fragment.newer= this.topFragment;
fragment.older= this.topFragment.older;
this.topFragment.older.newer= fragment;
this.topFragment.older= fragment;
return fragment;
}
}
}
if (!create) {
return null;
}
final Fragment<V> fragment= createFragment(number);
if (this.currentFragmentCount >= this.maxFragmentCount) {
if (removeOldestFragment() < low) {
low--;
}
}
{ if (array.length == this.currentFragmentCount) {
this.fragments= new Fragment[Math.min(this.currentFragmentCount * 2, this.maxFragmentCount)];
System.arraycopy(array, 0, this.fragments, 0, low);
}
System.arraycopy(array, low, this.fragments, low + 1, this.currentFragmentCount - low);
this.fragments[low]= fragment;
this.currentFragmentCount++;
}
fragment.newer= this.topFragment;
fragment.older= this.topFragment.older;
this.topFragment.older.newer= fragment;
this.topFragment.older= fragment;
return fragment;
}
private Fragment<V> createFragment(final long number) {
final long rowBeginIdx= (number / this.fragmentCountInRow) * this.fragmentRowCount;
final long rowEndIdx= Math.min(rowBeginIdx + this.fragmentRowCount, this.rowCount);
final long columnBeginIdx= (number % this.fragmentCountInRow) * this.fragmentColCount;
final long columnEndIdx= Math.min(columnBeginIdx + this.fragmentColCount, this.columnCount);
return new Fragment<>(number,
rowBeginIdx, (rowEndIdx - rowBeginIdx),
columnBeginIdx, (columnEndIdx - columnBeginIdx) );
}
private Fragment<V> clearFragment(final int idx) {
final Fragment<V> oldFragment= this.fragments[idx];
final Fragment<V> newFragment= new Fragment<>(oldFragment.number,
oldFragment.getRowBeginIdx(), oldFragment.getRowCount(),
oldFragment.getColumnBeginIdx(), oldFragment.getColumnCount() );
if (oldFragment.newer != null) {
newFragment.newer= oldFragment.newer;
newFragment.newer.older= newFragment;
}
if (oldFragment.older != null) {
newFragment.older= oldFragment.older;
newFragment.older.newer= newFragment;
}
return this.fragments[idx]= newFragment;
}
private int removeOldestFragment() {
final Fragment<V> fragment= nonNullAssert(this.bottomFragment.newer);
if ((fragment.state & Fragment.SCHEDULED) != 0) {
fragment.state &= ~Fragment.SCHEDULED;
this.scheduledCount--;
}
if (this.scheduleNext == fragment) {
this.scheduleNext= fragment.older;
}
final int idx= indexOf(fragment.number);
this.currentFragmentCount--;
System.arraycopy(this.fragments, idx + 1, this.fragments, idx, this.currentFragmentCount - idx);
this.fragments[this.currentFragmentCount]= null;
fragment.newer.older= this.bottomFragment;
this.bottomFragment.newer= fragment.newer;
fragment.newer= null;
fragment.older= null;
return idx;
}
public void clear(final long rowCount) {
final Fragment<V>[] array= this.fragments; // < this.currentFragmentCount
for (int i= 0; i < this.currentFragmentCount; i++) {
array[i].state &= ~Fragment.SCHEDULED;
array[i]= null;
}
this.currentFragmentCount= 0;
this.topFragment.older= this.bottomFragment;
this.bottomFragment.newer= this.topFragment;
this.scheduledCount= 0;
this.scheduleNext= this.topFragment;
if (rowCount >= 0) {
this.rowCount= rowCount;
}
}
public Fragment<V>[] getScheduledFragments() {
final Fragment<V>[] scheduledFragments= new Fragment[this.scheduledCount];
Fragment<V> fragment= this.topFragment.older;
int i= 0;
while (i < this.scheduledCount) {
if ((fragment.state & Fragment.SCHEDULED) != 0) {
scheduledFragments[i++]= fragment;
}
fragment= fragment.older;
}
return scheduledFragments;
}
public @Nullable Fragment<V> getNextScheduledFragment() {
if (this.scheduledCount == 0) {
return null;
}
Fragment<V> fragment= this.scheduleNext.older;
while (true) {
if ((fragment.state & Fragment.SCHEDULED) != 0) {
this.scheduleNext= fragment;
return fragment;
}
fragment= fragment.older;
}
}
public void updateAssignment(final RDataAssignment assignment, final Status status) {
if (this.assignments.remove(assignment)) {
this.scheduledCount--;
}
}
public void updateFragment(final Fragment<V> fragment, final V rObject) {
if ((fragment.state & Fragment.SET) != 0) {
throw new IllegalStateException();
}
fragment.rObject= rObject;
if ((fragment.state & Fragment.SCHEDULED) != 0) {
this.scheduledCount--;
}
fragment.state= Fragment.SET;
}
}