File Source: ConfigFile.java
/*
P/P * Method: com.dmdirc.util.ConfigFile__static_init
*/
1 /*
2 * Copyright (c) 2006-2009 Chris Smith, Shane Mc Cormack, Gregory Holmes
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 */
22
23 package com.dmdirc.util;
24
25 import java.io.File;
26 import java.io.FileNotFoundException;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.nio.charset.Charset;
30 import java.util.ArrayList;
31 import java.util.GregorianCalendar;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Map;
35
36 /**
37 * Reads and writes a standard DMDirc config file.
38 *
39 * @author chris
40 */
41 public class ConfigFile extends TextFile {
42
43 /** A list of domains in this config file. */
44 private final List<String> domains = new ArrayList<String>();
45
46 /** The values associated with each flat domain. */
47 private final MapList<String, String> flatdomains = new MapList<String, String>();
48
49 /** The key/value sets associated with each key domain. */
50 private final Map<String, Map<String, String>> keydomains
51 = new HashMap<String, Map<String, String>>();
52
53 /** Whether or not we should automatically create domains. */
54 private boolean automake;
55
56 /**
57 * Creates a new read-only Config File from the specified input stream.
58 *
59 * @param is The input stream to read
60 */
61 public ConfigFile(final InputStream is) {
/*
P/P * Method: void com.dmdirc.util.ConfigFile(InputStream)
*
* Postconditions:
* init'ed(this.charset)
* this.domains == &new ArrayList(ConfigFile#1)
* this.flatdomains == &new MapList(ConfigFile#2)
* this.is == is
* init'ed(this.is)
* this.keydomains == &new HashMap(ConfigFile#3)
* new ArrayList(ConfigFile#1) num objects == 1
* new HashMap(ConfigFile#3) num objects == 1
* new HashMap(MapList#1) num objects == 1
* new MapList(ConfigFile#2) num objects == 1
* ...
*/
62 super(is, Charset.forName("UTF-8"));
63 }
64
65 /**
66 * Creates a new Config File from the specified file.
67 *
68 * @param file The file to read/write
69 */
70 public ConfigFile(final File file) {
/*
P/P * Method: void com.dmdirc.util.ConfigFile(File)
*
* Postconditions:
* init'ed(this.charset)
* this.domains == &new ArrayList(ConfigFile#1)
* this.file == file
* init'ed(this.file)
* this.flatdomains == &new MapList(ConfigFile#2)
* this.keydomains == &new HashMap(ConfigFile#3)
* new ArrayList(ConfigFile#1) num objects == 1
* new HashMap(ConfigFile#3) num objects == 1
* new HashMap(MapList#1) num objects == 1
* new MapList(ConfigFile#2) num objects == 1
* ...
*/
71 super(file, Charset.forName("UTF-8"));
72 }
73
74 /**
75 * Creates a new Config File from the specified file.
76 *
77 * @param filename The name of the file to read/write
78 */
79 public ConfigFile(final String filename) {
/*
P/P * Method: void com.dmdirc.util.ConfigFile(String)
*
* Postconditions:
* init'ed(this.charset)
* this.domains == &new ArrayList(ConfigFile#1)
* this.file == &new File(ConfigFile#1)
* this.flatdomains == &new MapList(ConfigFile#2)
* this.keydomains == &new HashMap(ConfigFile#3)
* new ArrayList(ConfigFile#1) num objects == 1
* new File(ConfigFile#1) num objects == 1
* new HashMap(ConfigFile#3) num objects == 1
* new HashMap(MapList#1) num objects == 1
* new MapList(ConfigFile#2) num objects == 1
* ...
*/
80 this(new File(filename));
81 }
82
83 /**
84 * Sets the "automake" value of this config file. If automake is set to
85 * true, any calls to getKeyDomain will automatically create the domain
86 * if it did not previously exist.
87 *
88 * @param automake The new value of the automake setting of this file
89 */
90 public void setAutomake(final boolean automake) {
/*
P/P * Method: void setAutomake(bool)
*
* Postconditions:
* this.automake == automake
* init'ed(this.automake)
*/
91 this.automake = automake;
92 }
93
94 /**
95 * Reads the data from the file.
96 *
97 * @throws FileNotFoundException if the file is not found
98 * @throws IOException if an i/o exception occured when reading
99 * @throws InvalidConfigFileException if the config file isn't valid
100 */
101 public void read() throws FileNotFoundException, IOException, InvalidConfigFileException {
/*
P/P * Method: void read()
*
* Preconditions:
* this.domains != null
* init'ed(this.file)
* this.flatdomains != null
* this.flatdomains.map != null
* this.keydomains != null
* (soft) init'ed(this.is)
*
* Presumptions:
* java.util.Iterator:next(...)@112 != null
* java.util.Map:get(...)@142 != null
*
* Postconditions:
* this.lines == &new ArrayList(readLines#4)
* new ArrayList(readLines#4) num objects == 1
* new ArrayList(readLines#4) num objects == 0
*
* Test Vectors:
* java.lang.String:charAt(...)@115: {9}, {0..8, 10..216-1}
* java.lang.String:charAt(...)@115: {0..31, 33..216-1}, {32}
* java.lang.String:endsWith(...)@122: {0}, {1}
* java.lang.String:endsWith(...)@122: {1}, {0}
* java.lang.String:indexOf(...)@120: {0}, {-231..-1, 1..232-1}
* java.lang.String:isEmpty(...)@115: {1}, {0}
* java.lang.String:isEmpty(...)@120: {0}, {1}
* java.util.Iterator:hasNext(...)@112: {0}, {1}
* java.util.Map:containsKey(...)@132: {1}, {0}
* java.util.Map:containsKey(...)@84: {1}, {0}
*/
102 String domain = null;
103 boolean keydomain = false;
104 int offset;
105
106 keydomains.clear();
107 flatdomains.clear();
108 domains.clear();
109
110 readLines();
111
112 for (String line : getLines()) {
113 String tline = line;
114
115 while (!tline.isEmpty() && (tline.charAt(0) == '\t' ||
116 tline.charAt(0) == ' ')) {
117 tline = tline.substring(1);
118 }
119
120 if (tline.indexOf('#') == 0 || tline.isEmpty()) {
121 continue;
122 } else if (
123 (tline.endsWith(":") && !tline.endsWith("\\:"))
124 && findEquals(tline) == -1) {
125 domain = unescape(tline.substring(0, tline.length() - 1));
126
127 domains.add(domain);
128
129 keydomain = keydomains.containsKey(domain)
130 || flatdomains.containsValue("keysections", domain);
131
132 if (keydomain && !keydomains.containsKey(domain)) {
133 keydomains.put(domain, new HashMap<String, String>());
134 } else if (!keydomain && !flatdomains.containsKey(domain)) {
135 flatdomains.add(domain);
136 }
137 } else if (domain != null && keydomain
138 && (offset = findEquals(tline)) != -1) {
139 final String key = unescape(tline.substring(0, offset));
140 final String value = unescape(tline.substring(offset + 1));
141
142 keydomains.get(domain).put(key, value);
143 } else if (domain != null && !keydomain) {
144 flatdomains.add(domain, unescape(tline));
145 } else {
146 throw new InvalidConfigFileException("Unknown or unexpected" +
147 " line encountered: " + tline);
148 }
149 }
150 }
151
152 /**
153 * Writes the contents of this ConfigFile to disk.
154 *
155 * @throws IOException if the write operation fails
156 */
157 public void write() throws IOException {
/*
P/P * Method: void write()
*
* Preconditions:
* this.domains != null
* this.file != null
* (soft) this.flatdomains != null
* (soft) this.flatdomains.map != null
* (soft) this.keydomains != null
*
* Presumptions:
* java.util.GregorianCalendar:getTime(...)@166 != null
* java.util.Iterator:next(...)@170 != null
* java.util.Iterator:next(...)@180 != null
* java.util.Iterator:next(...)@184 != null
* java.util.Map:entrySet(...)@184 != null
* ...
*
* Test Vectors:
* java.lang.String:equals(...)@171: {0}, {1}
* java.util.Iterator:hasNext(...)@170: {0}, {1}
* java.util.Iterator:hasNext(...)@184: {0}, {1}
* java.util.Map:containsKey(...)@84: {0}, {1}
*/
158 if (!isWritable()) {
159 throw new UnsupportedOperationException("Cannot write to a file "
160 + "that isn't writable");
161 }
162
163 final List<String> lines = new ArrayList<String>();
164
165 lines.add("# This is a DMDirc configuration file.");
166 lines.add("# Written on: " + new GregorianCalendar().getTime().toString());
167
168 writeMeta(lines);
169
170 for (String domain : domains) {
171 if ("keysections".equals(domain)) {
172 continue;
173 }
174
175 lines.add("");
176
177 lines.add(escape(domain) + ':');
178
179 if (flatdomains.containsKey(domain)) {
180 for (String entry : flatdomains.get(domain)) {
181 lines.add(" " + escape(entry));
182 }
183 } else {
184 for (Map.Entry<String, String> entry : keydomains.get(domain).entrySet()) {
185 lines.add(" " + escape(entry.getKey()) + "="
186 + escape(entry.getValue()));
187 }
188 }
189 }
190
191 writeLines(lines);
192 }
193
194 /**
195 * Appends the meta-data (keysections) to the specified list of lines.
196 *
197 * @param lines The set of lines to be appended to
198 */
199 private void writeMeta(final List<String> lines) {
/*
P/P * Method: void writeMeta(List)
*
* Preconditions:
* lines != null
* this.domains != null
* (soft) this.keydomains != null
*
* Test Vectors:
* java.lang.String:equals(...)@207: {0}, {1}
* java.util.Iterator:hasNext(...)@206: {0}, {1}
* java.util.Map:containsKey(...)@209: {0}, {1}
*/
200 lines.add("");
201 lines.add("# This section indicates which sections below take key/value");
202 lines.add("# pairs, rather than a simple list. It should be placed above");
203 lines.add("# any sections that take key/values.");
204 lines.add("keysections:");
205
206 for (String domain : domains) {
207 if ("keysections".equals(domain)) {
208 continue;
209 } else if (keydomains.containsKey(domain)) {
210 lines.add(" " + domain);
211 }
212 }
213 }
214
215 /**
216 * Retrieves all the key domains for this config file.
217 *
218 * @return This config file's key domains
219 */
220 public Map<String, Map<String, String>> getKeyDomains() {
/*
P/P * Method: Map getKeyDomains()
*
* Postconditions:
* return_value == this.keydomains
* init'ed(return_value)
*/
221 return keydomains;
222 }
223
224 /**
225 * Retrieves the key/values of the specified key domain.
226 *
227 * @param domain The domain to be retrieved
228 * @return A map of keys to values in the specified domain
229 */
230 public Map<String, String> getKeyDomain(final String domain) {
/*
P/P * Method: Map getKeyDomain(String)
*
* Preconditions:
* init'ed(this.automake)
* this.keydomains != null
* (soft) this.domains != null
*
* Postconditions:
* init'ed(return_value)
*
* Test Vectors:
* this.automake: {0}, {1}
* java.util.Map:containsKey(...)@267: {1}, {0}
*/
231 if (automake && !isKeyDomain(domain)) {
232 domains.add(domain);
233 keydomains.put(domain, new HashMap<String, String>());
234 }
235
236 return keydomains.get(domain);
237 }
238
239 /**
240 * Retrieves the content of the specified flat domain.
241 *
242 * @param domain The domain to be retrieved
243 * @return A list of lines in the specified domain
244 */
245 public List<String> getFlatDomain(final String domain) {
/*
P/P * Method: List getFlatDomain(String)
*
* Preconditions:
* this.flatdomains != null
* this.flatdomains.map != null
*
* Postconditions:
* init'ed(return_value)
*/
246 return flatdomains.get(domain);
247 }
248
249 /**
250 * Determines if this config file has the specified domain.
251 *
252 * @param domain The domain to check for
253 * @return True if the domain is known, false otherwise
254 */
255 public boolean hasDomain(final String domain) {
/*
P/P * Method: bool hasDomain(String)
*
* Preconditions:
* this.keydomains != null
* (soft) this.flatdomains != null
* (soft) this.flatdomains.map != null
*
* Postconditions:
* init'ed(return_value)
*/
256 return keydomains.containsKey(domain) || flatdomains.containsKey(domain);
257 }
258
259 /**
260 * Determines if this config file has the specified domain, and the domain
261 * is a key domain.
262 *
263 * @param domain The domain to check for
264 * @return True if the domain is known and keyed, false otherwise
265 */
266 public boolean isKeyDomain(final String domain) {
/*
P/P * Method: bool isKeyDomain(String)
*
* Preconditions:
* this.keydomains != null
*
* Postconditions:
* init'ed(return_value)
*/
267 return keydomains.containsKey(domain);
268 }
269
270 /**
271 * Determines if this config file has the specified domain, and the domain
272 * is a flat domain.
273 *
274 * @param domain The domain to check for
275 * @return True if the domain is known and flat, false otherwise
276 */
277 public boolean isFlatDomain(final String domain) {
/*
P/P * Method: bool isFlatDomain(String)
*
* Preconditions:
* this.flatdomains != null
* this.flatdomains.map != null
*
* Postconditions:
* init'ed(return_value)
*/
278 return flatdomains.containsKey(domain);
279 }
280
281 /**
282 * Adds a new flat domain to this config file.
283 *
284 * @param name The name of the domain to be added
285 * @param data The content of the domain
286 */
287 public void addDomain(final String name, final List<String> data) {
/*
P/P * Method: void addDomain(String, List)
*
* Preconditions:
* this.domains != null
* this.flatdomains != null
* this.flatdomains.map != null
*/
288 domains.add(name);
289 flatdomains.add(name, data);
290 }
291
292 /**
293 * Adds a new key domain to this config file.
294 *
295 * @param name The name of the domain to be added
296 * @param data The content of the domain
297 */
298 public void addDomain(final String name, final Map<String, String> data) {
/*
P/P * Method: void addDomain(String, Map)
*
* Preconditions:
* this.domains != null
* this.keydomains != null
*/
299 domains.add(name);
300 keydomains.put(name, data);
301 }
302
303 /**
304 * Unescapes any escaped characters in the specified input string.
305 *
306 * @param input The string to unescape
307 * @return The string with all escape chars (\) resolved
308 */
309 protected static String unescape(final String input) {
/*
P/P * Method: String unescape(String)
*
* Preconditions:
* input != null
*
* Postconditions:
* java.lang.StringBuilder:toString(...)._tainted == 0
* return_value == &java.lang.StringBuilder:toString(...)
*
* Test Vectors:
* java.lang.String:charAt(...)@314: {110}, {92}, {114}
*/
310 boolean escaped = false;
311 final StringBuilder temp = new StringBuilder();
312
313 for (int i = 0; i < input.length(); i++) {
314 final char ch = input.charAt(i);
315
316 if (escaped) {
317 if (ch == 'n') {
318 temp.append('\n');
319 } else if (ch == 'r') {
320 temp.append('\r');
321 } else {
322 temp.append(ch);
323 }
324
325 escaped = false;
326 } else if (ch == '\\') {
327 escaped = true;
328 } else {
329 temp.append(ch);
330 }
331 }
332
333 return temp.toString();
334 }
335
336 /**
337 * Escapes the specified input string by prefixing all occurances of
338 * \, \n, \r, =, # and : with backslashes.
339 *
340 * @param input The string to be escaped
341 * @return A backslash-armoured version of the string
342 */
343 protected static String escape(final String input) {
/*
P/P * Method: String escape(String)
*
* Preconditions:
* input != null
*
* Postconditions:
* return_value != null
*/
344 return input.replaceAll("\\\\", "\\\\\\\\").replaceAll("\n", "\\\\n")
345 .replaceAll("\r", "\\\\r").replaceAll("=", "\\\\=")
346 .replaceAll(":", "\\\\:").replaceAll("#", "\\\\#");
347 }
348
349 /**
350 * Finds the first non-escaped instance of '=' in the specified string.
351 *
352 * @param input The string to be searched
353 * @return The offset of the first non-escaped instance of '=', or -1.
354 */
355 protected static int findEquals(final String input) {
/*
P/P * Method: int findEquals(String)
*
* Preconditions:
* input != null
*
* Postconditions:
* return_value in {-1..232-2}
*
* Test Vectors:
* java.lang.String:charAt(...)@361: {0..91, 93..216-1}, {92}
* java.lang.String:charAt(...)@363: {0..60, 62..216-1}, {61}
*/
356 boolean escaped = false;
357
358 for (int i = 0; i < input.length(); i++) {
359 if (escaped) {
360 escaped = false;
361 } else if (input.charAt(i) == '\\') {
362 escaped = true;
363 } else if (input.charAt(i) == '=') {
364 return i;
365 }
366 }
367
368 return -1;
369 }
370 }
SofCheck Inspector Build Version : 2.17854
| ConfigFile.java |
2009-Jun-25 01:54:24 |
| ConfigFile.class |
2009-Sep-02 17:04:17 |