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.lang.String.format; 023import static java.util.Objects.requireNonNull; 024import static javax.xml.stream.XMLStreamConstants.CDATA; 025import static javax.xml.stream.XMLStreamConstants.CHARACTERS; 026import static javax.xml.stream.XMLStreamConstants.END_ELEMENT; 027import static javax.xml.stream.XMLStreamConstants.START_ELEMENT; 028 029import java.util.ArrayList; 030import java.util.Collections; 031import java.util.HashMap; 032import java.util.List; 033import java.util.Map; 034import java.util.Objects; 035import java.util.function.Function; 036import java.util.stream.IntStream; 037import java.util.stream.Stream; 038 039import javax.xml.stream.XMLStreamException; 040import javax.xml.stream.XMLStreamReader; 041 042import io.jenetics.xml.stream.Reader.Type; 043 044/** 045 * XML reader class, used for reading objects in XML format. 046 * <b>XML</b> 047 * <pre> {@code 048 * <int-chromosome length="3"> 049 * <min>-2147483648</min> 050 * <max>2147483647</max> 051 * <alleles> 052 * <allele>-1878762439</allele> 053 * <allele>-957346595</allele> 054 * <allele>-88668137</allele> 055 * </alleles> 056 * </int-chromosome> 057 * } </pre> 058 * 059 * <b>Reader definition</b> 060 * {@snippet lang="java": 061 * final Reader<IntegerChromosome> reader = 062 * elem( 063 * (Object[] v) -> { 064 * final int length = (int)v[0]; 065 * final int min = (int)v[1]; 066 * final int max = (int)v[2]; 067 * final List<Integer> alleles = (List<Integer>)v[3]; 068 * assert alleles.size() == length; 069 * 070 * return IntegerChromosome.of( 071 * alleles.stream() 072 * .map(value -> IntegerGene.of(value, min, max)) 073 * .toArray(IntegerGene[]::new) 074 * ); 075 * }, 076 * "int-chromosome", 077 * attr("length").map(Integer::parseInt), 078 * elem("min", text().map(Integer::parseInt)), 079 * elem("max", text().map(Integer::parseInt)), 080 * elem("alleles", 081 * elems(elem("allele", text().map(Integer::parseInt))) 082 * ) 083 * ); 084 * } 085 * 086 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a> 087 * @version 3.9 088 * @since 3.9 089 */ 090public abstract class Reader<T> { 091 092 /** 093 * Represents the XML element type. 094 */ 095 enum Type { 096 097 /** 098 * Denotes a element reader. 099 */ 100 ELEM, 101 102 /** 103 * Denotes a element attribute reader. 104 */ 105 ATTR, 106 107 /** 108 * Denotes a reader of elements of the same type. 109 */ 110 LIST, 111 112 /** 113 * Denotes a reader of the text of a element. 114 */ 115 TEXT 116 117 } 118 119 private final String _name; 120 private final Type _type; 121 122 /** 123 * Create a new XML reader with the given name and type. 124 * 125 * @param name the element name of the reader 126 * @param type the element type of the reader 127 * @throws NullPointerException if one of the give arguments is {@code null} 128 */ 129 Reader(final String name, final Type type) { 130 _name = requireNonNull(name); 131 _type = requireNonNull(type); 132 } 133 134 /** 135 * Read the given type from the underlying XML stream {@code reader}. 136 * {@snippet lang="java": 137 * try (AutoCloseableXMLStreamReader xml = XML.reader(in)) { 138 * // Move the XML stream to the first element. 139 * xml.next(); 140 * return reader.read(xml); 141 * } 142 * } 143 * 144 * @param xml the underlying XML stream {@code reader} 145 * @return the data read from the XML stream, maybe {@code null} 146 * @throws XMLStreamException if an error occurs while reading the value 147 * @throws NullPointerException if the given {@code xml} stream reader is 148 * {@code null} 149 */ 150 public abstract T read(final XMLStreamReader xml) throws XMLStreamException; 151 152 /** 153 * Create a new reader for the new mapped type {@code B}. 154 * 155 * @param mapper the mapper function 156 * @param <B> the target type of the new reader 157 * @return a new reader 158 * @throws NullPointerException if the given {@code mapper} function is 159 * {@code null} 160 */ 161 public <B> Reader<B> map(final Function<? super T, ? extends B> mapper) { 162 requireNonNull(mapper); 163 164 return new Reader<>(_name, _type) { 165 @Override 166 public B read(final XMLStreamReader xml) 167 throws XMLStreamException { 168 try { 169 return mapper.apply(Reader.this.read(xml)); 170 } catch (RuntimeException e) { 171 throw new XMLStreamException(e); 172 } 173 } 174 }; 175 } 176 177 /** 178 * Return the name of the element processed by this reader. 179 * 180 * @return the element name the reader is processing 181 */ 182 String name() { 183 return _name; 184 } 185 186 /** 187 * Return the element type of the reader. 188 * 189 * @return the element type of the reader 190 */ 191 Type type() { 192 return _type; 193 } 194 195 @Override 196 public String toString() { 197 return format("Reader[%s, %s]", name(), type()); 198 } 199 200 201 /* ************************************************************************* 202 * Static reader factory methods. 203 * ************************************************************************/ 204 205 /** 206 * Return a {@code Reader} for reading an attribute of an element. 207 * <p> 208 * <b>XML</b> 209 * <pre> {@code <element length="3"/>} 210 * 211 * <b>Reader definition</b> 212 * {@snippet lang="java": 213 * final Reader<Integer> reader = 214 * elem( 215 * v -> (Integer)v[0], 216 * "element", 217 * attr("length").map(Integer::parseInt) 218 * ); 219 * } </pre> 220 * 221 * @param name the attribute name 222 * @return an attribute reader 223 * @throws NullPointerException if the given {@code name} is {@code null} 224 */ 225 public static Reader<String> attr(final String name) { 226 return new AttrReader(name); 227 } 228 229 /** 230 * Return a {@code Reader} for reading the text of an element. 231 * <p> 232 * <b>XML</b> 233 * <pre> {@code <element>1234<element>} 234 * 235 * <b>Reader definition</b> 236 * {@snippet lang="java": 237 * final Reader<Integer> reader = 238 * elem( 239 * v -> (Integer)v[0], 240 * "element", 241 * text().map(Integer::parseInt) 242 * ); 243 * } </pre> 244 * 245 * @return an element text reader 246 */ 247 public static Reader<String> text() { 248 return new TextReader(); 249 } 250 251 /** 252 * Return a {@code Reader} for reading an object of type {@code T} from the 253 * XML element with the given {@code name}. 254 * 255 * <p> 256 * <b>XML</b> 257 * <pre> {@code <property name="size">1234<property>} 258 * 259 * <b>Reader definition</b> 260 * {@snippet lang="java": 261 * final Reader<Property> reader = 262 * elem( 263 * v -> { 264 * final String name = (String)v[0]; 265 * final Integer value = (Integer)v[1]; 266 * return Property.of(name, value); 267 * }, 268 * "property", 269 * attr("name"), 270 * text().map(Integer::parseInt) 271 * ); 272 * } </pre> 273 * 274 * @param generator the generator function, which build the result object 275 * from the given parameter array 276 * @param name the name of the root (subtree) element 277 * @param children the child element reader, which creates the values 278 * forwarded to the {@code generator} function 279 * @param <T> the reader result type 280 * @return a node reader 281 * @throws NullPointerException if one of the given arguments is {@code null} 282 * @throws IllegalArgumentException if the given child readers contains more 283 * than one <em>text</em> reader 284 */ 285 public static <T> Reader<T> elem( 286 final Function<Object[], T> generator, 287 final String name, 288 final Reader<?>... children 289 ) { 290 requireNonNull(name); 291 requireNonNull(generator); 292 Stream.of(requireNonNull(children)).forEach(Objects::requireNonNull); 293 294 return new ElemReader<>(name, generator, List.of(children), Type.ELEM); 295 } 296 297 /** 298 * Return a {@code Reader} which reads the value from the child elements of 299 * the given parent element {@code name}. 300 * <p> 301 * <b>XML</b> 302 * <pre> {@code 303 * <min><property name="size">1234<property></min>} 304 * </pre> 305 * 306 * <b>Reader definition</b> 307 * {@snippet lang="java": 308 * final Reader<Property> reader = 309 * elem("min", 310 * elem( 311 * v -> { 312 * final String name = (String)v[0]; 313 * final Integer value = (Integer)v[1]; 314 * return Property.of(name, value); 315 * }, 316 * "property", 317 * attr("name"), 318 * text().map(Integer::parseInt) 319 * ) 320 * ); 321 * } 322 * 323 * @param name the parent element name 324 * @param reader the child elements reader 325 * @param <T> the result type 326 * @return a node reader 327 * @throws NullPointerException if one of the given arguments is {@code null} 328 */ 329 public static <T> Reader<T> elem( 330 final String name, 331 final Reader<? extends T> reader 332 ) { 333 requireNonNull(name); 334 requireNonNull(reader); 335 336 return elem( 337 v -> { 338 @SuppressWarnings("unchecked") 339 T value = v.length > 0 ? (T)v[0] : null; 340 return value; 341 }, 342 name, 343 reader 344 ); 345 } 346 347 /** 348 * Return a {@code Reader} which collects the elements, read by the given 349 * child {@code reader}, and returns it as a list of these elements. 350 * <p> 351 * <b>XML</b> 352 * <pre> {@code 353 * <properties length="3"> 354 * <property>-1878762439</property> 355 * <property>-957346595</property> 356 * <property>-88668137</property> 357 * </properties> 358 * } </pre> 359 * 360 * <b>Reader definition</b> 361 * {@snippet lang="java": 362 * Reader<List<Integer>> reader = 363 * elem( 364 * v -> (List<Integer>)v[0], 365 * "properties", 366 * elems(elem("property", text().map(Integer::parseInt))) 367 * ); 368 * } 369 * 370 * @param reader the child element reader 371 * @param <T> the element type 372 * @return a list reader 373 */ 374 public static <T> Reader<List<T>> elems(final Reader<? extends T> reader) { 375 return new ListReader<>(reader); 376 } 377} 378 379 380/* ***************************************************************************** 381 * XML reader implementations. 382 * ****************************************************************************/ 383 384/** 385 * Reader implementation for reading the attribute of the current node. 386 * 387 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a> 388 * @version 3.9 389 * @since 3.9 390 */ 391final class AttrReader extends Reader<String> { 392 393 AttrReader(final String name) { 394 super(name, Type.ATTR); 395 } 396 397 @Override 398 public String read(final XMLStreamReader xml) throws XMLStreamException { 399 xml.require(START_ELEMENT, null, null); 400 return xml.getAttributeValue(null, name()); 401 } 402 403} 404 405/** 406 * Reader implementation for reading the text of the current node. 407 * 408 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a> 409 * @version 3.9 410 * @since 3.9 411 */ 412final class TextReader extends Reader<String> { 413 414 TextReader() { 415 super("", Type.TEXT); 416 } 417 418 @Override 419 public String read(final XMLStreamReader xml) throws XMLStreamException { 420 final StringBuilder out = new StringBuilder(); 421 422 int type = xml.getEventType(); 423 do { 424 out.append(xml.getText()); 425 } while (xml.hasNext() && (type = xml.next()) == CHARACTERS || type == CDATA); 426 427 428 return out.toString(); 429 } 430} 431 432/** 433 * Reader implementation for a reading list of elements. 434 * 435 * @param <T> the element type 436 * 437 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a> 438 * @version 3.9 439 * @since 3.9 440 */ 441final class ListReader<T> extends Reader<List<T>> { 442 443 private final Reader<? extends T> _adoptee; 444 445 ListReader(final Reader<? extends T> adoptee) { 446 super(adoptee.name(), Type.LIST); 447 _adoptee = adoptee; 448 } 449 450 @Override 451 public List<T> read(final XMLStreamReader xml) throws XMLStreamException { 452 xml.require(START_ELEMENT, null, name()); 453 return Collections.singletonList(_adoptee.read(xml)); 454 } 455} 456 457/** 458 * The main XML element reader implementation. 459 * 460 * @param <T> the reader data type 461 * 462 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a> 463 * @version 3.9 464 * @since 3.9 465 */ 466final class ElemReader<T> extends Reader<T> { 467 468 // Given parameters. 469 private final Function<Object[], T> _creator; 470 private final List<Reader<?>> _children; 471 472 // Derived parameters. 473 private final Map<String, Integer> _readerIndexMapping = new HashMap<>(); 474 private final int[] _attrReaderIndexes; 475 private final int[] _textReaderIndex; 476 477 ElemReader( 478 final String name, 479 final Function<Object[], T> creator, 480 final List<Reader<?>> children, 481 final Type type 482 ) { 483 super(name, type); 484 485 _creator = requireNonNull(creator); 486 _children = requireNonNull(children); 487 488 for (int i = 0; i < _children.size(); ++i) { 489 _readerIndexMapping.put(_children.get(i).name(), i); 490 } 491 _attrReaderIndexes = IntStream.range(0, _children.size()) 492 .filter(i -> _children.get(i).type() == Type.ATTR) 493 .toArray(); 494 _textReaderIndex = IntStream.range(0, _children.size()) 495 .filter(i -> _children.get(i).type() == Type.TEXT) 496 .toArray(); 497 498 if (_textReaderIndex.length > 1) { 499 throw new IllegalArgumentException( 500 "Found more than one TEXT reader." 501 ); 502 } 503 } 504 505 @Override 506 public T read(final XMLStreamReader xml) 507 throws XMLStreamException 508 { 509 xml.require(START_ELEMENT, null, name()); 510 511 final List<ReaderResult> results = _children.stream() 512 .map(ReaderResult::of) 513 .toList(); 514 515 final ReaderResult text = _textReaderIndex.length == 1 516 ? results.get(_textReaderIndex[0]) 517 : null; 518 519 for (int i : _attrReaderIndexes) { 520 final ReaderResult result = results.get(i); 521 result.put(result.reader().read(xml)); 522 } 523 524 if (xml.hasNext()) { 525 xml.next(); 526 527 boolean hasNext = false; 528 do { 529 switch (xml.getEventType()) { 530 case START_ELEMENT -> { 531 final ReaderResult result = results.get( 532 _readerIndexMapping.get(xml.getLocalName()) 533 ); 534 if (result != null) { 535 result.put(result.reader().read(xml)); 536 if (xml.hasNext()) { 537 hasNext = true; 538 xml.next(); 539 } else { 540 hasNext = false; 541 } 542 } 543 } 544 case CHARACTERS, CDATA -> { 545 if (text != null) { 546 text.put(text.reader().read(xml)); 547 } else { 548 xml.next(); 549 } 550 hasNext = true; 551 } 552 case END_ELEMENT -> { 553 if (name().equals(xml.getLocalName())) { 554 try { 555 return _creator.apply(results.stream() 556 .map(ReaderResult::value) 557 .toArray()); 558 } catch (RuntimeException e) { 559 throw new XMLStreamException(e); 560 } 561 } 562 } 563 } 564 565 } while (hasNext); 566 } 567 568 throw new XMLStreamException(format( 569 "Premature end of file while reading '%s'.", name() 570 )); 571 } 572 573} 574 575/** 576 * Helper interface for storing the XML reader (intermediate) results. 577 */ 578interface ReaderResult { 579 580 /** 581 * Return the underlying XML reader, which reads the result. 582 * 583 * @return return the underlying XML reader 584 */ 585 Reader<?> reader(); 586 587 /** 588 * Put the given {@code value} to the reader result. 589 * 590 * @param value the reader result 591 */ 592 void put(final Object value); 593 594 /** 595 * Return the current reader result value. 596 * 597 * @return the current reader result value 598 */ 599 Object value(); 600 601 /** 602 * Create a reader result for the given XML reader 603 * 604 * @param reader the XML reader 605 * @return a reader result for the given reader 606 */ 607 static ReaderResult of(final Reader<?> reader) { 608 return reader.type() == Type.LIST 609 ? new ListResult(reader) 610 : new ValueResult(reader); 611 } 612 613} 614 615/** 616 * Result object for values read from XML elements. 617 */ 618final class ValueResult implements ReaderResult { 619 620 private final Reader<?> _reader; 621 private Object _value; 622 623 ValueResult(final Reader<?> reader) { 624 _reader = reader; 625 } 626 627 @Override 628 public void put(final Object value) { 629 _value = value; 630 } 631 632 @Override 633 public Reader<?> reader() { 634 return _reader; 635 } 636 637 638 @Override 639 public Object value() { 640 return _value; 641 } 642 643} 644 645/** 646 * Result object for list values read from XML elements. 647 */ 648final class ListResult implements ReaderResult { 649 650 private final Reader<?> _reader; 651 private final List<Object> _value = new ArrayList<>(); 652 653 ListResult(final Reader<?> reader) { 654 _reader = reader; 655 } 656 657 @Override 658 public void put(final Object value) { 659 if (value instanceof List) { 660 _value.addAll((List<?>)value); 661 } else { 662 _value.add(value); 663 } 664 } 665 666 @Override 667 public Reader<?> reader() { 668 return _reader; 669 } 670 671 @Override 672 public List<Object> value() { 673 return _value; 674 } 675 676}