View Javadoc
1   /*
2    * CSVeed (https://github.com/42BV/CSVeed)
3    *
4    * Copyright 2013-2023 CSVeed.
5    *
6    * All rights reserved. This program and the accompanying materials
7    * are made available under the terms of The Apache Software License,
8    * Version 2.0 which accompanies this distribution, and is available at
9    * https://www.apache.org/licenses/LICENSE-2.0.txt
10   */
11  package org.csveed.row;
12  
13  import java.io.IOException;
14  import java.io.Reader;
15  import java.util.ArrayList;
16  import java.util.List;
17  
18  import org.csveed.api.Header;
19  import org.csveed.api.Row;
20  import org.csveed.report.CsvException;
21  import org.csveed.report.RowError;
22  import org.csveed.token.ParseException;
23  import org.csveed.token.ParseStateMachine;
24  
25  /**
26   * Builds up a List of cells (String) per read row. Note that this class is stateful, so it can support a per-row parse
27   * approach as well.
28   */
29  public class RowReaderImpl implements RowReader {
30  
31      /** The state machine. */
32      private ParseStateMachine stateMachine = new ParseStateMachine();
33  
34      /** The row instructions. */
35      private RowInstructionsImpl rowInstructions;
36  
37      /** The max number of columns. */
38      private int maxNumberOfColumns = -1;
39  
40      /** The header. */
41      private HeaderImpl header;
42  
43      /** The reader. */
44      private Reader reader;
45  
46      /**
47       * Instantiates a new row reader impl.
48       *
49       * @param reader
50       *            the reader
51       */
52      public RowReaderImpl(Reader reader) {
53          this(reader, new RowInstructionsImpl());
54      }
55  
56      /**
57       * Instantiates a new row reader impl.
58       *
59       * @param reader
60       *            the reader
61       * @param instructionsInterface
62       *            the instructions interface
63       */
64      public RowReaderImpl(Reader reader, RowInstructions instructionsInterface) {
65          this.reader = reader;
66          this.rowInstructions = (RowInstructionsImpl) instructionsInterface;
67          stateMachine.setSymbolMapping(rowInstructions.getSymbolMapping());
68      }
69  
70      @Override
71      public List<Row> readRows() {
72          List<Row> allRows = new ArrayList<>();
73          while (!isFinished()) {
74              Row row = readRow();
75              if (row != null && row.size() > 0) {
76                  allRows.add(row);
77              }
78          }
79          return allRows;
80      }
81  
82      @Override
83      public Row readRow() {
84          getHeader();
85          Line unmappedLine = readBareLine();
86          if (unmappedLine == null) {
87              return null;
88          }
89          checkNumberOfColumns(unmappedLine);
90          return new RowImpl(unmappedLine, getHeader());
91      }
92  
93      @Override
94      public int getCurrentLine() {
95          return this.stateMachine.getCurrentLine();
96      }
97  
98      @Override
99      public Header getHeader() {
100         return header == null && rowInstructions.isUseHeader() ? readHeader() : header;
101     }
102 
103     /**
104      * Gets the max number of columns.
105      *
106      * @return the max number of columns
107      */
108     public int getMaxNumberOfColumns() {
109         return this.maxNumberOfColumns;
110     }
111 
112     @Override
113     public Header readHeader() {
114         if (header != null) {
115             return header;
116         }
117         Line unmappedLine = readBareLine();
118         if (unmappedLine == null) {
119             return null;
120         }
121         header = new HeaderImpl(unmappedLine);
122         return header;
123     }
124 
125     /**
126      * Check number of columns.
127      *
128      * @param unmappedLine
129      *            the unmapped line
130      */
131     private void checkNumberOfColumns(Line unmappedLine) {
132         if (maxNumberOfColumns == -1) {
133             maxNumberOfColumns = header == null ? unmappedLine.size() : header.size();
134         }
135         if (unmappedLine.size() != maxNumberOfColumns) {
136             throw new CsvException(new RowError("The expected number of columns is " + maxNumberOfColumns
137                     + ", whereas it was " + unmappedLine.size(), unmappedLine.reportOnEndOfLine(), getCurrentLine()));
138         }
139     }
140 
141     @Override
142     public boolean isFinished() {
143         return stateMachine.isFinished();
144     }
145 
146     /**
147      * Log settings.
148      */
149     protected void logSettings() {
150         rowInstructions.logSettings();
151         this.stateMachine.getSymbolMapping().logSettings();
152     }
153 
154     /**
155      * Read bare line.
156      *
157      * @return the line
158      */
159     protected Line readBareLine() {
160         logSettings();
161 
162         LineWithInfo line = null;
163         while (line == null && !stateMachine.isFinished()) {
164             line = new LineWithInfo();
165             while (!stateMachine.isFinished()) {
166                 final String token;
167                 final int symbol;
168                 try {
169                     symbol = reader.read();
170                 } catch (IOException err) {
171                     throw new RuntimeException(err);
172                 }
173                 try {
174                     token = stateMachine.offerSymbol(symbol);
175                 } catch (ParseException e) {
176                     throw new CsvException(new RowError(e.getMessage(), line.reportOnEndOfLine(), getCurrentLine()));
177                 }
178                 if (stateMachine.isTrash()) {
179                     continue;
180                 }
181                 if (stateMachine.isTokenStart()) {
182                     line.markStartOfColumn();
183                 }
184                 if (token != null) {
185                     line.addCell(token);
186                 }
187                 line.addCharacter(symbol);
188                 if (stateMachine.isLineFinished()) {
189                     break;
190                 }
191             }
192             line = stateMachine.ignoreLine() && rowInstructions.isSkipEmptyLines() ? null : line;
193         }
194         return line;
195     }
196 
197     @Override
198     public RowInstructions getRowInstructions() {
199         return this.rowInstructions;
200     }
201 
202 }