001 /*
002 * Java Genetic Algorithm Library (jenetics-6.3.0).
003 * Copyright (c) 2007-2021 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.xml.stream;
021
022 import static java.util.Objects.requireNonNull;
023
024 import java.util.function.Function;
025
026 import javax.xml.stream.XMLStreamException;
027 import 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 * <pre>{@code
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 * }</pre>
058 *
059 * How to write the XML writing is shown by the next code snippet.
060 *
061 * <pre>{@code
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 * }</pre>
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
073 public 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 * <pre>{@code
124 * final Writer<String> writer1 = elem("element", attr("attribute"));
125 * }</pre>
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 * <pre>{@code
149 * final Writer<MyObject> = elem("element", attr("version", "1.0"));
150 * }</pre>
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 a 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 a XML prolog element written by the given {@code writer}. The default
269 * values for encoding and version is 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 }
|