// Copyright (c) 2009 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.debug.core.model; import java.util.AbstractList; import java.util.List; import org.chromium.debug.core.ChromiumDebugPlugin; import org.chromium.sdk.CallbackSemaphore; import org.chromium.sdk.ExceptionData; import org.chromium.sdk.FunctionScopeExtension; import org.chromium.sdk.JsEvaluateContext; import org.chromium.sdk.JsFunction; import org.chromium.sdk.JsObjectProperty; import org.chromium.sdk.JsScope; import org.chromium.sdk.JsScope.WithScope; import org.chromium.sdk.JsValue; import org.chromium.sdk.JsVariable; import org.chromium.sdk.RelayOk; import org.eclipse.core.runtime.CoreException; 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.debug.core.model.IVariable; import org.eclipse.debug.ui.actions.IWatchExpressionFactoryAdapter; /** * An IVariable implementation over a JsVariable instance. This is class is a base implementation, * and it contains several concrete implementations as nested classes. */ public abstract class Variable extends DebugElementImpl.WithEvaluate implements IVariable { /** * Wraps {@link JsVariable}. It extracts its {@link JsValue} if possible or provides error * message as a {@link Value}. */ public static Variable forRealValue(EvaluateContext evaluateContext, JsVariable jsVariable, boolean isInternalProperty, Real.HostObject hostObject) { ValueBase value; if (jsVariable.isReadable()) { JsValue jsValue = jsVariable.getValue(); if (jsValue == null) { JsObjectProperty objectProperty = jsVariable.asObjectProperty(); if (objectProperty == null) { value = new ValueBase.ErrorMessageValue(evaluateContext, "Variable value is unavailable"); } else { // This is blocking. Consider making this call async and the entire method async // to parallel if for several properties. value = calculateAccessorPropertyBlocking(objectProperty, evaluateContext); if (value == null) { value = new ValueBase.ErrorMessageValue(evaluateContext, "Unreadable object property"); } } } else { SelfAsHostObject selfAsHostObject = new SelfAsHostObject(jsVariable); value = Value.create(evaluateContext, jsValue, selfAsHostObject); } } else { value = new ValueBase.ErrorMessageValue(evaluateContext, "Unreadable variable"); } return new Real(evaluateContext, jsVariable, value, isInternalProperty, hostObject); } private static ValueBase calculateAccessorPropertyBlocking(final JsObjectProperty property, final EvaluateContext evaluateContext) { if (property.getGetterAsFunction() == null) { return new ValueBase.ErrorMessageValue(evaluateContext, "Property has undefined getter"); } class Callback implements JsEvaluateContext.EvaluateCallback { ValueBase result = null; @Override public void success(JsVariable variable) { result = Value.create(evaluateContext, variable.getValue(), new SelfAsHostObject(property)); } @Override public void failure(String errorMessage) { result = new ValueBase.ErrorMessageValue(evaluateContext, "Failed to evaluate property value: " + errorMessage); } } Callback callback = new Callback(); CallbackSemaphore callbackSemaphore = new CallbackSemaphore(); RelayOk relayOk = property.evaluateGet(callback, callbackSemaphore); callbackSemaphore.acquireDefault(relayOk); return callback.result; } public static Variable forException(EvaluateContext evaluateContext, ExceptionData exceptionData) { Value value = Value.create(evaluateContext, exceptionData.getExceptionValue(), null); return new Variable.Virtual(evaluateContext, "<exception>", JAVASCRIPT_REFERENCE_TYPE_NAME, value); } public static Variable forScope(EvaluateContext evaluateContext, JsScope scope, ValueBase.ValueAsHostObject selfAsHostObject) { ValueBase scopeValue = new ValueBase.ScopeValue(evaluateContext, scope, selfAsHostObject); String scopeVariableName = "<" + scope.getType() + ">"; return forScope(evaluateContext, scopeVariableName, scopeValue); } public static Variable forWithScope(EvaluateContext evaluateContext, WithScope withScope) { Value value = Value.create(evaluateContext, withScope.getWithArgument(), null); return forScope(evaluateContext, "<with>", value); } private static Variable forScope(EvaluateContext evaluateContext, String scopeName, ValueBase scopeValue) { return new Variable.Virtual(evaluateContext, scopeName, "<scope>", scopeValue); } public static Variable forFunctionScopes(EvaluateContext evaluateContext, final JsFunction jsFunction, final FunctionScopeExtension functionScopeExtension) { ValueBase value = new ValueBase.ValueWithLazyVariables(evaluateContext) { @Override public String getReferenceTypeName() throws DebugException { return "<function scope>"; } @Override public boolean isAllocated() throws DebugException { return true; } @Override public boolean hasVariables() throws DebugException { return !functionScopeExtension.getScopes(jsFunction).isEmpty(); } @Override protected IVariable[] calculateVariables() { List<? extends JsScope> list = functionScopeExtension.getScopes(jsFunction); // Put scopes in the opposite order: innermost first. // Closure tends to be parameterized by the innermost variable at most. List<? extends JsScope> reverseList = reverseList(list); return StackFrame.wrapScopes(getEvaluateContext(), reverseList, null); } @Override public Value asRealValue() { return null; } @Override public String getValueString() { return ""; } private <T> List<T> reverseList(final List<T> input) { return new AbstractList<T>() { @Override public T get(int index) { return input.get(input.size() - index - 1); } @Override public int size() { return input.size(); } }; } }; return forScope(evaluateContext, "<function scope>", value); } /** * Represents a real variable -- wraps {@link JsVariable}. */ public static class Real extends Variable { private final JsVariable jsVariable; private final HostObject hostObject; /** * Specifies whether this variable is internal property (__proto__ etc). * TODO(peter.rybin): use it in UI. */ private final boolean isInternalProperty; Real(EvaluateContext evaluateContext, JsVariable jsVariable, ValueBase value, boolean isInternalProperty, HostObject hostObject) { super(evaluateContext, value); this.jsVariable = jsVariable; this.isInternalProperty = isInternalProperty; this.hostObject = hostObject; } @Override public String getName() { return jsVariable.getName(); } @Override public String getReferenceTypeName() { return JAVASCRIPT_REFERENCE_TYPE_NAME; } @Override protected String createWatchExpression() { return jsVariable.getFullyQualifiedName(); } @Override public Real asRealVariable() { return this; } public JsVariable getJsVariable() { return jsVariable; } public HostObject getHostObject() { return hostObject; } /** * If variable is a property of some object, it need an access to this object. This is used * to build an expression for getting property descriptor. */ public interface HostObject { /** * @return a JavaScript descriptor that return a value of that object -- the same that * {@link JsVariable#getFullyQualifiedName()} returns */ String getExpression(); } } /** * Represents some auxiliary variable. Its name and reference type are provided by a caller. */ private static class Virtual extends Variable { private final String name; private final String referenceTypeName; Virtual(EvaluateContext evaluateContext, String name, String referenceTypeName, ValueBase value) { super(evaluateContext, value); this.name = name; this.referenceTypeName = referenceTypeName; } @Override public String getName() { return name; } @Override public String getReferenceTypeName() { return referenceTypeName; } @Override public Real asRealVariable() { return null; } @Override protected String createWatchExpression() { return null; } } /** * Implements ValueAsHostObject based on JsVariable. This goes to the * corresponding Value instance. */ private static class SelfAsHostObject implements ValueBase.ValueAsHostObject { private final JsVariable jsVariable; SelfAsHostObject(JsVariable jsVariable) { this.jsVariable = jsVariable; } @Override public String getExpression() { return jsVariable.getFullyQualifiedName(); } } private final ValueBase value; protected Variable(EvaluateContext evaluateContext, ValueBase value) { super(evaluateContext); this.value = value; } @Override public abstract String getName(); @Override public abstract String getReferenceTypeName(); @Override public ValueBase getValue() { return value; } @Override public boolean hasValueChanged() throws DebugException { return false; } public void setValue(String expression) throws DebugException { } public void setValue(IValue value) throws DebugException { } public boolean supportsValueModification() { return false; // TODO(apavlov): fix once V8 supports it } public boolean verifyValue(IValue value) throws DebugException { return verifyValue(value.getValueString()); } public boolean verifyValue(String expression) { return true; } public boolean verifyValue(JsValue value) { return verifyValue(value.getValueString()); } /** * @return expression or null */ protected abstract String createWatchExpression(); public abstract Real asRealVariable(); @SuppressWarnings("unchecked") @Override public Object getAdapter(Class adapter) { if (IWatchExpressionFactoryAdapter.class == adapter) { return EXPRESSION_FACTORY_ADAPTER; } return super.getAdapter(adapter); } private final static IWatchExpressionFactoryAdapter EXPRESSION_FACTORY_ADAPTER = new IWatchExpressionFactoryAdapter() { public String createWatchExpression(IVariable variable) throws CoreException { Variable castVariable = (Variable) variable; String expressionText = castVariable.createWatchExpression(); if (expressionText == null) { throw new CoreException(new Status(IStatus.ERROR, ChromiumDebugPlugin.PLUGIN_ID, Messages.Variable_CANNOT_BUILD_EXPRESSION)); } return expressionText; } }; /** * A type of JavaScript reference. All JavaScript references have no type. */ private static final String JAVASCRIPT_REFERENCE_TYPE_NAME = ""; }