001 /*
002 * Java Genetic Algorithm Library (jenetics-4.1.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 }
|