Skip to content

Method: lambda$load$1(Class)

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.HashMap;
30: import java.util.Map;
31: import java.util.concurrent.CountDownLatch;
32: import java.util.concurrent.Executor;
33: import java.util.concurrent.TimeUnit;
34: import java.util.concurrent.atomic.AtomicReference;
35: import java.io.IOException;
36: import javafx.fxml.FXMLLoader;
37: import javafx.scene.Node;
38: import javafx.application.Platform;
39: import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
40: import it.tidalwave.ui.javafx.JavaFXBinder;
41: import it.tidalwave.ui.javafx.JavaFXMenuBarControl;
42: import it.tidalwave.ui.javafx.JavaFXToolBarControl;
43: import it.tidalwave.ui.javafx.NodeAndDelegate;
44: import it.tidalwave.ui.javafx.impl.util.JavaFXSafeProxy;
45: import org.slf4j.LoggerFactory;
46: import it.tidalwave.util.PreferencesHandler;
47: import it.tidalwave.util.ReflectionUtils;
48: import lombok.Getter;
49: import lombok.RequiredArgsConstructor;
50: import lombok.Setter;
51: import lombok.extern.slf4j.Slf4j;
52:
53: /***************************************************************************************************************************************************************
54: *
55: * The implementation of {@link NodeAndDelegate}.
56: *
57: * @author Fabrizio Giudici
58: *
59: **************************************************************************************************************************************************************/
60: @RequiredArgsConstructor @Getter @Slf4j
61: public class DefaultNodeAndDelegate<T> implements NodeAndDelegate<T>
62: {
63: private static final String P_TIMEOUT = DefaultNodeAndDelegate.class.getName() + ".initTimeout";
64: private static final int INITIALIZER_TIMEOUT = Integer.getInteger(P_TIMEOUT, 10);
65:
66: public static final Map<Class<?>, Object> BEANS = new HashMap<>();
67:
68: @Getter
69: private static final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
70:
71: @Getter
72: private static final JavaFXBinder javaFxBinder = new DefaultJavaFXBinder(executor);
73:
74: @Getter
75: private static final JavaFXToolBarControl toolBarControl = new DefaultJavaFXToolBarControl();
76:
77: @Getter
78: private static final JavaFXMenuBarControl menuBarControl = new DefaultJavaFXMenuBarControl();
79:
80: // @Getter
81: // private static final JavaFXPanelGroupControl panelGroupControl = new DefaultJavaFXPanelGroupControl();
82:
83: @Getter @Setter
84: private static boolean logDelegateInvocations = false;
85:
86: static
87: {
88: executor.setWaitForTasksToCompleteOnShutdown(false);
89: executor.setThreadNamePrefix("javafxBinder-");
90: // Fix for STB-26
91: executor.setCorePoolSize(1);
92: executor.setMaxPoolSize(1);
93: executor.setQueueCapacity(10000);
94: BEANS.put(JavaFXBinder.class, javaFxBinder);
95: BEANS.put(Executor.class, executor);
96: BEANS.put(JavaFXToolBarControl.class, toolBarControl);
97: BEANS.put(JavaFXMenuBarControl.class, menuBarControl);
98: // BEANS.put(JavaFXPanelGroupControl.class, panelGroupControl);
99: BEANS.put(PreferencesHandler.class, PreferencesHandler.getInstance());
100: }
101:
102: @Nonnull
103: private final Node node;
104:
105: @Nonnull
106: private final T delegate;
107:
108: /***********************************************************************************************************************************************************
109: * Creates a {@link NodeAndDelegate} for the given presentation class. The FXML resource name is inferred by
110: * default, For instance, is the class is named {@code JavaFXFooBarPresentation}, the resource name is
111: * {@code FooBar.fxml} and searched in the same packages as the class.
112: *
113: * @see #of(java.lang.Class, java.lang.String)
114: *
115: * @since 1.0-ALPHA-13
116: *
117: * @param presentationClass the class of the presentation for which the resources must be created.
118: **********************************************************************************************************************************************************/
119: @Nonnull
120: public static <T> NodeAndDelegate<T> of (@Nonnull final Class<T> presentationClass)
121: {
122: final var resource = presentationClass.getSimpleName().replaceAll("^JavaFX", "")
123: .replaceAll("^JavaFx", "")
124: .replaceAll("Presentation$", "")
125: + ".fxml";
126: return of(presentationClass, resource);
127: }
128:
129: /***********************************************************************************************************************************************************
130: * Creates a {@link NodeAndDelegate} for the given presentation class.
131: *
132: * @param presentationClass the class of the presentation for which the resources must be created.
133: * @param fxmlResourcePath the path of the FXML resource
134: **********************************************************************************************************************************************************/
135: @Nonnull
136: public static <T> NodeAndDelegate<T> of (@Nonnull final Class<T> presentationClass, @Nonnull final String fxmlResourcePath)
137: {
138: final var log = LoggerFactory.getLogger(NodeAndDelegate.class);
139: log.debug("of({}, {})", presentationClass, fxmlResourcePath);
140:
141: final var latch = new CountDownLatch(1);
142: final var nad = new AtomicReference<NodeAndDelegate<T>>();
143: final var exception = new AtomicReference<RuntimeException>();
144:
145: if (Platform.isFxApplicationThread())
146: {
147: try
148: {
149: return load(presentationClass, fxmlResourcePath);
150: }
151: catch (IOException e)
152: {
153: exception.set(new RuntimeException(e));
154: }
155: }
156:
157: Platform.runLater(() ->
158: {
159: try
160: {
161: nad.set(load(presentationClass, fxmlResourcePath));
162: }
163: catch (RuntimeException e)
164: {
165: exception.set(e);
166: }
167: catch (Exception e)
168: {
169: exception.set(new RuntimeException(e));
170: }
171:
172: latch.countDown();
173: });
174:
175: try
176: {
177: log.debug("Waiting for NodeAndDelegate initialisation in JavaFX thread...");
178: log.debug("If deadlocks and you need longer time with the debugger, set {} (current value: {})", P_TIMEOUT, INITIALIZER_TIMEOUT);
179: latch.await(INITIALIZER_TIMEOUT, TimeUnit.SECONDS); // FIXME
180: }
181: catch (InterruptedException e)
182: {
183: throw new RuntimeException(e);
184: }
185:
186: if (exception.get() != null)
187: {
188: throw exception.get();
189: }
190:
191: if (nad.get() == null)
192: {
193: final var message = String.format("Likely deadlock in the JavaFX Thread: couldn't create NodeAndDelegate: %s, %s",
194: presentationClass, fxmlResourcePath);
195: throw new RuntimeException(message);
196: }
197:
198: return nad.get();
199: }
200:
201: @Nonnull
202: public static <T> NodeAndDelegate<T> load (@Nonnull final Class<T> clazz, @Nonnull final String resource)
203: throws IOException
204: {
205: final var log = LoggerFactory.getLogger(NodeAndDelegate.class);
206: log.debug("NodeAndDelegate({}, {})", clazz, resource);
207: assert Platform.isFxApplicationThread() : "Not in JavaFX UI Thread";
208: final var loader = new FXMLLoader(clazz.getResource(resource), null, null,
209: type -> ReflectionUtils.instantiateWithDependencies(type, BEANS));
210: try
211: {
212: final Node node = loader.load();
213: final T jfxController = loader.getController();
214: ReflectionUtils.injectDependencies(jfxController, BEANS);
215: final var interfaces = jfxController.getClass().getInterfaces();
216:
217: if (interfaces.length == 0)
218: {
219: log.warn("{} has no interface: not creating safe proxy", jfxController.getClass());
220: log.debug(">>>> load({}, {}) completed", clazz, resource);
221: return new DefaultNodeAndDelegate<>(node, jfxController);
222: }
223: else
224: {
225: final var safeDelegate = JavaFXSafeProxy.of(jfxController, interfaces);
226: log.debug(">>>> load({}, {}) completed", clazz, resource);
227: return new DefaultNodeAndDelegate<>(node, safeDelegate);
228: }
229: }
230: catch (IllegalStateException e)
231: {
232: final var message = String.format("ERROR: Cannot find resource: %s/%s", clazz.getPackageName().replace('.','/'), resource);
233: log.error("ERROR: Cannot find resource: {}", message);
234: throw new IllegalStateException(message);
235: }
236: }
237: }