blob: ec70ed1d43ec232be6380b082a0ab01e4e75e80a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2010 SAP AG.
* 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:
* SAP AG - initial API and implementation
*******************************************************************************/
package org.eclipse.mat.inspections;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import org.eclipse.mat.SnapshotException;
import org.eclipse.mat.collect.ArrayInt;
import org.eclipse.mat.collect.HashMapIntObject;
import org.eclipse.mat.collect.SetInt;
import org.eclipse.mat.internal.Messages;
import org.eclipse.mat.query.Column;
import org.eclipse.mat.query.Column.SortDirection;
import org.eclipse.mat.query.Bytes;
import org.eclipse.mat.query.IContextObject;
import org.eclipse.mat.query.IContextObjectSet;
import org.eclipse.mat.query.IIconProvider;
import org.eclipse.mat.query.IQuery;
import org.eclipse.mat.query.IResult;
import org.eclipse.mat.query.IResultTree;
import org.eclipse.mat.query.ResultMetaData;
import org.eclipse.mat.query.annotations.Argument;
import org.eclipse.mat.query.annotations.Category;
import org.eclipse.mat.query.annotations.CommandName;
import org.eclipse.mat.query.annotations.Menu;
import org.eclipse.mat.query.annotations.Menu.Entry;
import org.eclipse.mat.snapshot.ISnapshot;
import org.eclipse.mat.snapshot.model.IClass;
import org.eclipse.mat.snapshot.query.IHeapObjectArgument;
import org.eclipse.mat.snapshot.query.Icons;
import org.eclipse.mat.util.IProgressListener;
import org.eclipse.mat.util.VoidProgressListener;
@Category(Category.HIDDEN)
@CommandName("class_references")
@Menu( { @Entry(icon = "/META-INF/icons/class_refs_outbound.gif"), //
@Entry(options = "-inbound", icon = "/META-INF/icons/class_refs_inbound.gif") //
})
public class ClassReferrersQuery implements IQuery
{
@Argument
public ISnapshot snapshot;
@Argument(flag = Argument.UNFLAGGED)
public IHeapObjectArgument objects;
@Argument(isMandatory = false)
public boolean inbound = false;
public IResult execute(IProgressListener listener) throws Exception
{
return inbound ? new InboundClasses(snapshot, objects.getIds(listener))//
: new OutboundClasses(snapshot, objects.getIds(listener));
}
public interface Type
{
int NEW = 0;
int MIXED = 1;
int OLD_FAD = 2;
}
// //////////////////////////////////////////////////////////////
// result (also usable outside the query)
// //////////////////////////////////////////////////////////////
public static class InboundClasses extends Tree
{
public InboundClasses(ISnapshot snapshot, int[] objectIds)
{
super(snapshot, objectIds);
}
protected int[] children(ClassNode node) throws SnapshotException
{
return snapshot.getInboundRefererIds(node.objects.toArray(), new VoidProgressListener());
}
public URL getIcon(Object row)
{
ClassNode node = (ClassNode) row;
if (node.type == Type.NEW)
return Icons.CLASS_IN;
else if (node.type == Type.MIXED)
return Icons.CLASS_IN_MIXED;
return Icons.CLASS_IN_OLD;
}
}
public static class OutboundClasses extends Tree
{
public OutboundClasses(ISnapshot snapshot, int[] objectIds)
{
super(snapshot, objectIds);
}
protected int[] children(ClassNode node) throws SnapshotException
{
return snapshot.getOutboundReferentIds(node.objects.toArray(), new VoidProgressListener());
}
public URL getIcon(Object row)
{
ClassNode node = (ClassNode) row;
if (node.type == Type.NEW)
return Icons.CLASS_OUT;
else if (node.type == Type.MIXED)
return Icons.CLASS_OUT_MIXED;
return Icons.CLASS_OUT_OLD;
}
}
private abstract static class Tree implements IResultTree, IIconProvider
{
protected ISnapshot snapshot;
private List<ClassNode> treeNodes;
public Tree(ISnapshot snapshot, int[] objectIds)
{
this.snapshot = snapshot;
this.treeNodes = prepare(objectIds, null);
}
public ResultMetaData getResultMetaData()
{
return null;
}
public final Column[] getColumns()
{
return new Column[] { new Column(Messages.Column_ClassName), //
new Column(Messages.Column_Objects, int.class), //
new Column(Messages.Column_ShallowHeap, Bytes.class).sorting(SortDirection.DESC) }; //
}
public final List<?> getElements()
{
return treeNodes;
}
private final List<ClassNode> prepare(int[] ids, ClassNode parent)
{
try
{
HashMapIntObject<ClassNode> class2node = new HashMapIntObject<ClassNode>();
for (int ii = 0; ii < ids.length; ii++)
{
int objectId = ids[ii];
IClass clazz = snapshot.getClassOf(objectId);
ClassNode node = class2node.get(clazz.getObjectId());
if (node == null)
{
node = new ClassNode(clazz.getObjectId(), parent);
node.label = clazz.getName();
class2node.put(node.classId, node);
}
node.objects.add(objectId);
node.shallowHeap = node.shallowHeap.add(snapshot.getHeapSize(objectId));
}
return Arrays.asList(class2node.getAllValues(new ClassNode[0]));
}
catch (SnapshotException e)
{
throw new RuntimeException(e);
}
}
public final List<ClassNode> getChildren(Object parent)
{
try
{
int[] childrenIds = children((ClassNode) parent);
List<ClassNode> answer = prepare(childrenIds, (ClassNode) parent);
for (ClassNode classNode : answer)
checkDeadEnd(classNode);
return answer;
}
catch (SnapshotException e)
{
throw new RuntimeException(e);
}
}
protected abstract int[] children(ClassNode node) throws SnapshotException;
public final boolean hasChildren(Object element)
{
return true;
}
public final Object getColumnValue(Object row, int columnIndex)
{
ClassNode node = (ClassNode) row;
switch (columnIndex)
{
case 0:
return node.label;
case 1:
return node.objects.size();
case 2:
return node.shallowHeap;
}
return null;
}
public final IContextObject getContext(final Object row)
{
return new IContextObjectSet()
{
public int getObjectId()
{
return ((ClassNode) row).classId;
}
public int[] getObjectIds()
{
return ((ClassNode) row).objects.toArray();
}
public String getOQL()
{
return null;
}
};
}
public synchronized void checkDeadEnd(ClassNode node)
{
if (node.parent == null)
return;
// check for identical classes in path
SetInt ids = new SetInt();
for (int i = 0; i < node.objects.size(); i++)
ids.add(node.objects.get(i));
ClassNode parentNode = node;
while (parentNode.parent != null && !ids.isEmpty())
{
parentNode = parentNode.parent;
if (parentNode.classId == node.classId)
{
for (int i = 0; i < parentNode.objects.size(); i++)
ids.remove(parentNode.objects.get(i));
}
}
if (ids.isEmpty())
node.type = Type.OLD_FAD;
else if (ids.size() != node.objects.size())
node.type = Type.MIXED;
else
node.type = Type.NEW;
}
}
private static class ClassNode
{
int classId;
ArrayInt objects = new ArrayInt();
int type = Type.NEW;
String label;
Bytes shallowHeap = new Bytes(0);
ClassNode parent;
private ClassNode(int classId, ClassNode parent)
{
this.classId = classId;
this.parent = parent;
}
}
}