/*******************************************************************************
* Copyright (c) 2016 Zend Technologies 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:
* Zend Technologies - initial API and implementation
*******************************************************************************/
package org.eclipse.php.internal.debug.core.xdebug.dbgp.model;
import java.util.*;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IValue;
import org.eclipse.debug.core.model.IVariable;
import org.eclipse.php.internal.debug.core.model.IVariableFacet;
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.VirtualPartition;
import org.eclipse.php.internal.debug.core.xdebug.dbgp.protocol.DBGpResponse;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Abstract class for DBGp container values that can contain child variables.
*
* @author Bartlomiej Laczkowski
*/
public abstract class AbstractDBGpContainerValue extends AbstractDBGpValue {
/**
* Virtual partition for paging variables set.
*/
protected class DBGpPage implements IVariableProvider {
private final int fPage;
private IVariable[] fPartitionVariables = null;
public DBGpPage(int page) {
this.fPage = page;
}
@Override
public synchronized IVariable[] getVariables() throws DebugException {
// Should be synchronized and lazy
if (fPartitionVariables == null) {
Node node = getOwner().getNode(fPage);
if (canProcess(node)) {
return new IVariable[] { new DBGpUnreachableVariable(getDebugTarget()) };
}
NodeList childProperties = node.getChildNodes();
int childrenReceived = childProperties.getLength();
fPartitionVariables = new IVariable[childrenReceived];
if (childrenReceived > 0) {
for (int i = 0; i < childrenReceived; i++) {
Node childProperty = childProperties.item(i);
IVariable child = createVariable(childProperty);
fPartitionVariables[i] = merge(child);
}
}
// Add partition variables to current variables storage
IVariable[] concat = Arrays.copyOf(fCurrentVariables,
fCurrentVariables.length + fPartitionVariables.length);
System.arraycopy(fPartitionVariables, 0, concat, fCurrentVariables.length, fPartitionVariables.length);
fCurrentVariables = concat;
}
return fPartitionVariables;
}
}
/**
* DBGp unreachable variable (is shown if i.e. XDebug max array depth
* parameter is exceeded while unfolding variables).
*
* @author Bartlomiej Laczkowski
*/
protected static class DBGpUnreachableVariable extends DBGpElement implements IVariable, IVariableFacet {
protected class DBGpUnreachableValue extends DBGpElement implements IValue {
public DBGpUnreachableValue(IDebugTarget target) {
super(target);
}
@Override
public String getReferenceTypeName() throws DebugException {
return null;
}
@Override
public String getValueString() throws DebugException {
return null;
}
@Override
public boolean isAllocated() throws DebugException {
return false;
}
@Override
public IVariable[] getVariables() throws DebugException {
return new IVariable[] {};
}
@Override
public boolean hasVariables() throws DebugException {
return false;
}
}
private DBGpUnreachableValue fValue;
protected final Set<Facet> fFacets = new HashSet<Facet>();
/**
* Creates new DBGp uninitialized variable.
*
* @param target
*/
public DBGpUnreachableVariable(IDebugTarget target) {
super(target);
fValue = new DBGpUnreachableValue(target);
addFacets(Facet.VIRTUAL_UNINIT);
}
@Override
public void setValue(String expression) throws DebugException {
// ignore
}
@Override
public void setValue(IValue value) throws DebugException {
// ignore
}
@Override
public boolean supportsValueModification() {
return false;
}
@Override
public boolean verifyValue(String expression) throws DebugException {
return false;
}
@Override
public boolean verifyValue(IValue value) throws DebugException {
return false;
}
@Override
public IValue getValue() throws DebugException {
return fValue;
}
@Override
public String getName() throws DebugException {
return DataType.PHP_UNINITIALIZED.getText();
}
@Override
public String getReferenceTypeName() throws DebugException {
return null;
}
@Override
public boolean hasValueChanged() throws DebugException {
return false;
}
@Override
public boolean hasFacet(Facet facet) {
return fFacets.contains(facet);
}
@Override
public void addFacets(Facet... facets) {
for (Facet facet : facets)
this.fFacets.add(facet);
}
}
protected IVariable[] fCurrentVariables = null;
protected IVariable[] fPreviousVariables = null;
protected Map<String, IVirtualPartition> fCurrentPartitions = new LinkedHashMap<>();
protected Map<String, IVirtualPartition> fPreviousPartitions = new LinkedHashMap<>();
/**
* Creates new DBGp container value.
*
* @param owner
*/
public AbstractDBGpContainerValue(DBGpVariable owner) {
super(owner);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.php.internal.debug.core.xdebug.dbgp.model.AbstractDBGpValue#
* getVariables()
*/
@Override
public synchronized IVariable[] getVariables() throws DebugException {
// Should be synchronized and lazy
if (fCurrentVariables == null) {
fetchVariables();
}
if (!hasPages()) {
return fCurrentVariables;
}
return fCurrentPartitions.values().toArray(new IVariable[fCurrentPartitions.size()]);
}
protected abstract IVariable createVariable(Node descriptor);
/*
* (non-Javadoc)
*
* @see
* org.eclipse.php.internal.debug.core.xdebug.dbgp.model.AbstractDBGpValue#
* update(org.w3c.dom.Node)
*/
@Override
protected void update(Node descriptor) {
super.update(descriptor);
// Reset state
fPreviousVariables = fCurrentVariables;
fCurrentVariables = null;
// Check if has any child elements
String childCountNumber = DBGpResponse.getAttribute(fDescriptor, "numchildren"); //$NON-NLS-1$
int childCount = 0;
fHasVariables = false;
if (childCountNumber != null && childCountNumber.trim().length() != 0) {
try {
childCount = Integer.parseInt(childCountNumber);
if (childCount > 0)
fHasVariables = true;
} catch (NumberFormatException nfe) {
}
}
}
/**
* Checks if there are multiple pages with variables.
*
* @return <code>true</code> if there are multiple pages with variables,
* <code>false</code> otherwise
*/
protected boolean hasPages() {
return fCurrentPartitions.size() > 0;
}
/**
* Checks if given property node can be processed.
*
* @param property
* @return <code>true</code> if given property node can be processed,
* <code>false</code> otherwise
*/
protected boolean canProcess(Node property) {
return property == null || "error".equalsIgnoreCase(property.getNodeName()); //$NON-NLS-1$
}
/**
* Uses container value related node to fetch child elements and build child
* variables or multiple pages with variables.
*/
protected void fetchVariables() {
fCurrentVariables = new IVariable[] {};
fPreviousPartitions = fCurrentPartitions;
fCurrentPartitions = new LinkedHashMap<>();
String childCountString = DBGpResponse.getAttribute(fDescriptor, "numchildren"); //$NON-NLS-1$
int childCount = 0;
if (childCountString != null && childCountString.trim().length() != 0) {
try {
childCount = Integer.parseInt(childCountString);
} catch (NumberFormatException nfe) {
}
}
String pageSizeStr = null;
pageSizeStr = DBGpResponse.getAttribute(fDescriptor, "pagesize"); //$NON-NLS-1$
int pageSize = ((DBGpTarget) getDebugTarget()).getMaxChildren();
if (pageSizeStr != null && pageSizeStr.trim().length() != 0) {
try {
pageSize = Integer.parseInt(pageSizeStr);
} catch (NumberFormatException nfe) {
}
}
if (childCount <= pageSize) {
// Child number < page size, no need for paging.
NodeList childProperties = fDescriptor.getChildNodes();
int childrenReceived = childProperties.getLength();
// Check if descriptor is already filled up
if (childCount != childrenReceived) {
DBGpTarget target = (DBGpTarget) getDebugTarget();
switch (getOwner().getKind()) {
case EVAL: {
fDescriptor = target.eval(getOwner().getFullName(), 0);
break;
}
default: {
fDescriptor = target.getProperty(getOwner().getFullName(),
String.valueOf(getOwner().getStackLevel()), 0);
break;
}
}
if (canProcess(fDescriptor)) {
fCurrentVariables = new IVariable[] { new DBGpUnreachableVariable(getDebugTarget()) };
return;
}
childProperties = fDescriptor.getChildNodes();
childrenReceived = childProperties.getLength();
childCount = childrenReceived;
}
fCurrentVariables = new IVariable[childCount];
if (childrenReceived > 0) {
for (int i = 0; i < childrenReceived; i++) {
Node childProperty = childProperties.item(i);
IVariable child = createVariable(childProperty);
fCurrentVariables[i] = merge(child);
}
}
} else {
// Create multiple pages
int subCount = (int) Math.ceil((double) childCount / (double) pageSize);
for (int i = 0; i < subCount; i++) {
int startIndex = i * pageSize;
int endIndex = (i + 1) * pageSize - 1;
if (endIndex > childCount) {
endIndex = childCount - 1;
}
String partitionId = String.valueOf(startIndex) + '-' + String.valueOf(endIndex);
IVirtualPartition partition = fPreviousPartitions.get(partitionId);
if (partition != null) {
partition.setProvider(new DBGpPage(i));
fCurrentPartitions.put(partitionId, partition);
} else {
fCurrentPartitions.put(partitionId,
new VirtualPartition(this, new DBGpPage(i), startIndex, endIndex));
}
}
}
}
/**
* 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 DBGpVariable))
return variable;
DBGpVariable incoming = (DBGpVariable) variable;
if (incoming.getFullName().isEmpty())
return incoming;
for (IVariable stored : fPreviousVariables) {
if (stored instanceof DBGpVariable) {
DBGpVariable previous = (DBGpVariable) stored;
if (previous.getFullName().equals(incoming.getFullName())) {
((DBGpVariable) stored).update(incoming.getDescriptor());
return stored;
}
}
}
return variable;
}
}