001/* 002 * Java Genetic Algorithm Library (jenetics-4.3.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 */ 020package io.jenetics.xml; 021 022import static java.lang.String.format; 023import static java.util.Objects.requireNonNull; 024import static io.jenetics.xml.stream.Reader.attr; 025import static io.jenetics.xml.stream.Reader.elem; 026import static io.jenetics.xml.stream.Reader.elems; 027import static io.jenetics.xml.stream.Reader.text; 028 029import java.io.InputStream; 030import java.util.List; 031import java.util.function.Function; 032import java.util.function.IntFunction; 033import java.util.stream.Stream; 034 035import javax.xml.stream.XMLStreamException; 036 037import io.jenetics.BoundedGene; 038import io.jenetics.Chromosome; 039import io.jenetics.DoubleGene; 040import io.jenetics.EnumGene; 041import io.jenetics.Gene; 042import io.jenetics.IntegerGene; 043import io.jenetics.LongGene; 044import io.jenetics.util.CharSeq; 045import io.jenetics.util.ISeq; 046import io.jenetics.util.MSeq; 047 048import io.jenetics.xml.stream.AutoCloseableXMLStreamReader; 049import io.jenetics.xml.stream.Reader; 050import io.jenetics.xml.stream.XML; 051 052/** 053 * This class contains static fields and methods, for creating chromosome- and 054 * genotype readers for different gene types. 055 * 056 * <pre>{@code 057 * final Reader<Genotype<BitGene> bgr = 058 * Readers.Genotype.reader(Readers.BitChromosome.reader())); 059 * 060 * final Reader<Genotype<IntegerGene>> igr = 061 * Writers.Genotype.reader(Readers.IntegerChromosome.reader())); 062 * 063 * final Reader<Genotype<DoubleGene>> dgr = 064 * Readers.Genotype.reader(Readers.DoubleChromosome.reader())); 065 * }</pre> 066 * 067 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a> 068 * @version 3.9 069 * @since 3.9 070 */ 071public final class Readers { 072 private Readers() {} 073 074 /** 075 * Bit chromosome reader methods, which reads XML-representations of 076 * bit-chromosomes. 077 * <p> 078 * <b>XML</b> 079 * <pre> {@code 080 * <bit-chromosome length="20" ones-probability="0.5">11100011101011001010</bit-chromosome> 081 * }</pre> 082 * } 083 */ 084 public static final class BitChromosome { 085 private BitChromosome() {} 086 087 /** 088 * Return a XML reader for {@link io.jenetics.BitChromosome} objects. 089 * 090 * @return a chromosome reader 091 */ 092 public static Reader<io.jenetics.BitChromosome> reader() { 093 return elem( 094 v -> io.jenetics.BitChromosome.of( 095 (String)v[2], (int)v[0], (double)v[1] 096 ), 097 Writers.BitChromosome.ROOT_NAME, 098 attr(Writers.BitChromosome.LENGTH_NAME) 099 .map(Integer::parseInt), 100 attr(Writers.BitChromosome.ONES_PROBABILITY_NAME) 101 .map(Double::parseDouble), 102 text() 103 ); 104 } 105 106 /** 107 * Read a new {@link io.jenetics.BitChromosome} from the given input 108 * stream. 109 * 110 * @param in the data source of the bit-chromosome 111 * @return the bit-chromosome read from the input stream 112 * @throws XMLStreamException if reading the chromosome fails 113 * @throws NullPointerException if the given input stream is {@code null} 114 */ 115 public static io.jenetics.BitChromosome read(final InputStream in) 116 throws XMLStreamException 117 { 118 try (AutoCloseableXMLStreamReader xml = XML.reader(in)) { 119 xml.next(); 120 return reader().read(xml); 121 } 122 } 123 } 124 125 /** 126 * Reader methods for {@link io.jenetics.CharacterChromosome} objects. 127 * <p> 128 * <b>XML format</b> 129 * <pre> {@code 130 * <character-chromosome length="4"> 131 * <valid-alleles>ABCDEFGHIJKLMNOPQRSTUVWXYZ<valid-alleles> 132 * <alleles>ASDF</alleles> 133 * </character-chromosome> 134 * }</pre> 135 */ 136 public static final class CharacterChromosome { 137 private CharacterChromosome() {} 138 139 /** 140 * Return a XML reader for {@link io.jenetics.CharacterChromosome} 141 * objects. 142 * 143 * @return a chromosome reader 144 */ 145 public static Reader<io.jenetics.CharacterChromosome> reader() { 146 return elem( 147 v -> io.jenetics.CharacterChromosome.of( 148 (String)v[2], (CharSeq)v[1] 149 ), 150 Writers.CharacterChromosome.ROOT_NAME, 151 attr(Writers.CharacterChromosome.LENGTH_NAME) 152 .map(Integer::parseInt), 153 elem(Writers.CharacterChromosome.VALID_ALLELES_NAME, 154 text().map(CharSeq::new)), 155 elem(Writers.CharacterChromosome.ALLELES_NAME, text()) 156 ); 157 } 158 159 /** 160 * Read a new {@link io.jenetics.CharacterChromosome} from the given 161 * input stream. 162 * 163 * @param in the data source of the chromosome 164 * @return the bit-chromosome read from the input stream 165 * @throws XMLStreamException if reading the chromosome fails 166 * @throws NullPointerException if the given input stream is {@code null} 167 */ 168 public static io.jenetics.CharacterChromosome read(final InputStream in) 169 throws XMLStreamException 170 { 171 try (AutoCloseableXMLStreamReader xml = XML.reader(in)) { 172 xml.next(); 173 return reader().read(xml); 174 } 175 } 176 177 } 178 179 /** 180 * Reader methods for {@link io.jenetics.BoundedChromosome} objects. 181 * <p> 182 * <b>XML format</b> 183 * <pre> {@code 184 * <root-name length="3"> 185 * <min>aaa</min> 186 * <max>zzz</max> 187 * <alleles> 188 * <allele>iii</allele> 189 * <allele>fff</allele> 190 * <allele>ggg</allele> 191 * </alleles> 192 * </root-name> 193 * }</pre> 194 */ 195 public static final class BoundedChromosome { 196 private BoundedChromosome() {} 197 198 /** 199 * Create a bounded chromosome reader with the given configuration. 200 * 201 * @param name the root element name 202 * @param gene the gene creator 203 * @param genes the gene array creator 204 * @param chromosome the chromosome creator 205 * @param alleleReader the allele reader 206 * @param <A> the allele type 207 * @param <G> the gene type 208 * @param <C> the chromosome type 209 * @return a bounded chromosome reader 210 * @throws NullPointerException if one of the arguments is {@code null} 211 */ 212 public static < 213 A extends Comparable<? super A>, 214 G extends BoundedGene<A, G>, 215 C extends io.jenetics.BoundedChromosome<A, G> 216 > 217 Reader<C> reader( 218 final String name, 219 final BoundedGeneCreator<A, G> gene, 220 final IntFunction<G[]> genes, 221 final Function<G[], C> chromosome, 222 final Reader<? extends A> alleleReader 223 ) { 224 return elem(v -> { 225 final int length = (int)v[0]; 226 @SuppressWarnings("unchecked") 227 final A min = (A)v[1]; 228 @SuppressWarnings("unchecked") 229 final A max = (A)v[2]; 230 @SuppressWarnings("unchecked") 231 final List<A> alleles = (List<A>)v[3]; 232 233 if (alleles.size() != length) { 234 throw new IllegalArgumentException(format( 235 "Expected %d alleles, but got %d,", 236 length, alleles.size() 237 )); 238 } 239 240 return chromosome.apply( 241 alleles.stream() 242 .map(value -> gene.create(value, min, max)) 243 .toArray(genes) 244 ); 245 }, name, 246 attr(Writers.BoundedChromosome.LENGTH_NAME).map(Integer::parseInt), 247 elem(Writers.BoundedChromosome.MIN_NAME, alleleReader), 248 elem(Writers.BoundedChromosome.MAX_NAME, alleleReader), 249 elem(Writers.BoundedChromosome.ALLELES_NAME, 250 elems(elem(Writers.BoundedChromosome.ALLELE_NAME, alleleReader)) 251 ) 252 ); 253 } 254 255 } 256 257 /** 258 * Reader methods for {@link io.jenetics.IntegerChromosome} objects. 259 * <p> 260 * <b>XML format</b> 261 * <pre> {@code 262 * <int-chromosome length="3"> 263 * <min>-2147483648</min> 264 * <max>2147483647</max> 265 * <alleles> 266 * <allele>-1878762439</allele> 267 * <allele>-957346595</allele> 268 * <allele>-88668137</allele> 269 * </alleles> 270 * </int-chromosome> 271 * }</pre> 272 */ 273 public static final class IntegerChromosome { 274 private IntegerChromosome() {} 275 276 /** 277 * Return the default allele reader for the {@code IntegerChromosome}. 278 * 279 * @return the default allele reader 280 */ 281 public static Reader<Integer> alleleReader() { 282 return text().map(Integer::parseInt); 283 } 284 285 /** 286 * Return a {@link io.jenetics.IntegerChromosome} reader. 287 * 288 * @return a integer chromosome reader 289 */ 290 public static Reader<io.jenetics.IntegerChromosome> reader() { 291 return BoundedChromosome.reader( 292 Writers.IntegerChromosome.ROOT_NAME, 293 IntegerGene::of, 294 IntegerGene[]::new, 295 io.jenetics.IntegerChromosome::of, 296 alleleReader() 297 ); 298 } 299 300 /** 301 * Read a new {@link io.jenetics.IntegerChromosome} from the given 302 * input stream. 303 * 304 * @param in the data source of the chromosome 305 * @return a new chromosome 306 * @throws XMLStreamException if reading the chromosome fails 307 * @throws NullPointerException if the given input stream is {@code null} 308 */ 309 public static io.jenetics.IntegerChromosome read(final InputStream in) 310 throws XMLStreamException 311 { 312 try (AutoCloseableXMLStreamReader reader = XML.reader(in)) { 313 reader.next(); 314 return reader().read(reader); 315 } 316 } 317 318 } 319 320 /** 321 * Reader methods for {@link io.jenetics.LongChromosome} objects. 322 * <p> 323 * <b>XML format</b> 324 * <pre> {@code 325 * <long-chromosome length="3"> 326 * <min>-9223372036854775808</min> 327 * <max>9223372036854775807</max> 328 * <alleles> 329 * <allele>-1345217698116542402</allele> 330 * <allele>-7144755673073475303</allele> 331 * <allele>6053786736809578435</allele> 332 * </alleles> 333 * </long-chromosome> 334 * }</pre> 335 */ 336 public static final class LongChromosome { 337 private LongChromosome() {} 338 339 /** 340 * Return the default allele reader for the {@code LongChromosome}. 341 * 342 * @return the default allele reader 343 */ 344 public static Reader<Long> alleleReader() { 345 return text().map(Long::parseLong); 346 } 347 348 /** 349 * Return a {@link io.jenetics.LongChromosome} reader. 350 * 351 * @return a long chromosome reader 352 */ 353 public static Reader<io.jenetics.LongChromosome> reader() { 354 return BoundedChromosome.reader( 355 Writers.LongChromosome.ROOT_NAME, 356 LongGene::of, 357 LongGene[]::new, 358 io.jenetics.LongChromosome::of, 359 alleleReader() 360 ); 361 } 362 363 /** 364 * Read a new {@link io.jenetics.LongChromosome} from the given 365 * input stream. 366 * 367 * @param in the data source of the chromosome 368 * @return a new chromosome 369 * @throws XMLStreamException if reading the chromosome fails 370 * @throws NullPointerException if the given input stream is {@code null} 371 */ 372 public static io.jenetics.LongChromosome read(final InputStream in) 373 throws XMLStreamException 374 { 375 try (AutoCloseableXMLStreamReader reader = XML.reader(in)) { 376 reader.next(); 377 return reader().read(reader); 378 } 379 } 380 381 } 382 383 /** 384 * Reader methods for {@link io.jenetics.DoubleChromosome} objects. 385 * <p> 386 * <b>XML format</b> 387 * <pre> {@code 388 * <double-chromosome length="3"> 389 * <min>0.0</min> 390 * <max>1.0</max> 391 * <alleles> 392 * <allele>0.27251556008507416</allele> 393 * <allele>0.003140816229067145</allele> 394 * <allele>0.43947528327497376</allele> 395 * </alleles> 396 * </double-chromosome> 397 * }</pre> 398 */ 399 public static final class DoubleChromosome { 400 private DoubleChromosome() {} 401 402 /** 403 * Return the default allele reader for the {@code DoubleChromosome}. 404 * 405 * @return the default allele reader 406 */ 407 public static Reader<Double> alleleReader() { 408 return text().map(Double::parseDouble); 409 } 410 411 /** 412 * Return a {@link io.jenetics.DoubleChromosome} reader. 413 * 414 * @return a double chromosome reader 415 */ 416 public static Reader<io.jenetics.DoubleChromosome> reader() { 417 return BoundedChromosome.reader( 418 Writers.DoubleChromosome.ROOT_NAME, 419 DoubleGene::of, 420 DoubleGene[]::new, 421 io.jenetics.DoubleChromosome::of, 422 alleleReader() 423 ); 424 } 425 426 /** 427 * Read a new {@link io.jenetics.DoubleChromosome} from the given 428 * input stream. 429 * 430 * @param in the data source of the chromosome 431 * @return a new chromosome 432 * @throws XMLStreamException if reading the chromosome fails 433 * @throws NullPointerException if the given input stream is {@code null} 434 */ 435 public static io.jenetics.DoubleChromosome read(final InputStream in) 436 throws XMLStreamException 437 { 438 try (AutoCloseableXMLStreamReader reader = XML.reader(in)) { 439 reader.next(); 440 return reader().read(reader); 441 } 442 } 443 444 } 445 446 /** 447 * Reader methods for {@link io.jenetics.PermutationChromosome} objects. 448 * <p> 449 * <b>XML format</b> 450 * <pre> {@code 451 * <permutation-chromosome length="5"> 452 * <valid-alleles type="java.lang.Integer"> 453 * <allele>0</allele> 454 * <allele>1</allele> 455 * <allele>2</allele> 456 * <allele>3</allele> 457 * <allele>4</allele> 458 * </valid-alleles> 459 * <order>2 1 3 5 4</order> 460 * </permutation-chromosome> 461 * }</pre> 462 */ 463 public static final class PermutationChromosome { 464 private PermutationChromosome() {} 465 466 /** 467 * Return a reader for permutation chromosomes with the given allele 468 * reader. 469 * 470 * @param alleleReader the allele reader 471 * @param <A> the allele type 472 * @return a permutation chromosome reader 473 * @throws NullPointerException if the given allele reader is 474 * {@code null} 475 */ 476 public static <A> Reader<io.jenetics.PermutationChromosome<A>> 477 reader(final Reader<? extends A> alleleReader) { 478 requireNonNull(alleleReader); 479 480 return elem(v -> { 481 final int length = (int)v[0]; 482 @SuppressWarnings("unchecked") 483 final ISeq<A> validAlleles = ISeq.of((List<A>)v[1]); 484 485 final int[] order = Stream.of(((String) v[2]).split("\\s")) 486 .mapToInt(Integer::parseInt) 487 .toArray(); 488 489 final MSeq<EnumGene<A>> alleles = MSeq.ofLength(length); 490 for (int i = 0; i < length; ++i) { 491 final EnumGene<A> gene = EnumGene.of(order[i], validAlleles); 492 alleles.set(i, gene); 493 } 494 495 return new io.jenetics.PermutationChromosome<A>(alleles.toISeq()); 496 }, 497 Writers.PermutationChromosome.ROOT_NAME, 498 attr(Writers.PermutationChromosome.LENGTH_NAME).map(Integer::parseInt), 499 elem(Writers.PermutationChromosome.VALID_ALLELES_NAME, 500 elems(elem(Writers.PermutationChromosome.ALLELE_NAME, alleleReader)) 501 ), 502 elem(Writers.PermutationChromosome.ORDER_NAME, text()) 503 ); 504 } 505 506 /** 507 * Reads a new {@link io.jenetics.PermutationChromosome} from the given 508 * input stream. 509 * 510 * @param <A> the allele type 511 * @param in the data source of the chromosome 512 * @param alleleReader the allele reader 513 * @return a new permutation chromosome 514 * @throws XMLStreamException if reading the chromosome fails 515 * @throws NullPointerException if one of the arguments is {@code null} 516 */ 517 public static <A> io.jenetics.PermutationChromosome<A> 518 read(final InputStream in, final Reader<? extends A> alleleReader) 519 throws XMLStreamException 520 { 521 requireNonNull(alleleReader); 522 requireNonNull(in); 523 524 try (AutoCloseableXMLStreamReader xml = XML.reader(in)) { 525 xml.next(); 526 return PermutationChromosome.<A>reader(alleleReader).read(xml); 527 } 528 } 529 530 } 531 532 /** 533 * Writer methods for {@link io.jenetics.Genotype} objects. 534 * <p> 535 * <b>XML format</b> 536 * <pre> {@code 537 * <genotype length="2" ngenes="5"> 538 * <double-chromosome length="3"> 539 * <min>0.0</min> 540 * <max>1.0</max> 541 * <alleles> 542 * <allele>0.27251556008507416</allele> 543 * <allele>0.003140816229067145</allele> 544 * <allele>0.43947528327497376</allele> 545 * </alleles> 546 * </double-chromosome> 547 * <double-chromosome length="2"> 548 * <min>0.0</min> 549 * <max>1.0</max> 550 * <alleles> 551 * <allele>0.4026521545744768</allele> 552 * <allele>0.36137605952663554</allele> 553 * <alleles> 554 * </double-chromosome> 555 * </genotype> 556 * }</pre> 557 */ 558 public static final class Genotype { 559 private Genotype() {} 560 561 /** 562 * Create a genotype reader with he given chromosome reader. 563 * 564 * @param chromosomeReader the underlying chromosome reader 565 * @param <A> the allele type 566 * @param <G> the gene type 567 * @param <C> the chromosome type 568 * @return a genotype reader with he given chromosome reader 569 * @throws NullPointerException if the given {@code chromosomeReader} is 570 * {@code null} 571 */ 572 public static < 573 A, 574 G extends Gene<A, G>, 575 C extends Chromosome<G> 576 > 577 Reader<io.jenetics.Genotype<G>> 578 reader(final Reader<? extends C> chromosomeReader) { 579 requireNonNull(chromosomeReader); 580 581 return elem(v -> { 582 @SuppressWarnings("unchecked") 583 final List<C> chromosomes = (List<C>)v[2]; 584 final io.jenetics.Genotype<G> genotype = 585 io.jenetics.Genotype.of(chromosomes); 586 587 final int length = (int)v[0]; 588 final int ngenes = (int)v[1]; 589 if (length != genotype.length()) { 590 throw new IllegalArgumentException(format( 591 "Expected %d chromosome, but read %d.", 592 length, genotype.length() 593 )); 594 } 595 if (ngenes != genotype.geneCount()) { 596 throw new IllegalArgumentException(format( 597 "Expected %d genes, but read %d.", 598 ngenes, genotype.geneCount() 599 )); 600 } 601 602 return genotype; 603 }, 604 Writers.Genotype.ROOT_NAME, 605 attr(Writers.Genotype.LENGTH_NAME).map(Integer::parseInt), 606 attr(Writers.Genotype.NGENES_NAME).map(Integer::parseInt), 607 elems(chromosomeReader) 608 ); 609 } 610 611 /** 612 * Reads a genotype by using the given chromosome reader. 613 * 614 * @param <A> the allele type 615 * @param <G> the gene type 616 * @param <C> the chromosome type 617 * @param in the input stream to read the genotype from 618 * @param chromosomeReader the used chromosome reader 619 * @return a genotype by using the given chromosome reader 620 * @throws XMLStreamException if reading the genotype fails 621 * @throws NullPointerException if one of the arguments is {@code null} 622 */ 623 public static < 624 A, 625 G extends Gene<A, G>, 626 C extends Chromosome<G> 627 > 628 io.jenetics.Genotype<G> 629 read(final InputStream in, final Reader<? extends C> chromosomeReader) 630 throws XMLStreamException 631 { 632 requireNonNull(chromosomeReader); 633 requireNonNull(in); 634 635 try (AutoCloseableXMLStreamReader xml = XML.reader(in)) { 636 xml.next(); 637 return reader(chromosomeReader).read(xml); 638 } 639 } 640 } 641 642 /** 643 * This class contains static reader methods for 644 * {@link io.jenetics.Genotype} objects. 645 * <p> 646 * <b>XML format</b> 647 * <pre> {@code 648 * <genotypes length="1"> 649 * <genotype length="2" ngenes="5"> 650 * <double-chromosome length="3"> 651 * <min>0.0</min> 652 * <max>1.0</max> 653 * <alleles> 654 * <allele>0.27251556008507416</allele> 655 * <allele>0.003140816229067145</allele> 656 * <allele>0.43947528327497376</allele> 657 * </alleles> 658 * </double-chromosome> 659 * <double-chromosome length="2"> 660 * <min>0.0</min> 661 * <max>1.0</max> 662 * <alleles> 663 * <allele>0.4026521545744768</allele> 664 * <allele>0.36137605952663554</allele> 665 * <alleles> 666 * </double-chromosome> 667 * </genotype> 668 * </genotypes> 669 * }</pre> 670 */ 671 public static final class Genotypes { 672 private Genotypes() {} 673 674 /** 675 * Return a genotypes reader using the given chromosome reader. 676 * 677 * @param chromosomeReader the underlying chromosome reader 678 * @param <A> the allele type 679 * @param <G> the gene type 680 * @param <C> the chromosome type 681 * @return a genotypes reader using the given chromosome reader 682 * @throws NullPointerException if the given {@code chromosomeReader} is 683 * {@code null} 684 */ 685 @SuppressWarnings("unchecked") 686 public static < 687 A, 688 G extends Gene<A, G>, 689 C extends Chromosome<G> 690 > 691 Reader<List<io.jenetics.Genotype<G>>> 692 reader(final Reader<C> chromosomeReader) { 693 return elem( 694 p -> (List<io.jenetics.Genotype<G>>)p[0], 695 Writers.Genotypes.ROOT_NAME, 696 elems(Genotype.reader(chromosomeReader)) 697 ); 698 } 699 700 /** 701 * Reads the genotypes by using the given chromosome reader. 702 * 703 * @param <A> the allele type 704 * @param <G> the gene type 705 * @param <C> the chromosome type 706 * @param in the input stream to read the genotype from 707 * @param chromosomeReader the used chromosome reader 708 * @return a genotype by using the given chromosome reader 709 * @throws XMLStreamException if reading the genotype fails 710 * @throws NullPointerException if one of the arguments is {@code null} 711 */ 712 public static < 713 A, 714 G extends Gene<A, G>, 715 C extends Chromosome<G> 716 > 717 List<io.jenetics.Genotype<G>> 718 read(final InputStream in, final Reader<? extends C> chromosomeReader) 719 throws XMLStreamException 720 { 721 requireNonNull(chromosomeReader); 722 requireNonNull(in); 723 724 try (AutoCloseableXMLStreamReader xml = XML.reader(in)) { 725 xml.next(); 726 return reader(chromosomeReader).read(xml); 727 } 728 } 729 730 } 731 732 /** 733 * Reads the genotypes by using the given chromosome reader. 734 * 735 * @see Genotypes#read(InputStream, Reader) 736 * 737 * @param <A> the allele type 738 * @param <G> the gene type 739 * @param <C> the chromosome type 740 * @param in the input stream to read the genotype from 741 * @param chromosomeReader the used chromosome reader 742 * @return a genotype by using the given chromosome reader 743 * @throws XMLStreamException if reading the genotype fails 744 * @throws NullPointerException if one of the arguments is {@code null} 745 */ 746 public static < 747 A, 748 G extends Gene<A, G>, 749 C extends Chromosome<G> 750 > 751 List<io.jenetics.Genotype<G>> 752 read(final InputStream in, final Reader<? extends C> chromosomeReader) 753 throws XMLStreamException 754 { 755 return Genotypes.read(in, chromosomeReader); 756 } 757 758}