/* * Copyright (c) 2012, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.github.sdbg.integration.jdt.gwt; import com.github.sdbg.debug.core.SDBGDebugCorePlugin; import com.github.sdbg.debug.core.model.ISDBGLogicalStructureTypeExtensions; import com.github.sdbg.debug.core.model.ISDBGValue; import com.github.sdbg.debug.core.model.ISDBGVariable; import com.github.sdbg.debug.core.util.DecoratingSDBGValue; import com.github.sdbg.debug.core.util.DecoratingSDBGVariable; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.regex.Pattern; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.model.ILogicalStructureTypeDelegate; import org.eclipse.debug.core.model.ILogicalStructureTypeDelegate2; import org.eclipse.debug.core.model.IValue; import org.eclipse.debug.core.model.IVariable; /** * This ILogicalStructureTypeDelegate handles displaying of GWT SDM types, as described here: * https://docs.google.com/document/d/1-of2yVcVVzOeaOh6AUjFfuM3_6WgR8nEwkXM_wBCPXo/edit#heading= * h.c69j6hnt9f5l . Additionally, it also handles all variables in the local scope (local function * variables). TODO: In future, implement logical structures for maps and collections as in * * @skybrian's superdebug GWT module (see issue #6) */ public class GWTSDMStructureType implements ILogicalStructureTypeDelegate, ILogicalStructureTypeDelegate2, ISDBGLogicalStructureTypeExtensions { private static class ExactMatcher implements Matcher { private String name; public ExactMatcher(String name) { this.name = name; } @Override public boolean matches(String name) { return this.name.equals(name); } } private class GWTSDMLong extends GWTSDMValue { private Long value; public GWTSDMLong(ISDBGValue proxyValue, Long value) { super(false, proxyValue, new IVariable[0]); this.value = value; } @Override public void computeDetail(IValueCallback callback) { callback.detailComputed(value.toString()); } @Override public String getReferenceTypeName() throws DebugException { return "long"; } @Override public String getValueString() throws DebugException { return value.toString(); } @Override public boolean isNumber() { return true; } @Override public boolean isObject() { return false; } @Override public boolean isPrimitive() { return true; } } private class GWTSDMValue extends DecoratingSDBGValue { private boolean javaObject; public GWTSDMValue(boolean javaObject, ISDBGValue proxyValue, IVariable[] variables) { super(proxyValue, variables); this.javaObject = javaObject; } @Override public String getId() { return null; } @Override public String getReferenceTypeName() throws DebugException { String rawReferenceTypeName = super.getReferenceTypeName(); if (javaObject && hasGWTSuffix(rawReferenceTypeName)) { return removeGWTSuffix(rawReferenceTypeName); } else { return rawReferenceTypeName; } } @Override public String getValueString() throws DebugException { if (javaObject) { // Commented out because these IDs are not so useful - they are not really surviving to the next breakpoint // ... + (getId() != null ? " [id=" + getId() + "]" : ""); return getReferenceTypeName(); } else { return super.getValueString(); } } } private class GWTSDMVariable extends DecoratingSDBGVariable { private String newName; public GWTSDMVariable(String newName, ISDBGVariable proxyVariable) { super(proxyVariable); this.newName = newName; } @Override public String getName() throws DebugException { return newName; } @Override public String getReferenceTypeName() throws DebugException { // The hacks below are necessary, because this method is called by the UI *before* the value // held in this variable gets converted to its logical representation, if we don't do it, some // things which should be hidden may show through String rawReferenceTypeName = super.getReferenceTypeName(); if (!isExcludedFromLogicalStructure(getValue()) && isJavaObject((ISDBGValue) getValue()) && hasGWTSuffix(rawReferenceTypeName)) { return removeGWTSuffix(rawReferenceTypeName); } else { return rawReferenceTypeName; } } } private static interface Matcher { boolean matches(String name); } public GWTSDMStructureType() { } @Override public String getDescription(IValue value) { return "GWT SuperDevMode"; } @Override public ISDBGValue getLogicalStructure(IValue value) throws DebugException { ISDBGValue sValue = (ISDBGValue) value; if (sValue instanceof GWTSDMValue || isExcludedFromLogicalStructure(sValue)) { return sValue; } boolean javaObject = isJavaObject(sValue); if (javaObject || sValue.isScope()) { List<IVariable> translated = new ArrayList<IVariable>(); if (javaObject) { // A real Java object // Fetch and display all fields then fetchAllJavaFields(value, translated, new HashSet<String>()); } else { for (IVariable var : value.getVariables()) { boolean hasLogicalStructure = providesLogicalStructure(var.getValue()); boolean hasGWTSuffix = hasGWTSuffix(var.getName()); if (hasLogicalStructure || hasGWTSuffix) { translated.add(new GWTSDMVariable(hasGWTSuffix ? removeGWTSuffix(var.getName()) : var.getName(), (ISDBGVariable) var)); } else { translated.add(var); } } } return new GWTSDMValue(javaObject, sValue, translated.toArray(new IVariable[0])); } else { Long longValue = getLong(sValue); if (longValue != null) { return new GWTSDMLong(sValue, longValue); } else { return sValue; } } } @Override public String getVariableName(IVariable variable) throws DebugException { String name = variable.getName(); if (variable instanceof ISDBGVariable) { ISDBGVariable svar = (ISDBGVariable) variable; if (svar.isLocal() && hasGWTSuffix(name)) { name = removeGWTSuffix(name); } } return name; } @Override public boolean isValueDetailStringComputedByLogicalStructure(IValue value) throws DebugException { if (value instanceof ISDBGValue && !(value instanceof GWTSDMValue)) { ISDBGValue sval = (ISDBGValue) value; return !sval.isScope() && getLong(sval) != null; } return false; } @Override public boolean isValueStringComputedByLogicalStructure(IValue value) throws DebugException { if (value instanceof ISDBGValue && !(value instanceof GWTSDMValue) && !isExcludedFromLogicalStructure(value)) { ISDBGValue sval = (ISDBGValue) value; return !sval.isScope() && (isJavaObject(sval) || getLong(sval) != null); } return false; } @Override public boolean providesLogicalStructure(IValue value) { try { return value instanceof ISDBGValue && !(value instanceof GWTSDMValue) && !isExcludedFromLogicalStructure(value); } catch (DebugException e) { SDBGDebugCorePlugin.logError(e); return false; } } // Returns all properties which look like Java fields of the supplied value // // The supplied value is assumed to be a Java object, i.e. isJavaObject(value) should hold true // The prototype chain is also traversed for constant properties which are not available on the main object private void fetchAllJavaFields(IValue value, List<IVariable> variables, Collection<String> visited) throws DebugException { for (IVariable var : value.getVariables()) { String name = var.getName(); if (hasGWTSuffix(name)) { if (!name.equals("$H") && !isGWTInit(name) && !isGWTClass(name) && !isGWTCastableTypeMap(name) && !(var.getValue() != null && ((ISDBGValue) var.getValue()).isFunction())) { name = removeGWTSuffix(name); if (!visited.contains(name)) { visited.add(name); variables.add(new GWTSDMVariable(name, (ISDBGVariable) var)); } } } } IVariable protoVar = getOwnProperty(value, "__proto__"); if (protoVar != null) { IValue protoValue = protoVar.getValue(); if (value != null && isJavaClass((ISDBGValue) protoValue)) { fetchAllJavaFields(protoValue, variables, visited); } } } private Long getLong(ISDBGValue value) throws DebugException { if (!value.isObject() || value.isScope()) { return null; } else { IVariable[] variables = value.getVariables(); if (variables.length != 4) { return null; } long l = 0; for (IVariable var : variables) { String name = var.getName(); if (!name.equals("__proto__")) { if (!name.equals("h") && !name.equals("m") && !name.equals("l")) { return null; } IValue v = var.getValue(); if (!(value instanceof ISDBGValue)) { return null; } ISDBGValue sv = (ISDBGValue) v; Object rawValue = sv.getRawValue(); if (!(rawValue instanceof Number)) { return null; } long ll = ((Number) rawValue).longValue(); l |= ll << (name.equals("h") ? 44 : name.equals("m") ? 22 : 0); } } return l; } } private IVariable getOwnProperty(IValue value, Matcher matcher) throws DebugException { for (IVariable var : value.getVariables()) { if (matcher.matches(var.getName())) { return var; } } return null; } private IVariable getOwnProperty(IValue value, String property) throws DebugException { return getOwnProperty(value, new ExactMatcher(property)); } private boolean hasGWTSuffix(String name) { return name.endsWith("_g$"); } private boolean hasOwnProperty(IValue value, Matcher matcher) throws DebugException { return getOwnProperty(value, matcher) != null; } private boolean isExcludedFromLogicalStructure(IValue value) throws DebugException { if (!(value instanceof ISDBGValue)) { return false; } String excludeFromLogicalStructureStr = SDBGDebugCorePlugin.getPlugin().getExcludeFromLogicalStructure(); if (excludeFromLogicalStructureStr != null && excludeFromLogicalStructureStr.trim().length() > 0) { return Pattern.matches(excludeFromLogicalStructureStr, value.getReferenceTypeName()); } else { return false; } } private boolean isGWTCastableTypeMap(String name) { return hasGWTSuffix(name) && name.startsWith("castableTypeMap_"); } private boolean isGWTClass(String name) { return name.equals("___clazz$") || hasGWTSuffix(name) && name.startsWith("___clazz_"); } private boolean isGWTInit(String name) { return name.equals("$init") || hasGWTSuffix(name) && name.startsWith("$init_"); } private boolean isJavaClass(ISDBGValue value) throws DebugException { return value.isObject() && hasOwnProperty(value, new Matcher() { @Override public boolean matches(String name) { return isGWTClass(name); } }); } private boolean isJavaObject(ISDBGValue value) throws DebugException { if (value.isObject() && !value.isScope()) { IVariable proto = getOwnProperty(value, "__proto__"); return proto instanceof ISDBGVariable && isJavaClass((ISDBGValue) proto.getValue()); } else { return false; } } private String removeGWTSuffix(String name) { if (name.length() > 3) { int pos = name.lastIndexOf('_', name.length() - 4); if (pos > 0) { return name.substring(0, pos); } else { return name.substring(0, name.length() - 3); } } else { return name; } } }