001/* 002 * Java Genetic Algorithm Library (jenetics-7.2.0). 003 * Copyright (c) 2007-2023 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 * 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 073public 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 an 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 an XML prolog element written by the given {@code writer}. The default 269 * values for encoding and version are 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}