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 }
|