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