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