Writer.java
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 }