/
ActionFX.java
1440 lines (1313 loc) · 59 KB
/
ActionFX.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (c) 2020 Martin Koster
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.github.actionfx.core;
import java.io.File;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.function.Consumer;
import javax.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.actionfx.core.annotation.AFXApplication;
import com.github.actionfx.core.annotation.AFXControlValue;
import com.github.actionfx.core.annotation.AFXController;
import com.github.actionfx.core.annotation.AFXSubscribe;
import com.github.actionfx.core.annotation.ValidationMode;
import com.github.actionfx.core.container.BeanContainerFacade;
import com.github.actionfx.core.container.DefaultActionFXBeanContainer;
import com.github.actionfx.core.container.instantiation.ConstructorBasedInstantiationSupplier;
import com.github.actionfx.core.converter.ConversionService;
import com.github.actionfx.core.dialogs.DialogController;
import com.github.actionfx.core.events.PriorityAwareEventBus;
import com.github.actionfx.core.extension.ActionFXExtensionsBean;
import com.github.actionfx.core.extension.beans.BeanExtension;
import com.github.actionfx.core.instrumentation.ActionFXEnhancer;
import com.github.actionfx.core.instrumentation.ActionFXEnhancer.EnhancementStrategy;
import com.github.actionfx.core.instrumentation.ControllerWrapper;
import com.github.actionfx.core.instrumentation.bytebuddy.ActionFXByteBuddyEnhancer;
import com.github.actionfx.core.utils.AnnotationUtils;
import com.github.actionfx.core.utils.ReflectionUtils;
import com.github.actionfx.core.validation.ValidationResult;
import com.github.actionfx.core.view.View;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import javafx.application.Application;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Parent;
import javafx.stage.FileChooser.ExtensionFilter;
import javafx.stage.Stage;
import javafx.stage.Window;
/**
* Central handler for working with ActionFX. It provides routines to get views and controllers.
* <p>
* As ActionFX uses an internal bean container with dependency injection support, it is recommended to wire all
* controllers with {@code @Inject} instead of accessing them through this class (please note that there is also support
* of Spring's bean container through ActionFX's {@code actionfx-spring-boot} module).
* <p>
* <b>Using ActionFX:</b>
* <p>
* Before using this class, it needs to be setup in the {@code main()} or {@link Application#init()} method for the
* specific application.
* <p>
* <b>Expample:</b>
*
* <pre>
* ActionFX actionFX = ActionFX.builder().configurationClass(SampleApp.class).build();
* </pre>
*
* After the setup, an instance of this class is retrieved throughout the application via:
*
* <pre>
* ActionFX instance = ActionFX.getInstance();
* </pre>
*
* After the ActionFX instance is built, a component scan can be performed:
*
* <pre>
* ActionFX.getInstance().scanForActionFXComponents();
* </pre>
*
* This will populate ActionFX internal bean container and makes controller and views available for usage. Please note,
* that this step is not required, if you use Spring Boot and included module {@code afx-spring-boot} in your
* application classpath. The {@code afx-spring-boot} provides auto-configuration and scans for ActionFX components with
* its Spring bean container during start-up.
* <p>
* In case you need access to the ActionFX instance in a controller, you can alternatively inject the ActionFX instance
* via {@link Inject}. This might be helpful for unit testing your controller independently of a setup ActionFX instance
* (you can use e.g. Mockito to mock the {@link ActionFX} instance for unit-testing).
*
* <pre>
* @AFXController(viewId="mainView", fxml="some.fxml">
* public class MainController {
*
* @Inject
* private ActionFX actionFX;
*
* }
* </pre>
*
* @author koster
*
*/
public class ActionFX {
private static final Logger LOG = LoggerFactory.getLogger(ActionFX.class);
// 'protected' visibility to manipulate instance for unit testing
protected static ActionFX instance;
// internal framework state, starting with UNINITIALIZED. Further states are
// CONFIGURED and finally INITIALIZED
protected static ActionFXState actionFXState = ActionFXState.UNINITIALIZED;
// the internal bean container for managing ActionFX components
protected BeanContainerFacade beanContainer;
// the ID of the mainView that is displayed in JavaFXs primary Stage
protected String mainViewId;
// the package name to scan for ActionFX components
protected String scanPackage;
// byte code enhancement strategy to use within ActionFX (runtime agent or
// sub-classing)
protected EnhancementStrategy enhancementStrategy;
// the byte-code enhancer to use
protected ActionFXEnhancer enhancer;
// the primary stage of the JavaFX application
protected Stage primaryStage;
// central exception handler
protected UncaughtExceptionHandler uncaughtExceptionHandler;
// observable property for a locale for internationalization
protected ObservableValue<Locale> observableLocale;
// holds a list of custom controller extensions added by the user during
// ActionFX setup
protected ActionFXExtensionsBean actionFXExtensionsBean;
// the global validation mode to be applied to controls, in case these
// carry validation-related annotations. in that case, annotation do not need
// to mention a validation mode
protected ValidationMode validationGlobalMode;
// shall validation decorations be applied to scenegraph nodes in case of validation errors?
protected boolean validationApplyResultDecoration;
// shall decorations be applied to required fields in the scenegraph?
protected boolean validationApplyRequiredDecoration;
// what is the timeout in milliseconds ActionFX shall wait before starting to trigger a validation?
protected int validationStartTimeoutMs;
/**
* Internal constructor. Use {@link #builder()} method to create your application-specific instance of
* {@link ActionFX}.
*/
@SuppressFBWarnings(justification = "Design Decision")
ActionFX() {
instance = this; // NOSONAR
}
/**
* Creates a builder that is used for setting up the application-specific instance of {@link ActionFX}.
*
* @return the ActionFX builder instance that is used to setup the configuration
*/
public static ActionFXBuilder builder() {
if (instance != null) {
throw new IllegalStateException("ActionFX instance has already been built.");
}
return new ActionFXBuilder();
}
/**
* Gets an instance of {@link ActionFX}. Before calling this method, you need to setup your application-specific
* configuration that ActionFX shall handle by using the {@link #builder()} method, returning an instance of
* {@link ActionFXBuilder}.
*
* @return the built ActionFX instance
*/
public static ActionFX getInstance() {
if (instance == null) {
throw new IllegalStateException("ActionFX instance has not been built yet. Call ActionFX.build() first!");
}
return instance;
}
/**
* Returns {@code true}, in case the ActionFX instance is available and the state is either
* {@link ActionFXState#INITIALIZED} or {@link ActionFXState#CONFIGURED} , {@code false}, if the instance is not yet
* setup or the state is {@link ActionFXState#UNINITIALIZED}.
*
* @return {@code true}, in case the ActionFX instance is properly available and setup for use, {@code false}
* otherwise
*/
public static boolean isConfigured() {
return instance != null
&& (actionFXState == ActionFXState.INITIALIZED || actionFXState == ActionFXState.CONFIGURED);
}
/**
* Returns {@code true}, in case the ActionFX instance is available and the state is
* {@link ActionFXState#INITIALIZED} , {@code false}, if the instance is not yet setup or the state is
* {@link ActionFXState#UNINITIALIZED} or {@link ActionFXState#CONFIGURED}
*
* @return {@code true}, in case the ActionFX instance is properly available and initialized, {@code false}
* otherwise
*/
public static boolean isInitialized() {
return instance != null && actionFXState == ActionFXState.INITIALIZED;
}
/**
* Scans for ActionFX components, depending on the configured {@link #getScanPackage()}.
* <p>
* This method requires that the JavaFX thread is up-and-running, as certain components (e.g.
* {@link javafx.scene.web.WebView}) can only be instantiated within the JavaFX thread.
*/
public void scanForActionFXComponents() {
checkActionFXState(ActionFXState.CONFIGURED);
scanForActionFXComponentsInternal();
}
/**
* Internal component scanning routine.
*/
private void scanForActionFXComponentsInternal() {
// let's register some ActionFX-specific beans in the container before we do the
// component scan
beanContainer.addActionFXBeans(this);
// let's let the bean container implementation do the work
if (StringUtils.isNotEmpty(scanPackage)) {
beanContainer.runComponentScan(scanPackage);
}
actionFXState = ActionFXState.INITIALIZED;// NOSONAR
}
/**
* Adds a new controller to ActionFX' internal bean container. It is expected that the controller class is annotated
* by {@link AFXController}.
* <p>
* Please prefer scanning for controller classes via {@link #scanForActionFXComponents()} and a set
* {@code scanPackage} in the internal {@link ActionFXBuilder}.
* <p>
* Additionally, please keep in mind that you need to take care for controller dependencies on your own, i.e. if you
* inject a controller B into controller A, you need to add the controller class for B before you add the controller
* class for A.
*
* @param controllerClass
* the controller bean class
*/
public void addController(final Class<?> controllerClass) {
beanContainer.addControllerBeanDefinition(controllerClass);
}
/**
* Gets the view by the supplied {@code viewId}.
*
* @param viewId
* the view ID
* @return the view instance. If the {@code viewId} does not exists, an {@code IllegalArgumentException} is thrown.
*/
public View getView(final String viewId) {
final Object candidate = beanContainer.getBean(viewId);
if (candidate == null) {
throw new IllegalArgumentException("There is no view with ID='" + viewId
+ "' in the bean container of type '" + beanContainer.getClass().getCanonicalName() + "'!");
}
if (!View.class.isAssignableFrom(candidate.getClass())) {
throw new IllegalArgumentException(
"Bean with ID='" + viewId + "' is not of type '" + View.class.getCanonicalName()
+ "', but was of type '" + candidate.getClass().getCanonicalName() + "'!");
}
return (View) candidate;
}
/**
* Gets the controller defined by the supplied {@code controllerClass}.
*
* @param beanClass
* the bean class for that an instance shall be retrieved.
* @return the retrieved controller instance.If the {@code controllerClass} does not exists, {@code null} is
* returned.
*/
public <T> T getBean(final Class<T> beanClass) {
return beanContainer.getBean(beanClass);
}
/**
* Gets the controller by the supplied {@code controllerId}.
*
* @param beanId
* the bean ID for that an instance shall be retrieved.
* @return the retrieved controller instance.If the {@code controllerId} does not exists, {@code null} is returned.
*/
public <T> T getBean(final String beanId) {
return beanContainer.getBean(beanId);
}
/**
* Gets the {@link ResourceBundle} that is used internationalization of the view associated with the controller
* identified by {@code controllerId}.
* <p>
* The locale is retrieved from the {@link #getObservableLocale()} that has been configured with this instance of
* {@link ActionFX}.
*
* @param controllerId
* the ID of the controller
* @return the resource bundle, or {@code null}, in case there is no resource bundle associated with the given
* controller
*/
public ResourceBundle getControllerResourceBundle(final String controllerId) {
final Object controller = getBean(controllerId);
if (controller == null) {
return null;
}
return getControllerResourceBundle(controller.getClass());
}
/**
* Gets the {@link ResourceBundle} that is used internationalization of the view associated with the given
* {@code controllerClass}.
* <p>
* The locale is retrieved from the {@link #getObservableLocale()} that has been configured with this instance of
* {@link ActionFX}.
*
* @param controllerClass
* the controller class
* @return the resource bundle, or {@code null}, in case there is no resource bundle associated with the given
* controller
*/
public ResourceBundle getControllerResourceBundle(final Class<?> controllerClass) {
return beanContainer.resolveResourceBundle(controllerClass, observableLocale.getValue());
}
/**
* Gets an internationalized message from an underlying resource bundle that is registered for the supplied
* {@code controllerClass}.
* <p>
* In case the {@code messageKey} can not be resolved with the looked up resource bundle, the {@code defaultMessage}
* will be returned.
*
* @param controllerClass
* the controller class for that a resource bundle was defined
* @param messageKey
* the message key to look up
* @param defaultMessage
* the default message that is returned in case the look up for the supplied {@code messageKey} fails
* @return the internationalized message
*/
public String getMessage(final Class<?> controllerClass, final String messageKey, final String defaultMessage) {
final ResourceBundle resourceBundle = getControllerResourceBundle(controllerClass);
if (resourceBundle == null || StringUtils.isBlank(messageKey)) {
return defaultMessage;
}
try {
return resourceBundle.getString(messageKey);
} catch (final MissingResourceException e) {
return defaultMessage;
}
}
/**
* Returns the main view of the application.
*
* @return the main view
*/
public View getMainView() {
return getView(getMainViewId());
}
/**
* The ID / name of the main view that is displayed in JavaFX's primary {@link Stage}.
*
* @return the main view ID
*/
public String getMainViewId() {
return mainViewId;
}
/**
* Returns the {@link View} associated with the supplied {@code controller}.
*
* @param controller
* the controller for that the view shall be retrieved
* @return the view associated with the supplied controller
*/
public View getView(final Object controller) {
return ControllerWrapper.of(controller).getView();
}
/**
* Hides the view associated with the supplied {@code controller}. This method is useful for closing (modal) dialogs
* from its controller.
*
* @param controller
* the controller thats view shall be hidden
*/
public void hideView(final Object controller) {
getView(controller).hide();
}
/**
* Displays the main view in the primary stage.
*
* @param primaryStage
* the primary stage
*/
public void showMainView(final Stage primaryStage) {
setPrimaryStage(primaryStage);
final View view = getMainView();
view.show(primaryStage);
}
/**
* Shows the view of the supplied {@code controller}.
*
* @param controller
* the controller thats view shall be shown
*/
public void showView(final Object controller) {
getView(controller).show();
}
/**
* Shows the view of identified by the supplied {@code viewId}.
*
* @param viewId
* that viewId identifying the view to show
*/
public void showView(final String viewId) {
getView(viewId).show();
}
/**
* Shows the view of the supplied {@code controller} inside the given {@link Stage}.
*
* @param controller
* the controller thats view shall be shown
* @param stage
* the stage where the view shall be displayed inside
*/
public void showView(final Object controller, final Stage stage) {
getView(controller).show(stage);
}
/**
* Shows the view identified by the supplied {@code viewId} inside the given {@link Stage}.
*
* @param viewId
* that viewId identifying the view to show
* @param stage
* the stage where the view shall be displayed inside
*/
public void showView(final String viewId, final Stage stage) {
getView(viewId).show(stage);
}
/**
* Shows the view of the supplied {@code controller} in a modal dialog.
*
* @param controller
* the controller thats view shall be shown
*/
public void showViewAndWait(final Object controller) {
getView(controller).showAndWait();
}
/**
* Shows the view identified by the supplied {@code viewId} in a modal dialog.
*
* @param viewId
* that viewId identifying the view to show
*/
public void showViewAndWait(final String viewId) {
getView(viewId).showAndWait();
}
/**
* Performs an dock operation for a nested view injected into a controller via
* {@link com.github.actionfx.core.annotation.AFXNestedView}, in case the nested view is undocked.
*
* @param nestedViewId
* the view ID of the nested view (must be injected into a view via
* {@link com.github.actionfx.core.annotation.AFXNestedView}).
*/
public void dockNestedView(final String nestedViewId) {
if (isNestedViewDocked(nestedViewId)) {
return;
}
final View view = getView(nestedViewId);
view.reattachView();
}
/**
* Performs an undock operation for a nested view injected into a controller via
* {@link com.github.actionfx.core.annotation.AFXNestedView}, in case the nested view is docked.
*
* @param nestedViewId
* the view ID of the nested view (must be injected into a view via
* {@link com.github.actionfx.core.annotation.AFXNestedView}).
*/
public void undockNestedView(final String nestedViewId) {
if (!isNestedViewDocked(nestedViewId)) {
return;
}
final View view = getView(nestedViewId);
view.detachView();
view.show(new Stage());
}
/**
* Determines whether the nested view identfied by {@code nestedViewId} is currently docked into a parent
* scenegraph.
*
* @param nestedViewId
* the view ID of the nested view (must be injected into a view via
* {@link com.github.actionfx.core.annotation.AFXNestedView}).
* @return {@code true}, if the given nested view is currently docked into the parent scene graph, {@code false}, if
* the view is displayed undocked in its own stage.
*/
public boolean isNestedViewDocked(final String nestedViewId) {
final View view = getView(nestedViewId);
final Parent node = view.getRootNode();
return node.getParent() != null;
}
/**
* Retrieves ActionFX' conversion service.
*
* @return the conversion service
*/
public ConversionService getConversionService() {
return getBean(ConversionService.class);
}
/**
* Gets the custom ActionFX extensions (controller and bean definition extensions)
*
* @return the bean holding custom ActionFX extensions
*/
public ActionFXExtensionsBean getActionFXExtensionsBean() {
return actionFXExtensionsBean;
}
/**
* Retrieves ActionFX' internal event bus singleton.
*
* @return the event bus
*/
public PriorityAwareEventBus getEventBus() {
return getBean(PriorityAwareEventBus.class);
}
/**
* Displays a modal confirmation dialogue with the specified {@code title} and {@code message}.
*
* @param title
* the title of the dialog
* @param headerText
* the header text to be displayed in the dialog
* @param contentText
* the content text to be displayed in the dialog
* @return {@code true}, when the OK button has been pressed, {@code false} otherwise.
*/
public boolean showConfirmationDialog(final String title, final String headerText, final String contentText) {
return ((DialogController) getBean(BeanContainerFacade.DIALOG_CONTROLLER_BEANNAME))
.showConfirmationDialog(title, headerText, contentText);
}
/**
* Displays a modal warning dialogue with the specified {@code title} and {@code message}.
*
* @param title
* the title of the dialog
* @param headerText
* the header text to be displayed in the dialog
* @param contentText
* the content text to be displayed in the dialog
*/
public void showWarningDialog(final String title, final String headerText, final String contentText) {
((DialogController) getBean(BeanContainerFacade.DIALOG_CONTROLLER_BEANNAME)).showWarningDialog(title,
headerText, contentText);
}
/**
* Displays a modal information dialogue with the specified {@code title} and {@code message}.
*
* @param title
* the title of the dialog
* @param headerText
* the header text to be displayed in the dialog
* @param contentText
* the content text to be displayed in the dialog
*/
public void showInformationDialog(final String title, final String headerText, final String contentText) {
((DialogController) getBean(BeanContainerFacade.DIALOG_CONTROLLER_BEANNAME)).showInformationDialog(title,
headerText, contentText);
}
/**
* Displays a modal error dialogue with the specified {@code title} and {@code message}.
*
* @param title
* the title of the dialog
* @param headerText
* the header text to be displayed in the dialog
* @param contentText
* the content text to be displayed in the dialog
*/
public void showErrorDialog(final String title, final String headerText, final String contentText) {
((DialogController) getBean(BeanContainerFacade.DIALOG_CONTROLLER_BEANNAME)).showErrorDialog(title, headerText,
contentText);
}
/**
* Displays a directory chooser and returns the selected {@link File} descriptor.
*
* @param title
* the dialog title
* @param defaultDirectory
* the directory to show, when the dialog is opened
* @param owner
* the window owner
* @return the selected directory, or {@code null}, if no directory has been selected
*/
public File showDirectoryChooserDialog(final String title, final File defaultDirectory, final Window owner) {
return ((DialogController) getBean(BeanContainerFacade.DIALOG_CONTROLLER_BEANNAME))
.showDirectoryChooserDialog(title, defaultDirectory, owner);
}
/**
* Displays a file chooser and returns the selected <tt>File</tt> descriptor.
*
* @param title
* the dialog title
* @param defaultDirectory
* the directory to show, when the dialog is opened
* @param initialFileName
* the initially suggested file name
* @param fileExtensionFilter
* an optional file extension filter
* @param owner
* the window owner
* @return the selected file, or {@code null} if no file has been selected
*/
public File showFileOpenDialog(final String title, final File defaultDirectory, final String initialFileName,
final ExtensionFilter fileExtensionFilter, final Window owner) {
return ((DialogController) getBean(BeanContainerFacade.DIALOG_CONTROLLER_BEANNAME)).showFileOpenDialog(title,
defaultDirectory, initialFileName, fileExtensionFilter, owner);
}
/**
* Displays a file chooser and returns the selected {@link File} descriptor.
*
* @param title
* the dialog title
* @param defaultDirectory
* the directory to show, when the dialog is opened
* @param owner
* the window owner
* @return the selected file, or {@code null}, if no file has been selected
*/
public File showFileOpenDialog(final String title, final File defaultDirectory, final Window owner) {
return ((DialogController) getBean(BeanContainerFacade.DIALOG_CONTROLLER_BEANNAME)).showFileOpenDialog(title,
defaultDirectory, owner);
}
/**
* Displays a file chooser and returns the selected {@link File} descriptor.
*
* @param title
* the dialog title
* @param defaultDirectory
* the directory to show, when the dialog is opened
* @param owner
* the window owner
* @return the selected file, or {@code null}, if no file has been selected
*/
public File showFileSaveDialog(final String title, final File defaultDirectory, final Window owner) {
return ((DialogController) getBean(BeanContainerFacade.DIALOG_CONTROLLER_BEANNAME)).showFileSaveDialog(title,
defaultDirectory, owner);
}
/**
* Displays a file chooser and returns the selected {@link File} descriptor.
*
* @param title
* the dialog title
* @param defaultDirectory
* the directory to show, when the dialog is opened
* @param initialFileName
* the initially suggested file name
* @param fileExtensionFilter
* an optional file extension filter
* @param owner
* the window owner
* @return the selected file, or {@code null}, if no file has been selected
*/
public File showFileSaveDialog(final String title, final File defaultDirectory, final String initialFileName,
final ExtensionFilter fileExtensionFilter, final Window owner) {
return ((DialogController) getBean(BeanContainerFacade.DIALOG_CONTROLLER_BEANNAME)).showFileSaveDialog(title,
defaultDirectory, initialFileName, fileExtensionFilter, owner);
}
/**
* Displays a modal <tt>TextInputDialog</tt> that lets the user enter a single string value.
*
* @param title
* the title of the input dialog
* @param headerText
* a header text to be displayed inside the dialogue
* @param contentText
* a content text displayed in front of the input text field
* @return the entered string value, or <tt>null</tt>, if no value has been entered.
*/
public String showTextInputDialog(final String title, final String headerText, final String contentText) {
return ((DialogController) getBean(BeanContainerFacade.DIALOG_CONTROLLER_BEANNAME)).showTextInputDialog(title,
headerText, contentText);
}
/**
* Displays a modal <tt>TextInputDialog</tt> that lets the user enter a single string value. A default text is
* already pre-set.
*
* @param title
* the title of the input dialog
* @param headerText
* a header text to be displayed inside the dialogue
* @param contentText
* a content text displayed in front of the input text field
* @param defaultValue
* the pre-set default text
* @return the entered string value, or <tt>null</tt>, if no value has been entered.
*/
public String showTextInputDialog(final String title, final String headerText, final String contentText,
final String defaultValue) {
return ((DialogController) getBean(BeanContainerFacade.DIALOG_CONTROLLER_BEANNAME)).showTextInputDialog(title,
headerText, contentText, defaultValue);
}
/**
* Publishes the given {@code event} to all methods that are annotated by {@link AFXSubscribe} and which are
* listening to the type of {@code event}.
* <p>
* In case the annotated method has a method argument that is of the same type than {@code event}, then the
* {@code event} is used as method argument to that method.
* <p>
* Please note that methods annotated by {@link AFXSubscribe} can also have additional method argument, that are
* e.g. annotated by {@link AFXControlValue}.
*
* @param event
* the event to publish
*/
public void publishEvent(final Object event) {
getEventBus().publish(event);
}
/**
* Performs a validation on controls inside the given {@code controller}.
* <p>
* This requires that {@link com.github.actionfx.core.validation.Validator} instances are registered inside the
* {@link View} associated with the given {@code controller}, or that the {@code controller} instance itself carries
* validation related annotations like {@link com.github.actionfx.core.annotation.AFXValidateRequired} or
* {@link com.github.actionfx.core.annotation.AFXValidateRegExp}.
*
* @param controller
* the controller referencing the JavaFX controls to validate
* @param applyValidationDecoration
* indicates whether validation errors shall be displayed as decorations, when there is a validation
* failure
* @return {@code true}, if all validations passed successfully, {@code false}, if there are validation errors found
* in the annotated controls.
*/
public ValidationResult validate(final Object controller, final boolean applyValidationDecoration) {
final View view = getView(controller);
return view.validate(applyValidationDecoration);
}
/**
* Performs a validation on controls inside the given {@code controller}.
* <p>
* This requires that {@link com.github.actionfx.core.validation.Validator} instances are registered inside the
* {@link View} associated with the given {@code controller}, or that the {@code controller} instance itself carries
* validation related annotations like {@link com.github.actionfx.core.annotation.AFXValidateRequired} or
* {@link com.github.actionfx.core.annotation.AFXValidateRegExp}.
* <p>
* This method applies validation decorations by default. If you don't want to display validation decoration and
* want to handle validation messages by yourself, please use {@link #validate(Object, boolean)}.
*
* @param controller
* the controller referencing the JavaFX controls to validate
* @return {@code true}, if all validations passed successfully, {@code false}, if there are validation errors found
* in the annotated controls.
*/
public ValidationResult validate(final Object controller) {
return validate(controller, true);
}
/**
* The package name with dot-notation "." that shall be scanned for ActionFX components.
*
* @return this builder
*/
public String getScanPackage() {
return scanPackage;
}
/**
* Grants access to the bean container. Mainly for testing purposes.
*
* @return the underlying bean container implementation
*/
public BeanContainerFacade getBeanContainer() {
return beanContainer;
}
/**
* The enhancement strategy to use within ActionFX.
*
* @return the enhancement strategy
*/
public EnhancementStrategy getEnhancementStrategy() {
return enhancementStrategy;
}
/**
* The enhancer to use within ActionFX.
*
* @return the enhancer
*/
public ActionFXEnhancer getEnhancer() {
return enhancer;
}
/**
* Gets the {@link UncaughtExceptionHandler}, if set.
*
* @return the {@link UncaughtExceptionHandler}.
*/
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler;
}
/**
* Gets the locale as an {@link ObservableValue} If not set by
* {@link ActionFXBuilder#observableLocale(ObservableValue)}, the default implementation will return the
* {@link java.util.Locale#getDefault()} locale.
*
* @return the observable locale
*/
public ObservableValue<Locale> getObservableLocale() {
return observableLocale;
}
/**
* Gets the global validation mode, if specified. When specifying a global validation mode as part of the setup
* phase of ActionFX, this global validation mode makes the specification of a particular validation mode inside a
* validation-related annotation like {@link com.github.actionfx.core.annotation.AFXValidateRequired} obsolete,
* reducing the number of attributes to be specified in these type of annotations.
*
* @return the global validation mode configured for ActionFX.
*/
public ValidationMode getValidationGlobalMode() {
return validationGlobalMode;
}
/**
* Flag that indicates, whether validation decorations for validation results shall be displayed for controls under
* validation.
*
* @return {@code true}, if validation decorations shall be applied to controls under validation, {@code false}
* otherwise.
*/
public boolean isValidationApplyResultDecoration() {
return validationApplyResultDecoration;
}
/**
* Flag that indicates, whether decorations for required fields shall be displayed for controls under validation.
*
* @return {@code true}, if validation decorations for required fields shall be applied to controls, {@code false}
* otherwise.
*/
public boolean isValidationApplyRequiredDecoration() {
return validationApplyRequiredDecoration;
}
/**
* A global timeout setting for staring a control validation after a change in a particular control occurs. If the
* returned value is {@code -1}, there is no global timeout setting and the timeout value needs to be defined in all
* validation related annotations directly (this might make more sense in many cases).
*
* @return the validation start timeout value in milliseconds, or {@code -1}, if there is no global setting for the
* validation start timeout.
*/
public int getValidationStartTimeoutMs() {
return validationStartTimeoutMs;
}
/**
* Checks, whether ActionFX is currently in {@code expectedState}. If ActionFX's state is different from the
* expected state, an {@link IllegalStateException} is thrown.
*
* @param expectedState
* the expected state to check.
*/
static void checkActionFXState(final ActionFXState expectedState) {
if (actionFXState != expectedState) {
throw new IllegalStateException(
"ActionFX is in state '" + actionFXState + "', while expected state was '" + expectedState + "'!");
}
}
/**
* Gets the primary stage, if it was set be the user.
*
* @return the primary stage
*/
public Stage getPrimaryStage() {
return primaryStage;
}
/**
* Users can set the primary stage here so that it is available throughout the application.
*
* @param primaryStage
* the primary stage
*/
public void setPrimaryStage(final Stage primaryStage) {
this.primaryStage = primaryStage;
}
/**
* Resets ActionFX to its initial state. Can be used in unit test. Use this method in productive code only when you
* know exactly what you are doing.
*/
@SuppressFBWarnings(justification = "Design Decision")
public void reset() {
instance = null;// NOSONAR
actionFXState = ActionFXState.UNINITIALIZED;// NOSONAR
}
/**
* Builder for setting up the singleton instance of {@link ActionFX}.
*
* @author koster
*
*/
public static class ActionFXBuilder {
private String mainViewId;
private String scanPackage;
private EnhancementStrategy enhancementStrategy;
private ActionFXEnhancer actionFXEnhancer;
private UncaughtExceptionHandler uncaughtExceptionHandler;
private ObservableValue<Locale> observableLocale;
private final List<Consumer<Object>> controllerExtensions = new ArrayList<>();
private final List<BeanExtension> beanExtensions = new ArrayList<>();
private BeanContainerFacade beanContainer;
private boolean enableBeanContainerAutodetection = true;
private ValidationMode validationGlobalMode = null;