1 /*
2 * Copyright 2019-2022 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.bremersee.gpx;
18
19 import jakarta.xml.bind.JAXBContext;
20 import jakarta.xml.bind.Unmarshaller;
21 import java.util.ArrayList;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Optional;
27 import org.bremersee.gpx.model.ExtensionsType;
28 import org.w3c.dom.Element;
29
30 /**
31 * GPX XML context helper.
32 *
33 * @author Christian Bremer
34 */
35 public abstract class GpxJaxbContextHelper {
36
37 private GpxJaxbContextHelper() {
38 }
39
40 /**
41 * Parses the elemants of the given GPX extensions ({@link ExtensionsType#getAnies()}) with the
42 * given {@link JAXBContext}:
43 *
44 * <p>If the {@link JAXBContext} can unmarshall the {@link Element} to a concrete object, a map
45 * entry will be created with the class of the object as key and a list with all unmarshalled
46 * objects as value.
47 *
48 * <p>If the {@link JAXBContext} cannot unmarshall the {@link Element} to a concrete object, a
49 * map entry will be created with the class of the element as key and a list with all elements as
50 * value.
51 *
52 * @param extensions the GPX extension
53 * @param jaxbContext the {@link JAXBContext} to parse the elements
54 * @return an unmodifiable map with the unmarshalled objects (key is the class of the objects,
55 * value is a list with all unmarshalled objects of this class)
56 */
57 public static Map<Class<?>, List<Object>> parseExtensions(
58 final ExtensionsType extensions,
59 final JAXBContext jaxbContext) {
60
61 try {
62 return parseExtensions(extensions, jaxbContext.createUnmarshaller());
63 } catch (final Exception ignored) {
64 return parseExtensions(extensions, (Unmarshaller) null);
65 }
66 }
67
68 /**
69 * Parses the elemants of the given GPX extensions ({@link ExtensionsType#getAnies()}) with the
70 * given {@link Unmarshaller}:
71 *
72 * <p>If the {@link Unmarshaller} can unmarshall the {@link Element} to a concrete object, a map
73 * entry will be created with the class of the object as key and a list with all unmarshalled
74 * objects as value.
75 *
76 * <p>If the {@link Unmarshaller} cannot unmarshall the {@link Element} to a concrete object, a
77 * map entry will be created with the class of the element as key and a list with all elements as
78 * value.
79 *
80 * @param extensions the GPX extension
81 * @param unmarshaller the {@link Unmarshaller} to parse the elements
82 * @return an unmodifiable map with the unmarshalled objects (key is the class of the objects,
83 * value is a list with all unmarshalled objects of this class)
84 */
85 public static Map<Class<?>, List<Object>> parseExtensions(
86 final ExtensionsType extensions,
87 final Unmarshaller unmarshaller) {
88
89 final Map<Class<?>, List<Object>> map = new HashMap<>();
90 if (extensions == null || extensions.getAnies() == null) {
91 return map;
92 }
93 for (final Element element : extensions.getAnies()) {
94 if (element != null) {
95 final Object strictElement = parseElement(element, unmarshaller);
96 final List<Object> values = map.computeIfAbsent(
97 strictElement.getClass(), k -> new ArrayList<>());
98 values.add(strictElement);
99 }
100 }
101 return Collections.unmodifiableMap(map);
102 }
103
104 private static Object parseElement(final Element element, final Unmarshaller unmarshaller) {
105 try {
106 return unmarshaller.unmarshal(element);
107
108 } catch (final Exception ignored) {
109 return element;
110 }
111 }
112
113 /**
114 * Find all extensions of the given type.
115 *
116 * @param cls the type
117 * @param instancesOf if {@code true} instanceof will be used, otherwise {@link
118 * Class#equals(Object)} will be used
119 * @param parsedExtensions the parsed extensions (see {@link GpxJaxbContextHelper#parseExtensions(ExtensionsType,
120 * JAXBContext)})
121 * @param <T> the type
122 * @return an unmodifiable list of all extensions of the given type
123 */
124 public static <T> List<T> findExtensions(
125 final Class<T> cls,
126 final boolean instancesOf,
127 final Map<Class<?>, List<Object>> parsedExtensions) {
128
129 if (cls == null || parsedExtensions == null || parsedExtensions.isEmpty()) {
130 return Collections.emptyList();
131 }
132 final List<T> list = new ArrayList<>();
133 for (Map.Entry<Class<?>, List<Object>> entry : parsedExtensions.entrySet()) {
134 final Class<?> c = entry.getKey();
135 if (cls.equals(c) || (instancesOf && cls.isAssignableFrom(c))) {
136 final List<Object> values = entry.getValue();
137 if (values != null) {
138 for (final Object value : values) {
139 if (value != null
140 && (cls.equals(value.getClass())
141 || (instancesOf && cls.isAssignableFrom(value.getClass())))) {
142 //noinspection unchecked
143 list.add((T) value);
144 }
145 }
146 }
147 }
148 }
149 return Collections.unmodifiableList(list);
150 }
151
152 /**
153 * Find all extensions of the given type.
154 *
155 * @param cls the type
156 * @param instancesOf if {@code true} instanceof will be used, otherwise {@link
157 * Class#equals(Object)} will be used
158 * @param extensions the GPX extensions
159 * @param jaxbContext the {@link JAXBContext} to parse the elements
160 * @param <T> the type
161 * @return an unmodifiable list of all extensions of the given type
162 */
163 public static <T> List<T> findExtensions(
164 final Class<T> cls,
165 final boolean instancesOf,
166 final ExtensionsType extensions,
167 final JAXBContext jaxbContext) {
168
169 return findExtensions(cls, instancesOf, parseExtensions(extensions, jaxbContext));
170 }
171
172 /**
173 * Find all extensions of the given type.
174 *
175 * @param cls the type
176 * @param instancesOf if {@code true} instanceof will be used, otherwise {@link
177 * Class#equals(Object)} will be used
178 * @param extensions the GPX extensions
179 * @param unmarshaller the {@link Unmarshaller} to parse the elements
180 * @param <T> the type
181 * @return an unmodifiable list of all extensions of the given type
182 */
183 public static <T> List<T> findExtensions(
184 final Class<T> cls,
185 final boolean instancesOf,
186 final ExtensionsType extensions,
187 final Unmarshaller unmarshaller) {
188
189 return findExtensions(cls, instancesOf, parseExtensions(extensions, unmarshaller));
190 }
191
192 /**
193 * Find the first extension of the given type.
194 *
195 * @param cls the type
196 * @param instancesOf if {@code true} instanceof will be used, otherwise {@link
197 * Class#equals(Object)} will be used
198 * @param parsedExtensions the parsed extensions (see {@link GpxJaxbContextHelper#parseExtensions(ExtensionsType,
199 * JAXBContext)})
200 * @param <T> the type
201 * @return {@link Optional#empty()} if there is no such element, otherwise an optional with the
202 * parsed element
203 */
204 public static <T> Optional<T> findFirstExtension(
205 final Class<T> cls,
206 final boolean instancesOf,
207 final Map<Class<?>, List<Object>> parsedExtensions) {
208
209 final List<T> list = findExtensions(cls, instancesOf, parsedExtensions);
210 return list.isEmpty() ? Optional.empty() : Optional.of(list.get(0));
211 }
212
213 /**
214 * Find the first extension of the given type.
215 *
216 * @param cls the type
217 * @param instancesOf if {@code true} instanceof will be used, otherwise {@link
218 * Class#equals(Object)} will be used
219 * @param extensions the GPX extensions
220 * @param jaxbContext the {@link JAXBContext} to parse the elements
221 * @param <T> the type
222 * @return {@link Optional#empty()} if there is no such element, otherwise an optional with the
223 * parsed element
224 */
225 public static <T> Optional<T> findFirstExtension(
226 final Class<T> cls,
227 final boolean instancesOf,
228 final ExtensionsType extensions,
229 final JAXBContext jaxbContext) {
230
231 final List<T> list = findExtensions(cls, instancesOf, extensions, jaxbContext);
232 return list.isEmpty() ? Optional.empty() : Optional.of(list.get(0));
233 }
234
235 /**
236 * Find the first extension of the given type.
237 *
238 * @param cls the type
239 * @param instancesOf if {@code true} instanceof will be used, otherwise {@link
240 * Class#equals(Object)} will be used
241 * @param extensions the GPX extensions
242 * @param unmarshaller the {@link Unmarshaller} to parse the elements
243 * @param <T> the type
244 * @return {@link Optional#empty()} if there is no such element, otherwise an optional with the
245 * parsed element
246 */
247 public static <T> Optional<T> findFirstExtension(
248 final Class<T> cls,
249 final boolean instancesOf,
250 final ExtensionsType extensions,
251 final Unmarshaller unmarshaller) {
252
253 final List<T> list = findExtensions(cls, instancesOf, extensions, unmarshaller);
254 return list.isEmpty() ? Optional.empty() : Optional.of(list.get(0));
255 }
256
257 }