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("<NULL>")} (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 — each row becomes one record — 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}