blob: 099cf6dc16e1cd0da3670e25ae44da12db48cc4e [file] [log] [blame]
/*=============================================================================#
# 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 java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
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.jcommons.status.ProgressMonitor;
import org.eclipse.statet.jcommons.status.StatusException;
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 ProgressMonitor m) 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 (m != null) {
try {
element= getThread().resolveReference(element, stamp, m);
}
catch (final StatusException 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$
}
}