blob: 4f891b26acd4ad225f36357004005e539b566837 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2010, 2019 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.internal.r.ui.dataeditor;
import static org.eclipse.statet.internal.r.ui.RUIPlugin.logError;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import com.ibm.icu.util.TimeZone;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Display;
import org.eclipse.statet.jcommons.collections.CopyOnWriteIdentityListSet;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.ImList;
import org.eclipse.statet.jcommons.status.ProgressMonitor;
import org.eclipse.statet.jcommons.status.Status;
import org.eclipse.statet.jcommons.status.StatusException;
import org.eclipse.statet.jcommons.ts.core.SystemRunnable;
import org.eclipse.statet.jcommons.ts.core.Tool;
import org.eclipse.statet.jcommons.ts.core.ToolRunnable;
import org.eclipse.statet.jcommons.ts.core.ToolService;
import org.eclipse.statet.ecommons.runtime.core.util.StatusUtils;
import org.eclipse.statet.ecommons.ui.util.UIAccess;
import org.eclipse.statet.ecommons.waltable.coordinate.PositionId;
import org.eclipse.statet.ecommons.waltable.data.ControlData;
import org.eclipse.statet.ecommons.waltable.data.IDataProvider;
import org.eclipse.statet.ecommons.waltable.sort.ISortModel;
import org.eclipse.statet.ecommons.waltable.sort.SortDirection;
import org.eclipse.statet.r.core.model.RElementName;
import org.eclipse.statet.r.core.tool.TmpUtils;
import org.eclipse.statet.r.nico.ICombinedRDataAdapter;
import org.eclipse.statet.r.ui.dataeditor.IRDataTableInput;
import org.eclipse.statet.r.ui.dataeditor.IRDataTableVariable;
import org.eclipse.statet.r.ui.dataeditor.RDataTableColumn;
import org.eclipse.statet.rj.data.RCharacterStore;
import org.eclipse.statet.rj.data.RDataUtils;
import org.eclipse.statet.rj.data.RFactorStore;
import org.eclipse.statet.rj.data.RIntegerStore;
import org.eclipse.statet.rj.data.RLanguage;
import org.eclipse.statet.rj.data.RObject;
import org.eclipse.statet.rj.data.RObjectFactory;
import org.eclipse.statet.rj.data.RStore;
import org.eclipse.statet.rj.data.RVector;
import org.eclipse.statet.rj.data.UnexpectedRDataException;
import org.eclipse.statet.rj.data.impl.DefaultRObjectFactory;
import org.eclipse.statet.rj.data.impl.RFactorStructStore;
import org.eclipse.statet.rj.services.BasicFQRObjectRef;
import org.eclipse.statet.rj.services.FQRObjectRef;
import org.eclipse.statet.rj.services.FunctionCall;
import org.eclipse.statet.rj.services.RService;
import org.eclipse.statet.rj.services.util.dataaccess.AbstractRDataAdapter;
import org.eclipse.statet.rj.services.util.dataaccess.LazyRStore;
import org.eclipse.statet.rj.services.util.dataaccess.LazyRStore.Fragment;
import org.eclipse.statet.rj.services.util.dataaccess.RDataAssignment;
import org.eclipse.statet.rj.ts.core.RToolService;
public abstract class AbstractRDataProvider<T extends RObject> implements IDataProvider {
public static final ControlData LOADING= new ControlData(ControlData.ASYNC, "loading...");
public static final ControlData ERROR= new ControlData(ControlData.ERROR, "ERROR");
public static final ControlData NA= new ControlData(ControlData.NA, "NA"); //$NON-NLS-1$
public static final ControlData DUMMY= new ControlData(0, ""); //$NON-NLS-1$
protected static final RElementName BASE_NAME= RElementName.create(RElementName.MAIN_DEFAULT, "x"); //$NON-NLS-1$
static final void checkCancel(final Exception e) throws StatusException {
if (e instanceof StatusException
&& ((StatusException) e).getStatus().getSeverity() == Status.CANCEL) {
throw (StatusException) e;
}
}
public static final class SortColumn {
private final long id;
public final boolean decreasing;
public SortColumn(final long columnId, final boolean decreasing) {
this.id= columnId;
this.decreasing= decreasing;
}
public long getId() {
return this.id;
}
public long getIdx() {
return (this.id & PositionId.NUM_MASK);
}
@Override
public int hashCode() {
final int h= (int) (this.id ^ (this.id >>> 32));
return (this.decreasing) ? (-h) : h;
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof SortColumn)) {
return false;
}
final SortColumn other= (SortColumn) obj;
return (this.id == other.id && this.decreasing == other.decreasing);
}
}
public static interface IDataProviderListener {
static final int ERROR_STRUCT_CHANGED= 1;
void onInputInitialized(boolean structChanged);
void onInputFailed(int error);
void onRowCountChanged();
void onRowsChanged(long begin, long end);
}
private static final long DEFAULT_WAIT= 25_000000;
private static final long CANCEL_WAIT= 200_000000;
private static final long BLOCKING_WAIT= 300_000000;
private class MainLock extends Lock implements LazyRStore.Updater {
private final List<Fragment> waitingFragments= new ArrayList<>();
@Override
public void scheduleUpdate(final LazyRStore store,
final RDataAssignment assignment, final Fragment fragment,
final int flags, final ProgressMonitor m) {
long waitNanos;
if (!this.scheduled) {
this.scheduled= true;
AbstractRDataProvider.this.schedule(AbstractRDataProvider.this.updateRunnable);
waitNanos= ((flags & FORCE_SYNC) != 0) ? 0 : DEFAULT_WAIT;
}
else {
waitNanos= ((flags & FORCE_SYNC) != 0) ? 0 : -1;
}
if (waitNanos >= 0 && fragment != null) {
this.waitingFragments.add(fragment);
this.worker.signalAll();
// long blockingMonitor= (monitor instanceof IProgressMonitorWithBlocking) ? 0 : -1;
try {
if (waitNanos == 0) {
do {
// wait required to check cancel state
waitNanos= this.requestor.awaitNanos(CANCEL_WAIT);
// if (blockingMonitor >= 0 && blockingMonitor < BLOCKING_WAIT) {
// blockingMonitor+= CANCEL_WAIT - waitNanos;
// if (blockingMonitor >= BLOCKING_WAIT) {
// ((IProgressMonitorWithBlocking) monitor).setBlocked(
// new Status(IStatus.INFO, RUI.BUNDLE_ID, "Waiting for R."));
// }
// }
}
while (this.waitingFragments.contains(fragment)
&& this.state == 0
&& (m == null || !m.isCanceled()) );
}
else {
do {
waitNanos= this.requestor.awaitNanos(waitNanos);
}
while (this.waitingFragments.contains(fragment)
&& this.state == 0
&& (m == null || !m.isCanceled())
&& (waitNanos > 0) );
}
}
catch (final InterruptedException e) {}
finally {
this.waitingFragments.remove(fragment);
// if (blockingMonitor >= BLOCKING_WAIT) {
// ((IProgressMonitorWithBlocking) monitor).clearBlocked();
// }
}
}
}
void notify(final Object obj) {
if (this.waitingFragments.remove(obj)) {
this.requestor.signalAll();
}
}
@Override
void clear() {
this.waitingFragments.clear();
super.clear();
}
}
protected class ColumnDataProvider implements IDataProvider {
public ColumnDataProvider() {
}
@Override
public long getColumnCount() {
return AbstractRDataProvider.this.getColumnCount();
}
@Override
public long getRowCount() {
return 1;
}
@Override
public Object getDataValue(final long columnIndex, final long rowIndex,
final int flags, final IProgressMonitor monitor) {
try {
final LazyRStore.Fragment<T> fragment= AbstractRDataProvider.this.fragmentsLock.getFragment(
AbstractRDataProvider.this.dataStore, 0, columnIndex,
flags, StatusUtils.convert(monitor) );
if (fragment != null) {
return getColumnName(fragment, columnIndex);
}
else {
return handleMissingData(flags);
}
}
catch (final LoadDataException e) {
return handleLoadDataException(e, flags);
}
}
@Override
public void setDataValue(final long columnIndex, final long rowIndex, final Object newValue) {
throw new UnsupportedOperationException();
}
}
protected class RowDataProvider implements IDataProvider {
private final LazyRStore<RVector<?>> rowNamesStore= new LazyRStore<>(AbstractRDataProvider.this.rowCount, 1,
10, AbstractRDataProvider.this.fragmentsLock);
public RowDataProvider() {
}
@Override
public long getColumnCount() {
return 1;
}
@Override
public long getRowCount() {
return AbstractRDataProvider.this.getRowCount();
}
@Override
public Object getDataValue(final long columnIndex, final long rowIndex,
final int flags, final IProgressMonitor monitor) {
try {
final LazyRStore.Fragment<RVector<?>> fragment= AbstractRDataProvider
.this.fragmentsLock.getFragment(this.rowNamesStore, rowIndex, 0,
flags, StatusUtils.convert(monitor) );
if (fragment != null) {
final RVector<?> vector= fragment.getRObject();
if (vector != null) {
RStore<?> names= vector.getNames();
if (names == null) {
names= vector.getData();
}
return names.get(rowIndex - fragment.getRowBeginIdx());
}
return Long.toString(rowIndex + 1);
}
else {
return handleMissingData(flags);
}
}
catch (final LoadDataException e) {
return handleLoadDataException(e, flags);
}
}
public long getRowIdx(final long rowIndex) {
try {
final LazyRStore.Fragment<RVector<?>> fragment= AbstractRDataProvider
.this.fragmentsLock.getFragment(this.rowNamesStore, rowIndex, 0, 0, null);
if (fragment != null) {
final RVector<?> vector= fragment.getRObject();
if (vector != null) {
final RStore<?> idxs= vector.getData();
return ((idxs.getStoreType() == RStore.INTEGER) ?
(long) idxs.getInt(rowIndex - fragment.getRowBeginIdx()) :
(long) idxs.getNum(rowIndex - fragment.getRowBeginIdx()) )
- 1;
}
return rowIndex;
}
else {
return -1;
}
}
catch (final LoadDataException e) {
return -2;
}
}
@Override
public void setDataValue(final long columnIndex, final long rowIndex, final Object newValue) {
throw new UnsupportedOperationException();
}
}
protected class SortModel implements ISortModel {
@Override
public List<Long> getSortedColumnIds() {
final SortColumn sortColumn= getSortColumn();
if (sortColumn != null) {
return Collections.singletonList(sortColumn.id);
}
return Collections.<Long>emptyList();
}
@Override
public void sort(final long columnId, final SortDirection sortDirection, final boolean accumulate) {
SortColumn sortColumn;
switch (sortDirection) {
case ASC:
sortColumn= new SortColumn(columnId, false);
break;
case DESC:
sortColumn= new SortColumn(columnId, true);
break;
default:
sortColumn= null;
break;
}
setSortColumn(sortColumn);
}
@Override
public int getSortOrder(final long columnId) {
final SortColumn sortColumn= getSortColumn();
if (sortColumn != null && sortColumn.id == columnId) {
return 0;
}
return -1;
}
@Override
public boolean isSorted(final long columnId) {
final SortColumn sortColumn= getSortColumn();
if (sortColumn != null && sortColumn.id == columnId) {
return true;
}
return false;
}
@Override
public SortDirection getSortDirection(final long columnId) {
final SortColumn sortColumn= getSortColumn();
if (sortColumn != null && sortColumn.id == columnId) {
return (!sortColumn.decreasing) ? SortDirection.ASC : SortDirection.DESC;
}
return SortDirection.NONE;
}
@Override
public void clear() {
setSortColumn(null);
}
}
private final ToolRunnable initRunnable= new SystemRunnable() {
@Override
public String getTypeId() {
return "r/dataeditor/init";
}
@Override
public String getLabel() {
return "Prepare Data Viewer (" + AbstractRDataProvider.this.input.getName() + ")";
}
@Override
public boolean canRunIn(final Tool tool) {
return true; // TODO
}
@Override
public boolean changed(final int event, final Tool tool) {
if (event == MOVING_FROM) {
return false;
}
return true;
}
@Override
public void run(final ToolService service, final ProgressMonitor m) throws StatusException {
runInit((RToolService) service, m);
}
};
private final ToolRunnable updateRunnable= new SystemRunnable() {
@Override
public String getTypeId() {
return "r/dataeditor/load"; //$NON-NLS-1$
}
@Override
public String getLabel() {
return "Load Data (" + AbstractRDataProvider.this.input.getName() + ")";
}
@Override
public boolean canRunIn(final Tool tool) {
return true; // TODO
}
@Override
public boolean changed(final int event, final Tool tool) {
switch (event) {
case MOVING_FROM:
return false;
case REMOVING_FROM:
case BEING_ABANDONED:
AbstractRDataProvider.this.fragmentsLock.lock();
try {
AbstractRDataProvider.this.fragmentsLock.scheduled= false;
AbstractRDataProvider.this.fragmentsLock.clear();
}
finally {
AbstractRDataProvider.this.fragmentsLock.unlock();
}
break;
default:
break;
}
return true;
}
@Override
public void run(final ToolService service, final ProgressMonitor m) throws StatusException {
runUpdate((RToolService) service, m);
}
};
private final ToolRunnable cleanRunnable= new SystemRunnable() {
@Override
public String getTypeId() {
return "r/dataeditor/clean"; //$NON-NLS-1$
}
@Override
public String getLabel() {
return "Clean Cache (" + AbstractRDataProvider.this.input.getName() + ")";
}
@Override
public boolean canRunIn(final Tool tool) {
return true; // TODO
}
@Override
public boolean changed(final int event, final Tool tool) {
switch (event) {
case MOVING_FROM:
case REMOVING_FROM:
return false;
default:
break;
}
return true;
}
@Override
public void run(final ToolService service, final ProgressMonitor m) throws StatusException {
runClean((RToolService) service, m);
}
};
private final Display realm;
private final IRDataTableInput input;
private final long columnCount;
private long fullRowCount;
private long rowCount;
private final CopyOnWriteIdentityListSet<IDataProviderListener> dataListeners= new CopyOnWriteIdentityListSet<>();
private boolean initScheduled;
private volatile boolean disposeScheduled;
private RDataTableContentDescription description;
private final IDataProvider columnDataProvider;
private final IDataProvider rowDataProvider;
private final IDataProvider columnLabelProvider;
private final IDataProvider rowLabelProvider;
private final MainLock fragmentsLock= new MainLock();
protected final AbstractRDataAdapter<T, T> adapter;
private final LazyRStore<T> dataStore;
private final List<Object> activeOperations= new ArrayList<>();
private boolean updateSorting;
private boolean updateFiltering;
private final StringBuilder rStringBuilder= new StringBuilder(128);
private TmpUtils.Item rTmpItem; // only in R jobs
private T rObjectStruct;
private final ISortModel sortModel;
private SortColumn sortColumn= null;
private String rCacheSort; // only in R jobs
private String filter;
private String rCacheFilter;
private boolean updateIdx; // only in R jobs
private String rCacheIdx; // only in R jobs
private String rCacheIdxR; // only in R jobs
private final FindManager findManager;
protected AbstractRDataProvider(final IRDataTableInput input,
final AbstractRDataAdapter<T, T> adapter, final T initialRObject) {
this.realm= UIAccess.getDisplay();
this.input= input;
this.adapter= adapter;
this.fullRowCount= this.rowCount= this.adapter.getRowCount(initialRObject);
this.columnCount= this.adapter.getColumnCount(initialRObject);
final int dataMax;
if (this.columnCount <= 25) {
dataMax= 10;
}
else if (this.columnCount <= 50) {
dataMax= 20;
}
else {
dataMax= 25;
}
this.dataStore= new LazyRStore<>(this.rowCount, this.columnCount, dataMax, this.fragmentsLock);
this.findManager= new FindManager(this);
this.columnDataProvider= createColumnDataProvider();
this.rowDataProvider= createRowDataProvider();
this.columnLabelProvider= createColumnLabelProvider();
this.rowLabelProvider= createRowLabelProvider();
this.sortModel= createSortModel();
}
public final IRDataTableInput getInput() {
return this.input;
}
protected final AbstractRDataAdapter<T, T> getAdapter() {
return this.adapter;
}
public final T getRObject() {
return this.rObjectStruct;
}
final int getLockState() {
return this.fragmentsLock.state;
}
public final void beginOperation(final Object o) {
this.fragmentsLock.lock();
try {
this.activeOperations.add(o);
}
finally {
this.fragmentsLock.unlock();
}
}
public final void endOperation(final Object o) {
this.fragmentsLock.lock();
try {
if (this.activeOperations.remove(o)
&& this.activeOperations.isEmpty() ) {
this.fragmentsLock.notifyWorker();
}
}
finally {
this.fragmentsLock.unlock();
}
}
final void schedule(final ToolRunnable runnable) {
try {
final Tool tool= this.input.getTool();
final Status status= tool.getQueue().add(runnable);
if (status.getSeverity() == Status.ERROR && !tool.isTerminated()) {
throw new StatusException(status);
}
}
catch (final StatusException e) {
clear(Lock.ERROR_STATE);
logError("An error occurred when scheduling job for data viewer.", e);
}
}
private void runInit(final RToolService r, final ProgressMonitor m) throws StatusException {
if (this.disposeScheduled) {
synchronized (this.initRunnable) {
this.initScheduled= false;
}
return;
}
try {
if (this.rTmpItem == null) {
// r.evalVoid("require(\"rj\", quietly= TRUE)", m);
this.rTmpItem= TmpUtils.newItem("viewer", r, m);
}
}
catch (final Exception e) {
synchronized (this.initRunnable) {
this.initScheduled= false;
}
checkCancel(e);
clear(Lock.ERROR_STATE);
logError("An error occurred when preparing tmp variables for data viewer.", e);
this.realm.syncExec(new Runnable() {
@Override
public void run() {
for (final IDataProviderListener listener : AbstractRDataProvider.this.dataListeners.toList()) {
listener.onInputFailed(0);
}
}
});
return;
}
try {
final RObject rObject= (r instanceof ICombinedRDataAdapter) ?
((ICombinedRDataAdapter) r).evalCombinedStruct(this.input.getElementName(), 0, 1, m) :
r.evalData(this.input.getFullName(), null, RObjectFactory.F_ONLY_STRUCT, 1, m);
if (this.rObjectStruct == null) {
this.rObjectStruct= this.adapter.validate(rObject);
}
else {
this.rObjectStruct= this.adapter.validate(rObject, this.rObjectStruct, 0);
}
}
catch (final Exception e) {
synchronized (this.initRunnable) {
this.initScheduled= false;
}
checkCancel(e);
clear(Lock.RELOAD_STATE);
logError("An error occurred when initializing structure data for data viewer.", e);
this.realm.syncExec(new Runnable() {
@Override
public void run() {
for (final IDataProviderListener listener : AbstractRDataProvider.this.dataListeners.toList()) {
listener.onInputFailed(IDataProviderListener.ERROR_STRUCT_CHANGED);
}
}
});
return;
}
final RDataTableContentDescription description;
try {
description= loadDescription(this.input.getElementName(), this.rObjectStruct, r, m);
}
catch (final Exception e) {
synchronized (this.initRunnable) {
this.initScheduled= false;
}
checkCancel(e);
clear(Lock.RELOAD_STATE);
logError("An error occurred when initializing default formats for data viewer.", e);
return;
}
this.realm.syncExec(new Runnable() {
@Override
public void run() {
AbstractRDataProvider.this.description= description;
final long rowCount= AbstractRDataProvider.this.adapter.getRowCount(AbstractRDataProvider.this.rObjectStruct);
final boolean rowsChanged= (rowCount != getRowCount());
clear(0, rowCount, rowCount, true, true, true);
synchronized (AbstractRDataProvider.this.initRunnable) {
AbstractRDataProvider.this.initScheduled= false;
}
// if (rowsChanged) {
// for (final IDataProviderListener listener : dataListeners) {
// listener.onRowCountChanged();
// }
// }
for (final IDataProviderListener listener : AbstractRDataProvider.this.dataListeners.toList()) {
listener.onInputInitialized(rowsChanged);
}
}
});
}
final TmpUtils.Item getTmpItem() {
return this.rTmpItem;
}
private void runUpdate(final RToolService r, final ProgressMonitor m) throws StatusException {
int work= -1;
while (true) {
try {
final boolean updateSorting;
final boolean updateFiltering;
this.fragmentsLock.lock();
try {
switch (work) {
case -1:
this.fragmentsLock.scheduled= false;
break;
case 0:
if (!this.activeOperations.isEmpty()) {
try {
this.fragmentsLock.worker.await();
}
catch (final InterruptedException e) {}
break;
}
else {
return;
}
default:
break;
}
work= 0;
updateSorting= this.updateSorting;
updateFiltering= this.updateFiltering;
}
finally {
this.fragmentsLock.unlock();
}
FQRObjectRef elementRef= null;
if (updateSorting) {
if (elementRef == null) {
elementRef= checkElementRef(this.input.getElementRef(), r, m);
this.adapter.check(elementRef, this.rObjectStruct, r, m);
}
updateSorting(r, m);
work++;
}
if (updateFiltering) {
if (elementRef == null) {
elementRef= checkElementRef(this.input.getElementRef(), r, m);
this.adapter.check(elementRef, this.rObjectStruct, r, m);
}
updateFiltering(r, m);
work++;
}
if (this.updateIdx) {
if (elementRef == null) {
elementRef= checkElementRef(this.input.getElementRef(), r, m);
this.adapter.check(elementRef, this.rObjectStruct, r, m);
}
updateIdx(r, m);
work++;
}
if (work == 0
&& this.rowDataProvider instanceof AbstractRDataProvider<?>.RowDataProvider) {
final LazyRStore<RVector<?>> namesStore= ((RowDataProvider) this.rowDataProvider).rowNamesStore;
while (true) {
final Fragment<RVector<?>> fragment;
this.fragmentsLock.lock();
try {
fragment= namesStore.getNextScheduledFragment();
}
finally {
this.fragmentsLock.unlock();
}
if (fragment == null) {
break;
}
if (elementRef == null) {
elementRef= checkElementRef(this.input.getElementRef(), r, m);
this.adapter.check(elementRef, this.rObjectStruct, r, m);
}
final RVector<?> fragmentObject= this.adapter.loadRowNames(elementRef,
this.rObjectStruct, fragment, this.rCacheIdx, r, m);
this.fragmentsLock.lock();
try {
namesStore.updateFragment(fragment, fragmentObject);
this.fragmentsLock.notify(fragment);
}
finally {
this.fragmentsLock.unlock();
}
notifyListener(fragment);
work++;
}
}
if (work == 0) {
while (true) {
final Fragment<T> fragment;
this.fragmentsLock.lock();
try {
fragment= this.dataStore.getNextScheduledFragment();
}
finally {
this.fragmentsLock.unlock();
}
if (fragment == null) {
break;
}
if (elementRef == null) {
elementRef= checkElementRef(this.input.getElementRef(), r, m);
this.adapter.check(elementRef, this.rObjectStruct, r, m);
}
final T fragmentObject= this.adapter.loadData(elementRef,
this.rObjectStruct, fragment, this.rCacheIdx, r, m);
this.fragmentsLock.lock();
try {
this.dataStore.updateFragment(fragment, fragmentObject);
this.fragmentsLock.notify(fragment);
}
finally {
this.fragmentsLock.unlock();
}
notifyListener(fragment);
work++;
}
}
}
catch (final Exception e) {
checkCancel(e);
clear(Lock.RELOAD_STATE);
logError(NLS.bind("An error occurred when loading data of ''{0}'' for data viewer.",
this.input.getFullName()),
e );
return;
}
}
}
private FQRObjectRef checkElementRef(final FQRObjectRef elementRef,
final RService r, final ProgressMonitor m) throws StatusException, UnexpectedRDataException {
RObject env= elementRef.getEnv();
switch (env.getRObjectType()) {
case RObject.TYPE_REFERENCE:
return elementRef;
case RObject.TYPE_LANGUAGE:
env= RDataUtils.checkRReference(
r.evalData(((RLanguage) env).getSource(),
null, 0, RService.DEPTH_REFERENCE, m ),
RObject.TYPE_ENVIRONMENT );
return new BasicFQRObjectRef(elementRef.getRHandle(), env, elementRef.getName());
default:
throw new UnexpectedRDataException(
"Unexpected R object type: " + RDataUtils.getObjectTypeName(env.getRObjectType()) );
}
}
private void updateSorting(
final RToolService r, final ProgressMonitor m) throws StatusException, UnexpectedRDataException {
cleanSorting(m);
final SortColumn sortColumn;
this.fragmentsLock.lock();
try {
sortColumn= this.sortColumn;
this.updateSorting= false;
}
finally {
this.fragmentsLock.unlock();
}
if (sortColumn != null) {
if (this.rCacheSort == null) {
this.rCacheSort= this.rTmpItem.createSub("order"); //$NON-NLS-1$
}
final StringBuilder cmd= getRCmdStringBuilder();
appendOrderCmd(cmd, sortColumn);
this.rTmpItem.set(this.rCacheSort, cmd.toString(), m);
}
}
private void updateFiltering(
final RToolService r, final ProgressMonitor m) throws StatusException, UnexpectedRDataException {
cleanFiltering(m);
String filter;
this.fragmentsLock.lock();
try {
filter= this.filter;
this.updateFiltering= false;
}
finally {
this.fragmentsLock.unlock();
}
final long filteredRowCount;
if (filter == null) {
filteredRowCount= getFullRowCount();
}
else {
if (this.rCacheFilter == null) {
this.rCacheFilter= this.rTmpItem.createSub("include"); //$NON-NLS-1$
}
this.rTmpItem.set(this.rCacheFilter, filter, m);
{ final FunctionCall call= r.createFunctionCall(RJTmp.GET_FILTERED_COUNT);
call.addChar(RJTmp.FILTER_PAR, this.rCacheFilter);
filteredRowCount= RDataUtils.checkSingleIntValue(call.evalData(m));
}
}
this.realm.syncExec(new Runnable() {
@Override
public void run() {
clear(0, filteredRowCount, getFullRowCount(), false, false, false);
for (final IDataProviderListener listener : AbstractRDataProvider.this.dataListeners.toList()) {
listener.onRowCountChanged();
}
}
});
}
private void updateIdx(
final RToolService r, final ProgressMonitor m) throws StatusException, UnexpectedRDataException {
cleanIdx(m);
if (this.rCacheSort != null || this.rCacheFilter != null) {
if (this.rCacheIdx == null) {
this.rCacheIdx= this.rTmpItem.createSub("idx"); //$NON-NLS-1$
}
if (this.rCacheFilter == null) { // fRCacheSort != null
final StringBuilder cmd= getRCmdStringBuilder();
cmd.append(TmpUtils.ENV_FQ_NAME+'$').append(this.rCacheSort);
this.rTmpItem.set(this.rCacheIdx, cmd.toString(), m);
}
else if (this.rCacheSort == null) { // fRCacheFilter != null
final FunctionCall call= r.createFunctionCall(RJTmp.SET_WHICH_INDEX);
call.addChar(RJTmp.NAME_PAR, this.rCacheIdx);
call.addChar(RJTmp.FILTER_PAR, this.rCacheFilter);
call.evalVoid(m);
}
else { // fRCacheSort != null && fRCacheFilter != null
final FunctionCall call= r.createFunctionCall(RJTmp.SET_FILTERED_INDEX);
call.addChar(RJTmp.NAME_PAR, this.rCacheIdx);
call.addChar(RJTmp.FILTER_PAR, this.rCacheFilter);
call.addChar(RJTmp.INDEX_PAR, this.rCacheSort);
call.evalVoid(m);
}
}
this.updateIdx= false;
}
String checkFilter() {
return this.rCacheFilter;
}
String checkRevIndex(final RToolService r, final ProgressMonitor m) throws StatusException {
if (this.rCacheIdx != null) {
if (this.rCacheIdxR == null) {
final String name= this.rTmpItem.createSub("idx.r"); //$NON-NLS-1$
try {
final FunctionCall call= r.createFunctionCall(RJTmp.SET_REVERSE_INDEX);
call.addChar(RJTmp.NAME_PAR, name);
call.addChar(RJTmp.INDEX_PAR, this.rCacheIdx);
call.addNum(RJTmp.LEN_PAR, getFullRowCount());
call.evalVoid(m);
return this.rCacheIdxR= name;
}
finally {
if (this.rCacheIdxR == null) {
this.rTmpItem.remove(name, m);
}
}
}
return this.rCacheIdxR;
}
else {
return null;
}
}
protected abstract RDataTableContentDescription loadDescription(RElementName name,
T struct,
RToolService r, ProgressMonitor m) throws StatusException, UnexpectedRDataException;
protected RDataTableColumn createColumn(final RStore store, final String expression,
final RElementName elementName, final long columnIndex, final String columnName,
final RToolService r, final ProgressMonitor m) throws StatusException, UnexpectedRDataException {
final ImList<String> classNames;
RObject rObject;
{ final FunctionCall call= r.createFunctionCall("class"); //$NON-NLS-1$
call.add(expression);
rObject= call.evalData(m);
final RVector<RCharacterStore> names= RDataUtils.checkRCharVector(rObject);
classNames= ImCollections.newList(names.getData().toArray());
}
RDataTableColumn column;
final RDataFormatter format= new RDataFormatter();
switch (store.getStoreType()) {
case RStore.LOGICAL:
format.setAutoWidth(5);
column= new RDataTableColumn(columnIndex, columnName, expression, elementName,
IRDataTableVariable.LOGI, store, classNames, format);
break;
case RStore.NUMERIC:
if (checkDateFormat(expression, classNames, format, r, m)) {
column= new RDataTableColumn(columnIndex, columnName, expression, elementName,
IRDataTableVariable.DATE, store, classNames, format);
break;
}
if (checkDateTimeFormat(expression, classNames, format, r, m)) {
column= new RDataTableColumn(columnIndex, columnName, expression, elementName,
IRDataTableVariable.DATETIME, store, classNames, format);
break;
}
{ final FunctionCall call= r.createFunctionCall("rj:::.getFormatInfo"); //$NON-NLS-1$
call.add("x", expression); //$NON-NLS-1$
rObject= call.evalData(m);
}
{ final RIntegerStore formatInfo= RDataUtils.checkRIntVector(rObject).getData();
RDataUtils.checkLengthGreaterOrEqual(formatInfo, 3);
format.setAutoWidth(Math.max(formatInfo.getInt(0), 3));
format.initNumFormat(formatInfo.getInt(1), formatInfo.getInt(2) > 0 ?
formatInfo.getInt(2) + 1 : 0);
column= new RDataTableColumn(columnIndex, columnName, expression, elementName,
IRDataTableVariable.NUM, store, classNames, format);
break;
}
case RStore.INTEGER:
if (checkDateFormat(expression, classNames, format, r, m)) {
column= new RDataTableColumn(columnIndex, columnName, expression, elementName,
IRDataTableVariable.DATE, store, classNames, format);
break;
}
if (checkDateTimeFormat(expression, classNames, format, r, m)) {
column= new RDataTableColumn(columnIndex, columnName, expression, elementName,
IRDataTableVariable.DATETIME, store, classNames, format);
break;
}
{ final FunctionCall call= r.createFunctionCall("rj:::.getFormatInfo"); //$NON-NLS-1$
call.add("x", expression); //$NON-NLS-1$
rObject= call.evalData(m);
}
{ final RIntegerStore formatInfo= RDataUtils.checkRIntVector(rObject).getData();
RDataUtils.checkLengthGreaterOrEqual(formatInfo, 1);
format.setAutoWidth(Math.max(formatInfo.getInt(0), 3));
column= new RDataTableColumn(columnIndex, columnName, expression, elementName,
IRDataTableVariable.INT, store, classNames, format);
break;
}
case RStore.CHARACTER:
{ final FunctionCall call= r.createFunctionCall("rj:::.getFormatInfo"); //$NON-NLS-1$
call.add("x", expression); //$NON-NLS-1$
rObject= call.evalData(m);
}
{ final RIntegerStore formatInfo= RDataUtils.checkRIntVector(rObject).getData();
RDataUtils.checkLengthGreaterOrEqual(formatInfo, 1);
format.setAutoWidth(Math.max(formatInfo.getInt(0), 3));
column= new RDataTableColumn(columnIndex, columnName, expression, elementName,
IRDataTableVariable.CHAR, store, classNames, format);
break;
}
case RStore.COMPLEX:
{ final FunctionCall call= r.createFunctionCall("rj:::.getFormatInfo"); //$NON-NLS-1$
call.add("x", expression); //$NON-NLS-1$
rObject= call.evalData(m);
}
{ final RIntegerStore formatInfo= RDataUtils.checkRIntVector(rObject).getData();
RDataUtils.checkLengthGreaterOrEqual(formatInfo, 3);
format.setAutoWidth(Math.max(formatInfo.getInt(0), 3));
format.initNumFormat(formatInfo.getInt(1), formatInfo.getInt(2) > 0 ?
formatInfo.getInt(2) + 1 : 0);
column= new RDataTableColumn(columnIndex, columnName, expression, elementName,
IRDataTableVariable.CPLX, store, classNames, format);
break;
}
case RStore.RAW:
format.setAutoWidth(2);
column= new RDataTableColumn(columnIndex, columnName, expression, elementName,
IRDataTableVariable.RAW, store, classNames, format);
break;
case RStore.FACTOR:
{ final FunctionCall call= r.createFunctionCall("levels"); //$NON-NLS-1$
call.add(expression);
rObject= call.evalData(m);
}
{ format.setAutoWidth(3);
final RCharacterStore levels= RDataUtils.checkRCharVector(rObject).getData();
final int l= RDataUtils.checkIntLength(levels);
for (int i= 0; i < l; i++) {
if (!levels.isNA(i)) {
final int length= levels.getChar(i).length();
if (length > format.getAutoWidth()) {
format.setAutoWidth(length);
}
}
}
format.initFactorLevels(levels);
column= new RDataTableColumn(columnIndex, columnName, expression, elementName,
IRDataTableVariable.FACTOR, RFactorStructStore.addLevels((RFactorStore) store, levels),
classNames, format);
break;
}
default:
throw new UnexpectedRDataException("store type: " + store.getStoreType());
}
return column;
}
protected boolean checkDateFormat(final String expression, final List<String> classNames,
final RDataFormatter formatter,
final RToolService r, final ProgressMonitor m) throws StatusException, UnexpectedRDataException {
if (classNames.contains("Date")) { //$NON-NLS-1$
formatter.initDateFormat(RDataFormatter.MILLIS_PER_DAY);
formatter.setAutoWidth(10);
return true;
}
return false;
}
protected boolean checkDateTimeFormat(final String expression, final List<String> classNames,
final RDataFormatter formatter,
final RToolService r, final ProgressMonitor m) throws StatusException, UnexpectedRDataException {
RObject rObject;
if (classNames.contains("POSIXct")) { //$NON-NLS-1$
formatter.initDateTimeFormat(RDataFormatter.MILLIS_PER_SECOND);
formatter.setAutoWidth(27);
{ final FunctionCall call= r.createFunctionCall("base::attr"); //$NON-NLS-1$
call.add(expression);
call.addChar("tzone"); //$NON-NLS-1$
rObject= call.evalData(m);
}
if (rObject.getRObjectType() != RObject.TYPE_NULL) {
formatter.setDateTimeZone(TimeZone.getTimeZone(RDataUtils.checkSingleCharValue(rObject)));
}
return true;
}
return false;
}
protected RDataTableColumn createNamesColumn(final String expression, final long count,
final RToolService r, final ProgressMonitor m) throws StatusException, UnexpectedRDataException {
final RObject names= r.evalData(expression, null, RObjectFactory.F_ONLY_STRUCT, 1, m);
if (names != null && names.getRObjectType() == RObject.TYPE_VECTOR
&& names.getLength() == count
&& (names.getData().getStoreType() == RStore.CHARACTER
|| names.getData().getStoreType() == RStore.INTEGER)) {
return createColumn(names.getData(), expression, null, -1, null, r, m);
}
return createAutoNamesColumn(count);
}
private RDataTableColumn createAutoNamesColumn(final long count) {
final RDataFormatter format= new RDataFormatter();
format.setAutoWidth(Math.max(Long.toString(count).length(), 3));
return new RDataTableColumn(-1, null, null, null,
IRDataTableVariable.INT, DefaultRObjectFactory.INT_STRUCT_DUMMY,
ImCollections.newList(RObject.CLASSNAME_INTEGER),
format);
}
protected abstract void appendOrderCmd(StringBuilder cmd, SortColumn sortColumn);
private void runClean(final RToolService r, final ProgressMonitor m) throws StatusException {
clear(Lock.ERROR_STATE);
cleanSorting(m);
cleanFiltering(m);
this.findManager.clean(m);
this.rTmpItem.dispose(m);
this.rTmpItem= null;
}
private void cleanSorting(final ProgressMonitor m) throws StatusException {
cleanIdx(m);
if (this.rCacheSort != null) {
this.rTmpItem.remove(this.rCacheSort, m);
this.rCacheSort= null;
}
}
private void cleanFiltering(final ProgressMonitor m) throws StatusException {
cleanIdx(m);
if (this.rCacheFilter != null) {
this.rTmpItem.remove(this.rCacheFilter, m);
this.rCacheFilter= null;
}
}
private void cleanIdx(final ProgressMonitor m) throws StatusException {
this.updateIdx= true;
if (this.rCacheIdx != null) {
this.rTmpItem.remove(this.rCacheIdx, m);
this.rCacheIdx= null;
}
if (this.rCacheIdxR != null) {
this.rTmpItem.remove(this.rCacheIdxR, m);
this.rCacheIdxR= null;
}
}
protected final StringBuilder getRCmdStringBuilder() {
this.rStringBuilder.setLength(0);
return this.rStringBuilder;
}
public boolean getAllColumnsEqual() {
return false;
}
public RDataTableContentDescription getDescription() {
return this.description;
}
@Override
public long getColumnCount() {
return this.columnCount;
}
public long getFullRowCount() {
return this.fullRowCount;
}
@Override
public long getRowCount() {
return this.rowCount;
}
@Override
public Object getDataValue(final long columnIndex, final long rowIndex, final int flags,
final IProgressMonitor monitor) {
try {
final LazyRStore.Fragment<T> fragment= this.fragmentsLock.getFragment(
this.dataStore, rowIndex, columnIndex,
flags, StatusUtils.convert(monitor) );
if (fragment != null) {
return getDataValue(fragment, rowIndex, columnIndex);
}
else {
return handleMissingData(flags);
}
}
catch (final LoadDataException e) {
return handleLoadDataException(e, flags);
}
}
protected abstract Object getDataValue(LazyRStore.Fragment<T> fragment, long rowIdx, long columnIdx);
@Override
public void setDataValue(final long columnIndex, final long rowIndex, final Object newValue) {
throw new UnsupportedOperationException();
}
protected abstract Object getColumnName(LazyRStore.Fragment<T> fragment, long columnIdx);
public boolean hasRealColumns() {
return true;
}
public boolean hasRealRows() {
return true;
}
public IDataProvider getColumnDataProvider() {
return this.columnDataProvider;
}
public IDataProvider getRowDataProvider() {
return this.rowDataProvider;
}
public IDataProvider getColumnLabelProvider() {
return this.columnLabelProvider;
}
public IDataProvider getRowLabelProvider() {
return this.rowLabelProvider;
}
protected IDataProvider createColumnDataProvider() {
return new ColumnDataProvider();
}
protected IDataProvider createRowDataProvider() {
return new RowDataProvider();
}
protected IDataProvider createColumnLabelProvider() {
return null;
}
protected IDataProvider createRowLabelProvider() {
return null;
}
protected ISortModel createSortModel() {
return new SortModel();
}
private Object handleMissingData(final int flags) {
return ((flags & IDataProvider.FORCE_SYNC) != 0) ? ERROR : LOADING;
}
private Object handleLoadDataException(final LoadDataException e, final int flags) {
if (!e.isRecoverable()) {
return ERROR;
}
reset();
return handleMissingData(flags);
}
public ISortModel getSortModel() {
return this.sortModel;
}
public SortColumn getSortColumn() {
return this.sortColumn;
}
private void setSortColumn(final SortColumn column) {
this.fragmentsLock.lock();
try {
if (Objects.equals(this.sortColumn, column)) {
return;
}
this.sortColumn= column;
clear(-1, -1, -1, true, false, false);
this.findManager.reset(false);
}
finally {
this.fragmentsLock.unlock();
}
}
public void setFilter(final String filter) {
this.fragmentsLock.lock();
try {
if ((this.filter != null) ? this.filter.equals(filter) : null == filter) {
return;
}
this.filter= filter;
clear(-1, -1, -1, false, true, false);
this.findManager.reset(true);
this.fragmentsLock.scheduleUpdate(null, null, null, 0, null);
}
finally {
this.fragmentsLock.unlock();
}
}
public String getFilter() {
return this.filter;
}
public void addFindListener(final IFindListener listener) {
this.findManager.addFindListener(listener);
}
public void removeFindListener(final IFindListener listener) {
this.findManager.removeFindListener(listener);
}
public void find(final FindTask task) {
this.findManager.find(task);
}
public long[] toDataIdxs(final long columnIndex, final long rowIndex) {
if (getFilter() != null || getSortColumn() != null) {
if (this.rowDataProvider instanceof AbstractRDataProvider.RowDataProvider) {
final long rowIdx= ((AbstractRDataProvider.RowDataProvider) this.rowDataProvider)
.getRowIdx(rowIndex);
return new long[] { columnIndex, rowIdx };
}
return new long[] { columnIndex, -2 };
}
return new long[] { columnIndex, rowIndex };
}
public void addDataChangedListener(final IDataProviderListener listener) {
this.dataListeners.add(listener);
}
public void removeDataChangedListener(final IDataProviderListener listener) {
this.dataListeners.remove(listener);
}
protected void notifyListener(final LazyRStore.Fragment<?> item) {
try {
for (final IDataProviderListener listener : this.dataListeners.toList()) {
listener.onRowsChanged(item.getRowBeginIdx(), item.getRowEndIdx());
}
}
catch (final Exception e) {
logError("An error occurred when notifying about row updates.", e);
}
}
public void reset() {
clear(Lock.PAUSE_STATE);
synchronized (this.initRunnable) {
if (this.initScheduled) {
return;
}
this.initScheduled= true;
}
try {
final Status status= this.input.getTool().getQueue().add(this.initRunnable);
if (status.getSeverity() >= Status.ERROR) {
throw new StatusException(status);
}
}
catch (final StatusException e) {
}
}
private void clear(final int newState) {
clear(newState, -1, -1, true, true, true);
}
private void clear(final int newState, final long filteredRowCount, final long fullRowCount,
final boolean updateSorting, final boolean updateFiltering,
final boolean clearFind) {
this.fragmentsLock.lock();
try {
this.dataStore.clear(filteredRowCount);
if (this.rowDataProvider instanceof AbstractRDataProvider<?>.RowDataProvider) {
((RowDataProvider) this.rowDataProvider).rowNamesStore.clear(filteredRowCount);
}
this.updateSorting |= updateSorting;
this.updateFiltering |= updateFiltering;
if (newState >= 0 && this.fragmentsLock.state < Lock.ERROR_STATE) {
this.fragmentsLock.state= newState;
}
this.fragmentsLock.clear();
if (filteredRowCount >= 0) {
this.rowCount= filteredRowCount;
this.fullRowCount= fullRowCount;
}
if (clearFind) {
this.findManager.clear(newState);
}
}
finally {
this.fragmentsLock.unlock();
}
}
public void dispose() {
this.disposeScheduled= true;
schedule(this.cleanRunnable);
}
}