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.util.Objects.requireNonNull;
023
024import java.util.function.Function;
025
026import javax.xml.stream.XMLStreamException;
027import javax.xml.stream.XMLStreamWriter;
028
029/**
030 * XML writer interface, used for writing objects in XML format. The following
031 * XML will show the marshaled representation of an {@code IntegerChromosome}.
032 * <pre> {@code
033 * <int-chromosome length="3">
034 *     <min>-2147483648</min>
035 *     <max>2147483647</max>
036 *     <alleles>
037 *         <allele>-1878762439</allele>
038 *         <allele>-957346595</allele>
039 *         <allele>-88668137</allele>
040 *     </alleles>
041 * </int-chromosome>
042 * } </pre>
043 *
044 * The XML has been written by the following {@code Writer} definition.
045 * {@snippet lang="java":
046 * final Writer<IntegerChromosome> writer =
047 *     elem("int-chromosome",
048 *         attr("length").map(ch -> ch.length()),
049 *         elem("min", Writer.<Integer>text().map(ch -> ch.getMin())),
050 *         elem("max", Writer.<Integer>text().map(ch -> ch.getMax())),
051 *         elem("alleles",
052 *             elems("allele",  Writer.<Integer>text())
053 *                 .map(ch -> ISeq.of(ch).map(g -> g.getAllele()))
054 *         )
055 *     );
056 * }
057 *
058 * How to write the XML writing is shown by the next code snippet.
059 * {@snippet lang="java":
060 * final IntegerChromosome ch = IntegerChromosome.of(MIN_VALUE, MAX_VALUE, 3);
061 * try (AutoCloseableXMLStreamWriter xml = XML.writer(out, indent)) {
062 *     write(ch, xml);
063 * }
064 * }
065 *
066 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
067 * @version 3.9
068 * @since 3.9
069 */
070@FunctionalInterface
071public interface Writer<T> {
072
073        /**
074         * Write the data of type {@code T} to the given XML stream writer.
075         *
076         * @param xml the underlying {@code XMLStreamWriter}, where the value is
077         *        written to
078         * @param data the value to write
079         * @throws XMLStreamException if writing the data fails
080         * @throws NullPointerException if one of the arguments is {@code null}
081         */
082        void write(final XMLStreamWriter xml, final T data)
083                throws XMLStreamException;
084
085        /**
086         * Maps this writer to a different base type. Mapping to a different data
087         * type is necessary when you are going to write <em>sub</em>-objects of
088         * your basic data type {@code T}. E.g. the chromosome length or the
089         * {@code min} and {@code max} value of an {@code IntegerChromosome}.
090         *
091         * @param mapper the mapper function
092         * @param <B> the new data type of returned writer
093         * @return a writer with changed type
094         */
095        default <B> Writer<B> map(final Function<? super B, ? extends T> mapper) {
096                return (xml, data) -> {
097                        if (data != null) {
098                                final T value = mapper.apply(data);
099                                if (value != null) {
100                                        write(xml, value);
101                                }
102                        }
103                };
104        }
105
106
107        /* *************************************************************************
108         * *************************************************************************
109         * Static factory methods.
110         * *************************************************************************
111         * ************************************************************************/
112
113        /* *************************************************************************
114         * Creating attribute writer.
115         * ************************************************************************/
116
117        /**
118         * Writes the attribute with the given {@code name} to the current
119         * <em>outer</em> element.
120         * {@snippet lang="java":
121         * final Writer<String> writer1 = elem("element", attr("attribute"));
122         * }
123         *
124         * @see #attr(String, Object)
125         *
126         * @param name the attribute name
127         * @param <T> the writer base type
128         * @return a new writer instance
129         * @throws NullPointerException if the attribute {@code name} is {@code null}
130         */
131        static <T> Writer<T> attr(final String name) {
132                requireNonNull(name);
133
134                return (xml, data) -> {
135                        if (data != null) {
136                                xml.writeAttribute(name, data.toString());
137                        }
138                };
139        }
140
141        /**
142         * Writes the attribute with the given {@code name} and a constant
143         * {@code value} to the current <em>outer</em> element.
144         * {@snippet lang="java":
145         * final Writer<MyObject> writer = elem("element", attr("version", "1.0"));
146         * }
147         *
148         * @param name the attribute name
149         * @param value the attribute value
150         * @param <T> the writer base type
151         * @return a new writer instance
152         * @throws NullPointerException if one of the {@code name} is {@code null}
153         */
154        static <T> Writer<T> attr(
155                final String name,
156                final Object value
157        ) {
158                return attr(name).map(data -> value);
159        }
160
161
162        /* *************************************************************************
163         * Creating element writer.
164         * ************************************************************************/
165
166        /**
167         * Create a new {@code Writer}, which writes an XML element with the given
168         * name and writes the given children into it.
169         *
170         * @param name the root element name
171         * @param children the XML child elements
172         * @param <T> the writer base type
173         * @return a new writer instance
174         * @throws NullPointerException if one of the arguments is {@code null}
175         */
176        @SafeVarargs
177        static <T> Writer<T> elem(
178                final String name,
179                final Writer<? super T>... children
180        ) {
181                requireNonNull(name);
182                requireNonNull(children);
183
184                return (xml, data) -> {
185                        if (data != null) {
186                                xml.writeStartElement(name);
187                                for (Writer<? super T> child : children) {
188                                        child.write(xml, data);
189                                }
190                                xml.writeEndElement();
191                        }
192                };
193        }
194
195        /**
196         * Create a new text {@code Writer}, which writes the given data as string
197         * to the outer element.
198         *
199         * @param <T> the data type, which is written as string to the outer element
200         * @return a new text writer
201         */
202        static <T> Writer<T> text() {
203                return (xml, data) -> {
204                        if (data != null) {
205                                xml.writeCharacters(data.toString());
206                        }
207                };
208        }
209
210        /**
211         * Creates a new {@code Writer}, which writes the given {@code children} as
212         * sub-elements, defined by the given {@code childWriter}.
213         *
214         * @param name the enclosing element name used for each data value
215         * @param writer the sub-element writer
216         * @param <T> the writer base type
217         * @return a new writer instance
218         * @throws NullPointerException if one of the arguments is {@code null}
219         */
220        static <T> Writer<Iterable<T>> elems(
221                final String name,
222                final Writer<? super T> writer
223        ) {
224                requireNonNull(name);
225                requireNonNull(writer);
226
227                return (xml, data) -> {
228                        if (data != null) {
229                                for (T value : data) {
230                                        if (value != null) {
231                                                xml.writeStartElement(name);
232                                                writer.write(xml, value);
233                                                xml.writeEndElement();
234                                        }
235                                }
236                        }
237                };
238        }
239
240        /**
241         * Creates a new {@code Writer}, which writes the given {@code children} as
242         * sub-elements, defined by the given {@code childWriter}.
243         *
244         * @param writer the sub-element writer
245         * @param <T> the writer base type
246         * @return a new writer instance
247         * @throws NullPointerException if one of the arguments is {@code null}
248         */
249        static <T> Writer<Iterable<T>> elems(final Writer<? super T> writer) {
250                requireNonNull(writer);
251
252                return (xml, data) -> {
253                        if (data != null) {
254                                for (T value : data) {
255                                        if (value != null) {
256                                                writer.write(xml, value);
257                                        }
258                                }
259                        }
260                };
261        }
262
263        /**
264         * Adds an XML prolog element written by the given {@code writer}. The default
265         * values for encoding and version are set to "UTF-8" and "1.0", respectively.
266         *
267         * <pre> {@code
268         * <?xml version="1.0" encoding="UTF-8"?>
269         * } </pre>
270         *
271         * @param writer the root element writer
272         * @param <T> the writer data type
273         * @return a new writer instance
274         */
275        static <T> Writer<T> doc(final Writer<? super T> writer) {
276                return (xml, data) -> {
277                        xml.writeStartDocument("UTF-8", "1.0");
278                        writer.write(xml, data);
279                        xml.writeEndDocument();
280                };
281        }
282
283
284        /* *************************************************************************
285         * Service lookup
286         * ************************************************************************/
287
288
289        /*
290        public static abstract class Provider<T> {
291                private static final Map<Class<?>, Object>
292                        PROVIDERS = new ConcurrentHashMap<>();
293
294                public abstract Class<T> type();
295                public abstract Writer<T> writer();
296
297                @SuppressWarnings({"unchecked", "rawtypes"})
298                public static <T> Optional<Provider<T>> of(final Class<T> type) {
299                        requireNonNull(type);
300
301                        return (Optional<Provider<T>>)PROVIDERS.computeIfAbsent(type, t -> {
302                                final ServiceLoader<Provider> loader =
303                                        ServiceLoader.load(Provider.class);
304
305                                return StreamSupport.stream(loader.spliterator(), false)
306                                        .filter(p -> p.type() == type)
307                                        .findFirst();
308                        });
309                }
310        }
311        */
312
313}