blob: 6645686a62e104b31e19f346571a316fdb0248c1 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2012, 2021 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.datafilter;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.osgi.util.NLS;
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.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.internal.r.ui.dataeditor.RDataTableContentDescription;
import org.eclipse.statet.r.ui.dataeditor.IRDataTableVariable;
import org.eclipse.statet.r.ui.dataeditor.RDataTableColumn;
import org.eclipse.statet.rj.data.RFactorStore;
import org.eclipse.statet.rj.data.UnexpectedRDataException;
import org.eclipse.statet.rj.ts.core.RToolService;
public class FilterSet {
private final static int POST_DELAY= 400;
private final static int STD_DELAY= 1;
private final static int NO_DELAY= 2;
private RDataTableContentDescription input;
private boolean inputUpdate;
private final Object updateLock= new Object();
private boolean updateScheduled;
private final ToolRunnable updateRunnable= new SystemRunnable() {
@Override
public String getTypeId() {
return "r/datafilter/load"; //$NON-NLS-1$
}
@Override
public String getLabel() {
return NLS.bind(Messages.UpdateJob_label, FilterSet.this.input.getLabel());
}
@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:
synchronized (FilterSet.this.updateLock) {
FilterSet.this.updateScheduled= false;
FilterSet.this.updateLock.notifyAll();
}
break;
default:
break;
}
return true;
}
@Override
public void run(final ToolService service, final ProgressMonitor m) throws StatusException {
runUpdate((RToolService) service, m);
}
};
private boolean updateAll;
private final List<VariableFilter> filters= new ArrayList<>();
private final List<String> filterNames= new ArrayList<>();
private final CopyOnWriteIdentityListSet<IFilterListener> listeners= new CopyOnWriteIdentityListSet<>();
private final CopyOnWriteIdentityListSet<IFilterListener> postListeners= new CopyOnWriteIdentityListSet<>();
private volatile int listenerScheduled;
private final Runnable listenerRunnable= new Runnable() {
@Override
public void run() {
final int schedule= FilterSet.this.listenerScheduled;
FilterSet.this.listenerScheduled= 0;
for (final IFilterListener listener : FilterSet.this.listeners.toList()) {
listener.filterChanged();
}
if (schedule != NO_DELAY) {
FilterSet.this.postListenerTime= System.nanoTime() + POST_DELAY;
if (FilterSet.this.postListenerScheduled) {
return;
}
FilterSet.this.postListenerScheduled= true;
FilterSet.this.realm.timerExec(POST_DELAY, FilterSet.this.postListenerRunnable);
}
else {
FilterSet.this.postListenerTime= System.nanoTime();
FilterSet.this.postListenerScheduled= true;
FilterSet.this.postListenerRunnable.run();
}
}
};
private boolean postListenerScheduled;
private long postListenerTime;
private final Runnable postListenerRunnable= new Runnable() {
@Override
public void run() {
if (FilterSet.this.listenerScheduled > 0) {
FilterSet.this.postListenerScheduled= false;
return;
}
final long time= FilterSet.this.postListenerTime - System.nanoTime();
if (time > 20) {
FilterSet.this.realm.timerExec((int) time, this);
return;
}
FilterSet.this.postListenerScheduled= false;
for (final IFilterListener listener : FilterSet.this.postListeners.toList()) {
listener.filterChanged();
}
}
};
private final Realm realm;
private boolean enabled;
private String filterRExpression;
public FilterSet(final Realm realm) {
this.realm= realm;
this.enabled= true;
}
public Realm getRealm() {
return this.realm;
}
protected void runInRealm(final Runnable runnable) {
if (this.realm.isCurrent()) {
runnable.run();
}
else {
this.realm.asyncExec(runnable);
}
}
public void updateInput(final RDataTableContentDescription input) {
synchronized (this.updateLock) {
this.input= input;
this.inputUpdate= true;
}
synchronized (this) {
int idx= 0;
if (input != null) {
final List<RDataTableColumn> columns= input.getDataColumns();
for (; idx < columns.size(); idx++) {
final RDataTableColumn column= columns.get(idx);
if (column.getRExpression() == null || column.getName() == null) {
continue;
}
final VariableFilter filter= createFilter(getDefaultFilter(column), column);
if (filter == null) {
continue;
}
final int oldIdx= this.filterNames.indexOf(column.getName());
if (oldIdx >= 0) {
final VariableFilter oldFilter= this.filters.get(oldIdx);
filter.load(oldFilter);
if (idx != oldIdx) {
this.filterNames.remove(oldIdx);
this.filters.remove(oldIdx);
this.filterNames.add(idx, column.getName());
this.filters.add(idx, filter);
}
else {
this.filters.set(idx, filter);
}
filterReplaced(idx, oldFilter, filter);
continue;
}
this.filterNames.add(idx, column.getName());
this.filters.add(idx, filter);
filterAdded(idx, filter);
}
}
while (this.filters.size() > idx) {
this.filterNames.remove(idx);
final VariableFilter oldFilter= this.filters.remove(idx);
filterRemoved(oldFilter);
}
}
synchronized (this.updateLock) {
this.inputUpdate= false;
scheduleUpdate(true);
}
}
protected void filterRemoved(final VariableFilter oldFilter) {
}
protected void filterReplaced(final int idx, final VariableFilter oldFilter, final VariableFilter newFilter) {
}
protected void filterAdded(final int idx, final VariableFilter newFilter) {
}
public FilterType getDefaultFilter(final RDataTableColumn column) {
switch (column.getVarType()) {
case IRDataTableVariable.LOGI:
case IRDataTableVariable.FACTOR:
case IRDataTableVariable.RAW:
return FilterType.LEVEL;
case IRDataTableVariable.INT:
case IRDataTableVariable.NUM:
case IRDataTableVariable.DATE:
case IRDataTableVariable.DATETIME:
return FilterType.INTERVAL;
case IRDataTableVariable.CHAR:
return FilterType.TEXT;
default:
return null;
}
}
public ImList<FilterType> getAvailableFilters(final RDataTableColumn column) {
switch (column.getVarType()) {
case IRDataTableVariable.LOGI:
case IRDataTableVariable.RAW:
return ImCollections.newList(FilterType.LEVEL);
case IRDataTableVariable.FACTOR:
if (((RFactorStore) column.getDataStore()).isOrdered()) {
return ImCollections.newList(FilterType.LEVEL, FilterType.INTERVAL);
}
return ImCollections.newList(FilterType.LEVEL);
case IRDataTableVariable.INT:
case IRDataTableVariable.NUM:
case IRDataTableVariable.DATE:
case IRDataTableVariable.DATETIME:
return ImCollections.newList(FilterType.INTERVAL, FilterType.LEVEL);
case IRDataTableVariable.CHAR:
return ImCollections.newList(FilterType.TEXT, FilterType.LEVEL);
default:
return null;
}
}
public VariableFilter replace(final VariableFilter currentFilter, final FilterType filterType) {
synchronized (this.updateLock) {
this.inputUpdate= true;
}
final VariableFilter filter;
synchronized (this) {
final int idx= this.filters.indexOf(currentFilter);
if (idx < 0) {
return currentFilter;
}
filter= createFilter(filterType, currentFilter.getColumn());
if (filter == null) {
return currentFilter;
}
filter.load(currentFilter);
this.filters.set(idx, filter);
filterReplaced(idx, currentFilter, filter);
}
synchronized (this.updateLock) {
this.inputUpdate= false;
filter.scheduleUpdate();
}
return filter;
}
protected VariableFilter createFilter(final FilterType filterType, final RDataTableColumn column) {
if (filterType == null) {
return null;
}
switch (filterType.getId()) {
case 0:
return new LevelVariableFilter(this, column);
case 1:
return new IntervalVariableFilter(this, column);
case 2:
return new TextVariableFilter(this, column);
default:
throw new UnsupportedOperationException(filterType.toString());
}
}
public synchronized ImList<VariableFilter> getFilters() {
return ImCollections.toList(this.filters);
}
protected void scheduleUpdate(final boolean all) {
synchronized (this.updateLock) {
if (all) {
this.updateAll= true;
}
if (this.updateScheduled || this.inputUpdate) {
return;
}
if (this.input != null) {
this.input.getRHandle().getQueue().add(this.updateRunnable);
this.updateScheduled= true;
}
}
}
protected Tool getTool() {
final RDataTableContentDescription input= this.input;
return (input != null) ? input.getRHandle() : null;
}
private void runUpdate(final RToolService r, final ProgressMonitor m) {
final boolean all;
synchronized (this.updateLock) {
this.updateScheduled= false;
if (this.inputUpdate || this.input == null || this.input.getRHandle() != r.getTool()) {
return;
}
all= this.updateAll;
this.updateAll= false;
}
final VariableFilter[] filters;
synchronized (this) {
filters= this.filters.toArray(new VariableFilter[this.filters.size()]);
}
for (int i= 0; i < filters.length; i++) {
final VariableFilter filter= filters[i];
if (all || filter.updateScheduled) {
filter.updateScheduled= false;
Exception error= null;
try {
filter.update(r, m);
}
catch (final StatusException e) {
error= e;
}
catch (final UnexpectedRDataException e) {
error= e;
}
if (error != null) {
error.printStackTrace();
filters[i].setError(error.getMessage());
}
}
}
updateFilter(true);
}
public void addListener(final IFilterListener listener) {
this.listeners.add(listener);
}
public void removeListener(final IFilterListener listener) {
this.listeners.remove(listener);
}
public void addPostListener(final IFilterListener listener) {
this.postListeners.add(listener);
}
public void removePostListener(final IFilterListener listener) {
this.postListeners.remove(listener);
}
public String getFilterRExpression() {
return this.filterRExpression;
}
public String getFilterRExpression(String varExpression, final int nameFlags) {
final VariableFilter[] filters;
synchronized (this) {
if (this.inputUpdate || this.input == null ) {
return null;
}
filters= this.filters.toArray(new VariableFilter[this.filters.size()]);
}
if (varExpression == null) {
varExpression= this.input.getElementName().getDisplayName(nameFlags);
}
final StringBuilder sb= new StringBuilder();
for (int i= 0; i < filters.length; i++) {
final VariableFilter filter= filters[i];
final String rExpression= filter.getFilterRExpression(varExpression, nameFlags);
if (rExpression != null && !rExpression.isEmpty()) {
if (sb.length() > 0) {
sb.append(" & "); //$NON-NLS-1$
}
sb.append(rExpression);
}
}
return sb.toString();
}
public void setEnabled(final boolean enabled) {
if (this.enabled == enabled) {
return;
}
this.enabled= enabled;
notifyListeners(NO_DELAY);
}
public boolean getEnabled() {
return this.enabled;
}
void updateFilter(final boolean delay) {
final VariableFilter[] filters;
synchronized (this) {
if (this.inputUpdate || this.input == null ) {
return;
}
filters= this.filters.toArray(new VariableFilter[this.filters.size()]);
}
final StringBuilder sb= new StringBuilder();
for (int i= 0; i < filters.length; i++) {
final VariableFilter filter= filters[i];
final String rExpression= filter.getFilterRExpression();
if (rExpression != null && !rExpression.isEmpty()) {
if (sb.length() > 0) {
sb.append(" & "); //$NON-NLS-1$
}
sb.append(rExpression);
}
}
String filterRExpression;
if (sb.length() == 0) {
filterRExpression= null;
if (this.filterRExpression == null) {
return;
}
}
else {
filterRExpression= sb.toString();
if (filterRExpression.equals(this.filterRExpression)) {
return;
}
}
this.filterRExpression= filterRExpression;
notifyListeners((delay) ? STD_DELAY : NO_DELAY);
}
private void notifyListeners(final int mode) {
final int schedule= this.listenerScheduled;
if (schedule >= mode) {
return;
}
this.listenerScheduled= mode;
runInRealm(this.listenerRunnable);
}
}