Package: JavaFXSafeComponentBuilder
JavaFXSafeComponentBuilder
name | instruction | branch | complexity | line | method | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
builderFor(Class) |
|
|
|
|
|
||||||||||||||||||||
builderFor(Class, Class) |
|
|
|
|
|
||||||||||||||||||||
copyFxmlFields(Object, Object) |
|
|
|
|
|
||||||||||||||||||||
createComponentInstance() |
|
|
|
|
|
||||||||||||||||||||
createComponentInstanceInJAT() |
|
|
|
|
|
||||||||||||||||||||
createInstance(Class, Object) |
|
|
|
|
|
||||||||||||||||||||
createInstance(Object) |
|
|
|
|
|
||||||||||||||||||||
lambda$createComponentInstanceInJAT$0(AtomicReference, CountDownLatch) |
|
|
|
|
|
||||||||||||||||||||
static {...} |
|
|
|
|
|
Coverage
1: /*
2: * *************************************************************************************************************************************************************
3: *
4: * SteelBlue: DCI User Interfaces
5: * http://tidalwave.it/projects/steelblue
6: *
7: * Copyright (C) 2015 - 2024 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.role.ui.javafx.impl.util;
27:
28: import java.lang.ref.WeakReference;
29: import java.lang.reflect.InvocationTargetException;
30: import java.lang.reflect.Proxy;
31: import javax.annotation.Nonnull;
32: import java.util.HashMap;
33: import java.util.Map;
34: import java.util.concurrent.CountDownLatch;
35: import java.util.concurrent.atomic.AtomicReference;
36: import javafx.fxml.FXML;
37: import javafx.fxml.FXMLLoader;
38: import javafx.application.Platform;
39: import it.tidalwave.ui.javafx.JavaFXSafeProxyCreator;
40: import it.tidalwave.util.ReflectionUtils;
41: import lombok.RequiredArgsConstructor;
42: import lombok.extern.slf4j.Slf4j;
43: import static lombok.AccessLevel.PRIVATE;
44:
45: /***************************************************************************************************************************************************************
46: *
47: * @stereotype Factory
48: *
49: * @author Fabrizio Giudici
50: *
51: **************************************************************************************************************************************************************/
52: @RequiredArgsConstructor(access = PRIVATE) @Slf4j
53: public final class JavaFXSafeComponentBuilder<I, T extends I>
54: {
55: @Nonnull
56: private final Class<T> componentClass;
57:
58: @Nonnull
59: private final Class<I> interfaceClass;
60:
61: private WeakReference<T> presentationRef = new WeakReference<>(null);
62:
63: @Nonnull
64: public static <J, X extends J> JavaFXSafeComponentBuilder<J, X> builderFor (@Nonnull final Class<X> componentClass)
65: {
66: final var interfaceClass = (Class<J>)componentClass.getInterfaces()[0]; // FIXME: guess
67: return new JavaFXSafeComponentBuilder<>(componentClass, interfaceClass);
68: }
69:
70: @Nonnull
71: public static <J, X extends J> JavaFXSafeComponentBuilder<J, X> builderFor (@Nonnull final Class<J> interfaceClass,
72: @Nonnull final Class<X> componentClass)
73: {
74: return new JavaFXSafeComponentBuilder<>(componentClass, interfaceClass);
75: }
76:
77: /***********************************************************************************************************************************************************
78: * Creates an instance of a surrogate JavaFX delegate. JavaFX delegates (controllers in JavaFX jargon) are those
79: * objects with fields annotated with {@link @FXML} that are created by the {@link FXMLLoader} starting from a
80: * {@code .fxml} file. Sometimes a surrogate delegate is needed, that is a class that is not mapped to any
81: * {@link @FXML} file, but whose fields are copied from another existing delegate.
82: *
83: * @param componentClass the class of the surrogate
84: * @param fxmlFieldsSource the existing JavaFX delegate with {@code @FXML} annotated fields.
85: * @return the new surrogate delegate
86: **********************************************************************************************************************************************************/
87: @Nonnull
88: public static <J, X extends J> X createInstance (@Nonnull final Class<X> componentClass,
89: @Nonnull final Object fxmlFieldsSource)
90: {
91: final JavaFXSafeComponentBuilder<J, X> builder = builderFor(componentClass);
92: return builder.createInstance(fxmlFieldsSource);
93: }
94:
95: /***********************************************************************************************************************************************************
96: * Creates an instance of a surrogate JavaFX delegate. JavaFX delegates (controllers in JavaFX jargon) are those
97: * objects with fields annotated with {@link @FXML} that are created by the {@link FXMLLoader} starting from a
98: * {@code .fxml} file. Sometimes a surrogate delegate is needed, that is a class that is not mapped to any
99: * {@link @FXML} file, but whose fields are copied from another existing delegate.
100: *
101: * @param fxmlFieldsSource the existing JavaFX delegate with {@code @FXML} annotated fields.
102: * @return the new surrogate delegate
103: **********************************************************************************************************************************************************/
104: @Nonnull
105: public synchronized T createInstance (@Nonnull final Object fxmlFieldsSource)
106: {
107: log.trace("createInstance({})", fxmlFieldsSource);
108: var presentation = presentationRef.get();
109:
110:• if (presentation == null)
111: {
112:• presentation = Platform.isFxApplicationThread() ? createComponentInstance() : createComponentInstanceInJAT();
113: copyFxmlFields(presentation, fxmlFieldsSource); // FIXME: in JFX thread?
114:
115: try // FIXME // FIXME: in JFX thread?
116: {
117: presentation.getClass().getDeclaredMethod("initialize").invoke(presentation);
118: }
119: catch (NoSuchMethodException | SecurityException | IllegalAccessException
120: | InvocationTargetException e)
121: {
122: log.warn("No postconstruct in {}", presentation);
123: }
124:
125: presentation = (T)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
126: new Class[] { interfaceClass },
127: new JavaFXSafeProxy<>(presentation));
128: presentationRef = new WeakReference<>(presentation);
129: }
130:
131: return presentation;
132: }
133:
134: /***********************************************************************************************************************************************************
135: *
136: **********************************************************************************************************************************************************/
137: @Nonnull
138: private T createComponentInstance()
139: {
140: return ReflectionUtils.instantiateWithDependencies(componentClass, JavaFXSafeProxyCreator.BEANS);
141: }
142:
143: /***********************************************************************************************************************************************************
144: *
145: **********************************************************************************************************************************************************/
146: @Nonnull
147: private T createComponentInstanceInJAT()
148: {
149: final var reference = new AtomicReference<T>();
150: final var countDownLatch = new CountDownLatch(1);
151:
152: Platform.runLater(() ->
153: {
154: reference.set(createComponentInstance());
155: countDownLatch.countDown();
156: });
157:
158: try
159: {
160: countDownLatch.await();
161: }
162: catch (InterruptedException e)
163: {
164: log.error("", e);
165: throw new RuntimeException(e);
166: }
167:
168: return reference.get();
169: }
170:
171: /***********************************************************************************************************************************************************
172: * Inject fields annotated with {@link FXML} in {@code source} to {@code target}.
173: *
174: * @param target the target object
175: * @param source the source object
176: **********************************************************************************************************************************************************/
177: private void copyFxmlFields (@Nonnull final Object target, @Nonnull final Object source)
178: {
179: log.debug("injecting {} with fields from {}", target, source);
180: final Map<String, Object> valuesMapByFieldName = new HashMap<>();
181:
182:• for (final var field : source.getClass().getDeclaredFields())
183: {
184:• if (field.getAnnotation(FXML.class) != null)
185: {
186: final var name = field.getName();
187:
188: try
189: {
190: field.setAccessible(true);
191: final var value = field.get(source);
192: valuesMapByFieldName.put(name, value);
193: log.trace(">>>> available field {}: {}", name, value);
194: }
195: catch (IllegalArgumentException | IllegalAccessException e)
196: {
197: throw new RuntimeException("Cannot read field " + name + " from " + source, e);
198: }
199: }
200: }
201:
202:• for (final var field : target.getClass().getDeclaredFields())
203: {
204: final var fxml = field.getAnnotation(FXML.class);
205:
206:• if (fxml != null)
207: {
208: final var name = field.getName();
209: final var value = valuesMapByFieldName.get(name);
210:
211:• if (value == null)
212: {
213: throw new RuntimeException("Can't inject " + name + ": available: " + valuesMapByFieldName.keySet());
214: }
215:
216: field.setAccessible(true);
217:
218: try
219: {
220: field.set(target, value);
221: }
222: catch (IllegalArgumentException | IllegalAccessException e)
223: {
224: throw new RuntimeException("Cannot inject field " + name + " to " + target, e);
225: }
226: }
227: }
228:
229: ReflectionUtils.injectDependencies(target, JavaFXSafeProxyCreator.BEANS);
230: }
231: }