/******************************************************************************* * Copyright (c) 2009 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Zend Technologies *******************************************************************************/ package org.eclipse.php.internal.debug.core.zend.model; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.model.IValue; import org.eclipse.debug.core.model.IVariable; import org.eclipse.php.internal.debug.core.model.IPHPDataType; import org.eclipse.php.internal.debug.core.model.IVirtualPartition; import org.eclipse.php.internal.debug.core.model.IVirtualPartition.IVariableProvider; import org.eclipse.php.internal.debug.core.model.PHPDebugElement; import org.eclipse.php.internal.debug.core.model.VirtualPartition; import org.eclipse.php.internal.debug.core.zend.debugger.Expression; import org.eclipse.php.internal.debug.core.zend.debugger.ExpressionValue; import org.eclipse.php.internal.debug.core.zend.debugger.ExpressionsManager; import org.eclipse.php.internal.debug.core.zend.debugger.ExpressionsUtil; /** * Value of a PHP variable. */ public class PHPValue extends PHPDebugElement implements IValue, IPHPDataType { private static final int ARRAY_PARTITION_BOUNDARY = 100; protected Expression fExpression; protected ExpressionValue fExpressionValue; protected IVariable[] fCurrentVariables = null; protected IVariable[] fPreviousVariables = null; protected Map<String, IVirtualPartition> fCurrentPartitions = new LinkedHashMap<>(); protected Map<String, IVirtualPartition> fPreviousPartitions = new LinkedHashMap<>(); public PHPValue(PHPDebugTarget target, Expression expression) { super(target); fExpressionValue = expression.getValue(); fExpression = expression; } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.IValue#isAllocated() */ public boolean isAllocated() throws DebugException { return true; } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.IValue#hasVariables() */ public boolean hasVariables() throws DebugException { switch (fExpressionValue.getDataType()) { case PHP_ARRAY: case PHP_OBJECT: case PHP_VIRTUAL_CLASS: return fExpressionValue.getChildrenCount() > 0; default: break; } return false; } @Override public DataType getDataType() { return fExpressionValue.getDataType(); } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.IValue#getReferenceTypeName() */ public String getReferenceTypeName() throws DebugException { return getDataType().getText().toUpperCase(); } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.IValue#getValueString() */ public String getValueString() throws DebugException { return fExpressionValue.getValueAsString(); } public String getValueDetail() throws DebugException { return ExpressionsUtil.getInstance(((PHPDebugTarget) getDebugTarget()).getExpressionManager()) .getValueDetail(fExpression); } /* * (non-Javadoc) * * @see org.eclipse.debug.core.model.IValue#getVariables() */ public synchronized IVariable[] getVariables() throws DebugException { if (fCurrentVariables == null) { requestVariables(); if (fCurrentVariables == null) { fCurrentVariables = new IVariable[0]; } // Check if we should divide it into partitions if (fExpressionValue.getDataType() == DataType.PHP_ARRAY && fCurrentVariables.length >= ARRAY_PARTITION_BOUNDARY) { createPartitions(); } } if (!hasPartitions()) { return fCurrentVariables; } return fCurrentPartitions.values().toArray(new IVariable[fCurrentPartitions.size()]); } public String getValue() throws DebugException { return String.valueOf(fExpressionValue.getValue()); } public void updateValue(ExpressionValue value) { fExpressionValue = value; createVariables(fExpressionValue); } protected Expression getExpression() { return fExpression; } /** * Checks if there are multiple partitions with variables. * * @return <code>true</code> if there are multiple partitions with * variables, <code>false</code> otherwise */ protected boolean hasPartitions() { return fCurrentPartitions.size() > 0; } protected void update(Expression expression) { // Reset variables state fPreviousVariables = fCurrentVariables; fCurrentVariables = null; // Bind to new expression fExpression = expression; fExpressionValue = fExpression.getValue(); } /** * Merges incoming variable. Merge is done by means of checking if related * child variable existed in "one step back" state of a container. If * related variable existed, it is updated with the use of the most recent * descriptor and returned instead of the incoming one. * * @param variable * @param descriptor * @return merged variable */ protected IVariable merge(IVariable variable) { if (fPreviousVariables == null) return variable; if (!(variable instanceof PHPVariable)) return variable; PHPVariable incoming = (PHPVariable) variable; if (incoming.getFullName().isEmpty()) return incoming; for (IVariable stored : fPreviousVariables) { if (stored instanceof PHPVariable) { PHPVariable previous = (PHPVariable) stored; if (previous.getFullName().equals(incoming.getFullName())) { ((PHPVariable) stored).update(incoming.getExpression()); return stored; } } } return variable; } private void requestVariables() { PHPDebugTarget debugTarget = (PHPDebugTarget) getDebugTarget(); ExpressionsManager expressionManager = debugTarget.getExpressionManager(); Expression variable = fExpression; expressionManager.update(variable, 1); fExpressionValue = variable.getValue(); createVariables(fExpressionValue); } private void createVariables(ExpressionValue value) { Expression[] children = value.getChildren(); if (children != null) { fCurrentVariables = new PHPVariable[children.length]; for (int i = 0; i < children.length; i++) { IVariable incoming = new PHPVariable((PHPDebugTarget) getDebugTarget(), children[i]); fCurrentVariables[i] = merge(incoming); } } } private void createPartitions() { int numChild = fCurrentVariables.length; int partitions = (int) Math.ceil(numChild / (double) 100); fPreviousPartitions = fCurrentPartitions; fCurrentPartitions = new LinkedHashMap<>(); for (int i = 0; i < partitions; i++) { int startIndex = i * ARRAY_PARTITION_BOUNDARY; int endIndex = (i + 1) * ARRAY_PARTITION_BOUNDARY - 1; if (endIndex > numChild) { endIndex = numChild - 1; } final IVariable[] vars = Arrays.copyOfRange(fCurrentVariables, startIndex, endIndex + 1); IVariableProvider variableProvider = new IVariableProvider() { @Override public IVariable[] getVariables() throws DebugException { return vars; } }; String partitionId = String.valueOf(startIndex) + '-' + String.valueOf(endIndex); IVirtualPartition partition = fPreviousPartitions.get(partitionId); if (partition != null) { partition.setProvider(variableProvider); fCurrentPartitions.put(partitionId, partition); } else { fCurrentPartitions.put(partitionId, new VirtualPartition(this, variableProvider, startIndex, endIndex)); } } } }