001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2010–2011 ApexIdentity Inc. 015 * Portions Copyright 2011-2014 ForgeRock AS. 016 */ 017 018package org.forgerock.openig.text; 019 020import java.io.File; 021import java.io.FileInputStream; 022import java.io.IOException; 023import java.io.InputStreamReader; 024import java.nio.charset.Charset; 025import java.util.ArrayList; 026import java.util.HashMap; 027import java.util.Iterator; 028import java.util.List; 029import java.util.Map; 030 031/** 032 * Allows records to be retrieved from a delimiter-separated file using key and value. Once 033 * constructed, an instance of this class is thread-safe, meaning the object can be long-lived, 034 * and multiple concurrent calls to {@link #getRecord(String, String) getRecord} is fully 035 * supported. 036 */ 037public class SeparatedValuesFile { 038 039 /** The file containing the separated values to be read. */ 040 private final File file; 041 042 /** The character set the file is encoded in. */ 043 private final Charset charset; 044 045 /** The separator specification to split lines into fields. */ 046 private final Separator separator; 047 048 /** Does the first line of the file contain the set of defined field keys. */ 049 private boolean header; 050 051 /** 052 * Explicit field keys in the order they appear in a record, overriding any existing field header, 053 * or empty to use field header. 054 */ 055 private final List<String> fields = new ArrayList<String>(); 056 057 /** 058 * Builds a new SeparatedValuesFile reading the given {@code file} using a the {@link Separators#COMMA} 059 * separator specification and {@code UTF-8} charset. This constructor consider the file has a header line. 060 * <p> 061 * It is equivalent to call: 062 * <code> new SeparatedValuesFile(file, "UTF-8"); </code> 063 * 064 * @param file 065 * file to read from 066 * @see #SeparatedValuesFile(File, Charset) 067 */ 068 public SeparatedValuesFile(final File file) { 069 this(file, Charset.forName("UTF-8")); 070 } 071 072 /** 073 * Builds a new SeparatedValuesFile reading the given {@code file} using a the {@link Separators#COMMA} 074 * separator specification. This constructor consider the file has a header line. 075 * <p> 076 * It is equivalent to call: 077 * <code> new SeparatedValuesFile(file, charset, Separators.COMMA.getSeparator()); </code> 078 * 079 * @param file 080 * file to read from 081 * @param charset 082 * {@link Charset} of the file (non-null) 083 * @see #SeparatedValuesFile(File, Charset, Separator) 084 */ 085 public SeparatedValuesFile(final File file, final Charset charset) { 086 this(file, charset, Separators.COMMA.getSeparator()); 087 } 088 089 /** 090 * Builds a new SeparatedValuesFile reading the given {@code file}. This constructor consider the file has a header 091 * line. 092 * <p> 093 * It is equivalent to call: 094 * <code> new SeparatedValuesFile(file, charset, separator, true); </code> 095 * 096 * @param file 097 * file to read from 098 * @param charset 099 * {@link Charset} of the file (non-null) 100 * @param separator 101 * separator specification 102 * @see #SeparatedValuesFile(File, Charset, Separator, boolean) 103 */ 104 public SeparatedValuesFile(final File file, final Charset charset, final Separator separator) { 105 this(file, charset, separator, true); 106 } 107 108 /** 109 * Builds a new SeparatedValuesFile reading the given {@code file}. 110 * 111 * @param file 112 * file to read from 113 * @param charset 114 * {@link Charset} of the file (non-null) 115 * @param separator 116 * separator specification 117 * @param header 118 * does the file has a header first line ? 119 */ 120 public SeparatedValuesFile(final File file, 121 final Charset charset, 122 final Separator separator, 123 final boolean header) { 124 this.file = file; 125 this.charset = charset; 126 this.separator = separator; 127 this.header = header; 128 } 129 130 /** 131 * Returns the explicit field keys in the order they appear in a record, overriding any existing field header, 132 * or empty to use field header. 133 * @return the explicit field keys in the order they appear in a record 134 */ 135 public List<String> getFields() { 136 return fields; 137 } 138 139 /** 140 * Returns a record from the file where the specified key is equal to the specified value. 141 * 142 * @param key the key to use to lookup the record 143 * @param value the value that the key should have to find a matching record. 144 * @return the record with the matching value, or {@code null} if no such record could be found. 145 * @throws IOException if an I/O exception occurs. 146 */ 147 public Map<String, String> getRecord(String key, String value) throws IOException { 148 Map<String, String> map = null; 149 SeparatedValuesReader reader = new SeparatedValuesReader( 150 new InputStreamReader(new FileInputStream(file), charset), 151 separator 152 ); 153 try { 154 List<String> fields = this.fields; 155 if (header) { 156 // first line in the file is the field header 157 List<String> record = reader.next(); 158 if (record != null && fields.size() == 0) { 159 // use header fields 160 fields = record; 161 } 162 } 163 if (fields.size() > 0) { 164 int index = fields.indexOf(key); 165 if (index >= 0) { 166 // requested key exists 167 List<String> record; 168 while ((record = reader.next()) != null) { 169 if (record.get(index).equals(value)) { 170 map = new HashMap<String, String>(fields.size()); 171 Iterator<String> fi = fields.iterator(); 172 Iterator<String> ri = record.iterator(); 173 while (fi.hasNext() && ri.hasNext()) { 174 // assign field-value pairs in map 175 map.put(fi.next(), ri.next()); 176 } 177 break; 178 } 179 } 180 } 181 } 182 } finally { 183 reader.close(); 184 } 185 return map; 186 } 187}