001/* 002 * Java Genetic Algorithm Library (jenetics-8.3.0). 003 * Copyright (c) 2007-2025 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 */ 020package io.jenetics.xml.stream; 021 022import static java.util.Objects.requireNonNull; 023 024import java.util.function.Function; 025 026import javax.xml.stream.XMLStreamException; 027import 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 * {@snippet lang="java": 046 * final Writer<IntegerChromosome> writer = 047 * elem("int-chromosome", 048 * attr("length").map(ch -> ch.length()), 049 * elem("min", Writer.<Integer>text().map(ch -> ch.getMin())), 050 * elem("max", Writer.<Integer>text().map(ch -> ch.getMax())), 051 * elem("alleles", 052 * elems("allele", Writer.<Integer>text()) 053 * .map(ch -> ISeq.of(ch).map(g -> g.getAllele())) 054 * ) 055 * ); 056 * } 057 * 058 * How to write the XML writing is shown by the next code snippet. 059 * {@snippet lang="java": 060 * final IntegerChromosome ch = IntegerChromosome.of(MIN_VALUE, MAX_VALUE, 3); 061 * try (AutoCloseableXMLStreamWriter xml = XML.writer(out, indent)) { 062 * write(ch, xml); 063 * } 064 * } 065 * 066 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a> 067 * @version 3.9 068 * @since 3.9 069 */ 070@FunctionalInterface 071public interface Writer<T> { 072 073 /** 074 * Write the data of type {@code T} to the given XML stream writer. 075 * 076 * @param xml the underlying {@code XMLStreamWriter}, where the value is 077 * written to 078 * @param data the value to write 079 * @throws XMLStreamException if writing the data fails 080 * @throws NullPointerException if one of the arguments is {@code null} 081 */ 082 void write(final XMLStreamWriter xml, final T data) 083 throws XMLStreamException; 084 085 /** 086 * Maps this writer to a different base type. Mapping to a different data 087 * type is necessary when you are going to write <em>sub</em>-objects of 088 * your basic data type {@code T}. E.g. the chromosome length or the 089 * {@code min} and {@code max} value of an {@code IntegerChromosome}. 090 * 091 * @param mapper the mapper function 092 * @param <B> the new data type of returned writer 093 * @return a writer with changed type 094 */ 095 default <B> Writer<B> map(final Function<? super B, ? extends T> mapper) { 096 return (xml, data) -> { 097 if (data != null) { 098 final T value = mapper.apply(data); 099 if (value != null) { 100 write(xml, value); 101 } 102 } 103 }; 104 } 105 106 107 /* ************************************************************************* 108 * ************************************************************************* 109 * Static factory methods. 110 * ************************************************************************* 111 * ************************************************************************/ 112 113 /* ************************************************************************* 114 * Creating attribute writer. 115 * ************************************************************************/ 116 117 /** 118 * Writes the attribute with the given {@code name} to the current 119 * <em>outer</em> element. 120 * {@snippet lang="java": 121 * final Writer<String> writer1 = elem("element", attr("attribute")); 122 * } 123 * 124 * @see #attr(String, Object) 125 * 126 * @param name the attribute name 127 * @param <T> the writer base type 128 * @return a new writer instance 129 * @throws NullPointerException if the attribute {@code name} is {@code null} 130 */ 131 static <T> Writer<T> attr(final String name) { 132 requireNonNull(name); 133 134 return (xml, data) -> { 135 if (data != null) { 136 xml.writeAttribute(name, data.toString()); 137 } 138 }; 139 } 140 141 /** 142 * Writes the attribute with the given {@code name} and a constant 143 * {@code value} to the current <em>outer</em> element. 144 * {@snippet lang="java": 145 * final Writer<MyObject> writer = elem("element", attr("version", "1.0")); 146 * } 147 * 148 * @param name the attribute name 149 * @param value the attribute value 150 * @param <T> the writer base type 151 * @return a new writer instance 152 * @throws NullPointerException if one of the {@code name} is {@code null} 153 */ 154 static <T> Writer<T> attr( 155 final String name, 156 final Object value 157 ) { 158 return attr(name).map(data -> value); 159 } 160 161 162 /* ************************************************************************* 163 * Creating element writer. 164 * ************************************************************************/ 165 166 /** 167 * Create a new {@code Writer}, which writes an XML element with the given 168 * name and writes the given children into it. 169 * 170 * @param name the root element name 171 * @param children the XML child elements 172 * @param <T> the writer base type 173 * @return a new writer instance 174 * @throws NullPointerException if one of the arguments is {@code null} 175 */ 176 @SafeVarargs 177 static <T> Writer<T> elem( 178 final String name, 179 final Writer<? super T>... children 180 ) { 181 requireNonNull(name); 182 requireNonNull(children); 183 184 return (xml, data) -> { 185 if (data != null) { 186 xml.writeStartElement(name); 187 for (Writer<? super T> child : children) { 188 child.write(xml, data); 189 } 190 xml.writeEndElement(); 191 } 192 }; 193 } 194 195 /** 196 * Create a new text {@code Writer}, which writes the given data as string 197 * to the outer element. 198 * 199 * @param <T> the data type, which is written as string to the outer element 200 * @return a new text writer 201 */ 202 static <T> Writer<T> text() { 203 return (xml, data) -> { 204 if (data != null) { 205 xml.writeCharacters(data.toString()); 206 } 207 }; 208 } 209 210 /** 211 * Creates a new {@code Writer}, which writes the given {@code children} as 212 * sub-elements, defined by the given {@code childWriter}. 213 * 214 * @param name the enclosing element name used for each data value 215 * @param writer the sub-element writer 216 * @param <T> the writer base type 217 * @return a new writer instance 218 * @throws NullPointerException if one of the arguments is {@code null} 219 */ 220 static <T> Writer<Iterable<T>> elems( 221 final String name, 222 final Writer<? super T> writer 223 ) { 224 requireNonNull(name); 225 requireNonNull(writer); 226 227 return (xml, data) -> { 228 if (data != null) { 229 for (T value : data) { 230 if (value != null) { 231 xml.writeStartElement(name); 232 writer.write(xml, value); 233 xml.writeEndElement(); 234 } 235 } 236 } 237 }; 238 } 239 240 /** 241 * Creates a new {@code Writer}, which writes the given {@code children} as 242 * sub-elements, defined by the given {@code childWriter}. 243 * 244 * @param writer the sub-element writer 245 * @param <T> the writer base type 246 * @return a new writer instance 247 * @throws NullPointerException if one of the arguments is {@code null} 248 */ 249 static <T> Writer<Iterable<T>> elems(final Writer<? super T> writer) { 250 requireNonNull(writer); 251 252 return (xml, data) -> { 253 if (data != null) { 254 for (T value : data) { 255 if (value != null) { 256 writer.write(xml, value); 257 } 258 } 259 } 260 }; 261 } 262 263 /** 264 * Adds an XML prolog element written by the given {@code writer}. The default 265 * values for encoding and version are set to "UTF-8" and "1.0", respectively. 266 * 267 * <pre> {@code 268 * <?xml version="1.0" encoding="UTF-8"?> 269 * } </pre> 270 * 271 * @param writer the root element writer 272 * @param <T> the writer data type 273 * @return a new writer instance 274 */ 275 static <T> Writer<T> doc(final Writer<? super T> writer) { 276 return (xml, data) -> { 277 xml.writeStartDocument("UTF-8", "1.0"); 278 writer.write(xml, data); 279 xml.writeEndDocument(); 280 }; 281 } 282 283 284 /* ************************************************************************* 285 * Service lookup 286 * ************************************************************************/ 287 288 289 /* 290 public static abstract class Provider<T> { 291 private static final Map<Class<?>, Object> 292 PROVIDERS = new ConcurrentHashMap<>(); 293 294 public abstract Class<T> type(); 295 public abstract Writer<T> writer(); 296 297 @SuppressWarnings({"unchecked", "rawtypes"}) 298 public static <T> Optional<Provider<T>> of(final Class<T> type) { 299 requireNonNull(type); 300 301 return (Optional<Provider<T>>)PROVIDERS.computeIfAbsent(type, t -> { 302 final ServiceLoader<Provider> loader = 303 ServiceLoader.load(Provider.class); 304 305 return StreamSupport.stream(loader.spliterator(), false) 306 .filter(p -> p.type() == type) 307 .findFirst(); 308 }); 309 } 310 } 311 */ 312 313}