Writer.java
001 /*
002  * Java Genetic Algorithm Library (jenetics-4.3.0).
003  * Copyright (c) 2007-2018 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 -> ch.toSeq().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     public 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     public default <B> Writer<B>
098     map(final Function<? super B, ? extends T> mapper) {
099         return (xml, data-> {
100             if (data != null) {
101                 final T value = mapper.apply(data);
102                 if (value != null) {
103                     write(xml, value);
104                 }
105             }
106         };
107     }
108 
109 
110     /* *************************************************************************
111      * *************************************************************************
112      * Static factory methods.
113      * *************************************************************************
114      * ************************************************************************/
115 
116     /* *************************************************************************
117      * Creating attribute writer.
118      * ************************************************************************/
119 
120     /**
121      * Writes the attribute with the given {@code name} to the current
122      <em>outer</em> element.
123      *
124      <pre>{@code
125      * final Writer<String> writer1 = elem("element", attr("attribute"));
126      * }</pre>
127      *
128      @see #attr(String, Object)
129      *
130      @param name the attribute name
131      @param <T> the writer base type
132      @return a new writer instance
133      @throws NullPointerException if the attribute {@code name} is {@code null}
134      */
135     public static <T> Writer<T> attr(final String name) {
136         requireNonNull(name);
137 
138         return (xml, data-> {
139             if (data != null) {
140                 xml.writeAttribute(name, data.toString());
141             }
142         };
143     }
144 
145     /**
146      * Writes the attribute with the given {@code name} and a constant
147      * {@code value} to the current <em>outer</em> element.
148      *
149      <pre>{@code
150      * final Writer<MyObject> = elem("element", attr("version", "1.0"));
151      * }</pre>
152      *
153      @param name the attribute name
154      @param value the attribute value
155      @param <T> the writer base type
156      @return a new writer instance
157      @throws NullPointerException if one of the {@code name} is {@code null}
158      */
159     public static <T> Writer<T> attr(
160         final String name,
161         final Object value
162     ) {
163         return attr(name).map(data -> value);
164     }
165 
166 
167     /* *************************************************************************
168      * Creating element writer.
169      * ************************************************************************/
170 
171     /**
172      * Create a new {@code Writer}, which writes a XML element with the given
173      * name and writes the given children into it.
174      *
175      @param name the root element name
176      @param children the XML child elements
177      @param <T> the writer base type
178      @return a new writer instance
179      @throws NullPointerException if one of the arguments is {@code null}
180      */
181     @SafeVarargs
182     public static <T> Writer<T> elem(
183         final String name,
184         final Writer<? super T>... children
185     ) {
186         requireNonNull(name);
187         requireNonNull(children);
188 
189         return (xml, data-> {
190             if (data != null) {
191                 xml.writeStartElement(name);
192                 for (Writer<? super T> child : children) {
193                     child.write(xml, data);
194                 }
195                 xml.writeEndElement();
196             }
197         };
198     }
199 
200     /**
201      * Create a new text {@code Writer}, which writes the given data as string
202      * to the outer element.
203      *
204      @param <T> the data type, which is written as string to the outer element
205      @return a new text writer
206      */
207     public static <T> Writer<T> text() {
208         return (xml, data-> {
209             if (data != null) {
210                 xml.writeCharacters(data.toString());
211             }
212         };
213     }
214 
215     /**
216      * Creates a new {@code Writer}, which writes the given {@code children} as
217      * sub-elements, defined by the given {@code childWriter}.
218      *
219      @param name the enclosing element name used for each data value
220      @param writer the sub-element writer
221      @param <T> the writer base type
222      @return a new writer instance
223      @throws NullPointerException if one of the arguments is {@code null}
224      */
225     public static <T> Writer<Iterable<T>> elems(
226         final String name,
227         final Writer<? super T> writer
228     ) {
229         requireNonNull(name);
230         requireNonNull(writer);
231 
232         return (xml, data-> {
233             if (data != null) {
234                 for (T value : data) {
235                     if (value != null) {
236                         xml.writeStartElement(name);
237                         writer.write(xml, value);
238                         xml.writeEndElement();
239                     }
240                 }
241             }
242         };
243     }
244 
245     /**
246      * Creates a new {@code Writer}, which writes the given {@code children} as
247      * sub-elements, defined by the given {@code childWriter}.
248      *
249      @param writer the sub-element writer
250      @param <T> the writer base type
251      @return a new writer instance
252      @throws NullPointerException if one of the arguments is {@code null}
253      */
254     public static <T> Writer<Iterable<T>> elems(final Writer<? super T> writer) {
255         requireNonNull(writer);
256 
257         return (xml, data-> {
258             if (data != null) {
259                 for (T value : data) {
260                     if (value != null) {
261                         writer.write(xml, value);
262                     }
263                 }
264             }
265         };
266     }
267 
268     /**
269      * Adds a XML prolog element written by the given {@code writer}. The default
270      * values for encoding and version is set to "UTF-8" and "1.0", respectively.
271      *
272      <pre> {@code
273      <?xml version="1.0" encoding="UTF-8"?>
274      * }</pre>
275      *
276      @param writer the root element writer
277      @param <T> the writer data type
278      @return a new writer instance
279      */
280     public static <T> Writer<T> doc(final Writer<? super T> writer) {
281         return (xml, data-> {
282             xml.writeStartDocument("UTF-8""1.0");
283             writer.write(xml, data);
284             xml.writeEndDocument();
285         };
286     }
287 
288 
289     /* *************************************************************************
290      * Service lookup
291      * ************************************************************************/
292 
293 
294     /*
295     public static abstract class Provider<T> {
296         private static final Map<Class<?>, Object>
297             PROVIDERS = new ConcurrentHashMap<>();
298 
299         public abstract Class<T> type();
300         public abstract Writer<T> writer();
301 
302         @SuppressWarnings({"unchecked", "rawtypes"})
303         public static <T> Optional<Provider<T>> of(final Class<T> type) {
304             requireNonNull(type);
305 
306             return (Optional<Provider<T>>)PROVIDERS.computeIfAbsent(type, t -> {
307                 final ServiceLoader<Provider> loader =
308                     ServiceLoader.load(Provider.class);
309 
310                 return StreamSupport.stream(loader.spliterator(), false)
311                     .filter(p -> p.type() == type)
312                     .findFirst();
313             });
314         }
315     }
316     */
317 
318 }