001package org.cpsolver.ifs.util; 002 003import java.io.BufferedReader; 004import java.io.File; 005import java.io.FileReader; 006import java.io.FileWriter; 007import java.io.IOException; 008import java.io.PrintWriter; 009import java.io.Reader; 010import java.io.Serializable; 011import java.io.Writer; 012import java.util.ArrayList; 013import java.util.Calendar; 014import java.util.Collection; 015import java.util.Date; 016import java.util.HashMap; 017import java.util.Iterator; 018import java.util.List; 019import java.util.Locale; 020 021/** 022 * Support for CSV (comma separated) text files. 023 * 024 * @author Tomáš Müller 025 * @version IFS 1.3 (Iterative Forward Search)<br> 026 * Copyright (C) 2006 - 2014 Tomáš Müller<br> 027 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 028 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 029 * <br> 030 * This library is free software; you can redistribute it and/or modify 031 * it under the terms of the GNU Lesser General Public License as 032 * published by the Free Software Foundation; either version 3 of the 033 * License, or (at your option) any later version. <br> 034 * <br> 035 * This library is distributed in the hope that it will be useful, but 036 * WITHOUT ANY WARRANTY; without even the implied warranty of 037 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 038 * Lesser General Public License for more details. <br> 039 * <br> 040 * You should have received a copy of the GNU Lesser General Public 041 * License along with this library; if not see 042 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 043 */ 044 045public class CSVFile implements Serializable { 046 private static final long serialVersionUID = 1L; 047 HashMap<String, Integer> iHeaderMap = null; 048 CSVLine iHeader = null; 049 List<CSVLine> iLines = null; 050 String iSeparator = ","; 051 String iQuotationMark = "\""; 052 053 public CSVFile() { 054 } 055 056 public CSVFile(File file) throws IOException { 057 load(file); 058 } 059 060 public CSVFile(File file, String separator) throws IOException { 061 setSeparator(separator); 062 load(file); 063 } 064 065 public CSVFile(File file, String separator, String quotationMark) throws IOException { 066 setSeparator(separator); 067 setQuotationMark(quotationMark); 068 load(file); 069 } 070 071 public CSVFile(Reader file) throws IOException { 072 load(file); 073 } 074 075 public CSVFile(Reader file, String separator) throws IOException { 076 setSeparator(separator); 077 load(file); 078 } 079 080 public CSVFile(Reader file, String separator, String quotationMark) throws IOException { 081 setSeparator(separator); 082 setQuotationMark(quotationMark); 083 load(file); 084 } 085 086 public void setSeparator(String separator) { 087 iSeparator = separator; 088 } 089 090 public String getSeparator() { 091 return iSeparator; 092 } 093 094 public void setQuotationMark(String quotationMark) { 095 iQuotationMark = quotationMark; 096 } 097 098 public String getQuotationMark() { 099 return iQuotationMark; 100 } 101 102 public void load(File file) throws IOException { 103 load(new FileReader(file)); 104 } 105 106 public void load(Reader file) throws IOException { 107 LineReader reader = null; 108 try { 109 reader = new LineReader(file); 110 iHeader = new CSVLine(reader.readLine()); // read header 111 iHeaderMap = new HashMap<String, Integer>(); 112 iLines = new ArrayList<CSVLine>(); 113 int idx = 0; 114 for (Iterator<CSVField> i = iHeader.fields(); i.hasNext(); idx++) { 115 CSVField field = i.next(); 116 iHeaderMap.put(field.toString(), idx); 117 } 118 String line = null; 119 while ((line = reader.readLine()) != null) { 120 if (line.trim().length() == 0) 121 continue; 122 iLines.add(new CSVLine(line)); 123 } 124 } finally { 125 if (reader != null) 126 reader.close(); 127 } 128 } 129 130 public void save(File file) throws IOException { 131 save(new FileWriter(file)); 132 } 133 134 public void save(Writer file) throws IOException { 135 PrintWriter writer = null; 136 try { 137 writer = new PrintWriter(file); 138 if (iHeader != null) 139 writer.println(iHeader.toString()); 140 141 if (iLines != null) { 142 for (CSVLine line : iLines) { 143 writer.println(line.toString()); 144 } 145 } 146 147 writer.flush(); 148 } finally { 149 if (writer != null) 150 writer.close(); 151 } 152 } 153 154 public CSVLine getHeader() { 155 return iHeader; 156 } 157 158 public void setHeader(CSVLine header) { 159 iHeader = header; 160 } 161 162 public List<CSVLine> getLines() { 163 return iLines; 164 } 165 166 public int size() { 167 return iLines.size(); 168 } 169 170 public boolean isEmpty() { 171 return iLines.isEmpty(); 172 } 173 174 public CSVLine getLine(int idx) { 175 return iLines.get(idx); 176 } 177 178 public Iterator<CSVLine> lines() { 179 return iLines.iterator(); 180 } 181 182 public void addLine(CSVLine line) { 183 if (iLines == null) 184 iLines = new ArrayList<CSVLine>(); 185 iLines.add(line); 186 } 187 188 public void addLine(String line) { 189 if (iLines == null) 190 iLines = new ArrayList<CSVLine>(); 191 iLines.add(new CSVLine(line)); 192 } 193 194 public List<CSVLine> filter(CSVFilter filter) { 195 List<CSVLine> ret = new ArrayList<CSVLine>(); 196 for (CSVLine line : iLines) { 197 if (filter.match(line)) 198 ret.add(line); 199 } 200 return ret; 201 } 202 203 public CSVLine addLine() { 204 CSVLine line = new CSVLine(); 205 addLine(line); 206 return line; 207 } 208 209 public CSVLine addLine(CSVField fields[]) { 210 CSVLine line = new CSVLine(fields); 211 addLine(line); 212 return line; 213 } 214 215 public CSVLine addLine(Collection<CSVField> fields) { 216 CSVLine line = new CSVLine(fields); 217 addLine(line); 218 return line; 219 } 220 221 public CSVLine setHeader(CSVField fields[]) { 222 CSVLine header = new CSVLine(fields); 223 setHeader(header); 224 return header; 225 } 226 227 public CSVLine setHeader(Collection<CSVField> fields) { 228 CSVLine header = new CSVLine(fields); 229 setHeader(header); 230 return header; 231 } 232 233 /** Representation of a line of a CSV file */ 234 public class CSVLine implements Serializable { 235 private static final long serialVersionUID = 1L; 236 List<CSVField> iFields = new ArrayList<CSVField>(iHeader == null ? 10 : iHeader.size()); 237 238 public CSVLine(String line) { 239 int idx = 0; 240 int newIdx = 0; 241 int fromIdx = 0; 242 while ((newIdx = line.indexOf(iSeparator, fromIdx)) >= 0) { 243 String field = line.substring(idx, newIdx); 244 if (iQuotationMark != null && field.startsWith(iQuotationMark) && (!field.endsWith(iQuotationMark) || field.length() == 1)) { 245 fromIdx = newIdx + iSeparator.length(); 246 continue; 247 } 248 iFields.add(new CSVField(field, iQuotationMark)); 249 idx = newIdx + iSeparator.length(); 250 fromIdx = idx; 251 } 252 iFields.add(new CSVField(line.substring(idx), iQuotationMark)); 253 } 254 255 public CSVLine() { 256 } 257 258 public CSVLine(CSVField fields[]) { 259 for (int i = 0; i < fields.length; i++) 260 iFields.add(fields[i]); 261 } 262 263 public CSVLine(Collection<CSVField> fields) { 264 iFields.addAll(fields); 265 } 266 267 public List<CSVField> getFields() { 268 return iFields; 269 } 270 271 public int size() { 272 return iFields.size(); 273 } 274 275 public boolean isEmpty() { 276 return iFields.isEmpty(); 277 } 278 279 public CSVField getField(int idx) { 280 try { 281 return iFields.get(idx); 282 } catch (ArrayIndexOutOfBoundsException e) { 283 return null; 284 } 285 } 286 287 public void setField(int idx, CSVField field) { 288 iFields.set(idx, field); 289 } 290 291 public Iterator<CSVField> fields() { 292 return iFields.iterator(); 293 } 294 295 public CSVField getField(String name) { 296 Integer idx = iHeaderMap.get(name); 297 return (idx == null ? null : getField(idx.intValue())); 298 } 299 300 public void setField(String name, CSVField field) { 301 Integer idx = iHeaderMap.get(name); 302 if (idx != null) 303 setField(idx.intValue(), field); 304 } 305 306 @Override 307 public String toString() { 308 StringBuffer sb = new StringBuffer(); 309 for (Iterator<CSVField> i = iFields.iterator(); i.hasNext();) { 310 CSVField field = i.next(); 311 if (field != null) 312 sb.append((iQuotationMark == null ? "" : iQuotationMark) + field.toString() 313 + (iQuotationMark == null ? "" : iQuotationMark)); 314 if (i.hasNext()) 315 sb.append(iSeparator); 316 } 317 return sb.toString(); 318 } 319 320 public void debug(int offset, PrintWriter out) { 321 int idx = 0; 322 for (Iterator<CSVField> i = iFields.iterator(); i.hasNext();) { 323 CSVField field = i.next(); 324 if (field == null || field.toString().length() == 0) 325 continue; 326 for (int j = 0; j < offset; j++) 327 out.print(" "); 328 out.println(iHeader.getField(idx) + "=" + (iQuotationMark == null ? "" : iQuotationMark) + field 329 + (iQuotationMark == null ? "" : iQuotationMark)); 330 } 331 } 332 } 333 334 /** Representation of a field of a CSV file */ 335 public static class CSVField implements Serializable { 336 private static final long serialVersionUID = 1L; 337 String iField = null; 338 339 public CSVField(String field, String quotationMark) { 340 field = field.trim(); 341 if (quotationMark != null && field.startsWith(quotationMark) && field.endsWith(quotationMark)) 342 field = field.substring(1, field.length() - 1); 343 iField = field; 344 } 345 346 public CSVField(Object field) { 347 set(field == null ? "" : field.toString()); 348 } 349 350 public CSVField(int field) { 351 set(field); 352 } 353 354 public CSVField(boolean field) { 355 set(field); 356 } 357 358 public CSVField(double field) { 359 set(field); 360 } 361 362 public CSVField(long field) { 363 set(field); 364 } 365 366 public CSVField(float field) { 367 set(field); 368 } 369 370 public void set(Object value) { 371 iField = (value == null ? "" : value.toString()); 372 } 373 374 public void set(int value) { 375 iField = String.valueOf(value); 376 } 377 378 public void set(boolean value) { 379 iField = (value ? "1" : "0"); 380 } 381 382 public void set(double value) { 383 iField = String.valueOf(value); 384 } 385 386 public void set(long value) { 387 iField = String.valueOf(value); 388 } 389 390 public void set(float value) { 391 iField = String.valueOf(value); 392 } 393 394 @Override 395 public String toString() { 396 return (iField == null ? "" : iField); 397 } 398 399 public boolean isEmpty() { 400 return (iField.length() == 0); 401 } 402 403 public int toInt() { 404 return toInt(0); 405 } 406 407 public int toInt(int defaultValue) { 408 try { 409 return Integer.parseInt(iField); 410 } catch (NumberFormatException e) { 411 return defaultValue; 412 } 413 } 414 415 public long toLong() { 416 return toLong(0); 417 } 418 419 public long toLong(long defaultValue) { 420 try { 421 return Long.parseLong(iField); 422 } catch (NumberFormatException e) { 423 return defaultValue; 424 } 425 } 426 427 public double toDouble() { 428 return toDouble(0); 429 } 430 431 public double toDouble(double defaultValue) { 432 try { 433 return Double.parseDouble(iField); 434 } catch (NumberFormatException e) { 435 return defaultValue; 436 } 437 } 438 439 public Date toDate() { 440 int month = Integer.parseInt(iField.substring(0, 2)); 441 int day = Integer.parseInt(iField.substring(3, 5)); 442 int year = Integer.parseInt(iField.substring(6, 8)); 443 Calendar c = Calendar.getInstance(Locale.US); 444 c.set(year, month - 1, day, 0, 0, 0); 445 return c.getTime(); 446 } 447 448 public boolean toBoolean() { 449 return "Y".equalsIgnoreCase(iField) || "on".equalsIgnoreCase(iField) || "true".equalsIgnoreCase(iField) 450 || "1".equalsIgnoreCase(iField); 451 } 452 } 453 454 /** An interface for filtering lines of a CSV file */ 455 public static interface CSVFilter { 456 public boolean match(CSVLine line); 457 } 458 459 public static CSVFilter eq(String name, String value) { 460 return (new CSVFilter() { 461 String n, v; 462 463 @Override 464 public boolean match(CSVLine line) { 465 return line.getField(n).equals(v); 466 } 467 468 private CSVFilter set(String n, String v) { 469 this.n = n; 470 this.v = v; 471 return this; 472 } 473 }).set(name, value); 474 } 475 476 public static CSVFilter and(CSVFilter first, CSVFilter second) { 477 return (new CSVFilter() { 478 CSVFilter a, b; 479 480 @Override 481 public boolean match(CSVLine line) { 482 return a.match(line) && b.match(line); 483 } 484 485 private CSVFilter set(CSVFilter a, CSVFilter b) { 486 this.a = a; 487 this.b = b; 488 return this; 489 } 490 }).set(first, second); 491 } 492 493 public static CSVFilter or(CSVFilter first, CSVFilter second) { 494 return (new CSVFilter() { 495 CSVFilter a, b; 496 497 @Override 498 public boolean match(CSVLine line) { 499 return a.match(line) || b.match(line); 500 } 501 502 private CSVFilter set(CSVFilter a, CSVFilter b) { 503 this.a = a; 504 this.b = b; 505 return this; 506 } 507 }).set(first, second); 508 } 509 510 public static CSVFilter not(CSVFilter filter) { 511 return (new CSVFilter() { 512 CSVFilter f; 513 514 @Override 515 public boolean match(CSVLine line) { 516 return !f.match(line); 517 } 518 519 private CSVFilter set(CSVFilter f) { 520 this.f = f; 521 return this; 522 } 523 }).set(filter); 524 } 525 526 private class LineReader extends BufferedReader { 527 LineReader(Reader r) { 528 super(r); 529 } 530 531 public boolean isOpenEnded(String line) { 532 if (iQuotationMark == null) return false; 533 int idx = 0; 534 int newIdx = 0; 535 int fromIdx = 0; 536 while ((newIdx = line.indexOf(iSeparator, fromIdx)) >= 0) { 537 String field = line.substring(idx, newIdx); 538 if (field.startsWith(iQuotationMark) && (!field.endsWith(iQuotationMark) || field.length() == 1)) { 539 fromIdx = newIdx + iSeparator.length(); 540 continue; 541 } 542 idx = newIdx + iSeparator.length(); 543 fromIdx = idx; 544 } 545 String field = line.substring(idx); 546 if (field.startsWith(iQuotationMark) && (!field.endsWith(iQuotationMark) || field.length() == 1)) return true; 547 return false; 548 } 549 550 @Override 551 public String readLine() throws IOException { 552 String line = super.readLine(); 553 while (line != null && isOpenEnded(line)) { 554 String next = super.readLine(); 555 if (next == null) return line; 556 line += "\n" + next; 557 } 558 return line; 559 } 560 } 561}