/******************************************************************************* | |
* Copyright (c) 2008, 2021 SAP AG. | |
* All rights reserved. This program and the accompanying materials | |
* are made available under the terms of the Eclipse Public License 2.0 | |
* which accompanies this distribution, and is available at | |
* https://www.eclipse.org/legal/epl-2.0/ | |
* | |
* SPDX-License-Identifier: EPL-2.0 | |
* | |
* Contributors: | |
* SAP AG - initial API and implementation | |
*******************************************************************************/ | |
package org.eclipse.mat.inspections; | |
import java.io.PrintWriter; | |
import java.io.StringWriter; | |
import java.net.URL; | |
import java.util.List; | |
import java.util.regex.Matcher; | |
import java.util.regex.Pattern; | |
import org.eclipse.mat.SnapshotException; | |
import org.eclipse.mat.internal.Messages; | |
import org.eclipse.mat.query.Bytes; | |
import org.eclipse.mat.query.Column; | |
import org.eclipse.mat.query.IContextObject; | |
import org.eclipse.mat.query.IContextObjectSet; | |
import org.eclipse.mat.query.IDecorator; | |
import org.eclipse.mat.query.IIconProvider; | |
import org.eclipse.mat.query.IQuery; | |
import org.eclipse.mat.query.IResultTable; | |
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.CommandName; | |
import org.eclipse.mat.query.annotations.HelpUrl; | |
import org.eclipse.mat.query.annotations.Icon; | |
import org.eclipse.mat.query.annotations.Usage; | |
import org.eclipse.mat.query.results.TextResult; | |
import org.eclipse.mat.snapshot.IOQLQuery; | |
import org.eclipse.mat.snapshot.ISnapshot; | |
import org.eclipse.mat.snapshot.OQL; | |
import org.eclipse.mat.snapshot.SnapshotFactory; | |
import org.eclipse.mat.snapshot.model.GCRootInfo; | |
import org.eclipse.mat.snapshot.model.IArray; | |
import org.eclipse.mat.snapshot.model.IClass; | |
import org.eclipse.mat.snapshot.model.IClassLoader; | |
import org.eclipse.mat.snapshot.model.IObject; | |
import org.eclipse.mat.snapshot.model.ObjectReference; | |
import org.eclipse.mat.snapshot.query.Icons; | |
import org.eclipse.mat.snapshot.query.ObjectListResult; | |
import org.eclipse.mat.util.IProgressListener; | |
@CommandName("oql") | |
@Icon("/META-INF/icons/oql.gif") | |
@Usage("oql \"select * from ...\"") | |
@HelpUrl("/org.eclipse.mat.ui.help/tasks/queryingheapobjects.html") | |
public class OQLQuery implements IQuery | |
{ | |
@Argument | |
public ISnapshot snapshot; | |
@Argument(flag = Argument.UNFLAGGED) | |
public String queryString = "select * from ..."; //$NON-NLS-1$ | |
@Argument(isMandatory = false) | |
public List<IContextObject> contexts; | |
public IOQLQuery.Result execute(IProgressListener listener) throws Exception | |
{ | |
try | |
{ | |
String oqlQuery = queryString; | |
if (contexts != null) | |
{ | |
// Merge the query with the objects | |
Pattern p = Pattern.compile("select (.*) from( .*)?", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$ | |
Matcher m = p.matcher(oqlQuery); | |
String rep; | |
if (m.find()) | |
rep = m.group(1); | |
else | |
rep = "*"; //$NON-NLS-1$ | |
StringBuilder sb = new StringBuilder(); | |
for (IContextObject c : contexts) | |
{ | |
if (c instanceof IContextObjectSet) | |
{ | |
IContextObjectSet context = (IContextObjectSet)c; | |
String q2 = context.getOQL(); | |
if (q2 != null) | |
OQL.union(sb, q2.replaceFirst("\\*", rep)); //$NON-NLS-1$ | |
else | |
{ | |
int o[] = context.getObjectIds(); | |
if (o.length >= 1) | |
{ | |
q2 = OQL.forObjectIds(context.getObjectIds()); | |
OQL.union(sb, q2.replaceFirst("\\*", rep)); //$NON-NLS-1$ | |
} | |
} | |
} | |
else | |
{ | |
if (c.getObjectId() >= 0) | |
{ | |
String q3 = OQL.forObjectId(c.getObjectId()); | |
OQL.union(sb, q3.replaceFirst("\\*", rep)); //$NON-NLS-1$ | |
} | |
} | |
if (sb.length() > 1000) | |
break; | |
} | |
if (sb.length() > 0) | |
{ | |
queryString = sb.toString(); | |
if (sb.length() > 1000) | |
throw new SnapshotException(Messages.OQLQuery_TooManyObjects); | |
} | |
} | |
IOQLQuery query = SnapshotFactory.createQuery(queryString); | |
Object result = query.execute(snapshot, listener); | |
if (result == null) | |
{ | |
return new OQLTextResult(Messages.OQLQuery_NoResult + "\n\n" + query, queryString); //$NON-NLS-1$ | |
} | |
else if (result instanceof IOQLQuery.Result) | |
{ | |
return (IOQLQuery.Result) result; | |
} | |
else if (result instanceof int[]) | |
{ | |
return new ObjectListResultImpl(snapshot, queryString, (int[]) result); | |
} | |
else if (result instanceof List<?>) | |
{ | |
return new ObjectTableResultImpl(snapshot, queryString, (List<?>) result); | |
} | |
else | |
{ | |
return new OQLTextResult(String.valueOf(result), queryString); | |
} | |
} | |
catch (IProgressListener.OperationCanceledException e) | |
{ | |
throw e; | |
} | |
catch (Exception e) | |
{ | |
StringBuilder buf = new StringBuilder(256); | |
buf.append(Messages.OQLQuery_ExecutedQuery + "\n"); //$NON-NLS-1$ | |
buf.append(queryString); | |
Throwable t = null; | |
if (e instanceof SnapshotException) | |
{ | |
buf.append("\n\n" + Messages.OQLQuery_ProblemReported + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ | |
buf.append(e.getMessage()); | |
t = e.getCause(); | |
} | |
else | |
{ | |
t = e; | |
} | |
if (t != null) | |
{ | |
buf.append("\n\n"); //$NON-NLS-1$ | |
StringWriter w = new StringWriter(); | |
PrintWriter o = new PrintWriter(w); | |
t.printStackTrace(o); | |
o.flush(); | |
buf.append(w.toString()); | |
} | |
return new OQLTextResult(buf.toString(), queryString); | |
} | |
} | |
static class ObjectListResultImpl extends ObjectListResult.Outbound implements IOQLQuery.Result, | |
IResultTree | |
{ | |
String queryString; | |
public ObjectListResultImpl(ISnapshot snapshot, String queryString, int[] objectIds) | |
{ | |
super(snapshot, objectIds); | |
this.queryString = queryString; | |
} | |
public String getOQLQuery() | |
{ | |
return queryString; | |
} | |
} | |
static class OQLTextResult extends TextResult implements IOQLQuery.Result | |
{ | |
String queryString; | |
public OQLTextResult(String text, String queryString) | |
{ | |
super(text); | |
this.queryString = queryString; | |
} | |
public String getOQLQuery() | |
{ | |
return queryString; | |
} | |
} | |
static class ObjectTableResultImpl implements IOQLQuery.Result, | |
IResultTable, IDecorator, IIconProvider | |
{ | |
String queryString; | |
List<?>objs; | |
boolean hasIObjects; | |
public ObjectTableResultImpl(ISnapshot snapshot, String queryString, List<?>objs) | |
{ | |
this.queryString = queryString; | |
this.objs = objs; | |
for (Object o : objs) | |
{ | |
if (o instanceof ObjectReference || o instanceof IObject) | |
{ | |
hasIObjects = true; | |
break; | |
} | |
} | |
} | |
public String getOQLQuery() | |
{ | |
return queryString; | |
} | |
@Override | |
public ResultMetaData getResultMetaData() | |
{ | |
return null; | |
} | |
@Override | |
public Column[] getColumns() | |
{ | |
if (hasIObjects) | |
return new Column[] { new Column(Messages.Column_ClassName).decorator(this), // | |
new Column(Messages.Column_ShallowHeap, Bytes.class).noTotals(), // | |
new Column(Messages.Column_RetainedHeap, Bytes.class).noTotals() }; | |
else | |
return new Column[] { new Column(Messages.OQLQuery_Column_Value) }; | |
} | |
@Override | |
public Object getColumnValue(Object row, int columnIndex) | |
{ | |
int rw = (Integer)row; | |
//Object o; | |
switch (columnIndex) | |
{ | |
case 0: | |
Object o = objs.get(rw); | |
if (o instanceof ObjectReference) | |
{ | |
try | |
{ | |
return ((ObjectReference)o).getObject().getDisplayName(); | |
} | |
catch (SnapshotException e) | |
{ | |
return new IllegalStateException(e); | |
} | |
} | |
else if (o instanceof IObject) | |
return ((IObject)o).getDisplayName(); | |
else if (o != null) | |
return o.toString(); | |
else | |
return null; | |
case 1: | |
if (!hasIObjects) | |
throw new IllegalArgumentException(); | |
o = objs.get(rw); | |
if (o instanceof ObjectReference) | |
{ | |
try | |
{ | |
return ((ObjectReference)o).getObject().getUsedHeapSize(); | |
} | |
catch (SnapshotException e) | |
{ | |
return new IllegalStateException(e); | |
} | |
} | |
else if (o instanceof IObject) | |
return ((IObject)o).getUsedHeapSize(); | |
else | |
return null; | |
case 2: | |
if (!hasIObjects) | |
throw new IllegalArgumentException(); | |
o = objs.get(rw); | |
if (o instanceof ObjectReference) | |
{ | |
try | |
{ | |
return ((ObjectReference)o).getObject().getRetainedHeapSize(); | |
} | |
catch (SnapshotException e) | |
{ | |
return new IllegalStateException(e); | |
} | |
} | |
else if (o instanceof IObject) | |
return ((IObject)o).getRetainedHeapSize(); | |
else | |
return null; | |
default: | |
throw new IllegalArgumentException(row.toString()); | |
} | |
} | |
@Override | |
public IContextObject getContext(Object row) | |
{ | |
if (!hasIObjects) | |
return null; | |
int rw = (Integer)row; | |
return new IContextObjectSet() { | |
@Override | |
public int getObjectId() | |
{ | |
Object o = objs.get(rw); | |
if (o instanceof ObjectReference) | |
try | |
{ | |
return ((ObjectReference)o).getObjectId(); | |
} | |
catch (SnapshotException e) | |
{ | |
return -1; | |
} | |
else if (o instanceof IObject) | |
{ | |
try | |
{ | |
return ((IObject)o).getObjectId(); | |
} | |
catch (RuntimeException e) | |
{ | |
if (e.getCause() instanceof SnapshotException) | |
return -1; | |
else | |
throw e; | |
} | |
} | |
return -1; | |
} | |
@Override | |
public int[] getObjectIds() | |
{ | |
int objId = getObjectId(); | |
if (objId == -1) | |
return new int[0]; | |
else | |
return new int[] {objId}; | |
} | |
@Override | |
public String getOQL() | |
{ | |
Object o = objs.get(rw); | |
if (o instanceof ObjectReference) | |
return OQL.forAddress(((ObjectReference)o).getObjectAddress()); | |
else if (o instanceof IObject) | |
return OQL.forAddress(((IObject)o).getObjectAddress()); | |
else | |
return null; | |
} | |
}; | |
} | |
@Override | |
public int getRowCount() | |
{ | |
return objs.size(); | |
} | |
@Override | |
public Object getRow(int rowId) | |
{ | |
return Integer.valueOf(rowId); | |
} | |
@Override | |
public String prefix(Object row) | |
{ | |
return null; | |
} | |
@Override | |
public String suffix(Object row) | |
{ | |
if (!hasIObjects) | |
return null; | |
int rw = (Integer)row; | |
Object o = objs.get(rw); | |
if (o instanceof ObjectReference) | |
{ | |
try | |
{ | |
GCRootInfo gc[] = ((ObjectReference) o).getObject().getGCRootInfo(); | |
if (gc != null) | |
return GCRootInfo.getTypeSetAsString(gc); | |
else | |
return null; | |
} | |
catch (SnapshotException e) | |
{ | |
// $JL-EXC$ | |
} | |
return Messages.OQLQuery_Unindexed; | |
} | |
else if (o instanceof IObject) | |
{ | |
try | |
{ | |
GCRootInfo gc[] = ((IObject) o).getGCRootInfo(); | |
if (gc != null) | |
return GCRootInfo.getTypeSetAsString(gc); | |
else | |
return null; | |
} | |
catch (SnapshotException e) | |
{ | |
// $JL-EXC$ | |
} | |
return Messages.OQLQuery_Unindexed; | |
} | |
return null; | |
} | |
@Override | |
public URL getIcon(Object row) | |
{ | |
if (!hasIObjects) | |
return null; | |
int rw = (Integer)row; | |
Object o = objs.get(rw); | |
if (o instanceof ObjectReference || o instanceof IObject) | |
{ | |
try | |
{ | |
IObject o2 = o instanceof IObject ? (IObject)o : ((ObjectReference) o).getObject(); | |
try | |
{ | |
int objectId = o2.getObjectId(); | |
return Icons.forObject(o2.getSnapshot(), objectId); | |
} | |
catch (RuntimeException e) | |
{ | |
if (!(e.getCause() instanceof SnapshotException)) | |
throw e; | |
} | |
if (o2 instanceof IClassLoader) | |
return Icons.CLASSLOADER_INSTANCE; | |
else if (o2 instanceof IClass) | |
return Icons.CLASS_INSTANCE; | |
else if (o2 instanceof IArray) | |
return Icons.ARRAY_INSTANCE; | |
else | |
return Icons.OBJECT_INSTANCE; | |
} | |
catch (SnapshotException e) | |
{ | |
} | |
} | |
return null; | |
} | |
} | |
} |