001/*
002 * Java Genetic Algorithm Library (jenetics-8.1.0).
003 * Copyright (c) 2007-2024 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 */
020package io.jenetics.xml.stream;
021
022import static java.lang.String.format;
023import static java.util.Objects.requireNonNull;
024import static javax.xml.stream.XMLStreamConstants.CDATA;
025import static javax.xml.stream.XMLStreamConstants.CHARACTERS;
026import static javax.xml.stream.XMLStreamConstants.END_ELEMENT;
027import static javax.xml.stream.XMLStreamConstants.START_ELEMENT;
028
029import java.util.ArrayList;
030import java.util.Collections;
031import java.util.HashMap;
032import java.util.List;
033import java.util.Map;
034import java.util.Objects;
035import java.util.function.Function;
036import java.util.stream.IntStream;
037import java.util.stream.Stream;
038
039import javax.xml.stream.XMLStreamException;
040import javax.xml.stream.XMLStreamReader;
041
042import io.jenetics.xml.stream.Reader.Type;
043
044/**
045 * XML reader class, used for reading objects in XML format.
046 * <b>XML</b>
047 * <pre> {@code
048 * <int-chromosome length="3">
049 *     <min>-2147483648</min>
050 *     <max>2147483647</max>
051 *     <alleles>
052 *         <allele>-1878762439</allele>
053 *         <allele>-957346595</allele>
054 *         <allele>-88668137</allele>
055 *     </alleles>
056 * </int-chromosome>
057 * } </pre>
058 *
059 * <b>Reader definition</b>
060 * {@snippet lang="java":
061 * final Reader<IntegerChromosome> reader =
062 *     elem(
063 *         (Object[] v) -> {
064 *             final int length = (int)v[0];
065 *             final int min = (int)v[1];
066 *             final int max = (int)v[2];
067 *             final List<Integer> alleles = (List<Integer>)v[3];
068 *             assert alleles.size() == length;
069 *
070 *             return IntegerChromosome.of(
071 *                 alleles.stream()
072 *                     .map(value -> IntegerGene.of(value, min, max))
073 *                     .toArray(IntegerGene[]::new)
074 *             );
075 *         },
076 *         "int-chromosome",
077 *         attr("length").map(Integer::parseInt),
078 *         elem("min", text().map(Integer::parseInt)),
079 *         elem("max", text().map(Integer::parseInt)),
080 *         elem("alleles",
081 *             elems(elem("allele", text().map(Integer::parseInt)))
082 *         )
083 *     );
084 * }
085 *
086 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
087 * @version 3.9
088 * @since 3.9
089 */
090public abstract class Reader<T> {
091
092        /**
093         * Represents the XML element type.
094         */
095        enum Type {
096
097                /**
098                 * Denotes a element reader.
099                 */
100                ELEM,
101
102                /**
103                 * Denotes a element attribute reader.
104                 */
105                ATTR,
106
107                /**
108                 * Denotes a reader of elements of the same type.
109                 */
110                LIST,
111
112                /**
113                 * Denotes a reader of the text of a element.
114                 */
115                TEXT
116
117        }
118
119        private final String _name;
120        private final Type _type;
121
122        /**
123         * Create a new XML reader with the given name and type.
124         *
125         * @param name the element name of the reader
126         * @param type the element type of the reader
127         * @throws NullPointerException if one of the give arguments is {@code null}
128         */
129        Reader(final String name, final Type type) {
130                _name = requireNonNull(name);
131                _type = requireNonNull(type);
132        }
133
134        /**
135         * Read the given type from the underlying XML stream {@code reader}.
136         *
137         * {@snippet lang="java":
138         * try (AutoCloseableXMLStreamReader xml = XML.reader(in)) {
139         *     // Move XML stream to first the element.
140         *     xml.next();
141         *     return reader.read(xml);
142         * }
143         * }
144         *
145         * @param xml the underlying XML stream {@code reader}
146         * @return the data read from the XML stream, maybe {@code null}
147         * @throws XMLStreamException if an error occurs while reading the value
148         * @throws NullPointerException if the given {@code xml} stream reader is
149         *         {@code null}
150         */
151        public abstract T read(final XMLStreamReader xml) throws XMLStreamException;
152
153        /**
154         * Create a new reader for the new mapped type {@code B}.
155         *
156         * @param mapper the mapper function
157         * @param <B> the target type of the new reader
158         * @return a new reader
159         * @throws NullPointerException if the given {@code mapper} function is
160         *         {@code null}
161         */
162        public <B> Reader<B> map(final Function<? super T, ? extends B> mapper) {
163                requireNonNull(mapper);
164
165                return new Reader<>(_name, _type) {
166                        @Override
167                        public B read(final XMLStreamReader xml)
168                                throws XMLStreamException {
169                                try {
170                                        return mapper.apply(Reader.this.read(xml));
171                                } catch (RuntimeException e) {
172                                        throw new XMLStreamException(e);
173                                }
174                        }
175                };
176        }
177
178        /**
179         * Return the name of the element processed by this reader.
180         *
181         * @return the element name the reader is processing
182         */
183        String name() {
184                return _name;
185        }
186
187        /**
188         * Return the element type of the reader.
189         *
190         * @return the element type of the reader
191         */
192        Type type() {
193                return _type;
194        }
195
196        @Override
197        public String toString() {
198                return format("Reader[%s, %s]", name(), type());
199        }
200
201
202        /* *************************************************************************
203         * Static reader factory methods.
204         * ************************************************************************/
205
206        /**
207         * Return a {@code Reader} for reading an attribute of an element.
208         * <p>
209         * <b>XML</b>
210         * <pre> {@code <element length="3"/>}
211         *
212         * <b>Reader definition</b>
213         * {@snippet lang="java":
214         * final Reader<Integer> reader =
215         *     elem(
216         *         v -> (Integer)v[0],
217         *         "element",
218         *         attr("length").map(Integer::parseInt)
219         *     );
220         * } </pre>
221         *
222         * @param name the attribute name
223         * @return an attribute reader
224         * @throws NullPointerException if the given {@code name} is {@code null}
225         */
226        public static Reader<String> attr(final String name) {
227                return new AttrReader(name);
228        }
229
230        /**
231         * Return a {@code Reader} for reading the text of an element.
232         * <p>
233         * <b>XML</b>
234         * <pre> {@code <element>1234<element>}
235         *
236         * <b>Reader definition</b>
237         * {@snippet lang="java":
238         * final Reader<Integer> reader =
239         *     elem(
240         *         v -> (Integer)v[0],
241         *         "element",
242         *         text().map(Integer::parseInt)
243         *     );
244         * } </pre>
245         *
246         * @return an element text reader
247         */
248        public static Reader<String> text() {
249                return new TextReader();
250        }
251
252        /**
253         * Return a {@code Reader} for reading an object of type {@code T} from the
254         * XML element with the given {@code name}.
255         *
256         * <p>
257         * <b>XML</b>
258         * <pre> {@code <property name="size">1234<property>}
259         *
260         * <b>Reader definition</b>
261         * {@snippet lang="java":
262         * final Reader<Property> reader =
263         *     elem(
264         *         v -> {
265         *             final String name = (String)v[0];
266         *             final Integer value = (Integer)v[1];
267         *             return Property.of(name, value);
268         *         },
269         *         "property",
270         *         attr("name"),
271         *         text().map(Integer::parseInt)
272         *     );
273         * } </pre>
274         *
275         * @param generator the generator function, which build the result object
276         *        from the given parameter array
277         * @param name the name of the root (subtree) element
278         * @param children the child element reader, which creates the values
279         *        forwarded to the {@code generator} function
280         * @param <T> the reader result type
281         * @return a node reader
282         * @throws NullPointerException if one of the given arguments is {@code null}
283         * @throws IllegalArgumentException if the given child readers contains more
284         *         than one <em>text</em> reader
285         */
286        public static <T> Reader<T> elem(
287                final Function<Object[], T> generator,
288                final String name,
289                final Reader<?>... children
290        ) {
291                requireNonNull(name);
292                requireNonNull(generator);
293                Stream.of(requireNonNull(children)).forEach(Objects::requireNonNull);
294
295                return new ElemReader<>(name, generator, List.of(children), Type.ELEM);
296        }
297
298        /**
299         * Return a {@code Reader} which reads the value from the child elements of
300         * the given parent element {@code name}.
301         * <p>
302         * <b>XML</b>
303         * <pre> {@code
304         * <min><property name="size">1234<property></min>}
305         * </pre>
306         *
307         * <b>Reader definition</b>
308         * {@snippet lang="java":
309         * final Reader<Property> reader =
310         *     elem("min",
311         *         elem(
312         *             v -> {
313         *                 final String name = (String)v[0];
314         *                 final Integer value = (Integer)v[1];
315         *                 return Property.of(name, value);
316         *             },
317         *             "property",
318         *             attr("name"),
319         *             text().map(Integer::parseInt)
320         *         )
321         *     );
322         * }
323         *
324         * @param name the parent element name
325         * @param reader the child elements reader
326         * @param <T> the result type
327         * @return a node reader
328         * @throws NullPointerException if one of the given arguments is {@code null}
329         */
330        public static <T> Reader<T> elem(
331                final String name,
332                final Reader<? extends T> reader
333        ) {
334                requireNonNull(name);
335                requireNonNull(reader);
336
337                return elem(
338                        v -> {
339                                @SuppressWarnings("unchecked")
340                                T value = v.length > 0 ? (T)v[0] : null;
341                                return value;
342                        },
343                        name,
344                        reader
345                );
346        }
347
348        /**
349         * Return a {@code Reader} which collects the elements, read by the given
350         * child {@code reader}, and returns it as a 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         * {@snippet lang="java":
363         * Reader<List<Integer>> reader =
364         *     elem(
365         *         v -> (List<Integer>)v[0],
366         *         "properties",
367         *         elems(elem("property", text().map(Integer::parseInt)))
368         *     );
369         * }
370         *
371         * @param reader the child element reader
372         * @param <T> the element type
373         * @return a list reader
374         */
375        public static <T> Reader<List<T>> elems(final Reader<? extends T> reader) {
376                return new ListReader<>(reader);
377        }
378}
379
380
381/* *****************************************************************************
382 * XML reader implementations.
383 * ****************************************************************************/
384
385/**
386 * Reader implementation for reading the attribute of the current node.
387 *
388 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
389 * @version 3.9
390 * @since 3.9
391 */
392final class AttrReader extends Reader<String> {
393
394        AttrReader(final String name) {
395                super(name, Type.ATTR);
396        }
397
398        @Override
399        public String read(final XMLStreamReader xml) throws XMLStreamException {
400                xml.require(START_ELEMENT, null, null);
401                return xml.getAttributeValue(null, name());
402        }
403
404}
405
406/**
407 * Reader implementation for reading the text of the current node.
408 *
409 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
410 * @version 3.9
411 * @since 3.9
412 */
413final class TextReader extends Reader<String> {
414
415        TextReader() {
416                super("", Type.TEXT);
417        }
418
419        @Override
420        public String read(final XMLStreamReader xml) throws XMLStreamException {
421                final StringBuilder out = new StringBuilder();
422
423                int type = xml.getEventType();
424                do {
425                        out.append(xml.getText());
426                } while (xml.hasNext() && (type = xml.next()) == CHARACTERS || type == CDATA);
427
428
429                return out.toString();
430        }
431}
432
433/**
434 * Reader implementation for reading list of elements.
435 *
436 * @param <T> the element type
437 *
438 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
439 * @version 3.9
440 * @since 3.9
441 */
442final class ListReader<T> extends Reader<List<T>> {
443
444        private final Reader<? extends T> _adoptee;
445
446        ListReader(final Reader<? extends T> adoptee) {
447                super(adoptee.name(), Type.LIST);
448                _adoptee = adoptee;
449        }
450
451        @Override
452        public List<T> read(final XMLStreamReader xml) throws XMLStreamException {
453                xml.require(START_ELEMENT, null, name());
454                return Collections.singletonList(_adoptee.read(xml));
455        }
456}
457
458/**
459 * The main XML element reader implementation.
460 *
461 * @param <T> the reader data type
462 *
463 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
464 * @version 3.9
465 * @since 3.9
466 */
467final class ElemReader<T> extends Reader<T> {
468
469        // Given parameters.
470        private final Function<Object[], T> _creator;
471        private final List<Reader<?>> _children;
472
473        // Derived parameters.
474        private final Map<String, Integer> _readerIndexMapping = new HashMap<>();
475        private final int[] _attrReaderIndexes;
476        private final int[] _textReaderIndex;
477
478        ElemReader(
479                final String name,
480                final Function<Object[], T> creator,
481                final List<Reader<?>> children,
482                final Type type
483        ) {
484                super(name, type);
485
486                _creator = requireNonNull(creator);
487                _children = requireNonNull(children);
488
489                for (int i = 0; i < _children.size(); ++i) {
490                        _readerIndexMapping.put(_children.get(i).name(), i);
491                }
492                _attrReaderIndexes = IntStream.range(0, _children.size())
493                        .filter(i -> _children.get(i).type() == Type.ATTR)
494                        .toArray();
495                _textReaderIndex = IntStream.range(0, _children.size())
496                        .filter(i -> _children.get(i).type() == Type.TEXT)
497                        .toArray();
498
499                if (_textReaderIndex.length > 1) {
500                        throw new IllegalArgumentException(
501                                "Found more than one TEXT reader."
502                        );
503                }
504        }
505
506        @Override
507        public T read(final XMLStreamReader xml)
508                throws XMLStreamException
509        {
510                xml.require(START_ELEMENT, null, name());
511
512                final List<ReaderResult> results = _children.stream()
513                        .map(ReaderResult::of)
514                        .toList();
515
516                final ReaderResult text = _textReaderIndex.length == 1
517                        ? results.get(_textReaderIndex[0])
518                        : null;
519
520                for (int i : _attrReaderIndexes) {
521                        final ReaderResult result = results.get(i);
522                        result.put(result.reader().read(xml));
523                }
524
525                if (xml.hasNext()) {
526                        xml.next();
527
528                        boolean hasNext = false;
529                        do {
530                switch (xml.getEventType()) {
531                    case START_ELEMENT -> {
532                        final ReaderResult result = results.get(
533                                                        _readerIndexMapping.get(xml.getLocalName())
534                        );
535                        if (result != null) {
536                            result.put(result.reader().read(xml));
537                            if (xml.hasNext()) {
538                                hasNext = true;
539                                xml.next();
540                            } else {
541                                hasNext = false;
542                            }
543                        }
544                    }
545                    case CHARACTERS, CDATA -> {
546                        if (text != null) {
547                            text.put(text.reader().read(xml));
548                        } else {
549                            xml.next();
550                        }
551                        hasNext = true;
552                    }
553                    case END_ELEMENT -> {
554                        if (name().equals(xml.getLocalName())) {
555                            try {
556                                return _creator.apply(results.stream()
557                                        .map(ReaderResult::value)
558                                        .toArray());
559                            } catch (RuntimeException e) {
560                                throw new XMLStreamException(e);
561                            }
562                        }
563                    }
564                }
565
566                        } while (hasNext);
567                }
568
569                throw new XMLStreamException(format(
570                        "Premature end of file while reading '%s'.", name()
571                ));
572        }
573
574}
575
576/**
577 * Helper interface for storing the XML reader (intermediate) results.
578 */
579interface ReaderResult {
580
581        /**
582         * Return the underlying XML reader, which reads the result.
583         *
584         * @return return the underlying XML reader
585         */
586        Reader<?> reader();
587
588        /**
589         * Put the given {@code value} to the reader result.
590         *
591         * @param value the reader result
592         */
593        void put(final Object value);
594
595        /**
596         * Return the current reader result value.
597         *
598         * @return the current reader result value
599         */
600        Object value();
601
602        /**
603         * Create a reader result for the given XML reader
604         *
605         * @param reader the XML reader
606         * @return a reader result for the given reader
607         */
608        static ReaderResult of(final Reader<?> reader) {
609                return reader.type() == Type.LIST
610                        ? new ListResult(reader)
611                        : new ValueResult(reader);
612        }
613
614}
615
616/**
617 * Result object for values read from XML elements.
618 */
619final class ValueResult implements ReaderResult {
620
621        private final Reader<?> _reader;
622        private Object _value;
623
624        ValueResult(final Reader<?> reader) {
625                _reader = reader;
626        }
627
628        @Override
629        public void put(final Object value) {
630                _value = value;
631        }
632
633        @Override
634        public Reader<?> reader() {
635                return _reader;
636        }
637
638
639        @Override
640        public Object value() {
641                return _value;
642        }
643
644}
645
646/**
647 * Result object for list values read from XML elements.
648 */
649final class ListResult implements ReaderResult {
650
651        private final Reader<?> _reader;
652        private final List<Object> _value = new ArrayList<>();
653
654        ListResult(final Reader<?> reader) {
655                _reader = reader;
656        }
657
658        @Override
659        public void put(final Object value) {
660                if (value instanceof List) {
661                        _value.addAll((List<?>)value);
662                } else {
663                        _value.add(value);
664                }
665        }
666
667        @Override
668        public Reader<?> reader() {
669                return _reader;
670        }
671
672        @Override
673        public List<Object> value() {
674                return _value;
675        }
676
677}