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