package com.jetbrains.actionscript.profiler.livetable;
import com.intellij.psi.search.GlobalSearchScope;
import com.jetbrains.actionscript.profiler.base.SortableListTreeTableModel;
import com.jetbrains.actionscript.profiler.sampler.*;
import com.jetbrains.actionscript.profiler.util.ResolveUtil;
import org.jetbrains.annotations.Nullable;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* @author: Fedor.Korotkov
*/
public class LiveModelController implements ObjectSampleHandler {
private static final int BYTES_IN_KB = 1000;
private final List<Sample> cache = new LinkedList<>();
private final ConcurrentLinkedQueue<Sample> queue = new ConcurrentLinkedQueue<>();
private final List<SizeInfoNode> filteredClasses = new ArrayList<>();
private GlobalSearchScope scope;
private volatile int allocatedMemorySize = 0;
public void updateScope(GlobalSearchScope scope) {
this.scope = scope;
}
public int getAllocatedMemorySize() {
return allocatedMemorySize / BYTES_IN_KB;
}
public void apply(SortableListTreeTableModel model) {
while (!queue.isEmpty()) {
cache.add(queue.poll());
}
final Iterator<Sample> iterator = cache.iterator();
while (iterator.hasNext()) {
final Sample sample = iterator.next();
if (sample instanceof CreateObjectSample) {
applyCreate(model, (CreateObjectSample)sample);
iterator.remove();
}
else if (sample instanceof DeleteObjectSample && applyDelete(model, (DeleteObjectSample)sample)) {
iterator.remove();
}
}
removeUselessNodes((MutableTreeNode)model.getRoot());
filterByScope(model);
}
private void filterByScope(SortableListTreeTableModel model) {
final MutableTreeNode root = (MutableTreeNode)model.getRoot();
int i = 0;
Iterator<SizeInfoNode> iterator = filteredClasses.iterator();
while (iterator.hasNext()) {
final SizeInfoNode node = iterator.next();
if (scope == null || ResolveUtil.containsInScope(node.getQName(), scope)) {
root.insert(node, root.getChildCount());
iterator.remove();
++i;
}
}
while (i < root.getChildCount()) {
final SizeInfoNode child = (SizeInfoNode)root.getChildAt(i);
if (scope != null && !ResolveUtil.containsInScope(child.getQName(), scope)) {
root.remove(i);
filteredClasses.add(child);
}
else {
++i;
}
}
}
private static void removeUselessNodes(MutableTreeNode root) {
for (int i = 0; i < root.getChildCount(); ++i) {
MutableTreeNode child = (MutableTreeNode)root.getChildAt(i);
if (child instanceof SizeInfoNode) {
if (((SizeInfoNode)child).getCount() <= 0) {
root.remove(child);
continue;
}
}
removeUselessNodes(child);
}
}
private void applyCreate(SortableListTreeTableModel model,
CreateObjectSample sample) {
final String className = sample.className;
final DefaultMutableTreeNode root = (DefaultMutableTreeNode)model.getRoot();
SizeInfoNode classNode = findChildByValue(root, className);
if (classNode == null) {
classNode = new SizeInfoNode(className, null, 0, 0);
root.insert(classNode, root.getChildCount());
}
classNode.incSize(sample.size);
SizeInfoNode node = classNode;
for (FrameInfo frameInfo : sample.frames) {
SizeInfoNode frameNode = findChildByValue(node, frameInfo.getQName());
if (frameNode == null) {
frameNode = new SizeInfoNode(frameInfo.toSimpleString(), frameInfo, 0, 0);
node.insert(frameNode, node.getChildCount());
}
node = frameNode;
node.incSize(sample.size);
}
}
private boolean applyDelete(SortableListTreeTableModel model,
DeleteObjectSample sample) {
final List<SizeInfoNode> nodesForUpdate = new ArrayList<>();
final String className = sample.className;
final DefaultMutableTreeNode root = (DefaultMutableTreeNode)model.getRoot();
SizeInfoNode classNode = findChildByValue(root, className);
if (classNode == null) {
return false;
}
nodesForUpdate.add(classNode);
SizeInfoNode node = classNode;
for (FrameInfo frameInfo : sample.frames) {
final SizeInfoNode frameNode = findChildByValue(node, frameInfo.getQName());
if (frameNode == null) {
return false;
}
nodesForUpdate.add(frameNode);
node = frameNode;
}
for (SizeInfoNode sizeInfoNode : nodesForUpdate) {
sizeInfoNode.decSize(sample.size);
}
return true;
}
@Nullable
private SizeInfoNode findChildByValue(TreeNode root, String name) {
for (int i = 0; i < root.getChildCount(); ++i) {
final TreeNode child = root.getChildAt(i);
if (!(child instanceof SizeInfoNode)) {
continue;
}
final SizeInfoNode sizeInfoNode = (SizeInfoNode)child;
if (matchByName(name, sizeInfoNode)) {
return (SizeInfoNode)child;
}
}
for (SizeInfoNode filteredNode : filteredClasses) {
if (matchByName(name, filteredNode)) {
return filteredNode;
}
}
return null;
}
private static boolean matchByName(String name, SizeInfoNode sizeInfoNode) {
if (sizeInfoNode.isMethod()) {
return name.equals(sizeInfoNode.getQName());
}
return name.equals(sizeInfoNode.getUserObject());
}
@Override
public void processCreateSample(CreateObjectSample createObjectSample) {
if (createObjectSample.className != null) {
queue.add(createObjectSample);
allocatedMemorySize += createObjectSample.size;
}
}
@Override
public void processDeleteSample(DeleteObjectSample deleteObjectSample) {
if (deleteObjectSample.className != null) {
queue.add(deleteObjectSample);
allocatedMemorySize -= deleteObjectSample.size;
}
}
}