| /*=============================================================================# |
| # Copyright (c) 2010, 2018 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 java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Objects; |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.debug.core.DebugException; |
| import org.eclipse.debug.core.model.IValue; |
| |
| import org.eclipse.statet.jcommons.lang.NonNullByDefault; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| |
| import org.eclipse.statet.internal.r.debug.core.RDebugCorePlugin; |
| import org.eclipse.statet.r.console.core.RProcessREnvironment; |
| import org.eclipse.statet.r.core.data.CombinedRElement; |
| import org.eclipse.statet.r.core.model.RElementName; |
| import org.eclipse.statet.r.debug.core.IRElementVariable; |
| import org.eclipse.statet.r.debug.core.IRValue; |
| import org.eclipse.statet.r.debug.core.IRVariable; |
| import org.eclipse.statet.rj.data.RArray; |
| import org.eclipse.statet.rj.data.RObject; |
| import org.eclipse.statet.rj.data.RReference; |
| |
| |
| @NonNullByDefault |
| public final class RElementVariable extends RVariable implements IRElementVariable { |
| |
| |
| public static final int DEFAULT_FRAGMENT_COUNT= 100; |
| |
| |
| public static @Nullable RElementName createFQElementName(IRVariable variable) { |
| final List<RElementName> segments= new ArrayList<>(); |
| byte lastRType= 0; |
| do { |
| if (variable instanceof IRElementVariable) { |
| final CombinedRElement element= ((IRElementVariable) variable).getElement(); |
| final RElementName elementName= element.getElementName(); |
| if (elementName.getType() == RElementName.MAIN_OTHER) { // detail e.g. of promise |
| return null; |
| } |
| lastRType= element.getRObjectType(); |
| segments.add(elementName); |
| } |
| variable= variable.getParent(); |
| } |
| while (variable != null); |
| |
| if (lastRType != RObject.TYPE_ENVIRONMENT) { |
| return null; |
| } |
| |
| Collections.reverse(segments); |
| |
| return RElementName.create(segments); |
| } |
| |
| private static boolean isEnv(final @Nullable RObject object) { |
| return (object != null && object.getRObjectType() == RObject.TYPE_ENVIRONMENT); |
| } |
| |
| |
| private final RMainThread thread; |
| |
| private CombinedRElement element; |
| |
| private int stamp; |
| |
| private @Nullable IRValue value; |
| |
| private @Nullable CombinedRElement previousElement; |
| private @Nullable IValue previousValue; |
| |
| |
| public RElementVariable(final CombinedRElement element, |
| final RMainThread thread, final int stamp, |
| final @Nullable IRVariable parent) { |
| super(thread.getDebugTarget(), parent); |
| this.thread= thread; |
| this.element= element; |
| this.stamp= stamp; |
| } |
| |
| |
| public synchronized boolean update(final CombinedRElement element, final int stamp) { |
| if (isValidUpdate(element)) { |
| this.previousElement= this.element; |
| this.previousValue= this.value; |
| |
| this.element= element; |
| this.stamp= stamp; |
| this.value= null; |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean isValidUpdate(final CombinedRElement element) { |
| if (element.getRObjectType() == this.element.getRObjectType()) { |
| switch (element.getRObjectType()) { |
| case RObject.TYPE_VECTOR: |
| return (element.getData().getStoreType() == this.element.getData().getStoreType()); |
| case RObject.TYPE_ARRAY: |
| return (element.getData().getStoreType() == this.element.getData().getStoreType() |
| && ((RArray<?>) element).getDim().getLength() == ((RArray<?>) this.element).getDim().getLength() ); |
| case RObject.TYPE_ENVIRONMENT: |
| case RObject.TYPE_S4OBJECT: |
| return (Objects.equals(element.getRClassName(), this.element.getRClassName())); |
| default: |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public synchronized void reset(final int stamp) { |
| this.stamp= stamp; |
| this.value= null; |
| } |
| |
| |
| @Override |
| public final RMainThread getThread() { |
| return this.thread; |
| } |
| |
| |
| public final CombinedRElement getCurrentElement() { |
| return this.element; |
| } |
| |
| public final @Nullable IRValue getCurrentValue() { |
| return this.value; |
| } |
| |
| public final int getCurrentStamp() { |
| return this.stamp; |
| } |
| |
| public final @Nullable CombinedRElement getPreviousElement() { |
| return this.previousElement; |
| } |
| |
| public final @Nullable IValue getPreviousValue() { |
| return this.previousValue; |
| } |
| |
| |
| @Override |
| public synchronized CombinedRElement getElement() { |
| return this.element; |
| } |
| |
| @Override |
| public @Nullable RElementName getFQElementName() { |
| return createFQElementName(this); |
| } |
| |
| @Override |
| public synchronized String getName() { |
| return this.element.getElementName().getDisplayName(); |
| } |
| |
| @Override |
| public synchronized String getReferenceTypeName() throws DebugException { |
| return this.element.getRClassName(); |
| } |
| |
| @Override |
| public boolean hasValueChanged() throws DebugException { |
| final CombinedRElement element; |
| final CombinedRElement previousElement; |
| final IValue previousValue; |
| synchronized (this) { |
| element= this.element; |
| previousElement= this.previousElement; |
| previousValue= this.previousValue; |
| } |
| if (previousElement != null) { |
| switch (element.getRObjectType()) { |
| case RObject.TYPE_VECTOR: |
| if (previousValue != null && element.getLength() == 1) { |
| return ((RVectorValue) getValue()).hasValueChanged(0); |
| } |
| break; |
| case RObject.TYPE_REFERENCE: |
| return (((RReference) element).getHandle() != ((RReference) previousElement).getHandle()); |
| default: |
| break; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public IRValue getValue() throws DebugException { |
| return this.getValue(null); |
| } |
| |
| public IRValue getValue(final @Nullable IProgressMonitor monitor) throws DebugException { |
| CombinedRElement element; |
| int stamp; |
| synchronized (this) { |
| if (this.value != null) { |
| return this.value; |
| } |
| |
| element= this.element; |
| stamp= this.stamp; |
| if (element.getRObjectType() != RObject.TYPE_REFERENCE) { |
| return this.value= createValue(element); |
| } |
| } |
| // (element.getRObjectType() == RObject.TYPE_REFERENCE) |
| if (monitor != null) { |
| try { |
| element= getThread().resolveReference(element, stamp, monitor); |
| } |
| catch (final CoreException e) { |
| throw new DebugException(new Status(IStatus.ERROR, RDebugCorePlugin.BUNDLE_ID, |
| DebugException.TARGET_REQUEST_FAILED, "Request failed: cannot resolve reference.", |
| e )); |
| } |
| } |
| else { |
| element= getThread().resolveReference(element, stamp); |
| } |
| IRValue value= null; |
| if (element.getRObjectType() == RObject.TYPE_ENVIRONMENT) { |
| value= createEnvValue((RProcessREnvironment) element, stamp); |
| } |
| synchronized (this) { |
| if (this.value != null) { |
| return this.value; |
| } |
| if (value == null) { |
| value= createValue(element); |
| } |
| return this.value= value; |
| } |
| } |
| |
| @Override |
| public synchronized boolean supportsValueModification() { |
| switch (this.element.getRObjectType()) { |
| case RObject.TYPE_VECTOR: |
| return (this.element.getLength() == 1); |
| default: |
| return false; |
| } |
| } |
| |
| @Override |
| public boolean verifyValue(final String expression) throws DebugException { |
| final CombinedRElement element; |
| synchronized (this) { |
| element= this.element; |
| } |
| switch (element.getRObjectType()) { |
| case RObject.TYPE_VECTOR: |
| if (element.getLength() == 1) { |
| return ((RVectorValue) getValue()).validateDataExpr(expression); |
| } |
| throw newNotSupported(); |
| default: |
| throw newNotSupported(); |
| } |
| } |
| |
| @Override |
| public void setValue(final String expression) throws DebugException { |
| final CombinedRElement element; |
| synchronized (this) { |
| element= this.element; |
| } |
| switch (element.getRObjectType()) { |
| case RObject.TYPE_VECTOR: |
| if (element.getLength() == 1) { |
| ((RVectorValue) getValue()).setDataExpr(0, expression); |
| return; |
| } |
| throw newNotSupported(); |
| default: |
| throw newNotSupported(); |
| } |
| } |
| |
| |
| private IRValue createValue(final CombinedRElement element) throws DebugException { |
| switch (element.getRObjectType()) { |
| case RObject.TYPE_VECTOR: |
| return new RVectorValue(this); |
| case RObject.TYPE_ARRAY: |
| return new RArrayValue(this); |
| case RObject.TYPE_LIST: |
| return new RListValue(this); |
| case RObject.TYPE_ENVIRONMENT: |
| return createEnvValue((RProcessREnvironment) element, this.stamp); |
| case RObject.TYPE_DATAFRAME: |
| case RObject.TYPE_S4OBJECT: |
| return new RListValue.ByName(this); |
| case RObject.TYPE_LANGUAGE: |
| return new RLanguageValue(this); |
| case RObject.TYPE_FUNCTION: |
| return new RFunctionValue(this); |
| case RObject.TYPE_PROMISE: |
| if (isEnv(element.getModelParent())) { |
| return new RPromiseValue(this); |
| } |
| //$FALL-THROUGH$ |
| default: |
| return new RElementVariableValue<>(this); |
| } |
| } |
| |
| private @Nullable IRValue createEnvValue(final RProcessREnvironment element, final int stamp) |
| throws DebugException { |
| final REnvValue envValue= this.thread.getEnvValue(element, stamp); |
| if (envValue != null) { |
| final RElementName elementName= element.getElementName(); |
| if (elementName != null && elementName.getNextSegment() == null |
| && envValue.setVariable(this)) { |
| return envValue; |
| } |
| return RValueProxy.create(envValue, this); |
| } |
| throw new DebugException(new Status(IStatus.ERROR, RDebugCorePlugin.BUNDLE_ID, |
| DebugException.TARGET_REQUEST_FAILED, "Request failed: reference is stale.", null)); |
| } |
| |
| |
| @Override |
| public int hashCode() { |
| return super.hashCode(); |
| } |
| |
| @Override |
| public boolean equals(@Nullable Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| obj= RVariableProxy.unproxy(obj); |
| return super.equals(obj); |
| } |
| |
| @Override |
| public String toString() { |
| return getClass().getName() + ": " + getName(); //$NON-NLS-1$ |
| } |
| |
| } |