| /*=============================================================================# |
| # Copyright (c) 2010, 2020 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.debug.core.model; |
| |
| import static org.eclipse.statet.internal.r.debug.core.model.RElementVariable.DEFAULT_FRAGMENT_COUNT; |
| import static org.eclipse.statet.rj.services.util.dataaccess.LazyRStore.DEFAULT_FRAGMENT_SIZE; |
| |
| import java.util.Objects; |
| |
| import org.eclipse.debug.core.DebugEvent; |
| import org.eclipse.debug.core.DebugException; |
| import org.eclipse.debug.core.model.IVariable; |
| |
| 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.status.ProgressMonitor; |
| import org.eclipse.statet.jcommons.status.StatusException; |
| |
| import org.eclipse.statet.ecommons.debug.core.model.VariablePartitionFactory; |
| |
| import org.eclipse.statet.r.core.data.CombinedRElement; |
| import org.eclipse.statet.r.core.data.RValueFormatter; |
| import org.eclipse.statet.r.core.data.RValueValidator; |
| import org.eclipse.statet.r.debug.core.IRVariable; |
| import org.eclipse.statet.rj.data.RArray; |
| import org.eclipse.statet.rj.data.RDataUtils; |
| import org.eclipse.statet.rj.data.RIntegerStore; |
| 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.services.FQRObjectRef; |
| 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.RArrayAsVectorDataAdapter; |
| import org.eclipse.statet.rj.services.util.dataaccess.RDataAssignment; |
| import org.eclipse.statet.rj.ts.core.RToolService; |
| |
| |
| @NonNullByDefault |
| public class RArrayValue extends RElementVariableValue<CombinedRElement> |
| implements IRIndexValueInternal { |
| |
| |
| private static final RArrayAsVectorDataAdapter ADAPTER= new RArrayAsVectorDataAdapter(); |
| |
| |
| final long length; |
| final RIntegerStore dim; |
| final int dimCount; |
| |
| private @Nullable LazyRStore<RVector<?>> dimNameStore; |
| private @Nullable LazyRStore<RVector<?>>[] dimItemNameStore; |
| private @Nullable LazyRStore<RVector<?>> dataStore; |
| |
| |
| public RArrayValue(final RElementVariable variable) { |
| super(variable); |
| |
| final RArray<?> element= getRObject(); |
| this.length= element.getLength(); |
| this.dim= element.getDim(); |
| this.dimCount= (int) this.dim.getLength(); |
| } |
| |
| |
| public final RArray<?> getRObject() { |
| return (RArray<?>) this.element; |
| } |
| |
| |
| public boolean hasValueChanged(final int[] dimIndex) throws DebugException { |
| final RArrayValue previousValue; |
| synchronized (this.variable) { |
| if (this != this.variable.getCurrentValue()) { |
| return false; |
| } |
| previousValue= (RArrayValue) this.variable.getPreviousValue(); |
| } |
| |
| if (previousValue != null) { |
| for (int i= 0; i < this.dimCount; i++) { |
| if (dimIndex[i] >= previousValue.dim.getInt(i)) { |
| return true; |
| } |
| } |
| final long previousIdx= RDataUtils.getDataIdx(previousValue.dim, dimIndex); |
| final long currentIdx= RDataUtils.getDataIdx(this.dim, dimIndex); |
| final Fragment<RVector<?>> previousFragment; |
| final Fragment<RVector<?>> currentFragment; |
| synchronized (previousValue) { |
| previousFragment= previousValue.getLoadedDataFragment(previousIdx); |
| if (previousFragment == null || previousFragment.getRObject() == null) { |
| return false; |
| } |
| } |
| synchronized (this) { |
| currentFragment= getDataFragment(currentIdx); |
| if (currentFragment == null || currentFragment.getRObject() == null) { |
| return false; |
| } |
| } |
| return (!Objects.equals( |
| currentFragment.getRObject().getData().get( |
| currentFragment.toLocalRowIdx(currentIdx) ), |
| previousFragment.getRObject().getData().get( |
| previousFragment.toLocalRowIdx(previousIdx) ))); |
| } |
| return false; |
| } |
| |
| |
| @Override |
| public String getValueString() throws DebugException { |
| final StringBuilder sb= new StringBuilder(); |
| sb.append('['); |
| if (this.dimCount > 0) { |
| sb.append(this.dim.getInt(0)); |
| for (int i= 1; i < this.dimCount; i++) { |
| sb.append('×'); |
| sb.append(this.dim.getInt(i)); |
| } |
| } |
| sb.append(']'); |
| |
| if (this.dimCount > 0) { |
| final String dimName= getDimName(this.dimCount - 1); |
| if (dimName != null) { |
| sb.append(" / "); //$NON-NLS-1$ |
| sb.append(dimName); |
| } |
| } |
| |
| return sb.toString(); |
| } |
| |
| @Override |
| public String getDetailString() { |
| return ""; //$NON-NLS-1$ |
| } |
| |
| |
| @Override |
| public boolean hasVariables() throws DebugException { |
| return (this.length > 0 && this.dim.getInt(this.dimCount - 1) > 0); |
| } |
| |
| @Override |
| public @NonNull IVariable[] getVariables() throws DebugException { |
| return getPartitionFactory().getVariables(this); |
| } |
| |
| |
| @Override |
| public final VariablePartitionFactory<IRIndexElementValue> getPartitionFactory() { |
| return RElementValue.PARTITION_FACTORY; |
| } |
| |
| @Override |
| public long getSize() throws DebugException { |
| return (this.length > 0) ? this.dim.getInt(this.dimCount - 1) : 0; |
| } |
| |
| @Override |
| public @NonNull IRVariable[] getVariables(final long offset, final int length) { |
| return getVariables(offset, length, this.variable); |
| } |
| |
| @Override |
| public @NonNull IRVariable[] getVariables(final long offset, final int length, final IRVariable parent) { |
| { final int n= this.dim.getInt(this.dimCount - 1); |
| if (n <= 0) { |
| throw new UnsupportedOperationException(); |
| } |
| if (offset < 0 || length < 0 || offset > n - length) { |
| throw new IllegalArgumentException(); |
| } |
| } |
| final int o= (int) offset; |
| final @NonNull IRVariable[] variables= new @NonNull IRVariable[length]; |
| if (this.dimCount == 1) { |
| for (int i= 0; i < length; i++) { |
| final int[] d= new int[] { o + i }; |
| variables[i]= new RArrayIndexVariable(this, d, parent); |
| } |
| } |
| else { |
| for (int i= 0; i < length; i++) { |
| final int[] d= new int[] { o + i }; |
| variables[i]= new RArrayDimVariable(this, d, parent); |
| } |
| } |
| return variables; |
| } |
| |
| |
| protected @Nullable String getDimName(final int dimIdx) { |
| synchronized (this.variable) { |
| if (this != this.variable.getCurrentValue()) { |
| return null; |
| } |
| } |
| |
| final Fragment<RVector<?>> fragment; |
| synchronized (this) { |
| fragment= getDimNameFragment(dimIdx); |
| if (fragment == null || fragment.getRObject() == null) { |
| return null; |
| } |
| } |
| |
| final RValueFormatter formatter= getDebugTarget().getValueFormatter(); |
| synchronized (formatter) { |
| return formatter.formatName( |
| fragment.getRObject().getData(), (int) fragment.toLocalRowIdx(dimIdx) ); |
| } |
| } |
| |
| private @Nullable Fragment<RVector<?>> getDimNameFragment(final int dimIdx) { |
| if (this.dimNameStore == null) { |
| this.dimNameStore= new LazyRStore<>(this.dimCount, 1, |
| DEFAULT_FRAGMENT_COUNT, |
| new RDataLoader<RVector<?>>() { |
| @Override |
| protected RVector<?> doLoad(final FQRObjectRef ref, |
| final Fragment<RVector<?>> fragment, |
| final RToolService r, final ProgressMonitor m) throws StatusException, UnexpectedRDataException { |
| return ADAPTER.loadDimNames(ref, getRObject(), fragment, |
| r, m ); |
| } |
| }); |
| } |
| return this.dimNameStore.getFragment(dimIdx, 0, 0, null); |
| } |
| |
| |
| protected @Nullable String getDimItemName(final int dimIdx, final int idx) { |
| synchronized (this.variable) { |
| if (this != this.variable.getCurrentValue()) { |
| return null; |
| } |
| } |
| |
| final Fragment<RVector<?>> fragment; |
| synchronized (this) { |
| fragment= getDimItemNamesFragment(dimIdx, idx); |
| if (fragment == null || fragment.getRObject() == null) { |
| return null; |
| } |
| } |
| |
| final RValueFormatter formatter= getDebugTarget().getValueFormatter(); |
| synchronized (formatter) { |
| return formatter.formatName( |
| fragment.getRObject().getData(), (int) fragment.toLocalRowIdx(idx) ); |
| } |
| } |
| |
| private @Nullable Fragment<RVector<?>> getDimItemNamesFragment(final int dimIdx, final int idx) { |
| if (this.dimItemNameStore == null) { |
| this.dimItemNameStore= new LazyRStore[this.dimCount]; |
| } |
| if (this.dimItemNameStore[dimIdx] == null) { |
| this.dimItemNameStore[dimIdx]= new LazyRStore<>(this.dim.get(dimIdx), 1, |
| DEFAULT_FRAGMENT_COUNT, new RDataLoader<RVector<?>>() { |
| @Override |
| protected RVector<?> doLoad(final FQRObjectRef ref, |
| final Fragment<RVector<?>> fragment, |
| final RToolService r, final ProgressMonitor m) throws StatusException, UnexpectedRDataException { |
| return ADAPTER.loadDimItemNames(ref, getRObject(), dimIdx, fragment, |
| r, m ); |
| } |
| }); |
| } |
| return this.dimItemNameStore[dimIdx].getFragment(idx, 0, 0, null); |
| } |
| |
| |
| protected final @Nullable RStore<?> getValueType() { |
| RStore<?> data= this.element.getData(); |
| if (data.getStoreType() == RStore.FACTOR) { |
| synchronized (this) { |
| final Fragment<RVector<?>> fragment= getDataFragmentAny(); |
| if (fragment == null || fragment.getRObject() == null) { |
| return null; |
| } |
| data= fragment.getRObject().getData(); |
| } |
| } |
| return data; |
| } |
| |
| protected @Nullable String getDataExpr(final int[] dimIndex) { |
| synchronized (this.variable) { |
| if (this != this.variable.getCurrentValue()) { |
| return null; |
| } |
| } |
| |
| final LazyRStore.Fragment<RVector<?>> fragment; |
| final long idx= RDataUtils.getDataIdx(this.dim, dimIndex); |
| synchronized (this) { |
| fragment= getDataFragment(idx); |
| if (fragment == null || fragment.getRObject() == null) { |
| return null; |
| } |
| } |
| |
| final RValueFormatter formatter= getDebugTarget().getValueFormatter(); |
| synchronized (formatter) { |
| return formatter.format( |
| fragment.getRObject().getData(), (int) fragment.toLocalRowIdx(idx) ); |
| } |
| } |
| |
| protected boolean validateDataExpr(final String expression) { |
| synchronized (this.variable) { |
| if (this != this.variable.getCurrentValue()) { |
| return false; |
| } |
| } |
| |
| final RStore<?> type= getValueType(); |
| final RValueValidator validator= getDebugTarget().getValueValidator(); |
| synchronized (validator) { |
| return validator.isValid(type, expression); |
| } |
| } |
| |
| protected void setDataExpr(final int[] dimIndex, final String expression) throws DebugException { |
| synchronized (this.variable) { |
| if (this != this.variable.getCurrentValue()) { |
| throw newRequestSetDataFailed(); |
| } |
| } |
| |
| final RStore<?> type= getValueType(); |
| final RValueValidator validator= getDebugTarget().getValueValidator(); |
| final RStore<?> data; |
| synchronized (validator) { |
| data= validator.toRData(type, expression); |
| } |
| if (data == null) { |
| throw newNotSupported(); |
| } |
| |
| final long idx= RDataUtils.getDataIdx(this.dim, dimIndex); |
| final RDataAssignment assignment= new RDataAssignment(idx, 0, data); |
| synchronized (this) { |
| setData(assignment); |
| } |
| } |
| |
| private LazyRStore<RVector<?>> ensureDataStore() { |
| if (this.dataStore == null) { |
| final int fragmentSize= estimateFragmentSize(); |
| this.dataStore= new LazyRStore<>(this.length, 1, |
| (int) Math.ceil((double) (DEFAULT_FRAGMENT_SIZE * DEFAULT_FRAGMENT_COUNT) / fragmentSize), fragmentSize, |
| new RDataLoader<RVector<?>>() { |
| @Override |
| protected void doSet(final FQRObjectRef ref, |
| final RDataAssignment assignment, |
| final RToolService r, final ProgressMonitor m) throws StatusException, UnexpectedRDataException { |
| ADAPTER.setData(ref, getRObject(), assignment, null, |
| r, m ); |
| } |
| @Override |
| protected RVector<?> doLoad(final FQRObjectRef ref, |
| final Fragment<RVector<?>> fragment, |
| final RToolService r, final ProgressMonitor m) throws StatusException, UnexpectedRDataException { |
| return ADAPTER.loadData(ref, getRObject(), fragment, null, |
| r, m ); |
| } |
| }); |
| } |
| return this.dataStore; |
| } |
| |
| private int estimateFragmentSize() { |
| if (this.dimCount <= 1) { |
| return DEFAULT_FRAGMENT_SIZE; |
| } |
| int size= this.dim.getInt(this.dim.getLength() - 1); |
| if (size > DEFAULT_FRAGMENT_SIZE) { |
| do { |
| if (size % 2 != 0) { |
| return DEFAULT_FRAGMENT_SIZE; |
| } |
| size /= 2; |
| } while (size > DEFAULT_FRAGMENT_SIZE); |
| } |
| else { |
| while (size <= DEFAULT_FRAGMENT_SIZE / 2) { |
| size *= 2; |
| } |
| } |
| return size; |
| } |
| |
| private @Nullable Fragment<RVector<?>> getDataFragment(final long idx) { |
| return ensureDataStore().getFragment(idx, 0, 0, null); |
| } |
| |
| private @Nullable Fragment<RVector<?>> getDataFragmentAny() { |
| final LazyRStore<RVector<?>> dataStore= ensureDataStore(); |
| Fragment<RVector<?>> fragment= dataStore.getLoadedFragmentAny(); |
| if (fragment == null) { |
| fragment= dataStore.getLoadedFragment(0, 0); |
| } |
| return fragment; |
| } |
| |
| private @Nullable Fragment<RVector<?>> getLoadedDataFragment(final long idx) { |
| return (this.dataStore != null) ? |
| this.dataStore.getLoadedFragment(idx, 0) : |
| null; |
| } |
| |
| public void setData(final RDataAssignment assignment) { |
| ensureDataStore().set(assignment, 0, null); |
| |
| this.variable.fireChangeEvent(DebugEvent.CONTENT); |
| } |
| |
| } |