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