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