XMLWriter.java
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.util.Objects.requireNonNull;
023 
024 import java.util.Optional;
025 import java.util.function.Function;
026 
027 import javax.xml.stream.XMLStreamException;
028 import javax.xml.stream.XMLStreamWriter;
029 
030 import org.w3c.dom.Document;
031 import org.w3c.dom.Element;
032 
033 /**
034  * Helper class for simplifying XML stream writing.
035  *
036  @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
037  @version 1.2
038  @since 1.0
039  */
040 @FunctionalInterface
041 interface XMLWriter<T> {
042 
043     /**
044      * Write the data of type {@code T} to the given XML stream writer.
045      *
046      @param xml the underlying {@code XMLStreamXMLWriter}, where the value is
047      *        written to
048      @param data the value to write
049      @throws XMLStreamException if writing the data fails
050      @throws NullPointerException if one of the arguments is {@code null}
051      */
052     void write(final XMLStreamWriter xml, final T data)
053         throws XMLStreamException;
054 
055     /**
056      * Maps this writer to a different base type. Mapping to a different data
057      * type is necessary when you are going to write <em>sub</em>-objects of
058      * your basic data type {@code T}. E.g. the chromosome length or the
059      * {@code min} and {@code max} value of an {@code IntegerChromosome}.
060      *
061      @param mapper the mapper function
062      @param <B> the new data type of returned writer
063      @return a writer with changed type
064      */
065     default <B> XMLWriter<B>
066     map(final Function<? super B, ? extends T> mapper) {
067         return (xml, data-> {
068             if (data != null) {
069                 final T value = mapper.apply(data);
070                 if (value != null && value != Optional.empty()) {
071                     write(xml, value);
072                 }
073             }
074         };
075     }
076 
077     /**
078      * Maps this writer to a different base type. Mapping to a different data
079      * type is necessary when you are going to write <em>sub</em>-objects of
080      * your basic data type {@code T}. E.g. the chromosome length or the
081      * {@code min} and {@code max} value of an {@code IntegerChromosome}.
082      *
083      @param mapper the mapper function
084      @param <B> the new data type of returned writer
085      @return a writer with changed type
086      */
087     default <B> XMLWriter<B>
088     flatMap(final Function<? super B, ? extends Optional<? extends T>> mapper) {
089         return (xml, data-> {
090             if (data != null) {
091                 final var value = mapper.apply(data);
092                 if (value.isPresent()) {
093                     write(xml, value.orElseThrow());
094                 }
095             }
096         };
097     }
098 
099     /* *************************************************************************
100      * *************************************************************************
101      * Static factory methods.
102      * *************************************************************************
103      * ************************************************************************/
104 
105     /* *************************************************************************
106      * Creating attribute writer.
107      * ************************************************************************/
108 
109     /**
110      * Writes the attribute with the given {@code name} to the current
111      <em>outer</em> element.
112      *
113      <pre>{@code
114      * final XMLWriter<String> writer1 = elem("element", attr("attribute"));
115      * }</pre>
116      *
117      @param name the attribute name
118      @param <T> the writer base type
119      @return a new writer instance
120      @throws NullPointerException if the attribute {@code name} is {@code null}
121      */
122     static <T> XMLWriter<T> attr(final String name) {
123         requireNonNull(name);
124 
125         return (xml, data-> {
126             if (data != null) {
127                 xml.writeAttribute(name, data.toString());
128             }
129         };
130     }
131 
132 
133     /* *************************************************************************
134      * Creating element writer.
135      * ************************************************************************/
136 
137     static <T> XMLWriter<T> ns(final String namespace) {
138         return (xml, data-> xml.writeDefaultNamespace(namespace);
139     }
140 
141     /**
142      * Create a new {@code XMLWriter}, which writes a XML element with the given
143      * name and writes the given children into it.
144      *
145      @param name the root element name
146      @param children the XML child elements
147      @param <T> the writer base type
148      @return a new writer instance
149      @throws NullPointerException if one of the arguments is {@code null}
150      */
151     @SafeVarargs
152     static <T> XMLWriter<T> elem(
153         final String name,
154         final XMLWriter<? super T>... children
155     ) {
156         requireNonNull(name);
157         requireNonNull(children);
158 
159         return (xml, data-> {
160             if (data != null) {
161                 if (children.length > 0) {
162                     xml.writeStartElement(name);
163                     for (XMLWriter<? super T> child : children) {
164                         child.write(xml, data);
165                     }
166                     xml.writeEndElement();
167                 }
168             }
169         };
170     }
171 
172     static XMLWriter<String> elem(final String name) {
173         return elem(name, text());
174     }
175 
176 
177     /**
178      * Create a new text {@code XMLWriter}, which writes the given data as string
179      * to the outer element.
180      *
181      @param <T> the data type, which is written as string to the outer element
182      @return a new text writer
183      */
184     static <T> XMLWriter<T> text() {
185         return (xml, data-> {
186             if (data != null) {
187                 xml.writeCharacters(data.toString());
188             }
189         };
190     }
191 
192     static <N extends Number> XMLWriter<N> number() {
193         return (xml, data-> {
194             if (data != null) {
195                 xml.writeCharacters(Double.toString(data.doubleValue()));
196             }
197         };
198     }
199 
200     static XMLWriter<Document> doc(final String name) {
201         requireNonNull(name);
202 
203         return (xml, data-> {
204             if (data != null) {
205                 final Element root = data.getDocumentElement();
206 
207                 XML.copy(root, new XMLStreamWriterAdapter(xml) {
208                     @Override
209                     public void writeEndDocument() {}
210                     @Override
211                     public void writeStartDocument() {}
212                     @Override
213                     public void writeStartDocument(String version) {}
214                     @Override
215                     public void writeStartDocument(String encoding, String version) {}
216                 });
217             }
218         };
219     }
220 
221     /**
222      * Creates a new {@code XMLWriter}, which writes the given {@code children} as
223      * sub-elements, defined by the given {@code childXMLWriter}.
224      *
225      @param writer the sub-element writer
226      @param <T> the writer base type
227      @return a new writer instance
228      @throws NullPointerException if one of the arguments is {@code null}
229      */
230     static <T> XMLWriter<Iterable<T>> elems(final XMLWriter<? super T> writer) {
231         requireNonNull(writer);
232 
233         return (xml, data-> {
234             if (data != null) {
235                 for (T value : data) {
236                     if (value != null && value != Optional.empty()) {
237                         writer.write(xml, value);
238                     }
239                 }
240             }
241         };
242     }
243 
244 }