// Copyright (c) 2011 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.Collections; import java.util.concurrent.atomic.AtomicReference; import org.chromium.debug.core.ChromiumDebugPlugin; import org.chromium.debug.core.model.Variable.Real.HostObject; import org.chromium.sdk.JsScope; import org.chromium.sdk.JsValue; import org.chromium.sdk.JsVariable; 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; /** * A base implementation of all Chromium values. This class merely adds new interface, * not behavior. The actual class may be based on {@link JsValue} (regular case) or other data * sources. */ public abstract class ValueBase extends DebugElementImpl.WithEvaluate implements IValue { /** * Downcasts IValue to ValueBase if possible or returns null. This should be used in context * where IValue is available, but it's unknown whether it's Chromium value or not. * Clients should use this method rather that do instanceof themselves, because the latter is * unsafe technique (you cannot track manual downcasts and consequently cannot refactor/manage * them). */ public static ValueBase cast(IValue value) { if (value instanceof ValueBase) { return (ValueBase) value; } else { return null; } } protected ValueBase(EvaluateContext evaluateContext) { super(evaluateContext); } /** * Downcasts to Value or return null. */ public abstract Value asRealValue(); public abstract String getValueString(); /** * Represents a value as {@link HostObject}. This will be passed to value subproperties. */ interface ValueAsHostObject extends Variable.Real.HostObject { } /** * A base implementation of Value that lazily calculates its inner variables. */ public static abstract class ValueWithLazyVariables extends ValueBase { static final IVariable[] EMPTY_VARIABLES = new IVariable[0]; private final AtomicReference<IVariable[]> variablesRef = new AtomicReference<IVariable[]>(null); protected ValueWithLazyVariables(EvaluateContext evaluateContext) { super(evaluateContext); } // This method could be blocking -- it gets called from a Worker thread. // All data should be prepared here. public IVariable[] getVariables() throws DebugException { try { // TODO: support clearing with cache clear. IVariable[] result = variablesRef.get(); if (result != null) { return result; } IVariable[] variables = calculateVariables(); variablesRef.compareAndSet(null, variables); return variablesRef.get(); } catch (RuntimeException e) { // Log it, because Eclipse is likely to ignore it. ChromiumDebugPlugin.log(e); // We shouldn't throw RuntimeException from here, because calling // ElementContentProvider#update will forget to call update.done(). throw new DebugException(new Status(IStatus.ERROR, ChromiumDebugPlugin.PLUGIN_ID, "Failed to read variables", e)); //$NON-NLS-1$ } } // Consider making it more lazy for IIndexedValue implementation's benefit. protected abstract IVariable[] calculateVariables(); } /** * Wraps {@link JsScope} as a Value. Scope's variables are inner variables of the Value. */ static class ScopeValue extends ValueBase.ValueWithLazyVariables { private final JsScope jsScope; private final ValueBase.ValueAsHostObject selfAsHostObject; ScopeValue(EvaluateContext evaluateContext, JsScope jsScope, ValueBase.ValueAsHostObject selfAsHostObject) { super(evaluateContext); this.jsScope = jsScope; this.selfAsHostObject = selfAsHostObject; } @Override public String getReferenceTypeName() throws DebugException { return "#Scope"; } @Override public String getValueString() { return null; } @Override public boolean isAllocated() throws DebugException { return true; } @Override public boolean hasVariables() throws DebugException { return true; } @Override public Value asRealValue() { return null; } @Override protected IVariable[] calculateVariables() { return StackFrame.wrapVariables(getEvaluateContext(), jsScope.getVariables(), Collections.<String>emptySet(), Collections.<JsVariable>emptyList(), selfAsHostObject, null); } } /** * Wraps string error message as a Value. The value has no inner variables. */ static class ErrorMessageValue extends ValueBase { private final String message; ErrorMessageValue(EvaluateContext evaluateContext, String message) { super(evaluateContext); this.message = message; } @Override public String getReferenceTypeName() throws DebugException { return REFERENCE_TYPE_NAME; } @Override public String getValueString() { return message; } @Override public boolean isAllocated() throws DebugException { return true; } @Override public IVariable[] getVariables() throws DebugException { return Value.EMPTY_VARIABLES; } @Override public boolean hasVariables() throws DebugException { return false; } @Override public Value asRealValue() { return null; } private static final String REFERENCE_TYPE_NAME = "#Error Message"; } }