CSVReader.java

  1. package com.opencsv;

  2. /*
  3.  Copyright 2005 Bytecode Pty Ltd.

  4.  Licensed under the Apache License, Version 2.0 (the "License");
  5.  you may not use this file except in compliance with the License.
  6.  You may obtain a copy of the License at

  7.  http://www.apache.org/licenses/LICENSE-2.0

  8.  Unless required by applicable law or agreed to in writing, software
  9.  distributed under the License is distributed on an "AS IS" BASIS,
  10.  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11.  See the License for the specific language governing permissions and
  12.  limitations under the License.
  13.  */

  14. import com.opencsv.bean.util.OrderedObject;
  15. import com.opencsv.exceptions.*;
  16. import com.opencsv.processor.RowProcessor;
  17. import com.opencsv.stream.reader.LineReader;
  18. import com.opencsv.validators.LineValidatorAggregator;
  19. import com.opencsv.validators.RowValidatorAggregator;
  20. import org.apache.commons.lang3.ObjectUtils;
  21. import org.apache.commons.lang3.StringUtils;

  22. import java.io.*;
  23. import java.nio.charset.CharacterCodingException;
  24. import java.nio.charset.MalformedInputException;
  25. import java.util.*;
  26. import java.util.zip.ZipException;

  27. /**
  28.  * A very simple CSV reader released under a commercial-friendly license.
  29.  *
  30.  * @author Glen Smith
  31.  */
  32. public class CSVReader implements Closeable, Iterable<String[]> {

  33.     public static final boolean DEFAULT_KEEP_CR = false;
  34.     public static final boolean DEFAULT_VERIFY_READER = true;
  35.     // context size in the exception message
  36.     static final int CONTEXT_MULTILINE_EXCEPTION_MESSAGE_SIZE = 100;

  37.     /**
  38.      * The default line to start reading.
  39.      */
  40.     public static final int DEFAULT_SKIP_LINES = 0;

  41.     /**
  42.      * The default limit for the number of lines in a multiline record.
  43.      * Less than one means no limit.
  44.      */
  45.     public static final int DEFAULT_MULTILINE_LIMIT = 0;

  46.     protected static final List<Class<? extends IOException>> PASSTHROUGH_EXCEPTIONS =
  47.             Collections.unmodifiableList(
  48.                     Arrays.asList(CharacterCodingException.class, CharConversionException.class,
  49.                             UnsupportedEncodingException.class, UTFDataFormatException.class,
  50.                             ZipException.class, FileNotFoundException.class, MalformedInputException.class));

  51.     public static final int READ_AHEAD_LIMIT = Character.SIZE / Byte.SIZE;
  52.     private static final int MAX_WIDTH = 100;
  53.     protected ICSVParser parser;
  54.     protected int skipLines;
  55.     protected BufferedReader br;
  56.     protected LineReader lineReader;
  57.     protected boolean hasNext = true;
  58.     protected boolean linesSkipped;
  59.     protected boolean keepCR;
  60.     protected boolean verifyReader;
  61.     protected int multilineLimit = DEFAULT_MULTILINE_LIMIT;
  62.     protected Locale errorLocale;

  63.     protected long linesRead = 0;
  64.     protected long recordsRead = 0;
  65.     protected String[] peekedLine = null;
  66.     final protected Queue<OrderedObject<String>> peekedLines = new LinkedList<>();

  67.     private final LineValidatorAggregator lineValidatorAggregator;
  68.     private final RowValidatorAggregator rowValidatorAggregator;
  69.     private final RowProcessor rowProcessor;

  70.     /**
  71.      * Constructs CSVReader using defaults for all parameters.
  72.      *
  73.      * @param reader The reader to an underlying CSV source.
  74.      */
  75.     public CSVReader(Reader reader) {
  76.         this(reader, DEFAULT_SKIP_LINES,
  77.                 new CSVParser(ICSVParser.DEFAULT_SEPARATOR,
  78.                         ICSVParser.DEFAULT_QUOTE_CHARACTER,
  79.                         ICSVParser.DEFAULT_ESCAPE_CHARACTER,
  80.                         ICSVParser.DEFAULT_STRICT_QUOTES,
  81.                         ICSVParser.DEFAULT_IGNORE_LEADING_WHITESPACE,
  82.                         ICSVParser.DEFAULT_IGNORE_QUOTATIONS,
  83.                         ICSVParser.DEFAULT_NULL_FIELD_INDICATOR,
  84.                         Locale.getDefault()),
  85.                 DEFAULT_KEEP_CR,
  86.                 DEFAULT_VERIFY_READER,
  87.                 DEFAULT_MULTILINE_LIMIT,
  88.                 Locale.getDefault(),
  89.                 new LineValidatorAggregator(),
  90.                 new RowValidatorAggregator(),
  91.                 null);
  92.     }

  93.     /**
  94.      * Constructs CSVReader with supplied CSVParser.
  95.      * <p>This constructor sets all necessary parameters for CSVReader, and
  96.      * intentionally has package access so only the builder can use it.</p>
  97.      *
  98.      * @param reader                  The reader to an underlying CSV source
  99.      * @param line                    The number of lines to skip before reading
  100.      * @param icsvParser              The parser to use to parse input
  101.      * @param keepCR                  True to keep carriage returns in data read, false otherwise
  102.      * @param verifyReader            True to verify reader before each read, false otherwise
  103.      * @param multilineLimit          Allow the user to define the limit to the number of lines in a multiline record. Less than one means no limit.
  104.      * @param errorLocale             Set the locale for error messages. If null, the default locale is used.
  105.      * @param lineValidatorAggregator contains all the custom defined line validators.
  106.      * @param rowValidatorAggregator  contains all the custom defined row validators.
  107.      * @param rowProcessor            Custom row processor to run on all columns on a csv record.
  108.      */
  109.     CSVReader(Reader reader, int line, ICSVParser icsvParser, boolean keepCR, boolean verifyReader, int multilineLimit,
  110.               Locale errorLocale, LineValidatorAggregator lineValidatorAggregator, RowValidatorAggregator rowValidatorAggregator,
  111.               RowProcessor rowProcessor) {
  112.         this.br =
  113.                 (reader instanceof BufferedReader ?
  114.                         (BufferedReader) reader :
  115.                         new BufferedReader(reader));
  116.         this.lineReader = new LineReader(br, keepCR);
  117.         this.skipLines = line;
  118.         this.parser = icsvParser;
  119.         this.keepCR = keepCR;
  120.         this.verifyReader = verifyReader;
  121.         this.multilineLimit = multilineLimit;
  122.         this.errorLocale = ObjectUtils.defaultIfNull(errorLocale, Locale.getDefault());
  123.         this.lineValidatorAggregator = lineValidatorAggregator;
  124.         this.rowValidatorAggregator = rowValidatorAggregator;
  125.         this.rowProcessor = rowProcessor;
  126.     }

  127.     /**
  128.      * @return The CSVParser used by the reader.
  129.      */
  130.     public ICSVParser getParser() {
  131.         return parser;
  132.     }

  133.     /**
  134.      * Returns the number of lines in the CSV file to skip before processing.
  135.      * This is useful when there are miscellaneous data at the beginning of a file.
  136.      *
  137.      * @return The number of lines in the CSV file to skip before processing.
  138.      */
  139.     public int getSkipLines() {
  140.         return skipLines;
  141.     }

  142.     /**
  143.      * Returns if the reader will keep carriage returns found in data or remove them.
  144.      *
  145.      * @return True if reader will keep carriage returns, false otherwise.
  146.      */
  147.     public boolean keepCarriageReturns() {
  148.         return keepCR;
  149.     }

  150.     /**
  151.      * Reads the entire file into a List with each element being a String[] of
  152.      * tokens.
  153.      * Since the current implementation returns a {@link java.util.LinkedList},
  154.      * you are strongly discouraged from using index-based access methods to
  155.      * get at items in the list. Instead, iterate over the list.
  156.      *
  157.      * @return A List of String[], with each String[] representing a line of the
  158.      * file.
  159.      * @throws IOException  If bad things happen during the read
  160.      * @throws CsvException If there is a failed validator
  161.      */
  162.     public List<String[]> readAll() throws IOException, CsvException {

  163.         List<String[]> allElements = new LinkedList<>();
  164.         while (hasNext) {
  165.             String[] nextLineAsTokens = readNext();
  166.             if (nextLineAsTokens != null) {
  167.                 allElements.add(nextLineAsTokens);
  168.             }
  169.         }
  170.         return allElements;

  171.     }

  172.     /**
  173.      * Reads the next line from the buffer and converts to a string array.
  174.      *
  175.      * @return A string array with each comma-separated element as a separate
  176.      * entry, or null if there is no more input.
  177.      * @throws IOException            If bad things happen during the read
  178.      * @throws CsvValidationException If a user-defined validator fails
  179.      */
  180.     public String[] readNext() throws IOException, CsvValidationException {
  181.         return flexibleRead(true, true);
  182.     }

  183.     /**
  184.      * Reads the next line from the buffer and converts to a string array without
  185.      * running the custom defined validators.  This is called by the bean readers when
  186.      * reading the header.
  187.      *
  188.      * @return A string array with each comma-separated element as a separate
  189.      * entry, or null if there is no more input.
  190.      * @throws IOException If bad things happen during the read.
  191.      */
  192.     public String[] readNextSilently() throws IOException {
  193.         try {
  194.             return flexibleRead(true, false);
  195.         } catch (CsvValidationException e) {
  196.             throw new CsvRuntimeException("A CSValidationException was thrown from the runNextSilently method which should not happen", e);
  197.         }
  198.     }

  199.     /**
  200.      * Reads the next line from the buffer and converts to a string array.
  201.      * The results are stored in {@link #peekedLines} and {@link #peekedLine}.
  202.      *
  203.      * @throws IOException If bad things happen during the read
  204.      */
  205.     private void primeNextRecord() throws IOException {

  206.         int linesInThisRecord = 0;
  207.         long lastSuccessfulLineRead = linesRead+1;
  208.         do {
  209.             String nextLine = getNextLine();
  210.             peekedLines.add(new OrderedObject<>(lastSuccessfulLineRead, nextLine));
  211.             linesInThisRecord++;

  212.             // If no more input is available, check if the record is finished
  213.             // or simply incomplete.
  214.             if (!hasNext) {
  215.                 if (parser.isPending()) {
  216.                     throw new CsvMalformedLineException(String.format(
  217.                             ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale).getString("unterminated.quote"),
  218.                             StringUtils.abbreviate(parser.getPendingText(), MAX_WIDTH)), lastSuccessfulLineRead, parser.getPendingText());
  219.                 }
  220.                 return;
  221.             }


  222.             // If we've crossed the multiline limit, signal an error.
  223.             if (multilineLimit > 0 && linesInThisRecord > multilineLimit) {

  224.                 // get current row records Read +1
  225.                 long row = this.recordsRead + 1L;

  226.                 String context = parser.getPendingText();

  227.                 // just to avoid out of index
  228.                 // to get the whole context use CsvMultilineLimitBrokenException::getContext()
  229.                 if (context.length() > CONTEXT_MULTILINE_EXCEPTION_MESSAGE_SIZE) {
  230.                     context = context.substring(0, CONTEXT_MULTILINE_EXCEPTION_MESSAGE_SIZE);
  231.                 }

  232.                 String messageFormat = ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale).getString("multiline.limit.broken");
  233.                 String message = String.format(errorLocale, messageFormat, multilineLimit, row, context);
  234.                 throw new CsvMultilineLimitBrokenException(message, row, parser.getPendingText(), multilineLimit);
  235.             }

  236.             // Combine multiple lines into one result
  237.             String[] r = parser.parseLineMulti(nextLine);
  238.             if (r.length > 0) {
  239.                 if (peekedLine == null) {
  240.                     peekedLine = r;
  241.                 } else {
  242.                     peekedLine = combineResultsFromMultipleReads(peekedLine, r);
  243.                 }
  244.             }

  245.         } while (parser.isPending());

  246.         /*
  247.          for bug #233 (https://sourceforge.net/p/opencsv/bugs/233/) if we want to keep carriage returns we ONLY
  248.          want to keep the carriage returns in the data and not from the end of lines if we were in a Windows system.
  249.          */

  250.         if (keepCR) {
  251.             int lastItemIndex = peekedLine.length - 1;
  252.             if (peekedLine[lastItemIndex] != null && peekedLine[lastItemIndex].endsWith("\r")) {
  253.                 peekedLine[lastItemIndex] = peekedLine[lastItemIndex].substring(0, peekedLine[lastItemIndex].length() - 1);
  254.             }
  255.         }
  256.     }

  257.     /**
  258.      * Runs all line validators on the input.
  259.      *
  260.      * @param lastSuccessfulLineRead The line number for error messages
  261.      * @param nextLine The input to be validated
  262.      * @throws CsvValidationException Only thrown if a user-supplied validator
  263.      *   throws it
  264.      */
  265.     private void validateLine(long lastSuccessfulLineRead, String nextLine) throws CsvValidationException {
  266.         try {
  267.             lineValidatorAggregator.validate(nextLine);
  268.         } catch (CsvValidationException cve) {
  269.             cve.setLineNumber(lastSuccessfulLineRead);
  270.             throw cve;
  271.         }
  272.     }

  273.     /**
  274.      * Increments the number of records read if the result passed in is not null.
  275.      *
  276.      * @param result           The result of the read operation
  277.      * @param lineStartOfRow   Line number that the row started on
  278.      * @throws CsvValidationException if there is a validation error caught by a custom RowValidator.
  279.      */
  280.     protected void validateResult(String[] result, long lineStartOfRow) throws CsvValidationException {
  281.         if (result != null) {
  282.             if (rowProcessor != null) {
  283.                 rowProcessor.processRow(result);
  284.             }
  285.             try {
  286.                 rowValidatorAggregator.validate(result);
  287.             } catch (CsvValidationException cve) {
  288.                 cve.setLineNumber(lineStartOfRow);
  289.                 throw cve;
  290.             }
  291.         }
  292.     }

  293.     /**
  294.      * For multi-line records this method combines the current result with the result from previous read(s).
  295.      *
  296.      * @param buffer   Previous data read for this record
  297.      * @param lastRead Latest data read for this record.
  298.      * @return String array with union of the buffer and lastRead arrays.
  299.      */
  300.     protected String[] combineResultsFromMultipleReads(String[] buffer, String[] lastRead) {
  301.         String[] t = new String[buffer.length + lastRead.length];
  302.         System.arraycopy(buffer, 0, t, 0, buffer.length);
  303.         System.arraycopy(lastRead, 0, t, buffer.length, lastRead.length);
  304.         return t;
  305.     }

  306.     /**
  307.      * Reads the next line from the file.
  308.      *
  309.      * @return The next line from the file without trailing newline, or null if
  310.      * there is no more input.
  311.      * @throws IOException If bad things happen during the read
  312.      */
  313.     protected String getNextLine() throws IOException {
  314.         if (isClosed()) {
  315.             hasNext = false;
  316.             return null;
  317.         }

  318.         if (!this.linesSkipped) {
  319.             for (int i = 0; i < skipLines; i++) {
  320.                 lineReader.readLine();
  321.                 linesRead++;
  322.             }
  323.             this.linesSkipped = true;
  324.         }
  325.         String nextLine = lineReader.readLine();
  326.         if (nextLine == null) {
  327.             hasNext = false;
  328.         } else {
  329.             linesRead++;
  330.         }

  331.         return hasNext ? nextLine : null;
  332.     }

  333.     /**
  334.      * Only useful for tests.
  335.      *
  336.      * @return The maximum number of lines allowed in a multiline record.
  337.      */
  338.     public int getMultilineLimit() {
  339.         return multilineLimit;
  340.     }

  341.     /**
  342.      * Checks to see if the file is closed.
  343.      * <p>Certain {@link IOException}s will be passed out, as they are
  344.      * indicative of a real problem, not that the file has already been closed.
  345.      * These exceptions are:<ul>
  346.      *     <li>CharacterCodingException</li>
  347.      *     <li>CharConversionException</li>
  348.      *     <li>FileNotFoundException</li>
  349.      *     <li>UnsupportedEncodingException</li>
  350.      *     <li>UTFDataFormatException</li>
  351.      *     <li>ZipException</li>
  352.      *     <li>MalformedInputException</li>
  353.      * </ul></p>
  354.      *
  355.      * @return {@code true} if the reader can no longer be read from
  356.      * @throws IOException If {@link #verifyReader()} was set to {@code true}
  357.      *   certain {@link IOException}s will still be passed out as they are
  358.      *   indicative of a problem, not end of file.
  359.      */
  360.     protected boolean isClosed() throws IOException {
  361.         if (!verifyReader) {
  362.             return false;
  363.         }
  364.         try {
  365.             br.mark(READ_AHEAD_LIMIT);
  366.             int nextByte = br.read();
  367.             br.reset(); // resets stream position, possible because its buffered
  368.             return nextByte == -1; // read() returns -1 at end of stream
  369.         } catch (IOException e) {
  370.             if (PASSTHROUGH_EXCEPTIONS.contains(e.getClass())) {
  371.                 throw e;
  372.             }

  373.             return true;
  374.         }
  375.     }

  376.     /**
  377.      * Closes the underlying reader.
  378.      *
  379.      * @throws IOException If the close fails
  380.      */
  381.     @Override
  382.     public void close() throws IOException {
  383.         br.close();
  384.     }

  385.     /**
  386.      * Creates an Iterator for processing the CSV data.
  387.      *
  388.      * @return A String[] iterator.
  389.      */
  390.     @Override
  391.     public Iterator<String[]> iterator() {
  392.         try {
  393.             CSVIterator it = new CSVIterator(this);
  394.             it.setErrorLocale(errorLocale);
  395.             return it;
  396.         } catch (IOException | CsvValidationException e) {
  397.             throw new RuntimeException(e);
  398.         }
  399.     }

  400.     /**
  401.      * Returns if the CSVReader will verify the reader before each read.
  402.      * <p>
  403.      * By default the value is true, which is the functionality for version 3.0.
  404.      * If set to false the reader is always assumed ready to read - this is the functionality
  405.      * for version 2.4 and before.
  406.      * </p>
  407.      * <p>
  408.      * The reason this method was needed was that certain types of readers would return
  409.      * false for their ready() methods until a read was done (namely readers created using Channels).
  410.      * This caused opencsv not to read from those readers.
  411.      * </p>
  412.      *
  413.      * @return True if CSVReader will verify the reader before reads.  False otherwise.
  414.      * @see <a href="https://sourceforge.net/p/opencsv/bugs/108/">Bug 108</a>
  415.      * @since 3.3
  416.      */
  417.     public boolean verifyReader() {
  418.         return this.verifyReader;
  419.     }

  420.     /**
  421.      * This method returns the number of lines that
  422.      * has been read from the reader passed into the CSVReader.
  423.      * <p>
  424.      * Given the following data:</p>
  425.      * <pre>
  426.      * First line in the file
  427.      * some other descriptive line
  428.      * a,b,c
  429.      *
  430.      * a,"b\nb",c
  431.      * </pre>
  432.      * <p>
  433.      * With a CSVReader constructed like so:<br>
  434.      * <code>
  435.      * CSVReader c = builder.withCSVParser(new CSVParser())<br>
  436.      * .withSkipLines(2)<br>
  437.      * .build();<br>
  438.      * </code><br>
  439.      * The initial call to getLinesRead() will be 0. After the first call to
  440.      * readNext() then getLinesRead() will return 3 (because the header was read).
  441.      * After the second call to read the blank line then getLinesRead() will
  442.      * return 4 (still a read). After the third call to readNext(), getLinesRead()
  443.      * will return 6 because it took two line reads to retrieve this record.
  444.      * Subsequent calls to readNext() (since we are out of data) will not
  445.      * increment the number of lines read.</p>
  446.      *
  447.      * @return The number of lines read by the reader (including skipped lines).
  448.      * @since 3.6
  449.      */
  450.     public long getLinesRead() {
  451.         return linesRead;
  452.     }

  453.     /**
  454.      * Used for debugging purposes, this method returns the number of records
  455.      * that has been read from the CSVReader.
  456.      * <p>
  457.      * Given the following data:</p>
  458.      * <pre>
  459.      * First line in the file
  460.      * some other descriptive line
  461.      * a,b,c
  462.      * a,"b\nb",c
  463.      * </pre><p>
  464.      * With a CSVReader constructed like so:<br>
  465.      * <code>
  466.      * CSVReader c = builder.withCSVParser(new CSVParser())<br>
  467.      * .withSkipLines(2)<br>
  468.      * .build();<br>
  469.      * </code><br>
  470.      * The initial call to getRecordsRead() will be 0. After the first call to
  471.      * readNext() then getRecordsRead() will return 1. After the second call to
  472.      * read the blank line then getRecordsRead() will return 2 (a blank line is
  473.      * considered a record with one empty field). After third call to readNext()
  474.      * getRecordsRead() will return 3 because even though it reads to retrieve
  475.      * this record, it is still a single record read. Subsequent calls to
  476.      * readNext() (since we are out of data) will not increment the number of
  477.      * records read.
  478.      * </p>
  479.      * <p>
  480.      * An example of this is in the linesAndRecordsRead() test in CSVReaderTest.
  481.      * </p>
  482.      *
  483.      * @return The number of records (array of Strings[]) read by the reader.
  484.      * @see <a href="https://sourceforge.net/p/opencsv/feature-requests/73/">Feature Request 73</a>
  485.      * @since 3.6
  486.      */
  487.     public long getRecordsRead() {
  488.         return recordsRead;
  489.     }

  490.     /**
  491.      * Skip a given number of lines.
  492.      *
  493.      * @param numberOfLinesToSkip The number of lines to skip
  494.      * @throws IOException If anything bad happens when reading the file
  495.      * @since 4.2
  496.      */
  497.     public void skip(int numberOfLinesToSkip) throws IOException {
  498.         for (int j = 0; j < numberOfLinesToSkip; j++) {
  499.             readNextSilently();
  500.         }
  501.     }

  502.     /**
  503.      * Sets the locale for all error messages.
  504.      *
  505.      * @param errorLocale Locale for error messages. If null, the default locale
  506.      *                    is used.
  507.      * @since 4.2
  508.      */
  509.     public void setErrorLocale(Locale errorLocale) {
  510.         this.errorLocale = ObjectUtils.defaultIfNull(errorLocale, Locale.getDefault());
  511.         if (parser != null) {
  512.             parser.setErrorLocale(this.errorLocale);
  513.         }
  514.     }

  515.     /**
  516.      * Returns the next line from the input without removing it from the
  517.      * CSVReader and not running any validators.
  518.      * Subsequent calls to this method will continue to return the same line
  519.      * until a call is made to {@link #readNext()} or any other method that
  520.      * advances the cursor position in the input. The first call to
  521.      * {@link #readNext()} after calling this method will return the same line
  522.      * this method does.
  523.      *
  524.      * @return The next line from the input, or {@code null} if there are no
  525.      *   more lines
  526.      * @throws IOException If bad things happen during the read operation
  527.      * @since 4.2
  528.      */
  529.     public String[] peek() throws IOException {
  530.         String[] result = null;
  531.         try {
  532.             result = flexibleRead(false, false);
  533.         } catch (CsvValidationException e) {
  534.             // Do nothing. We asked for no validation, so it can't really happen.
  535.         }
  536.         return result;
  537.     }

  538.     /**
  539.      * Reads a line of input, popping or validating as desired.
  540.      *
  541.      * @param popLine Whether the line returned should be popped off the queue
  542.      *                of input. If this is {@code true}, this method consumes
  543.      *                the line and further calls will return the next line of
  544.      *                input. If {@code false}, the line returned stays in the
  545.      *                queue and further calls to this method will return the
  546.      *                same line again.
  547.      * @param validate Whether all user-supplied validators should be run.
  548.      * @return The next line of input
  549.      * @throws IOException If this exception is thrown while reading
  550.      * @throws CsvValidationException If a user-supplied validator throws it
  551.      */
  552.     private String[] flexibleRead(boolean popLine, boolean validate) throws IOException, CsvValidationException {

  553.         if(peekedLines.isEmpty()) {
  554.             primeNextRecord();
  555.         }

  556.         if(validate) {
  557.             for(OrderedObject<String> orderedObject : peekedLines) {
  558.                 validateLine(orderedObject.getOrdinal(), orderedObject.getElement());
  559.             }
  560.             validateResult(peekedLine, linesRead);
  561.         }

  562.         String[] result = peekedLine;

  563.         if(popLine) {
  564.             peekedLines.clear();
  565.             peekedLine = null;
  566.             if(result != null) {
  567.                 recordsRead++;
  568.             }
  569.         }

  570.         return result;
  571.     }
  572. }