41
41
*
42
42
* @param <T> The type of DockingAction to build
43
43
* @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.
44
51
*/
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 >> {
46
53
47
54
/**
48
55
* Name for the {@code DockingAction}
@@ -54,6 +61,11 @@ public abstract class AbstractActionBuilder<T extends DockingActionIf, B extends
54
61
*/
55
62
protected String owner ;
56
63
64
+ /**
65
+ * Specifies the type of ActionContext that the built action works on.
66
+ */
67
+ protected Class <? extends ActionContext > actionContextClass ;
68
+
57
69
/**
58
70
* The {@code KeyBindingType} for this {@code DockingAction}
59
71
*/
@@ -62,7 +74,7 @@ public abstract class AbstractActionBuilder<T extends DockingActionIf, B extends
62
74
/**
63
75
* The callback to perform when the action is invoked
64
76
*/
65
- protected Consumer <ActionContext > actionCallback ;
77
+ protected Consumer <C > actionCallback ;
66
78
67
79
/**
68
80
* Description for the {@code DockingAction}. (optional)
@@ -147,17 +159,17 @@ public abstract class AbstractActionBuilder<T extends DockingActionIf, B extends
147
159
/**
148
160
* Predicate for determining if an action is enabled for a given context
149
161
*/
150
- private Predicate <ActionContext > enabledPredicate ;
162
+ private Predicate <C > enabledPredicate ;
151
163
152
164
/**
153
165
* Predicate for determining if an action should be included on the pop-up menu
154
166
*/
155
- private Predicate <ActionContext > popupPredicate ;
167
+ private Predicate <C > popupPredicate ;
156
168
157
169
/**
158
170
* Predicate for determining if an action is applicable for a given context
159
171
*/
160
- private Predicate <ActionContext > validContextPredicate ;
172
+ private Predicate <C > validContextPredicate ;
161
173
162
174
/**
163
175
* Builder constructor
@@ -167,6 +179,7 @@ public abstract class AbstractActionBuilder<T extends DockingActionIf, B extends
167
179
public AbstractActionBuilder (String name , String owner ) {
168
180
this .name = name ;
169
181
this .owner = owner ;
182
+ this .actionContextClass = ActionContext .class ;
170
183
}
171
184
172
185
/**
@@ -370,7 +383,8 @@ public B popupMenuGroup(String group) {
370
383
* @see #popupMenuGroup(String)
371
384
*/
372
385
public B popupMenuGroup (String group , String subGroup ) {
373
- popupSubGroup = group ;
386
+ popupGroup = group ;
387
+ popupSubGroup = subGroup ;
374
388
return self ();
375
389
}
376
390
@@ -446,7 +460,8 @@ public B toolBarGroup(String group) {
446
460
* @see #toolBarGroup(String)
447
461
*/
448
462
public B toolBarGroup (String group , String subGroup ) {
449
- toolBarSubGroup = group ;
463
+ toolBarGroup = group ;
464
+ toolBarSubGroup = subGroup ;
450
465
return self ();
451
466
}
452
467
@@ -484,7 +499,7 @@ public B keyBinding(String keyStrokeString) {
484
499
* @param action the callback to execute when the action is invoked
485
500
* @return this builder (for chaining)
486
501
*/
487
- public B onAction (Consumer <ActionContext > action ) {
502
+ public B onAction (Consumer <C > action ) {
488
503
actionCallback = action ;
489
504
return self ();
490
505
}
@@ -501,7 +516,7 @@ public B onAction(Consumer<ActionContext> action) {
501
516
* enabled state
502
517
* @return this builder (for chaining)
503
518
*/
504
- public B enabledWhen (Predicate <ActionContext > predicate ) {
519
+ public B enabledWhen (Predicate <C > predicate ) {
505
520
enabledPredicate = predicate ;
506
521
return self ();
507
522
}
@@ -524,7 +539,7 @@ public B enabledWhen(Predicate<ActionContext> predicate) {
524
539
* @return this builder (for chaining)
525
540
* @see #popupMenuPath(String...)
526
541
*/
527
- public B popupWhen (Predicate <ActionContext > predicate ) {
542
+ public B popupWhen (Predicate <C > predicate ) {
528
543
popupPredicate = predicate ;
529
544
return self ();
530
545
}
@@ -540,11 +555,68 @@ public B popupWhen(Predicate<ActionContext> predicate) {
540
555
* validity for a given {@link ActionContext}
541
556
* @return this builder (for chaining)
542
557
*/
543
- public B validContextWhen (Predicate <ActionContext > predicate ) {
558
+ public B validContextWhen (Predicate <C > predicate ) {
544
559
validContextPredicate = predicate ;
545
560
return self ();
546
561
}
547
562
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
+
548
620
protected void validate () {
549
621
if (actionCallback == null ) {
550
622
throw new IllegalStateException (
@@ -566,14 +638,34 @@ protected void decorateAction(DockingAction action) {
566
638
}
567
639
568
640
if (enabledPredicate != null ) {
569
- action .enabledWhen (enabledPredicate );
641
+ action .enabledWhen (adaptPredicate ( enabledPredicate ) );
570
642
}
571
643
if (validContextPredicate != null ) {
572
- action .validContextWhen (validContextPredicate );
644
+ action .validContextWhen (adaptPredicate ( validContextPredicate ) );
573
645
}
574
646
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 ;
576
663
}
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 ;
577
669
}
578
670
579
671
protected boolean isPopupAction () {
0 commit comments