/*******************************************************************************
* Copyright (c) 2007, 2012 Wind River Systems, Inc. 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:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.internal.debug.ui.model;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate;
import org.eclipse.tcf.debug.ui.ITCFChildren;
import org.eclipse.tcf.debug.ui.ITCFObject;
import org.eclipse.tcf.protocol.IToken;
import org.eclipse.tcf.util.TCFDataCache;
/**
* TCFChildren is a concrete type of TCF data cache that is used to cache a list of children.
*/
public abstract class TCFChildren extends TCFDataCache<Map<String,TCFNode>> implements ITCFChildren {
private final int pool_margin;
private final Map<String,TCFNode> node_pool = new LinkedHashMap<String,TCFNode>(32, 0.75f, true);
protected final TCFNode node;
private static final TCFNode[] EMPTY_NODE_ARRAY = new TCFNode[0];
private TCFNode[] array;
private Map<String,ITCFObject> obj_map;
TCFChildren(TCFNode node) {
super(node.channel);
this.node = node;
pool_margin = 0;
node.addDataCache(this);
}
TCFChildren(TCFNode node, int pool_margin) {
super(node.channel);
this.node = node;
this.pool_margin = pool_margin;
node.addDataCache(this);
}
/**
* Dispose the cache and all nodes in the nodes pool.
*/
@Override
public void dispose() {
assert !isDisposed();
node.removeDataCache(this);
for (TCFNode n : node_pool.values()) n.dispose();
node_pool.clear();
super.dispose();
}
/**
* Remove a node from cache.
* The method is called every time a node is disposed.
* @param id - node ID
*/
void onNodeDisposed(String id) {
node_pool.remove(id);
if (isValid()) {
array = null;
obj_map = null;
Map<String,TCFNode> data = getData();
if (data != null) data.remove(id);
}
}
private void addToPool(Map<String,TCFNode> data) {
assert !isDisposed();
for (TCFNode n : data.values()) {
assert data.get(n.id) == n;
assert n.parent == node;
node_pool.put(n.id, n);
}
if (node_pool.size() > data.size() + pool_margin) {
String[] arr = node_pool.keySet().toArray(new String[node_pool.size()]);
for (String id : arr) {
if (data.get(id) == null) {
node_pool.get(id).dispose();
if (node_pool.size() <= data.size() + pool_margin) break;
}
}
}
}
/**
* End cache pending state.
* @param token - pending command handle.
* @param error - data retrieval error or null
* @param data - up-to-date map of children nodes
*/
@Override
public void set(IToken token, Throwable error, Map<String,TCFNode> data) {
array = null;
obj_map = null;
if (isDisposed()) {
// A command can return data after the cache element has been disposed.
// Just ignore the data in such case.
super.set(token, null, null);
assert node_pool.isEmpty();
}
else if (data != null) {
super.set(token, error, data);
addToPool(data);
}
else {
super.set(token, error, new HashMap<String,TCFNode>());
}
}
/**
* Set given data to the cache, mark cache as valid, cancel any pending data retrieval.
* @param data - up-to-date data to store in the cache, null means empty collection of nodes.
*/
@Override
public void reset(Map<String,TCFNode> data) {
assert !isDisposed();
array = null;
obj_map = null;
if (data != null) {
super.reset(data);
addToPool(data);
}
else {
super.reset(new HashMap<String,TCFNode>());
}
}
/**
* Invalidate the cache. If retrieval is in progress - let it continue.
*/
@Override
public void reset() {
super.reset();
array = null;
obj_map = null;
}
/**
* Force cache to invalid state, cancel pending data retrieval if any.
*/
@Override
public void cancel() {
super.cancel();
array = null;
obj_map = null;
}
/**
* Add a node to collection of children.
* @param n - a node.
*/
void add(TCFNode n) {
assert !isDisposed();
assert !n.isDisposed();
assert node_pool.get(n.id) == null;
assert n.parent == node;
node_pool.put(n.id, n);
if (isValid()) {
array = null;
obj_map = null;
Map<String,TCFNode> data = getData();
if (data != null) data.put(n.id, n);
}
}
/**
* Return collection of all nodes, including current children as well as
* currently unused nodes from the pool.
* To get only current children use getData() method.
* @return Collection of nodes.
*/
Collection<TCFNode> getNodes() {
return node_pool.values();
}
/**
* Return current number of children.
* The cache must be valid for the method to work.
* @return number of children.
*/
public int size() {
assert isValid();
Map<String,TCFNode> data = getData();
return data == null ? 0 : data.size();
}
/**
* Return current children nodes as a sorted array.
* @return array of nodes.
*/
public TCFNode[] toArray() {
assert isValid();
if (array != null) return array;
Map<String,TCFNode> data = getData();
if (data == null || data.size() == 0) return array = EMPTY_NODE_ARRAY;
array = data.values().toArray(new TCFNode[data.size()]);
Arrays.sort(array);
return array;
}
/**
* Return current children nodes as a map of ITCFObject.
* @return map of ITCFObject.
*/
public Map<String,ITCFObject> getChildren() {
assert isValid();
if (obj_map != null) return obj_map;
Map<String,TCFNode> data = getData();
obj_map = new HashMap<String,ITCFObject>();
if (data == null) return obj_map;
for (TCFNode n : data.values()) obj_map.put(n.id, n);
return obj_map;
}
/**
* Return current children nodes in IChildrenUpdate object.
* @param update - children update request object.
* @param done - a call-back object, it is called when cache state changes.
* @return true if all done, false if data request is pending.
*/
boolean getData(IChildrenUpdate update, Runnable done) {
if (!validate(done)) return false;
TCFNode[] arr = toArray();
int offset = 0;
int r_offset = update.getOffset();
int r_length = update.getLength();
for (TCFNode n : arr) {
if (offset >= r_offset && offset < r_offset + r_length) {
update.setChild(n, offset);
}
offset++;
}
return true;
}
}