001 /*
002 * Java GPX Library (jpx-3.1.0).
003 * Copyright (c) 2016-2023 Franz Wilhelmstötter
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 *
017 * Author:
018 * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com)
019 */
020 package io.jenetics.jpx;
021
022 import static java.lang.String.format;
023 import static java.util.Arrays.asList;
024 import static java.util.Objects.requireNonNull;
025 import static javax.xml.stream.XMLStreamConstants.CDATA;
026 import static javax.xml.stream.XMLStreamConstants.CHARACTERS;
027 import static javax.xml.stream.XMLStreamConstants.COMMENT;
028 import static javax.xml.stream.XMLStreamConstants.END_DOCUMENT;
029 import static javax.xml.stream.XMLStreamConstants.END_ELEMENT;
030 import static javax.xml.stream.XMLStreamConstants.START_ELEMENT;
031 import static io.jenetics.jpx.Lists.copyOf;
032
033 import java.util.ArrayList;
034 import java.util.HashMap;
035 import java.util.List;
036 import java.util.Map;
037 import java.util.Objects;
038 import java.util.function.Function;
039 import java.util.stream.IntStream;
040 import java.util.stream.Stream;
041
042 import javax.xml.stream.XMLStreamException;
043 import javax.xml.stream.XMLStreamReader;
044
045 import org.w3c.dom.Document;
046
047 import io.jenetics.jpx.XMLReader.Type;
048
049 /**
050 * Simplifies the usage of the {@link XMLStreamReader}.
051 *
052 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
053 * @version 1.5
054 * @since 1.0
055 */
056 abstract class XMLReader<T> {
057
058 /**
059 * Represents the XML element type.
060 */
061 enum Type {
062
063 /**
064 * Denotes a element reader.
065 */
066 ELEM,
067
068 /**
069 * Denotes a element attribute reader.
070 */
071 ATTR,
072
073 /**
074 * Denotes a reader of elements of the same type.
075 */
076 LIST,
077
078 /**
079 * Denotes a reader of the text of a element.
080 */
081 TEXT
082
083 }
084
085 private final String _name;
086 private final Type _type;
087
088 /**
089 * Create a new XML reader with the given name and type.
090 *
091 * @param name the element name of the reader
092 * @param type the element type of the reader
093 * @throws NullPointerException if one of the give arguments is {@code null}
094 */
095 XMLReader(final String name, final Type type) {
096 _name = requireNonNull(name);
097 _type = requireNonNull(type);
098 }
099
100 /**
101 * Read the given type from the underlying XML stream {@code reader}.
102 *
103 * <pre>{@code
104 * try (AutoCloseableXMLStreamReader xml = XML.reader(in)) {
105 * // Move XML stream to first element.
106 * xml.next();
107 * return reader.read(xml);
108 * }
109 * }</pre>
110 *
111 * @param xml the underlying XML stream {@code reader}
112 * @param lenient lenient read mode
113 * @return the data read from the XML stream, maybe {@code null}
114 * @throws XMLStreamException if an error occurs while reading the value
115 * @throws NullPointerException if the given {@code xml} stream reader is
116 * {@code null}
117 */
118 public abstract T read(final XMLStreamReaderAdapter xml, final boolean lenient)
119 throws XMLStreamException;
120
121 /**
122 * Create a new reader for the new mapped type {@code B}.
123 *
124 * @param mapper the mapper function
125 * @param <B> the target type of the new reader
126 * @return a new reader
127 * @throws NullPointerException if the given {@code mapper} function is
128 * {@code null}
129 */
130 public <B> XMLReader<B> map(final Function<? super T, ? extends B> mapper) {
131 return map(mapper, null);
132 }
133
134 /**
135 * Create a new reader for the new mapped type {@code B}.
136 *
137 * @param mapper the mapper function
138 * @param devault the default value if the mapping function fails and the
139 * reader is in {@link io.jenetics.jpx.GPX.Reader.Mode#LENIENT} mode
140 * @param <B> the target type of the new reader
141 * @return a new reader
142 * @throws NullPointerException if the given {@code mapper} function is
143 * {@code null}
144 */
145 public <B> XMLReader<B> map(final Function<? super T, ? extends B> mapper, B devault) {
146 requireNonNull(mapper);
147
148 return new XMLReader<B>(_name, _type) {
149 @Override
150 public B read(final XMLStreamReaderAdapter xml, final boolean lenient)
151 throws XMLStreamException
152 {
153 try {
154 return mapper.apply(XMLReader.this.read(xml, lenient));
155 } catch (RuntimeException e) {
156 if (!lenient) {
157 throw new XMLStreamException(
158 format(
159 "Invalid value for '%s': %s",
160 _name, e.getMessage()
161 ),
162 e
163 );
164 } else {
165 return devault;
166 }
167 }
168 }
169 };
170 }
171
172 /**
173 * Return the name of the element processed by this reader.
174 *
175 * @return the element name the reader is processing
176 */
177 String name() {
178 return _name;
179 }
180
181 /**
182 * Return the element type of the reader.
183 *
184 * @return the element type of the reader
185 */
186 Type type() {
187 return _type;
188 }
189
190 @Override
191 public String toString() {
192 return format("Reader[%s, %s]", name(), type());
193 }
194
195
196 /* *************************************************************************
197 * Static reader factory methods.
198 * ************************************************************************/
199
200 /**
201 * Return a {@code Reader} for reading an attribute of an element.
202 * <p>
203 * <b>XML</b>
204 * <pre> {@code <element length="3"/>}</pre>
205 *
206 * <b>Reader definition</b>
207 * <pre>{@code
208 * final Reader<Integer> reader =
209 * elem(
210 * v -> (Integer)v[0],
211 * "element",
212 * attr("length").map(Integer::parseInt)
213 * );
214 * }</pre>
215 *
216 * @param name the attribute name
217 * @return an attribute reader
218 * @throws NullPointerException if the given {@code name} is {@code null}
219 */
220 public static XMLReader<String> attr(final String name) {
221 return new AttrReader(name);
222 }
223
224 /**
225 * Return a {@code Reader} for reading the text of an element.
226 * <p>
227 * <b>XML</b>
228 * <pre> {@code <element>1234<element>}</pre>
229 *
230 * <b>Reader definition</b>
231 * <pre>{@code
232 * final Reader<Integer> reader =
233 * elem(
234 * v -> (Integer)v[0],
235 * "element",
236 * text().map(Integer::parseInt)
237 * );
238 * }</pre>
239 *
240 * @return an element text reader
241 */
242 public static XMLReader<String> text() {
243 return new TextReader();
244 }
245
246 /**
247 * Return a {@code Reader} for reading an object of type {@code T} from the
248 * XML element with the given {@code name}.
249 *
250 * <p>
251 * <b>XML</b>
252 * <pre> {@code <property name="size">1234<property>}</pre>
253 *
254 * <b>Reader definition</b>
255 * <pre>{@code
256 * final XMLReader<Property> reader =
257 * elem(
258 * v -> {
259 * final String name = (String)v[0];
260 * final Integer value = (Integer)v[1];
261 * return Property.of(name, value);
262 * },
263 * "property",
264 * attr("name"),
265 * text().map(Integer::parseInt)
266 * );
267 * }</pre>
268 *
269 * @param generator the generator function, which build the result object
270 * from the given parameter array
271 * @param name the name of the root (sub-tree) element
272 * @param children the child element reader, which creates the values
273 * forwarded to the {@code generator} function
274 * @param <T> the reader result type
275 * @return a node reader
276 * @throws NullPointerException if one of the given arguments is {@code null}
277 * @throws IllegalArgumentException if the given child readers contains more
278 * than one <em>text</em> reader
279 */
280 public static <T> XMLReader<T> elem(
281 final Function<Object[], T> generator,
282 final String name,
283 final XMLReader<?>... children
284 ) {
285 requireNonNull(name);
286 requireNonNull(generator);
287 Stream.of(requireNonNull(children)).forEach(Objects::requireNonNull);
288
289 return new ElemReader<>(name, generator, asList(children), Type.ELEM);
290 }
291
292 /**
293 * Return a {@code Reader} which reads the value from the child elements of
294 * the given parent element {@code name}.
295 * <p>
296 * <b>XML</b>
297 * <pre> {@code <min><property name="size">1234<property></min>}</pre>
298 *
299 * <b>Reader definition</b>
300 * <pre>{@code
301 * final XMLReader<Property> reader =
302 * elem("min",
303 * elem(
304 * v -> {
305 * final String name = (String)v[0];
306 * final Integer value = (Integer)v[1];
307 * return Property.of(name, value);
308 * },
309 * "property",
310 * attr("name"),
311 * text().map(Integer::parseInt)
312 * )
313 * );
314 * }</pre>
315 *
316 * @param name the parent element name
317 * @param reader the child elements reader
318 * @param <T> the result type
319 * @return a node reader
320 * @throws NullPointerException if one of the given arguments is {@code null}
321 */
322 public static <T> XMLReader<T> elem(
323 final String name,
324 final XMLReader<? extends T> reader
325 ) {
326 requireNonNull(name);
327 requireNonNull(reader);
328
329 return elem(
330 v -> {
331 @SuppressWarnings("unchecked")
332 T value = v.length > 0 ? (T)v[0] : null;
333 return value;
334 },
335 name,
336 reader
337 );
338 }
339
340 public static XMLReader<String> elem(final String name) {
341 return elem(name, text());
342 }
343
344 public static XMLReader<Object> ignore(final String name) {
345 return new IgnoreReader(name);
346 }
347
348 /**
349 * Return a {@code XMLReader} which collects the elements, read by the given
350 * child {@code reader}, and returns it as list of these elements.
351 * <p>
352 * <b>XML</b>
353 * <pre> {@code
354 * <properties length="3">
355 * <property>-1878762439</property>
356 * <property>-957346595</property>
357 * <property>-88668137</property>
358 * </properties>
359 * }</pre>
360 *
361 * <b>Reader definition</b>
362 * <pre>{@code
363 * XMLReader<List<Integer>> reader =
364 * elem(
365 * v -> (List<Integer>)v[0],
366 * "properties",
367 * elems(elem("property", text().map(Integer::parseInt)))
368 * );
369 * }</pre>
370 *
371 * @param reader the child element reader
372 * @param <T> the element type
373 * @return a list reader
374 */
375 public static <T> XMLReader<List<T>> elems(final XMLReader<? extends T> reader) {
376 return new ListReader<T>(reader);
377 }
378
379 public static XMLReader<Document> doc(final String name) {
380 return new DocReader(name);
381 }
382 }
383
384
385 /* *****************************************************************************
386 * XML reader implementations.
387 * ****************************************************************************/
388
389 /**
390 * Reader implementation for reading the attribute of the current node.
391 *
392 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
393 * @version 1.2
394 * @since 1.2
395 */
396 final class AttrReader extends XMLReader<String> {
397
398 AttrReader(final String name) {
399 super(name, Type.ATTR);
400 }
401
402 @Override
403 public String read(final XMLStreamReaderAdapter xml, final boolean lenient)
404 throws XMLStreamException
405 {
406 xml.require(START_ELEMENT, null, null);
407 return xml.getAttributeValue(null, name());
408 }
409
410 }
411
412 /**
413 * Reader implementation for reading the text of the current node.
414 *
415 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
416 * @version 1.2
417 * @since 1.2
418 */
419 final class TextReader extends XMLReader<String> {
420
421 TextReader() {
422 super("", Type.TEXT);
423 }
424
425 @Override
426 public String read(final XMLStreamReaderAdapter xml, final boolean lenient)
427 throws XMLStreamException
428 {
429 final StringBuilder out = new StringBuilder();
430
431 int type = xml.getEventType();
432 do {
433 out.append(xml.getText());
434 } while (xml.hasNext() && (type = xml.next()) == CHARACTERS || type == CDATA);
435
436 return out.toString();
437 }
438 }
439
440 /**
441 * Reader implementation for reading list of elements.
442 *
443 * @param <T> the element type
444 *
445 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
446 * @version 1.2
447 * @since 1.2
448 */
449 final class ListReader<T> extends XMLReader<List<T>> {
450
451 private final XMLReader<? extends T> _adoptee;
452
453 ListReader(final XMLReader<? extends T> adoptee) {
454 super(adoptee.name(), Type.LIST);
455 _adoptee = adoptee;
456 }
457
458 @Override
459 public List<T> read(final XMLStreamReaderAdapter xml, final boolean lenient)
460 throws XMLStreamException
461 {
462 xml.require(START_ELEMENT, null, name());
463 final T element = _adoptee.read(xml, lenient);
464 return element != null ? List.of(element) : List.of();
465 }
466
467 }
468
469 /**
470 * This reader implementation ignores the content of the element with the given
471 * name.
472 *
473 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
474 * @version 1.2
475 * @since 1.2
476 */
477 final class IgnoreReader extends XMLReader<Object> {
478
479 private final XMLReader<Object> _reader;
480
481 IgnoreReader(final String name) {
482 super(name, Type.ELEM);
483 _reader = new ElemReader<>(name, o -> o, List.of(), Type.ELEM);
484 }
485
486 @Override
487 public Object read(final XMLStreamReaderAdapter xml, final boolean lenient)
488 throws XMLStreamException
489 {
490 return _reader.read(xml, true);
491 }
492 }
493
494 /**
495 * This reader implementation reads the XML nodes from a given base node.
496 *
497 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
498 * @version 1.5
499 * @since 1.5
500 */
501 final class DocReader extends XMLReader<Document> {
502
503 DocReader(final String name) {
504 super(name, Type.ELEM);
505 }
506
507 @Override
508 public Document read(final XMLStreamReaderAdapter xml, final boolean lenient)
509 throws XMLStreamException
510 {
511 Document doc = null;
512 try {
513 doc = XML.builder().newDocument();
514 XML.copy(new XMLStreamReaderAdapter(xml) {
515 @Override
516 public String getVersion() {
517 // Workaround for this bug:
518 //Caused by: java.lang.NullPointerException
519 // at java.xml/com.sun.org.apache.xerces.internal.dom.CoreDocumentImpl.setXmlVersion(CoreDocumentImpl.java:865)
520 // at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.SAX2DOM.setDocumentInfo(SAX2DOM.java:145)
521 // at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.SAX2DOM.startElement(SAX2DOM.java:155)
522 // at java.xml/com.sun.org.apache.xml.internal.serializer.ToXMLSAXHandler.closeStartTag(ToXMLSAXHandler.java:206)
523 // at java.xml/com.sun.org.apache.xml.internal.serializer.ToXMLSAXHandler.characters(ToXMLSAXHandler.java:526)
524 // at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.StAXStream2SAX.handleCharacters(StAXStream2SAX.java:248)
525 // at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.StAXStream2SAX.bridge(StAXStream2SAX.java:155)
526 // at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.StAXStream2SAX.parse(StAXStream2SAX.java:104)
527 // at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transformIdentity(TransformerImpl.java:707)
528 // at java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:774)
529 return super.getVersion() != null
530 ? super.getVersion()
531 : "1.0";
532 }
533 }, doc);
534 } catch (XMLStreamException|RuntimeException e) {
535 if (!lenient) {
536 throw e;
537 }
538 }
539 // Mark the next XML element as consumed, by the StaX transformer: #82
540 xml.consumed();
541 return doc;
542 }
543
544 }
545
546 /**
547 * The main XML element reader implementation.
548 *
549 * @param <T> the reader data type
550 *
551 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
552 * @version 1.2
553 * @since 1.2
554 */
555 final class ElemReader<T> extends XMLReader<T> {
556
557 // Given parameters.
558 private final Function<Object[], T> _creator;
559 private final List<XMLReader<?>> _children;
560
561 // Derived parameters.
562 private final Map<String, Integer> _readerIndexMapping = new HashMap<>();
563 private final int[] _attrReaderIndexes;
564 private final int[] _textReaderIndex;
565
566 ElemReader(
567 final String name,
568 final Function<Object[], T> creator,
569 final List<XMLReader<?>> children,
570 final Type type
571 ) {
572 super(name, type);
573
574 _creator = requireNonNull(creator);
575 _children = requireNonNull(children);
576
577 for (int i = 0; i < _children.size(); ++i) {
578 _readerIndexMapping.put(_children.get(i).name(), i);
579 }
580 _attrReaderIndexes = IntStream.range(0, _children.size())
581 .filter(i -> _children.get(i).type() == Type.ATTR)
582 .toArray();
583 _textReaderIndex = IntStream.range(0, _children.size())
584 .filter(i -> _children.get(i).type() == Type.TEXT)
585 .toArray();
586
587 if (_textReaderIndex.length > 1) {
588 throw new IllegalArgumentException(
589 "Found more than one TEXT reader."
590 );
591 }
592 }
593
594 @Override
595 public T read(final XMLStreamReaderAdapter xml, final boolean lenient)
596 throws XMLStreamException
597 {
598 while (xml.getEventType() == COMMENT) {
599 consumeComment(xml);
600 }
601 xml.require(START_ELEMENT, null, name());
602
603 final List<ReaderResult> results = _children.stream()
604 .map(ReaderResult::of)
605 .toList();
606
607 final ReaderResult text = _textReaderIndex.length == 1
608 ? results.get(_textReaderIndex[0])
609 : null;
610
611 for (int attrReaderIndex : _attrReaderIndexes) {
612 final ReaderResult result = results.get(attrReaderIndex);
613 try {
614 result.put(result.reader().read(xml, lenient));
615 } catch (IllegalArgumentException|NullPointerException e) {
616 if (!lenient) throw e;
617 }
618 }
619
620 if (xml.safeNext()) {
621 boolean hasNext = false;
622 do {
623 switch (xml.getEventType()) {
624 case COMMENT -> consumeComment(xml);
625 case START_ELEMENT -> {
626 final String localName = xml.getLocalName();
627 Integer index = _readerIndexMapping.get(localName);
628
629 if (index == null && !lenient) {
630 throw new XMLStreamException(format(
631 "Unexpected element <%s>.",
632 xml.getLocalName()
633 ));
634 }
635
636 final ReaderResult result = index != null
637 ? results.get(index)
638 : ReaderResult.of(elem(xml.getLocalName()));
639
640 if (result != null) {
641 throwUnexpectedElement(xml, lenient, result);
642 hasNext = xml.safeNext();
643 }
644 }
645 case CHARACTERS, CDATA -> {
646 if (text != null) {
647 throwUnexpectedElement(xml, lenient, text);
648 } else {
649 xml.next();
650 }
651 hasNext = true;
652 }
653 case END_ELEMENT, END_DOCUMENT -> {
654 try {
655 return _creator.apply(
656 results.stream()
657 .map(ReaderResult::value)
658 .toArray()
659 );
660 } catch (IllegalArgumentException|NullPointerException e) {
661 if (!lenient) {
662 throw new XMLStreamException(format(
663 "Invalid value for '%s'.", name()), e
664 );
665 } else {
666 return null;
667 }
668 }
669 }
670 }
671 } while (hasNext);
672 }
673
674 throw new XMLStreamException(format(
675 "Premature end of file while reading '%s'.", name()
676 ));
677 }
678
679 private void consumeComment(final XMLStreamReader xml) throws XMLStreamException {
680 assert xml.getEventType() == COMMENT;
681 if (xml.hasNext()) {
682 xml.next();
683 }
684 }
685
686 private void throwUnexpectedElement(
687 final XMLStreamReaderAdapter xml,
688 final boolean lenient,
689 final ReaderResult text
690 )
691 throws XMLStreamException
692 {
693 try {
694 text.put(text.reader().read(xml, lenient));
695 } catch (IllegalArgumentException|NullPointerException e) {
696 if (!lenient) {
697 final XMLStreamException exp = new XMLStreamException(format(
698 "Unexpected element <%s>.",
699 xml.getLocalName()
700 ));
701 exp.addSuppressed(e);
702 throw exp;
703 }
704 }
705 }
706 }
707
708 /**
709 * Helper interface for storing the XML reader (intermediate) results.
710 *
711 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
712 * @version 1.2
713 * @since 1.2
714 */
715 interface ReaderResult {
716
717 /**
718 * Return the underlying XML reader, which reads the result.
719 *
720 * @return return the underlying XML reader
721 */
722 XMLReader<?> reader();
723
724 /**
725 * Put the given {@code value} to the reader result.
726 *
727 * @param value the reader result
728 */
729 void put(final Object value);
730
731 /**
732 * Return the current reader result value.
733 *
734 * @return the current reader result value
735 */
736 Object value();
737
738 /**
739 * Create a reader result for the given XML reader
740 *
741 * @param reader the XML reader
742 * @return a reader result for the given reader
743 */
744 static ReaderResult of(final XMLReader<?> reader) {
745 return reader.type() == Type.LIST
746 ? new ListResult(reader)
747 : new ValueResult(reader);
748 }
749
750 }
751
752 /**
753 * Result object for values read from XML elements.
754 *
755 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
756 * @version 1.2
757 * @since 1.2
758 */
759 final class ValueResult implements ReaderResult {
760
761 private final XMLReader<?> _reader;
762 private Object _value;
763
764 ValueResult(final XMLReader<?> reader) {
765 _reader = reader;
766 }
767
768 @Override
769 public void put(final Object value) {
770 _value = value;
771 }
772
773 @Override
774 public XMLReader<?> reader() {
775 return _reader;
776 }
777
778
779 @Override
780 public Object value() {
781 return _value;
782 }
783
784 }
785
786 /**
787 * Result object for list values read from XML elements.
788 *
789 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
790 * @version 1.2
791 * @since 1.2
792 */
793 final class ListResult implements ReaderResult {
794
795 private final XMLReader<?> _reader;
796 private final List<Object> _value = new ArrayList<>();
797
798 ListResult(final XMLReader<?> reader) {
799 _reader = reader;
800 }
801
802 @Override
803 public void put(final Object value) {
804 if (value instanceof List<?> list) {
805 _value.addAll(list);
806 } else {
807 _value.add(value);
808 }
809 }
810
811 @Override
812 public XMLReader<?> reader() {
813 return _reader;
814 }
815
816 @Override
817 public List<Object> value() {
818 return copyOf(_value);
819 }
820
821 }
|