Package org.chromium.debug.core.model

Source Code of org.chromium.debug.core.model.Variable$Real

// 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.Collections;
import java.util.List;
import java.util.Map;

import org.chromium.debug.core.ChromiumDebugPlugin;
import org.chromium.sdk.CallbackSemaphore;
import org.chromium.sdk.FunctionScopeExtension;
import org.chromium.sdk.JsDeclarativeVariable;
import org.chromium.sdk.JsEvaluateContext;
import org.chromium.sdk.JsEvaluateContext.ResultOrException;
import org.chromium.sdk.JsFunction;
import org.chromium.sdk.JsObjectProperty;
import org.chromium.sdk.JsScope;
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.DebugEvent;
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, ExpressionTracker.Node trackerNode) {
    ValueBase value;
    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 parallelize it for several properties.
        value = calculateAccessorPropertyBlocking(objectProperty, evaluateContext, trackerNode);
        if (value == null) {
          value = new ValueBase.ErrorMessageValue(evaluateContext, "Unreadable object property");
        }
      }
    } else {
      value = Value.create(evaluateContext, jsValue, trackerNode);
    }

    return new Real(evaluateContext, jsVariable, value, isInternalProperty, trackerNode);
  }

  private static ValueBase calculateAccessorPropertyBlocking(final JsObjectProperty property,
      final EvaluateContext evaluateContext, final ExpressionTracker.Node expressionTrackerNode) {
    if (property.getGetterAsFunction() == null) {
      return new ValueBase.ErrorMessageValue(evaluateContext, "Property has undefined getter");
    }
    class Callback implements JsEvaluateContext.EvaluateCallback {
      ValueBase valueBase = null;

      @Override
      public void success(ResultOrException result) {
        valueBase = result.accept(new ResultOrException.Visitor<ValueBase>() {
          @Override public ValueBase visitResult(JsValue value) {
            return Value.create(evaluateContext, value, expressionTrackerNode);
          }
          @Override public ValueBase visitException(JsValue exception) {
            return new ValueBase.ErrorMessageValue(evaluateContext, "Evaluate failure", exception);
          }
        });
      }

      @Override public void failure(Exception cause) {
        valueBase = new ValueBase.ErrorMessageValue(evaluateContext,
            "Failed to evaluate property value: " + cause.getMessage());
      }
    }
    Callback callback = new Callback();
    CallbackSemaphore callbackSemaphore = new CallbackSemaphore();
    RelayOk relayOk = property.evaluateGet(callback, callbackSemaphore);
    callbackSemaphore.acquireDefault(relayOk);
    return callback.valueBase;
  }

  public static Variable forException(EvaluateContext evaluateContext,
      JsValue exceptionValue) {
    Value value = Value.create(evaluateContext, exceptionValue,
        ExpressionTracker.NO_EXPRESSION_NODE);
    return new Variable.Virtual(evaluateContext, "<exception>", JAVASCRIPT_REFERENCE_TYPE_NAME,
        value, null);
  }

  public static Variable forObjectScope(EvaluateContext evaluateContext,
      JsScope.ObjectBased scope, ExpressionTracker.Node expressionNode) {
    String scopeVariableName = "<" + scope.getType() + ">";
    Value value = Value.create(evaluateContext, scope.getScopeObject(), expressionNode);
    return forScopeImpl(evaluateContext, scopeVariableName, value);
  }

  private static Variable forScopeImpl(EvaluateContext evaluateContext, String scopeName,
      ValueBase scopeValue) {
    return new Variable.Virtual(evaluateContext, scopeName, "<scope>", scopeValue, null);
  }

  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,
            ExpressionTracker.FUNCTION_SCOPE_FACTORY);
      }

      @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 forScopeImpl(evaluateContext, "<function scope>", value);
  }

  public static Variable forEvaluateExpression(EvaluateContext evaluateContext, JsValue jsValue,
      String expression) {
    ExpressionTracker.Node expressionTrackerNode =
        ExpressionTracker.createExpressionNode(expression);
    ValueBase value = Value.create(evaluateContext, jsValue, expressionTrackerNode);
    return new Variable.Virtual(evaluateContext, expression, JAVASCRIPT_REFERENCE_TYPE_NAME, value,
        expressionTrackerNode.calculateQualifiedName());
  }

  /**
   * Represents a real variable -- wraps {@link JsVariable}.
   */
  public static class Real extends Variable {
    private final JsVariable jsVariable;
    private final ExpressionTracker.Node expressionTrackerNode;
    private volatile ValueBase value;

    /**
     * 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, ExpressionTracker.Node expressionTrackerNode) {
      super(evaluateContext);
      this.jsVariable = jsVariable;
      this.value = value;
      this.isInternalProperty = isInternalProperty;
      this.expressionTrackerNode = expressionTrackerNode;
    }

    @Override public String getName() {
      return jsVariable.getName();
    }
    @Override public ValueBase getValue() {
      return value;
    }
    @Override public String getReferenceTypeName() {
      return JAVASCRIPT_REFERENCE_TYPE_NAME;
    }
    @Override public Real asRealVariable() {
      return this;
    }
    public JsVariable getJsVariable() {
      return jsVariable;
    }

    @Override
    public boolean verifyValue(IValue value) throws DebugException {
      ValueBase valueBase = ValueBase.cast(value);
      if (valueBase == null) {
        return false;
      }
      Value realValue = valueBase.asRealValue();
      if (realValue == null) {
        return false;
      }
      return true;
    }

    @Override
    public boolean verifyValue(String expression) throws DebugException {
      ResultOrException resultOrException = evaluateExpressionString(expression);
      return resultOrException.accept(new ResultOrException.Visitor<Boolean>() {
        @Override public Boolean visitResult(JsValue value) {
          return true;
        }
        @Override public Boolean visitException(JsValue exception) {
          return false;
        }
      });
    }

    @Override
    public void setValue(IValue value) throws DebugException {
      ValueBase valueBase = ValueBase.cast(value);
      if (valueBase == null) {
        throw new IllegalArgumentException("Unrecognized type of value");
      }
      Value realValue = valueBase.asRealValue();
      if (realValue == null) {
        throw new IllegalArgumentException("Not a real value");
      }
      JsValue jsValue = realValue.getJsValue();
      setValue(jsValue);
    }

    public void setValue(String expression) throws DebugException {
      // TODO: support setters explicitly.
      ResultOrException newValueOrException = evaluateExpressionString(expression);
      if (newValueOrException.getResult() == null) {
        String message = getExceptionMessage(newValueOrException.getException(),
            getEvaluateContext().getJsEvaluateContext());
        Status status = new Status(IStatus.ERROR, ChromiumDebugPlugin.PLUGIN_ID,
            DebugException.TARGET_REQUEST_FAILED,
            "JavaScript compile exception: " + message, null);
        throw new DebugException(status);
      } else {
        setValue(newValueOrException.getResult());
      }
    }

    private ResultOrException evaluateExpressionString(String expression) throws DebugException {
      class CallbackImpl implements JsEvaluateContext.EvaluateCallback {
        ResultOrException resultOrException = null;
        Exception cause = null;
        @Override
        public void success(ResultOrException resultOrException) {
          this.resultOrException = resultOrException;
        }

        @Override public void failure(Exception cause) {
          this.cause = cause;
        }
      }
      CallbackImpl callback = new CallbackImpl();
      JsEvaluateContext evaluateContext = getEvaluateContext().getJsEvaluateContext();
      evaluateContext.evaluateSync(expression, null, callback);

      if (callback.resultOrException != null) {
        return callback.resultOrException;
      }

      Status status = new Status(IStatus.ERROR, ChromiumDebugPlugin.PLUGIN_ID,
          DebugException.TARGET_REQUEST_FAILED, "Failed to execute remote command", callback.cause);
      throw new DebugException(status);
    }

    private void setValue(JsValue newValue) throws DebugException {
      ValueChanger changer = createValueChanger();
      if (changer == null) {
        throw new UnsupportedOperationException();
      }

      ValueChanger.Callback callback = new ValueChanger.Callback();
      CallbackSemaphore syncCallback = new CallbackSemaphore();

      RelayOk relayOk = changer.setValue(newValue, callback, syncCallback);

      syncCallback.acquireDefault(relayOk);

      if (!callback.successful) {
        Status status;
        if (callback.exception == null) {
          status = new Status(IStatus.ERROR, ChromiumDebugPlugin.PLUGIN_ID,
              DebugException.TARGET_REQUEST_FAILED, "Failed to execute remote command",
              callback.cause);
        } else {
          String message = getExceptionMessage(callback.exception,
              getEvaluateContext().getJsEvaluateContext());
          status = new Status(IStatus.ERROR, ChromiumDebugPlugin.PLUGIN_ID,
              DebugException.TARGET_REQUEST_FAILED, "JavaScript exception: " + message, null);
        }
        throw new DebugException(status);
      }

      value = Value.create(getEvaluateContext(), jsVariable.getValue(), expressionTrackerNode);

      DebugEvent event = new DebugEvent(this, DebugEvent.CHANGE, DebugEvent.CONTENT);
      DebugTargetImpl.fireDebugEvent(event);
    }

    public boolean supportsValueModification() {
      return createValueChanger() != null;
    }

    @Override public String createWatchExpression() {
      return expressionTrackerNode.calculateQualifiedName();
    }
    public String createHolderWatchExpression() {
      return expressionTrackerNode.calculateParentQualifiedName();
    }

    private ValueChanger createValueChanger() {
      final JsDeclarativeVariable declarative = jsVariable.asDeclarativeVariable();
      if (declarative != null) {
        if (!declarative.isMutable()) {
          return null;
        }
        return new ValueChanger() {
          @Override
          RelayOk setValue(JsValue newValue,
              final Callback callback, CallbackSemaphore syncCallback) {
            JsDeclarativeVariable.SetValueCallback rawCallback =
                new JsDeclarativeVariable.SetValueCallback() {
              @Override public void success() {
                callback.success();
              }
              @Override public void failure(Exception cause) {
                callback.failure(cause);
              }
            };
            return declarative.setValue(newValue, rawCallback, syncCallback);
          }
        };
      }
      JsObjectProperty property = jsVariable.asObjectProperty();
      if (property != null) {
        // TODO: implement object property setting with brute-force property assignment.
        //       Note, that correct object instance is required (not a proto object instance).
        return null;
      }
      return null;
    }

    private static abstract class ValueChanger {
      abstract RelayOk setValue(JsValue newValue,
          Callback callback, CallbackSemaphore syncCallback);

      static class Callback {
        boolean successful = false;
        JsValue exception = null;
        Exception cause = null;
        void success() {
          successful = true;
        }
        void exceptionThrown(JsValue exception) {
          this.exception = exception;
        }
        void failure(Exception cause) {
          this.cause = cause;
        }
      }
    }
  }

  /**
   * 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;
    private final String watchExpression;
    private final ValueBase value;

    Virtual(EvaluateContext evaluateContext, String name, String referenceTypeName,
        ValueBase value, String watchExpression) {
      super(evaluateContext);
      this.name = name;
      this.value = value;
      this.referenceTypeName = referenceTypeName;
      this.watchExpression = watchExpression;
    }

    @Override public String getName() {
      return name;
    }
    @Override public ValueBase getValue() {
      return value;
    }
    @Override public String getReferenceTypeName() {
      return referenceTypeName;
    }
    @Override public Real asRealVariable() {
      return null;
    }
    @Override protected String createWatchExpression() {
      return watchExpression;
    }

    @Override public boolean supportsValueModification() {
      return false;
    }
    @Override public void setValue(String expression) throws DebugException {
      throw new UnsupportedOperationException();
    }
    @Override public void setValue(IValue value) throws DebugException {
      throw new UnsupportedOperationException();
    }
    @Override public boolean verifyValue(String expression) throws DebugException {
      throw new UnsupportedOperationException();
    }
    @Override public boolean verifyValue(IValue value) throws DebugException {
      throw new UnsupportedOperationException();
    }
  }

  protected Variable(EvaluateContext evaluateContext) {
    super(evaluateContext);
  }

  @Override public abstract String getName();

  @Override public abstract String getReferenceTypeName();

  @Override public abstract ValueBase getValue();

  @Override public boolean hasValueChanged() throws DebugException {
    return false;
  }

  /**
   * @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 = "";

  private static String getExceptionMessage(JsValue exception, JsEvaluateContext evaluateContext) {
    String expression = "(e instanceof Error) ? e.message : String(e)";
    Map<String, JsValue> context = Collections.singletonMap("e", exception);
    class CallbackImpl implements JsEvaluateContext.EvaluateCallback {
      String result = "<exception message not available>";
      @Override
      public void success(ResultOrException resultOrException) {
        String message = resultOrException.accept(new ResultOrException.Visitor<String>() {
          @Override public String visitResult(JsValue value) {
            return value.getValueString();
          }
          @Override public String visitException(JsValue exception) {
            return null;
          }
        });
        if (message != null) {
          result = message;
        }
      }

      @Override public void failure(Exception cause) {
      }
    }
    CallbackImpl callback = new CallbackImpl();
    evaluateContext.evaluateSync(expression, context, callback);
    return callback.result;
  }
}
TOP

Related Classes of org.chromium.debug.core.model.Variable$Real

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.