001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  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 */
017package org.apache.juneau.marshall.csv;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.io.*;
022import java.util.*;
023import java.util.concurrent.*;
024
025import org.apache.juneau.commons.bean.*;
026import org.apache.juneau.commons.collections.*;
027import org.apache.juneau.marshall.*;
028import org.apache.juneau.marshall.parser.*;
029import org.apache.juneau.marshall.stream.*;
030
031/**
032 * Parses CSV (Comma Separated Values) input into Java objects.
033 *
034 * <p>
035 * Parses RFC 4180-compliant CSV into collections of beans, maps, or simple values. Each row becomes an
036 * element; the header row defines column names.
037 *
038 * <h5 class='section'>JSON Parity Features (opt-in):</h5>
039 * <ul>
040 *   <li><b>Type discriminator</b> — Parse {@code _type} column when present; use {@link CsvParser.Builder#beanDictionary(Class[])
041 *       beanDictionary()} for polymorphic types.
042 *   <li><b>Byte arrays</b> — {@link CsvParser.Builder#byteArrayFormat(CsvByteArrayCellFormat) byteArrayFormat(BASE64)} or
043 *       {@code SEMICOLON_DELIMITED}; primitive arrays from {@code [1;2;3]}.
044 *   <li><b>Nested structures</b> — {@link CsvParser.Builder#allowNestedStructures(boolean) allowNestedStructures(true)}
045 *       parses inline {@code {key:val}} and {@code [val;val]} in cells.
046 *   <li><b>Null marker</b> — {@link CsvParser.Builder#nullValue(String) nullValue("&lt;NULL&gt;")} (default); cells
047 *       matching this are parsed as null.
048 * </ul>
049 *
050 * <h5 class='section'>Data Structures Not Supported:</h5>
051 * <ul>
052 *   <li><b>Parent/inherited properties</b> — Bean hierarchy cannot be reconstructed from flat columns.
053 *   <li><b>Optional wrappers</b> — Round-trip may differ from tree formats.
054 * </ul>
055 *
056 * <h5 class='section'>Best Supported:</h5>
057 * <p>
058 * Parsing into {@link Collection} of flat beans or maps, or single beans/maps with simple
059 * property types (primitives, strings, numbers, enums, dates, byte arrays, or nested structures when enabled).
060 *
061 * <h5 class='figure'>Example input (list of maps with a,b):</h5>
062 * <p class='bcode'>
063 *    a,b
064 *    foo,bar
065 * </p>
066 *
067 * <h5 class='figure'>Complex (list of beans with nested address, flattened):</h5>
068 * <p class='bcode'>
069 *    name,age,address_street,address_city,address_state,tags
070 *    Alice,30,123 Main St,Boston,MA,"a,b,c"
071 * </p>
072 *
073 * <h5 class='section'>Notes:</h5><ul>
074 *    <li class='note'>This class is thread safe and reusable.
075 * </ul>
076 *
077 */
078@SuppressWarnings({
079   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
080   "java:S9149", // Per-format static factories intentionally shadow the parent's.
081   "resource" // Closeable resources are owned by the caller's parser session; Eclipse JDT @Owning warning is by design.
082})
083public class CsvParser extends ReaderParser implements CsvMetaProvider, RecordReadable {
084
085   /**
086    * Builder class.
087    */
088   public static class Builder extends ReaderParser.Builder<Builder> {
089
090      private static final Cache<HashKey,CsvParser> CACHE = Cache.of(HashKey.class, CsvParser.class).build();
091
092      private CsvByteArrayCellFormat byteArrayFormat;
093      private boolean allowNestedStructures;
094      private String nullValue;
095
096      /**
097       * Constructor, default settings.
098       */
099      protected Builder() {
100         consumes("text/csv");
101         byteArrayFormat = CsvByteArrayCellFormat.BASE64;
102         nullValue = "<NULL>";
103      }
104
105      /**
106       * Copy constructor.
107       *
108       * @param copyFrom The builder to copy from.
109       *    <br>Cannot be <jk>null</jk>.
110       */
111      protected Builder(Builder copyFrom) {
112         super(reqnn("copyFrom", copyFrom));
113         byteArrayFormat = copyFrom.byteArrayFormat;
114         allowNestedStructures = copyFrom.allowNestedStructures;
115         nullValue = copyFrom.nullValue;
116      }
117
118      /**
119       * Copy constructor.
120       *
121       * @param copyFrom The bean to copy from.
122       *    <br>Cannot be <jk>null</jk>.
123       */
124      protected Builder(CsvParser copyFrom) {
125         super(reqnn("copyFrom", copyFrom));
126         byteArrayFormat = copyFrom.byteArrayFormat;
127         allowNestedStructures = copyFrom.allowNestedStructures;
128         nullValue = copyFrom.nullValue;
129      }
130
131      /**
132       * String that denotes null when parsing. Must match the serializer's nullValue.
133       *
134       * @param value The null marker string.
135       * @return This object.
136       */
137      public Builder nullValue(String value) {
138         nullValue = value;
139         return this;
140      }
141
142      /**
143       * Enables parsing of inline {@code {key:val}} and {@code [val;val]} notation in cells.
144       *
145       * @param value Whether to allow nested structures.
146       * @return This object.
147       */
148      public Builder allowNestedStructures(boolean value) {
149         allowNestedStructures = value;
150         return this;
151      }
152
153      /**
154       * Format for parsing {@code byte[]} arrays from CSV cells.
155       *
156       * <p>
157       * Must match the format used by the serializer. Default is {@link CsvByteArrayCellFormat#BASE64}.
158       *
159       * @param value The format to use.
160       * @return This object.
161       */
162      public Builder byteArrayFormat(CsvByteArrayCellFormat value) {
163         byteArrayFormat = value;
164         return this;
165      }
166
167      @Override /* Overridden from Context.Builder<?> */
168      public HashKey hashKey() {
169         return HashKey.of(super.hashKey(), byteArrayFormat, allowNestedStructures, nullValue);
170      }
171
172      @Override /* Overridden from Context.Builder<?> */
173      public CsvParser build() {
174         return cache(CACHE).build(CsvParser.class);
175      }
176
177      @Override /* Overridden from Context.Builder<?> */
178      public Builder copy() {
179         return new Builder(this);
180      }
181
182
183   }
184
185   /** Default parser, all default settings.*/
186   public static final CsvParser DEFAULT = new CsvParser(create());
187
188   /**
189    * Creates a new builder for this object.
190    *
191    * @return A new builder.
192    */
193   public static Builder create() {
194      return new Builder();
195   }
196
197   private final Map<ClassMeta<?>,CsvClassMeta> csvClassMetas = new ConcurrentHashMap<>();
198   private final Map<BeanPropertyMeta,CsvBeanPropertyMeta> csvBeanPropertyMetas = new ConcurrentHashMap<>();
199   private final CsvByteArrayCellFormat byteArrayFormat;
200   private final boolean allowNestedStructures;
201   private final String nullValue;
202
203   /**
204    * Constructor.
205    *
206    * @param builder The builder for this object.
207    */
208   public CsvParser(Builder builder) {
209      super(builder);
210      byteArrayFormat = or(builder.byteArrayFormat, CsvByteArrayCellFormat.BASE64);
211      allowNestedStructures = builder.allowNestedStructures;
212      nullValue = or(builder.nullValue, "<NULL>");
213   }
214
215   /**
216    * Returns the format for parsing {@code byte[]} arrays from CSV cells.
217    *
218    * @return The byte array format.
219    */
220   public CsvByteArrayCellFormat getByteArrayFormat() {
221      return byteArrayFormat;
222   }
223
224   /**
225    * Returns whether inline {@code {key:val}} and {@code [val;val]} notation parsing is enabled.
226    *
227    * @return Whether nested structures are allowed.
228    */
229   public boolean isAllowNestedStructures() {
230      return allowNestedStructures;
231   }
232
233   /**
234    * Returns the string that denotes null when parsing.
235    *
236    * @return The null marker.
237    */
238   public String getNullValue() {
239      return nullValue;
240   }
241
242   @Override /* Overridden from Context */
243   public Builder copy() {
244      return new Builder(this);
245   }
246
247   @Override /* Overridden from Context */
248   public CsvParserSession.Builder createSession() {
249      return CsvParserSession.create(this);
250   }
251
252   @Override /* Overridden from CsvMetaProvider */
253   public CsvBeanPropertyMeta getCsvBeanPropertyMeta(BeanPropertyMeta bpm) {
254      if (bpm == null)
255         return CsvBeanPropertyMeta.DEFAULT;
256      return csvBeanPropertyMetas.computeIfAbsent(bpm, k -> new CsvBeanPropertyMeta(k.getDelegateFor(), this));
257   }
258
259   @Override /* Overridden from CsvMetaProvider */
260   public CsvClassMeta getCsvClassMeta(ClassMeta<?> cm) {
261      return csvClassMetas.computeIfAbsent(cm, k -> new CsvClassMeta(k, this));
262   }
263
264   @Override /* Overridden from Context */
265   public CsvParserSession getSession() { return createSession().build(); }
266
267   /**
268    * Convenience delegator that opens a {@link RecordReader} over the input using
269    * <b>default session arguments</b> (mirrors {@link #read(Object, Class)}).
270    *
271    * <p>
272    * CSV is naturally row-oriented &mdash; each row becomes one record &mdash; but the current cursor
273    * is buffered (the full row list is materialized at parse); a streaming implementation is a Phase 3b
274    * deferred item, so {@link #isRecordStreaming()} returns <jk>false</jk>.
275    *
276    * <p>
277    * The real implementation lives on {@link CsvParserSession#readRecords(Object)}.  Callers that need
278    * request-derived configuration (locale, timezone, schema, swaps) should call {@link #createSession()}
279    * and invoke {@link CsvParserSession#readRecords(Object)} on the built session instead.
280    *
281    * @param input The input.
282    * @return A new {@link RecordReader} cursor.
283    * @throws IOException If a problem occurred opening the underlying input.
284    */
285   @Override /* RecordReadable */
286   public RecordReader readRecords(Object input) throws IOException {
287      return getSession().readRecords(input);
288   }
289
290   @Override /* RecordReadable */
291   public boolean isRecordStreaming() {
292      return false;
293   }
294}