1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.bremersee.xml;
18
19 import static java.lang.reflect.Modifier.isPublic;
20 import static java.lang.reflect.Modifier.isStatic;
21 import static java.lang.reflect.Modifier.isTransient;
22 import static org.springframework.util.ObjectUtils.isEmpty;
23
24 import java.lang.annotation.Annotation;
25 import java.lang.reflect.AnnotatedElement;
26 import java.lang.reflect.Field;
27 import java.lang.reflect.Method;
28 import java.util.Arrays;
29 import java.util.Collection;
30 import java.util.HashSet;
31 import java.util.Optional;
32 import java.util.Set;
33 import java.util.stream.Collectors;
34 import jakarta.xml.bind.annotation.XmlAccessType;
35 import jakarta.xml.bind.annotation.XmlAccessorType;
36 import jakarta.xml.bind.annotation.XmlAnyAttribute;
37 import jakarta.xml.bind.annotation.XmlAnyElement;
38 import jakarta.xml.bind.annotation.XmlAttachmentRef;
39 import jakarta.xml.bind.annotation.XmlAttribute;
40 import jakarta.xml.bind.annotation.XmlElement;
41 import jakarta.xml.bind.annotation.XmlElementRef;
42 import jakarta.xml.bind.annotation.XmlElementRefs;
43 import jakarta.xml.bind.annotation.XmlElementWrapper;
44 import jakarta.xml.bind.annotation.XmlElements;
45 import jakarta.xml.bind.annotation.XmlID;
46 import jakarta.xml.bind.annotation.XmlIDREF;
47 import jakarta.xml.bind.annotation.XmlList;
48 import jakarta.xml.bind.annotation.XmlMixed;
49 import jakarta.xml.bind.annotation.XmlRootElement;
50 import jakarta.xml.bind.annotation.XmlSeeAlso;
51 import jakarta.xml.bind.annotation.XmlTransient;
52 import jakarta.xml.bind.annotation.XmlType;
53 import lombok.AccessLevel;
54 import lombok.EqualsAndHashCode;
55 import lombok.NoArgsConstructor;
56 import lombok.NonNull;
57 import org.springframework.core.ResolvableType;
58 import org.springframework.core.annotation.AnnotationUtils;
59 import org.springframework.util.Assert;
60 import org.springframework.util.ClassUtils;
61 import org.springframework.util.ReflectionUtils;
62 import org.springframework.util.ReflectionUtils.FieldCallback;
63 import org.springframework.util.ReflectionUtils.FieldFilter;
64 import org.springframework.util.ReflectionUtils.MethodCallback;
65 import org.springframework.util.ReflectionUtils.MethodFilter;
66
67
68
69
70
71
72 @SuppressWarnings("SameNameButDifferent")
73 @NoArgsConstructor(access = AccessLevel.PACKAGE)
74 class JaxbDependenciesResolverImpl implements JaxbDependenciesResolver {
75
76 @SuppressWarnings("unchecked")
77 private static final Class<? extends Annotation>[] EXPLICIT_XML_ANNOTATIONS = new Class[]{
78 XmlAnyAttribute.class,
79 XmlAnyElement.class,
80 XmlAttachmentRef.class,
81 XmlAttribute.class,
82 XmlElement.class,
83 XmlElementRef.class,
84 XmlElementRefs.class,
85 XmlElements.class,
86 XmlElementWrapper.class,
87 XmlID.class,
88 XmlIDREF.class,
89 XmlList.class,
90 XmlMixed.class
91 };
92
93 @Override
94 public Class<?>[] resolveClasses(Object value) {
95 Set<ScanResult> scanResults = new HashSet<>();
96 if (value instanceof Class<?>[]) {
97 for (Class<?> clazz : (Class<?>[]) value) {
98 resolveClasses(clazz, scanResults);
99 }
100 } else {
101 resolveClasses(value, scanResults);
102 }
103 return ClassUtils.toClassArray(scanResults
104 .stream()
105 .map(ScanResult::getClazz)
106 .collect(Collectors.toSet()));
107 }
108
109 private boolean resolveClasses(Object value, Set<ScanResult> scanResults) {
110 if (isEmpty(value)
111 || stopResolving(ClassUtils.getUserClass(value), value, scanResults)) {
112 return false;
113 }
114 if (value instanceof Class) {
115 resolveClasses((Class<?>) value, scanResults);
116 return true;
117 }
118 if (value instanceof Collection) {
119 Collection<?> collection = (Collection<?>) value;
120 if (!collection.isEmpty()) {
121 for (Object v : collection) {
122 if (!resolveClasses(v, scanResults)) {
123 return true;
124 }
125 }
126 }
127 return true;
128 }
129 Class<?> clazz = ClassUtils.getUserClass(value);
130 resolveSuperClasses(clazz, value, scanResults);
131 ReflectionUtils.doWithFields(
132 clazz,
133 new XmlFieldCallback(value, scanResults),
134 new XmlFieldFilter(clazz));
135 ReflectionUtils.doWithMethods(
136 clazz,
137 new XmlMethodCallback(value, scanResults),
138 new XmlMethodFilter(clazz));
139 Optional.ofNullable(AnnotationUtils.findAnnotation(clazz, XmlSeeAlso.class))
140 .ifPresent(seeAlso -> Arrays.stream(seeAlso.value())
141 .forEach(seeAlsoClass -> resolveClasses(seeAlsoClass, scanResults)));
142 return true;
143 }
144
145 private void resolveClasses(Class<?> clazz, Set<ScanResult> scanResults) {
146 if (stopResolving(clazz, null, scanResults)) {
147 return;
148 }
149 resolveSuperClasses(clazz, null, scanResults);
150 ReflectionUtils.doWithFields(
151 clazz,
152 new XmlFieldCallback(null, scanResults),
153 new XmlFieldFilter(clazz));
154 ReflectionUtils.doWithMethods(
155 clazz,
156 new XmlMethodCallback(null, scanResults),
157 new XmlMethodFilter(clazz));
158 Optional.ofNullable(AnnotationUtils.findAnnotation(clazz, XmlSeeAlso.class))
159 .ifPresent(seeAlso -> Arrays.stream(seeAlso.value())
160 .forEach(seeAlsoClass -> resolveClasses(seeAlsoClass, scanResults)));
161 }
162
163 private void resolveSuperClasses(
164 Class<?> clazz,
165 Object source,
166 Set<ScanResult> scanResults) {
167
168 if (!stopResolving(clazz, source, scanResults)) {
169 scanResults.add(new ScanResult(clazz, source));
170 resolveSuperClasses(clazz.getSuperclass(), null, scanResults);
171 }
172 }
173
174 private boolean stopResolving(Class<?> clazz, Object source,
175 Set<ScanResult> scanResults) {
176 return isEmpty(clazz)
177 || (!isAnnotatedWithXml(clazz) && !Collection.class.isAssignableFrom(clazz))
178 || scanResults.contains(new ScanResult(clazz, source));
179 }
180
181 private boolean isAnnotatedWithXml(Class<?> clazz) {
182 return clazz.isAnnotationPresent(XmlRootElement.class)
183 || clazz.isAnnotationPresent(XmlType.class);
184 }
185
186 private void processXmlAnnotations(AnnotatedElement element,
187 Set<ScanResult> scanResults) {
188 processXmlElement(AnnotationUtils.findAnnotation(element, XmlElement.class), scanResults);
189 Optional
190 .ofNullable(AnnotationUtils.findAnnotation(element, XmlElements.class))
191 .map(XmlElements::value)
192 .ifPresent(a -> Arrays.stream(a).forEach(e -> processXmlElement(e, scanResults)));
193
194 processXmlElementRef(AnnotationUtils.findAnnotation(element, XmlElementRef.class), scanResults);
195 Optional
196 .ofNullable(AnnotationUtils.findAnnotation(element, XmlElementRefs.class))
197 .map(XmlElementRefs::value)
198 .ifPresent(a -> Arrays.stream(a).forEach(e -> processXmlElementRef(e, scanResults)));
199 }
200
201 private void processXmlElement(XmlElement annotation, Set<ScanResult> scanResults) {
202 Optional.ofNullable(annotation)
203 .map(XmlElement::type)
204 .filter(type -> !type.equals(XmlElement.DEFAULT.class))
205 .ifPresent(type -> resolveClasses(type, scanResults));
206 }
207
208 private void processXmlElementRef(XmlElementRef annotation,
209 Set<ScanResult> scanResults) {
210 Optional.ofNullable(annotation)
211 .map(XmlElementRef::type)
212 .filter(type -> !type.equals(XmlElementRef.DEFAULT.class))
213 .ifPresent(type -> resolveClasses(type, scanResults));
214 }
215
216 private class XmlFieldCallback implements FieldCallback {
217
218 private final Object value;
219
220 private final Set<ScanResult> scanResults;
221
222
223
224
225
226
227
228 XmlFieldCallback(Object value, Set<ScanResult> scanResults) {
229 this.value = value;
230 this.scanResults = scanResults;
231 }
232
233 @Override
234 public void doWith(@NonNull Field field) throws IllegalArgumentException {
235 ReflectionUtils.makeAccessible(field);
236 processXmlAnnotations(field, scanResults);
237 if (isEmpty(value)) {
238 if (Collection.class.isAssignableFrom(field.getType())) {
239 for (ResolvableType rt : ResolvableType.forField(field).getGenerics()) {
240 resolveClasses(rt.resolve(), scanResults);
241 }
242 } else {
243 resolveClasses(field.getType(), scanResults);
244 }
245 } else {
246 Object fieldValue = ReflectionUtils.getField(field, value);
247 if (!isEmpty(fieldValue)) {
248 if (fieldValue instanceof Collection && ((Collection<?>) fieldValue).isEmpty()) {
249 for (ResolvableType rt : ResolvableType.forField(field).getGenerics()) {
250 resolveClasses(rt.resolve(), scanResults);
251 }
252 } else {
253 resolveClasses(fieldValue, scanResults);
254 }
255 } else {
256 if (Collection.class.isAssignableFrom(field.getType())) {
257 for (ResolvableType rt : ResolvableType.forField(field).getGenerics()) {
258 resolveClasses(rt.resolve(), scanResults);
259 }
260 } else {
261 resolveClasses(field.getType(), scanResults);
262 }
263 }
264 }
265 }
266 }
267
268 private class XmlMethodCallback implements MethodCallback {
269
270 private final Object value;
271
272 private final Set<ScanResult> scanResults;
273
274
275
276
277
278
279
280 XmlMethodCallback(Object value, Set<ScanResult> scanResults) {
281 this.value = value;
282 this.scanResults = scanResults;
283 }
284
285 @Override
286 public void doWith(@NonNull Method method) throws IllegalArgumentException {
287 ReflectionUtils.makeAccessible(method);
288 processXmlAnnotations(method, scanResults);
289 if (isEmpty(value)) {
290 if (Collection.class.isAssignableFrom(method.getReturnType())) {
291 for (ResolvableType rt : ResolvableType.forMethodReturnType(method).getGenerics()) {
292 resolveClasses(rt.resolve(), scanResults);
293 }
294 } else {
295 resolveClasses(method.getReturnType(), scanResults);
296 }
297 } else {
298 Object methodValue = ReflectionUtils.invokeMethod(method, value);
299 if (!isEmpty(methodValue)) {
300 if (methodValue instanceof Collection && ((Collection<?>) methodValue).isEmpty()) {
301 for (ResolvableType rt : ResolvableType
302 .forMethodReturnType(method, ClassUtils.getUserClass(value))
303 .getGenerics()) {
304 resolveClasses(rt.resolve(), scanResults);
305 }
306 } else {
307 resolveClasses(methodValue, scanResults);
308 }
309 } else {
310 resolveClasses(method.getReturnType(), scanResults);
311 }
312 }
313 }
314 }
315
316 private static boolean anyXmlAnnotationPresent(AnnotatedElement element) {
317 return Arrays.stream(EXPLICIT_XML_ANNOTATIONS).anyMatch(element::isAnnotationPresent);
318 }
319
320 private static class XmlFieldFilter implements FieldFilter {
321
322 private final XmlAccessType accessType;
323
324
325
326
327
328
329 XmlFieldFilter(Class<?> clazz) {
330 this.accessType = Optional
331 .ofNullable(AnnotationUtils.findAnnotation(clazz, XmlAccessorType.class))
332 .map(XmlAccessorType::value)
333 .orElseGet(() -> Optional
334 .ofNullable(clazz.getPackage().getAnnotation(XmlAccessorType.class))
335 .map(XmlAccessorType::value)
336 .orElse(XmlAccessType.PUBLIC_MEMBER));
337 }
338
339 @Override
340 public boolean matches(Field field) {
341 int modifiers = field.getModifiers();
342 if (isStatic(modifiers)
343 || isTransient(modifiers)
344 || field.isAnnotationPresent(XmlTransient.class)) {
345 return false;
346 }
347 switch (accessType) {
348 case FIELD:
349 return true;
350 case PUBLIC_MEMBER:
351 return isPublic(modifiers) || anyXmlAnnotationPresent(field);
352 default:
353 return anyXmlAnnotationPresent(field);
354 }
355 }
356
357 }
358
359 private static class XmlMethodFilter implements MethodFilter {
360
361 private final XmlAccessType accessType;
362
363
364
365
366
367
368 XmlMethodFilter(Class<?> clazz) {
369 this.accessType = Optional
370 .ofNullable(AnnotationUtils.findAnnotation(clazz, XmlAccessorType.class))
371 .map(XmlAccessorType::value)
372 .orElseGet(() -> Optional
373 .ofNullable(clazz.getPackage().getAnnotation(XmlAccessorType.class))
374 .map(XmlAccessorType::value)
375 .orElse(XmlAccessType.PUBLIC_MEMBER));
376 }
377
378 @Override
379 public boolean matches(Method method) {
380 int modifiers = method.getModifiers();
381 if (isStatic(modifiers)
382 || !(method.getName().startsWith("get") || method.getName().startsWith("is"))
383 || method.getParameterCount() > 0
384 || method.getReturnType().equals(Void.class)
385 || method.isAnnotationPresent(XmlTransient.class)) {
386 return false;
387 }
388 switch (accessType) {
389 case PROPERTY:
390 return true;
391 case PUBLIC_MEMBER:
392 return isPublic(modifiers) || anyXmlAnnotationPresent(method);
393 default:
394 return anyXmlAnnotationPresent(method);
395 }
396 }
397 }
398
399 @SuppressWarnings("SameNameButDifferent")
400 @EqualsAndHashCode
401 private static class ScanResult {
402
403 private final Class<?> clazz;
404
405 private final Object source;
406
407
408
409
410
411
412
413 ScanResult(Class<?> clazz, Object source) {
414 Assert.notNull(clazz, "Class must be present.");
415 this.clazz = clazz;
416 this.source = source;
417 }
418
419
420
421
422
423
424 Class<?> getClazz() {
425 return clazz;
426 }
427 }
428
429 }