Skip to contentMethod: static {...}
1: /*
2: * *************************************************************************************************************************************************************
3: *
4: * SteelBlue: DCI User Interfaces
5: * http://tidalwave.it/projects/steelblue
6: *
7: * Copyright (C) 2015 - 2025 by Tidalwave s.a.s. (http://tidalwave.it)
8: *
9: * *************************************************************************************************************************************************************
10: *
11: * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
12: * You may obtain a copy of the License at
13: *
14: * http://www.apache.org/licenses/LICENSE-2.0
15: *
16: * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
17: * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
18: *
19: * *************************************************************************************************************************************************************
20: *
21: * git clone https://bitbucket.org/tidalwave/steelblue-src
22: * git clone https://github.com/tidalwave-it/steelblue-src
23: *
24: * *************************************************************************************************************************************************************
25: */
26: package it.tidalwave.ui.javafx.impl;
27:
28: import jakarta.annotation.Nonnull;
29: import java.util.concurrent.CountDownLatch;
30: import java.util.concurrent.TimeUnit;
31: import java.util.concurrent.atomic.AtomicReference;
32: import java.io.IOException;
33: import javafx.fxml.FXMLLoader;
34: import javafx.scene.Node;
35: import javafx.application.Platform;
36: import it.tidalwave.ui.javafx.NodeAndDelegate;
37: import it.tidalwave.ui.javafx.impl.util.JavaFXSafeProxy;
38: import org.slf4j.LoggerFactory;
39: import it.tidalwave.util.ReflectionUtils;
40: import lombok.Getter;
41: import lombok.RequiredArgsConstructor;
42: import lombok.extern.slf4j.Slf4j;
43: import static it.tidalwave.ui.javafx.impl.util.JavaFXSafeComponentBuilder.BEANS;
44:
45: /***************************************************************************************************************************************************************
46: *
47: * The implementation of {@link NodeAndDelegate}.
48: *
49: * @author Fabrizio Giudici
50: *
51: **************************************************************************************************************************************************************/
52: @RequiredArgsConstructor @Getter @Slf4j
53: public class DefaultNodeAndDelegate<T> implements NodeAndDelegate<T>
54: {
55: private static final String P_TIMEOUT = DefaultNodeAndDelegate.class.getName() + ".initTimeout";
56: private static final int INITIALIZER_TIMEOUT = Integer.getInteger(P_TIMEOUT, 10);
57:
58: @Nonnull
59: private final Node node;
60:
61: @Nonnull
62: private final T delegate;
63:
64: /***********************************************************************************************************************************************************
65: * Creates a {@link NodeAndDelegate} for the given presentation class. The FXML resource name is inferred by
66: * default, For instance, is the class is named {@code JavaFXFooBarPresentation}, the resource name is
67: * {@code FooBar.fxml} and searched in the same packages as the class.
68: *
69: * @see #of(java.lang.Class, java.lang.String)
70: *
71: * @since 1.0-ALPHA-13
72: *
73: * @param presentationClass the class of the presentation for which the resources must be created.
74: **********************************************************************************************************************************************************/
75: @Nonnull
76: public static <T> NodeAndDelegate<T> of (@Nonnull final Class<T> presentationClass)
77: {
78: final var resource = presentationClass.getSimpleName().replaceAll("^JavaFX", "")
79: .replaceAll("^JavaFx", "")
80: .replaceAll("Presentation$", "")
81: + ".fxml";
82: return of(presentationClass, resource);
83: }
84:
85: /***********************************************************************************************************************************************************
86: * Creates a {@link NodeAndDelegate} for the given presentation class.
87: *
88: * @param presentationClass the class of the presentation for which the resources must be created.
89: * @param fxmlResourcePath the path of the FXML resource
90: **********************************************************************************************************************************************************/
91: @Nonnull
92: public static <T> NodeAndDelegate<T> of (@Nonnull final Class<T> presentationClass, @Nonnull final String fxmlResourcePath)
93: {
94: final var log = LoggerFactory.getLogger(NodeAndDelegate.class);
95: log.debug("of({}, {})", presentationClass, fxmlResourcePath);
96:
97: final var latch = new CountDownLatch(1);
98: final var nad = new AtomicReference<NodeAndDelegate<T>>();
99: final var exception = new AtomicReference<RuntimeException>();
100: final var message = String.format("Likely deadlock in the JavaFX Thread: couldn't create NodeAndDelegate: %s, %s",
101: presentationClass, fxmlResourcePath);
102:
103: if (Platform.isFxApplicationThread())
104: {
105: try
106: {
107: return load(presentationClass, fxmlResourcePath);
108: }
109: catch (IOException e)
110: {
111: exception.set(new RuntimeException(e));
112: }
113: }
114:
115: Platform.runLater(() ->
116: {
117: try
118: {
119: nad.set(load(presentationClass, fxmlResourcePath));
120: }
121: catch (RuntimeException e)
122: {
123: exception.set(e);
124: }
125: catch (Exception e)
126: {
127: exception.set(new RuntimeException(e));
128: }
129:
130: latch.countDown();
131: });
132:
133: try
134: {
135: log.debug("Waiting for NodeAndDelegate initialisation in JavaFX thread...");
136: log.debug("If deadlocks and you need longer time with the debugger, set {} (current value: {})", P_TIMEOUT, INITIALIZER_TIMEOUT);
137:
138: if (!latch.await(INITIALIZER_TIMEOUT, TimeUnit.SECONDS))
139: {
140: throw new RuntimeException(message);
141: }
142: }
143: catch (InterruptedException e)
144: {
145: throw new RuntimeException(e);
146: }
147:
148: if (exception.get() != null)
149: {
150: throw exception.get();
151: }
152:
153: if (nad.get() == null)
154: {
155: throw new RuntimeException(message);
156: }
157:
158: return nad.get();
159: }
160:
161: @Nonnull
162: public static <T> NodeAndDelegate<T> load (@Nonnull final Class<T> clazz, @Nonnull final String resource)
163: throws IOException
164: {
165: final var log = LoggerFactory.getLogger(NodeAndDelegate.class);
166: log.debug("NodeAndDelegate({}, {})", clazz, resource);
167: assert Platform.isFxApplicationThread() : "Not in JavaFX UI Thread";
168: final var loader = new FXMLLoader(clazz.getResource(resource), null, null,
169: type -> ReflectionUtils.instantiateWithDependencies(type, BEANS));
170: try
171: {
172: final Node node = loader.load();
173: final T jfxController = loader.getController();
174: ReflectionUtils.injectDependencies(jfxController, BEANS);
175: final var interfaces = jfxController.getClass().getInterfaces();
176:
177: if (interfaces.length == 0)
178: {
179: log.warn("{} has no interface: not creating safe proxy", jfxController.getClass());
180: log.debug(">>>> load({}, {}) completed", clazz, resource);
181: return new DefaultNodeAndDelegate<>(node, jfxController);
182: }
183: else
184: {
185: final var safeDelegate = JavaFXSafeProxy.of(jfxController, interfaces);
186: log.debug(">>>> load({}, {}) completed", clazz, resource);
187: return new DefaultNodeAndDelegate<>(node, safeDelegate);
188: }
189: }
190: catch (IllegalStateException e)
191: {
192: final var message = String.format("ERROR: Cannot find resource: %s/%s", clazz.getPackageName().replace('.','/'), resource);
193: log.error("ERROR: Cannot find resource: {}", message);
194: throw new IllegalStateException(message);
195: }
196: }
197: }