Skip to content

Commit 7ddb5fd

Browse files
committed
Add object inspection and class instance handling to MAT CLI
- Introduced `ObjectInspectorResult`, `InstancesResult`, and `InstancesResultBuilder` to support object inspection and class-level instance handling. - Added `ObjectDisplayHelper` for rendering and previewing object details, including heap usage and field information. - Enhanced error handling with improved error hints for invalid arguments and missing classes. - Updated `ResultSerializer` and `SerializationOptions` to include object address resolution and refined suggestions for commands. - Improved CLI tests to cover object inspection and class instance-related functionality.
1 parent d810c5a commit 7ddb5fd

23 files changed

Lines changed: 1929 additions & 103 deletions

plugins/org.eclipse.mat.cli/src/org/eclipse/mat/cli/internal/CliArgumentParser.java

Lines changed: 106 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ public final class CliArgumentParser
2020
static final int MAX_LIMIT = 10000;
2121
static final int DEFAULT_TREE_DEPTH = 8;
2222
static final int DEFAULT_AGENT_TREE_DEPTH = 4;
23+
static final int DEFAULT_INSPECT_OBJECT_TREE_DEPTH = 3;
2324

2425
public CliArguments parse(String[] args) throws CliException
2526
{
2627
if (args == null || args.length == 0)
2728
return new CliArguments(null, null, null, null, CliArguments.OutputProfile.DEFAULT,
2829
CliArguments.OutputFormat.TEXT, false, true, DEFAULT_LIMIT, DEFAULT_TREE_DEPTH, null, null,
29-
null);
30+
null, null, false, null, null);
3031

3132
CliCommand command = null;
3233
CliCommand subjectCommand = null;
@@ -42,6 +43,10 @@ public CliArguments parse(String[] args) throws CliException
4243
int treeDepthLimit = DEFAULT_TREE_DEPTH;
4344
boolean treeDepthExplicit = false;
4445
String objectAddress = null;
46+
String className = null;
47+
String selectField = null;
48+
String fieldPath = null;
49+
boolean includeSubclasses = false;
4550
String oqlQuery = null;
4651
String oqlQueryFile = null;
4752
boolean oqlQueryStdin = false;
@@ -106,6 +111,34 @@ else if ("--object".equals(arg)) //$NON-NLS-1$
106111
{
107112
objectAddress = nextArg(args, ++ii, "--object"); //$NON-NLS-1$
108113
}
114+
else if (arg.startsWith("--class=")) //$NON-NLS-1$
115+
{
116+
className = arg.substring("--class=".length()); //$NON-NLS-1$
117+
}
118+
else if ("--class".equals(arg)) //$NON-NLS-1$
119+
{
120+
className = nextArg(args, ++ii, "--class"); //$NON-NLS-1$
121+
}
122+
else if (arg.startsWith("--select-field=")) //$NON-NLS-1$
123+
{
124+
selectField = arg.substring("--select-field=".length()); //$NON-NLS-1$
125+
}
126+
else if ("--select-field".equals(arg)) //$NON-NLS-1$
127+
{
128+
selectField = nextArg(args, ++ii, "--select-field"); //$NON-NLS-1$
129+
}
130+
else if (arg.startsWith("--field-path=")) //$NON-NLS-1$
131+
{
132+
fieldPath = arg.substring("--field-path=".length()); //$NON-NLS-1$
133+
}
134+
else if ("--field-path".equals(arg)) //$NON-NLS-1$
135+
{
136+
fieldPath = nextArg(args, ++ii, "--field-path"); //$NON-NLS-1$
137+
}
138+
else if ("--include-subclasses".equals(arg)) //$NON-NLS-1$
139+
{
140+
includeSubclasses = true;
141+
}
109142
else if (arg.startsWith("--query=")) //$NON-NLS-1$
110143
{
111144
oqlQuery = arg.substring("--query=".length()); //$NON-NLS-1$
@@ -179,19 +212,21 @@ else if (command.requiresSnapshot() && heapFile == null)
179212

180213
if (profile == CliArguments.OutputProfile.AGENT && !formatExplicit)
181214
format = CliArguments.OutputFormat.JSON;
182-
if (!treeDepthExplicit)
183-
treeDepthLimit = defaultTreeDepth(profile);
184215

185216
if (command == null)
186217
{
187218
if (help)
188219
{
189220
return new CliArguments(null, null, null, null, profile, format, verbose, true, limit,
190-
treeDepthLimit, objectAddress, oqlQuery, queryCommand);
221+
treeDepthLimit, objectAddress, className, selectField, fieldPath, includeSubclasses,
222+
oqlQuery, queryCommand);
191223
}
192224
throw CliException.usage("Missing command"); //$NON-NLS-1$
193225
}
194226

227+
if (!treeDepthExplicit)
228+
treeDepthLimit = defaultTreeDepth(command, profile);
229+
195230
if (command == CliCommand.OQL)
196231
oqlQuery = resolveExclusiveInput("oql", "--query", oqlQuery, "--query-file", oqlQueryFile, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
197232
"--query-stdin", oqlQueryStdin); //$NON-NLS-1$
@@ -203,7 +238,8 @@ else if (command == CliCommand.QUERY)
203238
limit = defaultLimit(command, queryCommand);
204239

205240
CliArguments parsed = new CliArguments(command, subjectCommand, subjectName, heapFile, profile, format, verbose,
206-
help, limit, treeDepthLimit, objectAddress, oqlQuery, queryCommand);
241+
help, limit, treeDepthLimit, objectAddress, className, selectField, fieldPath,
242+
includeSubclasses, oqlQuery, queryCommand);
207243
validate(parsed);
208244
return parsed;
209245
}
@@ -298,8 +334,23 @@ private void validate(CliArguments arguments) throws CliException
298334
switch (arguments.getCommand())
299335
{
300336
case PATH2GC:
337+
case INSPECT_OBJECT:
301338
if (isEmpty(arguments.getObjectAddress()))
302-
throw CliException.usage("path2gc requires --object 0x..."); //$NON-NLS-1$
339+
throw CliException.usage(arguments.getCommand().getToken() + " requires --object 0x..."); //$NON-NLS-1$
340+
if (!isEmpty(arguments.getSelectField()) && !isEmpty(arguments.getFieldPath()))
341+
throw CliException.usage("inspect-object accepts only one of --select-field or --field-path"); //$NON-NLS-1$
342+
if (arguments.getSelectField() != null && isEmpty(arguments.getSelectField()))
343+
throw CliException.usage("inspect-object requires a non-empty --select-field"); //$NON-NLS-1$
344+
if (arguments.getFieldPath() != null && isEmpty(arguments.getFieldPath()))
345+
throw CliException.usage("inspect-object requires a non-empty --field-path"); //$NON-NLS-1$
346+
if (!isEmpty(arguments.getSelectField()) && arguments.getSelectField().indexOf('.') >= 0)
347+
throw CliException.usage("--select-field accepts one field name. Use --field-path for dotted paths."); //$NON-NLS-1$
348+
if (!isEmpty(arguments.getFieldPath()) && hasEmptyPathSegment(arguments.getFieldPath()))
349+
throw CliException.usage("Invalid --field-path: " + arguments.getFieldPath()); //$NON-NLS-1$
350+
break;
351+
case INSTANCES:
352+
if (isEmpty(arguments.getClassName()))
353+
throw CliException.usage("instances requires --class <fqcn>"); //$NON-NLS-1$
303354
break;
304355
case OQL:
305356
if (isEmpty(arguments.getOqlQuery()))
@@ -420,9 +471,14 @@ public CliArguments partialParse(String[] args, CliArguments.OutputProfile profi
420471
File heapFile = null;
421472
boolean help = args == null || args.length == 0;
422473
String objectAddress = null;
474+
String className = null;
475+
String selectField = null;
476+
String fieldPath = null;
477+
boolean includeSubclasses = false;
423478
String oqlQuery = null;
424479
String queryCommand = null;
425-
int treeDepthLimit = defaultTreeDepth(profile);
480+
int treeDepthLimit = defaultTreeDepth(null, profile);
481+
boolean treeDepthExplicit = false;
426482

427483
if (args != null)
428484
{
@@ -440,8 +496,17 @@ else if (expectsValue(arg))
440496
String value = args[++ii];
441497
if ("--object".equals(arg)) //$NON-NLS-1$
442498
objectAddress = value;
499+
else if ("--class".equals(arg)) //$NON-NLS-1$
500+
className = value;
501+
else if ("--select-field".equals(arg)) //$NON-NLS-1$
502+
selectField = value;
503+
else if ("--field-path".equals(arg)) //$NON-NLS-1$
504+
fieldPath = value;
443505
else if ("--depth".equals(arg)) //$NON-NLS-1$
506+
{
444507
treeDepthLimit = safePartialDepth(value, treeDepthLimit);
508+
treeDepthExplicit = true;
509+
}
445510
else if ("--query".equals(arg)) //$NON-NLS-1$
446511
oqlQuery = value;
447512
else if ("--command".equals(arg)) //$NON-NLS-1$
@@ -452,18 +517,35 @@ else if (arg.startsWith("--object=")) //$NON-NLS-1$
452517
{
453518
objectAddress = arg.substring("--object=".length()); //$NON-NLS-1$
454519
}
520+
else if (arg.startsWith("--class=")) //$NON-NLS-1$
521+
{
522+
className = arg.substring("--class=".length()); //$NON-NLS-1$
523+
}
455524
else if (arg.startsWith("--query=")) //$NON-NLS-1$
456525
{
457526
oqlQuery = arg.substring("--query=".length()); //$NON-NLS-1$
458527
}
528+
else if (arg.startsWith("--select-field=")) //$NON-NLS-1$
529+
{
530+
selectField = arg.substring("--select-field=".length()); //$NON-NLS-1$
531+
}
532+
else if (arg.startsWith("--field-path=")) //$NON-NLS-1$
533+
{
534+
fieldPath = arg.substring("--field-path=".length()); //$NON-NLS-1$
535+
}
459536
else if (arg.startsWith("--depth=")) //$NON-NLS-1$
460537
{
461538
treeDepthLimit = safePartialDepth(arg.substring("--depth=".length()), treeDepthLimit); //$NON-NLS-1$
539+
treeDepthExplicit = true;
462540
}
463541
else if (arg.startsWith("--command=")) //$NON-NLS-1$
464542
{
465543
queryCommand = arg.substring("--command=".length()); //$NON-NLS-1$
466544
}
545+
else if ("--include-subclasses".equals(arg)) //$NON-NLS-1$
546+
{
547+
includeSubclasses = true;
548+
}
467549
else if (arg.startsWith("--")) //$NON-NLS-1$
468550
{
469551
continue;
@@ -502,8 +584,12 @@ else if (command.requiresSnapshot() && heapFile == null)
502584
}
503585
}
504586

587+
if (!treeDepthExplicit)
588+
treeDepthLimit = defaultTreeDepth(command, profile);
589+
505590
return new CliArguments(command, subjectCommand, subjectName, heapFile, profile, format, false, help,
506-
defaultLimit(command, queryCommand), treeDepthLimit, objectAddress, oqlQuery, queryCommand);
591+
defaultLimit(command, queryCommand), treeDepthLimit, objectAddress, className, selectField,
592+
fieldPath, includeSubclasses, oqlQuery, queryCommand);
507593
}
508594

509595
private int defaultLimit(CliCommand command, String queryCommand)
@@ -535,15 +621,25 @@ private boolean expectsValue(String option)
535621
{
536622
return "--profile".equals(option) || "--format".equals(option) || "--limit".equals(option) //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
537623
|| "--depth".equals(option) //$NON-NLS-1$
538-
|| "--object".equals(option) || "--query".equals(option) || "--query-file".equals(option) //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
624+
|| "--object".equals(option) || "--class".equals(option) //$NON-NLS-1$ //$NON-NLS-2$
625+
|| "--select-field".equals(option) || "--field-path".equals(option) //$NON-NLS-1$ //$NON-NLS-2$
626+
|| "--query".equals(option) //$NON-NLS-1$
627+
|| "--query-file".equals(option) //$NON-NLS-1$
539628
|| "--command".equals(option) || "--command-file".equals(option); //$NON-NLS-1$ //$NON-NLS-2$
540629
}
541630

542-
private int defaultTreeDepth(CliArguments.OutputProfile profile)
631+
private int defaultTreeDepth(CliCommand command, CliArguments.OutputProfile profile)
543632
{
633+
if (command == CliCommand.INSPECT_OBJECT)
634+
return DEFAULT_INSPECT_OBJECT_TREE_DEPTH;
544635
return profile == CliArguments.OutputProfile.AGENT ? DEFAULT_AGENT_TREE_DEPTH : DEFAULT_TREE_DEPTH;
545636
}
546637

638+
private boolean hasEmptyPathSegment(String fieldPath)
639+
{
640+
return fieldPath.startsWith(".") || fieldPath.endsWith(".") || fieldPath.indexOf("..") >= 0; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
641+
}
642+
547643
private int safePartialDepth(String value, int fallback)
548644
{
549645
try

plugins/org.eclipse.mat.cli/src/org/eclipse/mat/cli/internal/CliArguments.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,17 @@ public static OutputFormat parse(String value) throws CliException
5252
private final int limit;
5353
private final int treeDepthLimit;
5454
private final String objectAddress;
55+
private final String className;
56+
private final String selectField;
57+
private final String fieldPath;
58+
private final boolean includeSubclasses;
5559
private final String oqlQuery;
5660
private final String queryCommand;
5761

5862
CliArguments(CliCommand command, CliCommand subjectCommand, String subjectName, File heapFile, OutputProfile profile,
5963
OutputFormat format, boolean verbose, boolean help, int limit, int treeDepthLimit,
60-
String objectAddress, String oqlQuery, String queryCommand)
64+
String objectAddress, String className, String selectField, String fieldPath,
65+
boolean includeSubclasses, String oqlQuery, String queryCommand)
6166
{
6267
this.command = command;
6368
this.subjectCommand = subjectCommand;
@@ -70,6 +75,10 @@ public static OutputFormat parse(String value) throws CliException
7075
this.limit = limit;
7176
this.treeDepthLimit = treeDepthLimit;
7277
this.objectAddress = objectAddress;
78+
this.className = className;
79+
this.selectField = selectField;
80+
this.fieldPath = fieldPath;
81+
this.includeSubclasses = includeSubclasses;
7382
this.oqlQuery = oqlQuery;
7483
this.queryCommand = queryCommand;
7584
}
@@ -134,6 +143,31 @@ public String getObjectAddress()
134143
return objectAddress;
135144
}
136145

146+
public String getClassName()
147+
{
148+
return className;
149+
}
150+
151+
public String getSelectField()
152+
{
153+
return selectField;
154+
}
155+
156+
public String getFieldPath()
157+
{
158+
return fieldPath;
159+
}
160+
161+
public String getInspectionFieldPath()
162+
{
163+
return fieldPath != null ? fieldPath : selectField;
164+
}
165+
166+
public boolean isIncludeSubclasses()
167+
{
168+
return includeSubclasses;
169+
}
170+
137171
public String getOqlQuery()
138172
{
139173
return oqlQuery;

plugins/org.eclipse.mat.cli/src/org/eclipse/mat/cli/internal/CliCommand.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ public enum CliCommand
1414
SUMMARY("summary", true, false, false, false), //$NON-NLS-1$
1515
THREADS("threads", true, false, false, false), //$NON-NLS-1$
1616
HISTOGRAM("histogram", true, false, false, false), //$NON-NLS-1$
17+
INSTANCES("instances", true, false, false, false), //$NON-NLS-1$
18+
INSPECT_OBJECT("inspect-object", true, false, false, false), //$NON-NLS-1$
1719
TOP_CONSUMERS("top-consumers", true, false, false, false), //$NON-NLS-1$
1820
PATH2GC("path2gc", true, false, false, false), //$NON-NLS-1$
1921
OQL("oql", true, false, false, false), //$NON-NLS-1$

0 commit comments

Comments
 (0)