View Javadoc
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 }