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 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
48
49
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];
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
79
80
81
82
83
84
85
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
97
98
99
100
101
102
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);
114
115 try
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
173
174
175
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 }