Skip to content

Commit abf7719

Browse files
committed
Merge branch 'ActionBuilderActionContext_improvements'
2 parents 052e463 + d181cec commit abf7719

File tree

6 files changed

+358
-19
lines changed

6 files changed

+358
-19
lines changed

Ghidra/Framework/Docking/src/main/java/docking/action/builder/AbstractActionBuilder.java

Lines changed: 106 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,15 @@
4141
*
4242
* @param <T> The type of DockingAction to build
4343
* @param <B> the Type of action builder
44+
* @param <C> The type of ActionContext. By default, the ActionContext type always starts as
45+
* the base ActionContext class. If the client calls the {@link #withContext(Class)} method on
46+
* the builder, then that class (which must be a subclass of ActionContext) becomes the ActionContext
47+
* type that will be used for future calls to the builder methods that take predicates with
48+
* ActionContext (i.e. {@link #enabledWhen(Predicate)} and {@link #validContextWhen(Predicate)}.
49+
* This works by substituting a builder with a different ActionContext type when chaining after
50+
* the {@link #withContext(Class)} call.
4451
*/
45-
public abstract class AbstractActionBuilder<T extends DockingActionIf, B extends AbstractActionBuilder<T, B>> {
52+
public abstract class AbstractActionBuilder<T extends DockingActionIf, C extends ActionContext, B extends AbstractActionBuilder<T, C, B>> {
4653

4754
/**
4855
* Name for the {@code DockingAction}
@@ -54,6 +61,11 @@ public abstract class AbstractActionBuilder<T extends DockingActionIf, B extends
5461
*/
5562
protected String owner;
5663

64+
/**
65+
* Specifies the type of ActionContext that the built action works on.
66+
*/
67+
protected Class<? extends ActionContext> actionContextClass;
68+
5769
/**
5870
* The {@code KeyBindingType} for this {@code DockingAction}
5971
*/
@@ -62,7 +74,7 @@ public abstract class AbstractActionBuilder<T extends DockingActionIf, B extends
6274
/**
6375
* The callback to perform when the action is invoked
6476
*/
65-
protected Consumer<ActionContext> actionCallback;
77+
protected Consumer<C> actionCallback;
6678

6779
/**
6880
* Description for the {@code DockingAction}. (optional)
@@ -147,17 +159,17 @@ public abstract class AbstractActionBuilder<T extends DockingActionIf, B extends
147159
/**
148160
* Predicate for determining if an action is enabled for a given context
149161
*/
150-
private Predicate<ActionContext> enabledPredicate;
162+
private Predicate<C> enabledPredicate;
151163

152164
/**
153165
* Predicate for determining if an action should be included on the pop-up menu
154166
*/
155-
private Predicate<ActionContext> popupPredicate;
167+
private Predicate<C> popupPredicate;
156168

157169
/**
158170
* Predicate for determining if an action is applicable for a given context
159171
*/
160-
private Predicate<ActionContext> validContextPredicate;
172+
private Predicate<C> validContextPredicate;
161173

162174
/**
163175
* Builder constructor
@@ -167,6 +179,7 @@ public abstract class AbstractActionBuilder<T extends DockingActionIf, B extends
167179
public AbstractActionBuilder(String name, String owner) {
168180
this.name = name;
169181
this.owner = owner;
182+
this.actionContextClass = ActionContext.class;
170183
}
171184

172185
/**
@@ -370,7 +383,8 @@ public B popupMenuGroup(String group) {
370383
* @see #popupMenuGroup(String)
371384
*/
372385
public B popupMenuGroup(String group, String subGroup) {
373-
popupSubGroup = group;
386+
popupGroup = group;
387+
popupSubGroup = subGroup;
374388
return self();
375389
}
376390

@@ -446,7 +460,8 @@ public B toolBarGroup(String group) {
446460
* @see #toolBarGroup(String)
447461
*/
448462
public B toolBarGroup(String group, String subGroup) {
449-
toolBarSubGroup = group;
463+
toolBarGroup = group;
464+
toolBarSubGroup = subGroup;
450465
return self();
451466
}
452467

@@ -484,7 +499,7 @@ public B keyBinding(String keyStrokeString) {
484499
* @param action the callback to execute when the action is invoked
485500
* @return this builder (for chaining)
486501
*/
487-
public B onAction(Consumer<ActionContext> action) {
502+
public B onAction(Consumer<C> action) {
488503
actionCallback = action;
489504
return self();
490505
}
@@ -501,7 +516,7 @@ public B onAction(Consumer<ActionContext> action) {
501516
* enabled state
502517
* @return this builder (for chaining)
503518
*/
504-
public B enabledWhen(Predicate<ActionContext> predicate) {
519+
public B enabledWhen(Predicate<C> predicate) {
505520
enabledPredicate = predicate;
506521
return self();
507522
}
@@ -524,7 +539,7 @@ public B enabledWhen(Predicate<ActionContext> predicate) {
524539
* @return this builder (for chaining)
525540
* @see #popupMenuPath(String...)
526541
*/
527-
public B popupWhen(Predicate<ActionContext> predicate) {
542+
public B popupWhen(Predicate<C> predicate) {
528543
popupPredicate = predicate;
529544
return self();
530545
}
@@ -540,11 +555,68 @@ public B popupWhen(Predicate<ActionContext> predicate) {
540555
* validity for a given {@link ActionContext}
541556
* @return this builder (for chaining)
542557
*/
543-
public B validContextWhen(Predicate<ActionContext> predicate) {
558+
public B validContextWhen(Predicate<C> predicate) {
544559
validContextPredicate = predicate;
545560
return self();
546561
}
547562

563+
/**
564+
* Sets the specific ActionContext type to use for the various predicate calls
565+
* ({@link #validContextWhen(Predicate)}, {@link #enabledWhen(Predicate)}, and
566+
* {@link #popupWhen(Predicate)}).
567+
* <P>
568+
* In other words, this allows the client to specify the type of ActionContext that is valid for
569+
* the action being built.
570+
* <P>
571+
* To be effective, this method must be called <b>before</b> setting any of the predicates
572+
* such as the {@link #enabledWhen(Predicate)}. Once this method is called you can define your
573+
* predicates using the more specific ActionContext and be assured your predicates will only
574+
* be called when the current action context is the type (or sub-type) of the context you have
575+
* specified here.
576+
* <P>
577+
* For example, assume you have an action that is only enabled when the context is of type
578+
* FooActionContext. If you don't call this method to set the ActionContext type, you would have
579+
* to write your predicate something like this:
580+
* <pre>
581+
* builder.enabledWhen(context -> {
582+
* if (!(context instanceof FooContext)) {
583+
* return false;
584+
* }
585+
* return ((FooContext) context).isAwesome();
586+
* });
587+
* </pre>
588+
* But by first calling the builder method <CODE>withContext(FooContext.class)</CODE>, you can
589+
* simply write:
590+
*
591+
* <pre>
592+
* builder.enabledWhen(context -> return context.isAwesome() }
593+
* </pre>
594+
*
595+
* @param newActionContextClass the more specific ActionContext type.
596+
* @param <AC2> The new ActionContext type (as determined by the newActionContextClass) that
597+
* the returned builder will have.
598+
* @param <B2> the new builder type.
599+
* @return an ActionBuilder whose generic types have been modified to match the new ActionContext.
600+
* It still contains all the configuration that has been applied so far.
601+
*/
602+
@SuppressWarnings("unchecked")
603+
public <AC2 extends ActionContext, B2 extends AbstractActionBuilder<T, AC2, B2>> B2 withContext(
604+
Class<AC2> newActionContextClass) {
605+
606+
// To make this work, we need to return a builder whose ActionContext is AC2 and not AC
607+
// (which is what this builder is now)
608+
//
609+
// Since we "know" that the only thing that matters regarding the ActionContext type is that
610+
// the template type (AC) must match the type of actionContextClass instance variable, we
611+
// can get away with returning this same builder and casting it to be a builder with type
612+
// AC2 instead of AC. We can do this since we set the actionContextClass below
613+
614+
actionContextClass = newActionContextClass;
615+
616+
B2 newSelf = (B2) self();
617+
return newSelf;
618+
}
619+
548620
protected void validate() {
549621
if (actionCallback == null) {
550622
throw new IllegalStateException(
@@ -566,14 +638,34 @@ protected void decorateAction(DockingAction action) {
566638
}
567639

568640
if (enabledPredicate != null) {
569-
action.enabledWhen(enabledPredicate);
641+
action.enabledWhen(adaptPredicate(enabledPredicate));
570642
}
571643
if (validContextPredicate != null) {
572-
action.validContextWhen(validContextPredicate);
644+
action.validContextWhen(adaptPredicate(validContextPredicate));
573645
}
574646
if (popupPredicate != null) {
575-
action.popupWhen(enabledPredicate);
647+
action.popupWhen(adaptPredicate(popupPredicate));
648+
}
649+
}
650+
651+
/**
652+
* Since the built action will need a predicate that handles any action type, this method
653+
* creates a predicate that adapts a user supplied predicate for a more specific ActionContext
654+
* to a general predicate that can accept any ActionContext.
655+
* @param predicate the client supplied predicate that expects a more specific ActionContext
656+
* @return a predicate that can handle any ActionContext
657+
*/
658+
@SuppressWarnings("unchecked")
659+
private Predicate<ActionContext> adaptPredicate(Predicate<C> predicate) {
660+
if (actionContextClass == ActionContext.class) {
661+
// don't wrap the predicate if it doesn't need it
662+
return (Predicate<ActionContext>) predicate;
576663
}
664+
// Convert a sub-classed ActionContext predicate to a plain ActionContext predicate
665+
Predicate<ActionContext> predicateAdapter = (ac) -> {
666+
return actionContextClass.isInstance(ac) && predicate.test((C) ac);
667+
};
668+
return predicateAdapter;
577669
}
578670

579671
protected boolean isPopupAction() {

Ghidra/Framework/Docking/src/main/java/docking/action/builder/ActionBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
* Builder for {@link DockingAction}s
2222
*/
2323
public class ActionBuilder
24-
extends AbstractActionBuilder<DockingAction, ActionBuilder> {
24+
extends AbstractActionBuilder<DockingAction, ActionContext, ActionBuilder> {
2525

2626
/**
2727
* Builder constructor

Ghidra/Framework/Docking/src/main/java/docking/action/builder/MultiActionBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
* Builder for {@link MultiActionDockingAction}
2727
*/
2828
public class MultiActionBuilder
29-
extends AbstractActionBuilder<MultiActionDockingAction, MultiActionBuilder> {
29+
extends AbstractActionBuilder<MultiActionDockingAction, ActionContext, MultiActionBuilder> {
3030
/**
3131
* List of actions for the the MultActionDockingAction
3232
*/

Ghidra/Framework/Docking/src/main/java/docking/action/builder/MultiStateActionBuilder.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
* @param <T> The action state type
2828
*/
2929
public class MultiStateActionBuilder<T> extends
30-
AbstractActionBuilder<MultiStateDockingAction<T>, MultiStateActionBuilder<T>> {
30+
AbstractActionBuilder<MultiStateDockingAction<T>, ActionContext, MultiStateActionBuilder<T>> {
3131

3232
private BiConsumer<ActionState<T>, EventTrigger> actionStateChangedCallback;
3333
private boolean performActionOnButtonClick;
@@ -77,7 +77,7 @@ public MultiStateActionBuilder<T> performActionOnButtonClick(boolean b) {
7777
public MultiStateDockingAction<T> build() {
7878
validate();
7979
MultiStateDockingAction<T> action =
80-
new MultiStateDockingAction<T>(name, owner, isToolbarAction()) {
80+
new MultiStateDockingAction<>(name, owner, isToolbarAction()) {
8181

8282
@Override
8383
public void actionStateChanged(ActionState<T> newActionState,

Ghidra/Framework/Docking/src/main/java/docking/action/builder/ToggleActionBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
* Builder for {@link ToggleDockingAction}s
2222
*/
2323
public class ToggleActionBuilder extends
24-
AbstractActionBuilder<ToggleDockingAction, ToggleActionBuilder> {
24+
AbstractActionBuilder<ToggleDockingAction, ActionContext, ToggleActionBuilder> {
2525

2626
/**
2727
* The initial toggle state for the action

0 commit comments

Comments
 (0)