Skip to content

Concise creation of simple DynamicTableColumns #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

arvi18
Copy link

@arvi18 arvi18 commented Apr 21, 2025

This is a feature proposal and proof of concept to allow concise definition of DynamicTableColumns

I have been working a lot with tables recently where I use a Java record as a ROW_TYPE and want to quickly define the columns. E.g. for a example record

public record FunctionWithExtraInfo(Function func, String extraText, Integer someMagicNumber) {}

Currently, to my best knowledge, the best way to now create the TableColumnDescriptor for this is:

	protected TableColumnDescriptor<FunctionWithExtraInfo> createTableColumnDescriptor() {
		TableColumnDescriptor<FunctionWithExtraInfo> descriptor = new TableColumnDescriptor<>();

                descriptor.addVisibleColumn(new AbstractDynamicTableColumn<FunctionWithExtraInfo, Function, Object>(){

			@Override
			public String getColumnName() {
				return "Function";
			}

			@Override
			public Function getValue(FunctionWithExtraInfo rowObject, Settings settings, Object data, ServiceProvider serviceProvider) throws IllegalArgumentException {
				return rowObject.func()
			}
		});

		descriptor.addVisibleColumn(new AbstractDynamicTableColumn<FunctionWithExtraInfo, String, Object>(){

			@Override
			public String getColumnName() {
				return "Extra Text";
			}

			@Override
			public String getValue(FunctionWithExtraInfo rowObject, Settings settings, Object data, ServiceProvider serviceProvider) throws IllegalArgumentException {
				return rowObject.extraText();
			}
		});
                descriptor.addVisibleColumn(new AbstractDynamicTableColumn<FunctionWithExtraInfo, Integer, Object>(){

			@Override
			public String getColumnName() {
				return "Magic Number";
			}

			@Override
			public Integer getValue(FunctionWithExtraInfo rowObject, Settings settings, Object data, ServiceProvider serviceProvider) throws IllegalArgumentException {
				return rowObject.magicNumber()
			}
		});
}

This PR adds an extra PoC method to TableColumnDescriptor that allows achieving the same in significantly less lines, and with just the important information:

protected TableColumnDescriptor<FunctionWithExtraInfo> createTableColumnDescriptor() {
		TableColumnDescriptor<FunctionWithExtraInfo> descriptor = new TableColumnDescriptor<>();

                descriptor.addVisibleColumn("Function", Function.class, FunctionWithExtraInfo::func);
                descriptor.addVisibleColumn("Extra Info", String.class, FunctionWithExtraInfo::extraInfo);
                descriptor.addVisibleColumn("Magic Number", Integer.class, FunctionWithExtraInfo::magicNumber);
}

The second argument that explicitly specifies is needed because the type information will otherwise be lost at runtime. I consider this a small price to pay in terms of verbosity, as it is still significantly less lines than by default, and the Compiler still enforces that this type matches the return value of the accessor, so there is no risk of accidentally mixing types AFAIU.

I'd first like feedback if this approach has any drawbacks I'm not aware of, or if there is a better way to add TableColumns that I missed. If this is a worthwhile addition I can extend the extra methods to also cover the cases for hidden columns, and potentially sorting. It should also be possible to add a method that takes an accessor function that has the full signature public COLUMN_TYPE getValue(ROW_TYPE rowObject, Settings settings, Object data, ServiceProvider serviceProvider) instead of just public COLUMN_TYPE getValue(ROW_TYPE rowObject) if this is considered worthwhile.

Summary by CodeRabbit

  • New Features
    • Added support for creating table columns using simple functions or lambdas, making it easier to define and add new columns.
    • Users can now specify whether new columns should be visible or hidden by default when adding them.

@arvi18
Copy link
Author

arvi18 commented Apr 21, 2025

I have created a ton of columns over the years. I have never felt them to be too verbose. This is probably because I would put them at the bottom of the file, out of the way until needed. By having them as full classes, debugging and development are a bit easier. That being said, in your case, it seems like you are creating these tables quickly and perhaps throwing them away later. In this case, I can see the verbosity being annoying.

We do have another way of building tables, but it is quite a bit simpler. You get smaller code, but perhaps at the expense of less functionality. This class is the https://github.com/NationalSecurityAgency/ghidra/blob/a1f243ebbab2fbad75cdd6e76b463902523b4ead/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableWidget.java.

It was designed to take a row object type and a list of method names on that type that should be used to create columns for the user. I'm curious if you have seen and/or used that class.

@arvi18
Copy link
Author

arvi18 commented Apr 21, 2025

having them as full classes, debugging and development are a bit easier

do you have an example where this is easier? I'm using IntelliJ, so maybe that's something that just differs by IDE, but I can set breakpoints inside the anonymous accessor (if I provide one instead of a method reference), and I'm not sure what other debug/development scenarios would come up where it's relevant to have a standalone class

That being said, in your case, it seems like you are creating these tables quickly and perhaps throwing them away later.

The tables are for plugins that will hopefully not be thrown away, but the tables are often simple and most of the code ends up being column definitions. Given that each column takes ~10-12 lines of code to define, this quickly blows up. E.g. I have a tableModel with 8 columns, and most of the code in the file is just the column definitions.

A few of these plugins are also projects with other developers that are not that familiar with Ghidra. I think especially in those cases there is a lot of benefit from keeping the code concise so it only concerns the actually important information, instead of the same boiler plate code ten times with subtly different types.
When I first started using the tables this took me a while to realize because it was hard to discern what was actually going on.

@arvi18
Copy link
Author

arvi18 commented Apr 21, 2025

do you have an example where this is easier?

Perhaps it is more of a code organization preference. Also, we have some columns that contain more business logic that in your example. The more code for a column, the more it makes sense to be in its own class, IMO. We do this in most of our tables. You can see an example in:

https://github.com/NationalSecurityAgency/ghidra/blob/a1f243ebbab2fbad75cdd6e76b463902523b4ead/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/AbstractSymbolTableModel.java

Even when the business logic is not large, we sometimes have to add custom rendering or filtering logic. For simple tables, perhaps this is not needed. Honestly, we have just kind of always followed this pattern, with the setup inside of createTableColumnDescriptor() being as simple as adding the classes you want to be in the descriptor. Since we have never done it any other way, we have not considered adding more methods to the descriptor. That being said, your use case makes sense to me.

@arvi18
Copy link
Author

arvi18 commented Apr 21, 2025

I changed the API a bit to also support adding hidden tables. From my side I would be happy if this gets merged in it's current state, but I can also incorporate changes to the signature e.g. how to control the visibility, adding parameter for to override getColumnDescription, or other simple things I might have missed.

Copy link

coderabbitai bot commented Apr 21, 2025

Warning

Rate limit exceeded

@visz11 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 9 minutes and 36 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 2abfd4e and 27319f8.

📒 Files selected for processing (1)
  • Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableColumnDescriptor.java (3 hunks)

Walkthrough

This change adds two overloaded addColumn methods to the TableColumnDescriptor class, allowing columns to be added using a new functional interface called RowObjectAccessor. These methods enable users to define column value accessors with lambdas or functional objects, rather than requiring anonymous subclasses of DynamicTableColumn. The new methods support specifying column visibility and internally generate anonymous subclasses of AbstractDynamicTableColumn. Additionally, the RowObjectAccessor interface is introduced, and necessary imports are added to support the new functionality.

Changes

File(s) Change Summary
.../TableColumnDescriptor.java Added two overloaded addColumn methods accepting a RowObjectAccessor for column value access, with optional visibility argument. Introduced the RowObjectAccessor functional interface. Added necessary imports for new method signatures.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant TableColumnDescriptor
    participant RowObjectAccessor
    participant AbstractDynamicTableColumn

    User->>TableColumnDescriptor: addColumn(name, columnTypeClass, RowObjectAccessor, [visible])
    TableColumnDescriptor->>AbstractDynamicTableColumn: Create anonymous subclass with accessor
    AbstractDynamicTableColumn->>RowObjectAccessor: getValue(rowObject)
    TableColumnDescriptor->>TableColumnDescriptor: Add column (visible or hidden)
Loading

Poem

In the land of tables, columns bloom anew,
With lambdas and accessors, fresh code to pursue.
No more subclasses, just functions with ease,
Adding columns swiftly, as quick as you please.
The rabbit hops by, with a wink and a grin—
“Functional columns? Let’s hop right in!” 🐇


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableColumnDescriptor.java (1)

100-136: Implementation properly enables concise column creation

The new addColumn method successfully enables a more concise way to define table columns using functional interfaces rather than anonymous classes. The implementation correctly handles creating the underlying column object and setting its visibility.

I have a few notes on the implementation:

  1. The getSupportedRowType() method returns null due to type erasure limitations, which is noted in the comments. This could potentially affect code that relies on this information.
  2. The accessor function only receives the row object parameter, but does not have access to the settings, data, or serviceProvider parameters that are available in the full column implementation.

Consider enhancing the implementation to allow overriding additional column properties such as custom renderers, tooltips, or comparators. For example:

public <COLUMN_TYPE> void addColumn(
    String name, 
    Class<COLUMN_TYPE> columnTypeClass, 
    RowObjectAccessor<ROW_TYPE, COLUMN_TYPE> rowObjectAccessor, 
    boolean visible,
    ColumnProperties<ROW_TYPE, COLUMN_TYPE> properties) {
    // Implementation with optional properties
}

// Optional properties class
public class ColumnProperties<ROW_TYPE, COLUMN_TYPE> {
    private Comparator<COLUMN_TYPE> comparator;
    private TableCellRenderer renderer;
    // Additional properties

    // Builder methods
    public ColumnProperties<ROW_TYPE, COLUMN_TYPE> withComparator(Comparator<COLUMN_TYPE> comparator) {
        this.comparator = comparator;
        return this;
    }
    // Other builder methods
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2abfd4e and 27319f8.

📒 Files selected for processing (1)
  • Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableColumnDescriptor.java (3 hunks)
🔇 Additional comments (3)
Ghidra/Framework/Docking/src/main/java/docking/widgets/table/TableColumnDescriptor.java (3)

20-21: New imports are correctly added

The addition of these imports is necessary to support the new method signatures in the AbstractDynamicTableColumn implementation.


137-146: Convenience method correctly defaults to visible columns

This overloaded method provides a useful shortcut for the common case of adding visible columns.


183-192:

✅ Verification successful

Functional interface is appropriately defined

The RowObjectAccessor functional interface is well-designed with appropriate generic type parameters to maintain type safety between row objects and column values. The @FunctionalInterface annotation correctly indicates that this is intended to be used with lambda expressions or method references.

The current interface only supports accessing row data directly and throws IllegalArgumentException. Verify whether columns in your application commonly need access to Settings or ServiceProvider parameters. If they do, consider adding an alternative accessor interface:


🏁 Script executed:

#!/bin/bash
# Count how many column implementations use Settings or ServiceProvider in getValue
echo "Searching for columns that use Settings in their getValue method..."
rg -A 5 "getValue.*Settings.*\{" --type java

echo "Searching for columns that use ServiceProvider in their getValue method..."
rg -A 5 "getValue.*ServiceProvider.*\{" --type java

Length of output: 69073


RowObjectAccessor interface is sufficient as‑is

A search for table column accessors referencing Settings or ServiceProvider turned up no addColumn() usages capturing those parameters. While many DataType getValue methods take a Settings argument, none of the table column lambdas or implementations require Settings or ServiceProvider in their row‑value accessors. No additional functional interface is needed at this time.

@visz11
Copy link
Collaborator

visz11 commented Apr 23, 2025

@coderabbitai full review

Copy link

coderabbitai bot commented Apr 23, 2025

✅ Actions performed

Full review triggered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants