1 package org.asteriskjava.config;
2
3 import java.io.BufferedReader;
4 import java.io.FileReader;
5 import java.io.IOException;
6 import java.nio.CharBuffer;
7 import java.util.*;
8
9
10
11
12
13
14
15
16
17
18
19 public class ConfigFileReader
20 {
21 private static final int MAX_LINE_LENGTH = 8192;
22 private static char COMMENT_META = ';';
23 private static char COMMENT_TAG = '-';
24 private static final Set<Class<?>> WARNING_CLASSES;
25
26 static
27 {
28 WARNING_CLASSES = new HashSet<Class<?>>();
29 WARNING_CLASSES.add(MissingEqualSignException.class);
30 WARNING_CLASSES.add(UnknownDirectiveException.class);
31 WARNING_CLASSES.add(MissingDirectiveParameterException.class);
32 }
33
34 private StringBuilder commentBlock;
35 protected final Map<String, Category> categories;
36 private final List<ConfigParseException> warnings;
37
38 protected Category currentCategory;
39 private int currentCommentLevel = 0;
40
41 public ConfigFileReader()
42 {
43 this.commentBlock = new StringBuilder();
44 this.categories = new LinkedHashMap<String, Category>();
45 this.warnings = new ArrayList<ConfigParseException>();
46 }
47
48 public ConfigFile readFile(String configfile) throws IOException, ConfigParseException
49 {
50 final ConfigFile result;
51 final BufferedReader reader;
52
53 reader = new BufferedReader(new FileReader(configfile));
54 try
55 {
56 readFile(configfile, reader);
57 }
58 finally
59 {
60 try
61 {
62 reader.close();
63 }
64 catch (Exception e)
65 {
66
67 }
68 }
69
70 result = new ConfigFileImpl(configfile, new TreeMap<String, Category>(categories));
71
72
73 return result;
74 }
75
76 void reset()
77 {
78 commentBlock = new StringBuilder();
79 categories.clear();
80 warnings.clear();
81 currentCategory = null;
82 currentCommentLevel = 0;
83 }
84
85 Collection<Category> getCategories()
86 {
87 return categories.values();
88 }
89
90 public Collection<ConfigParseException> getWarnings()
91 {
92 return new ArrayList<ConfigParseException>(warnings);
93 }
94
95 void readFile(String configfile, BufferedReader reader) throws IOException, ConfigParseException
96 {
97 String line;
98 int lineno = 0;
99 CharBuffer buffer = CharBuffer.allocate(MAX_LINE_LENGTH);
100
101 reset();
102 while ((line = reader.readLine()) != null)
103 {
104 lineno++;
105 buffer.clear();
106 buffer.put(line);
107 buffer.put("\n");
108 buffer.flip();
109
110 processLine(configfile, lineno, buffer);
111 }
112 }
113
114 ConfigElement processLine(String configfile, int lineno, CharBuffer buffer) throws ConfigParseException
115 {
116 char c;
117 final StringBuilder processLineBuilder;
118 final StringBuilder lineCommentBuilder;
119
120 processLineBuilder = new StringBuilder(MAX_LINE_LENGTH);
121 lineCommentBuilder = new StringBuilder(MAX_LINE_LENGTH);
122 buffer.mark();
123
124 while (buffer.hasRemaining())
125 {
126 final int position;
127
128 position = buffer.position();
129 c = buffer.get();
130
131
132 if (c == COMMENT_META)
133 {
134 if (position >= 1 && buffer.get(position - 1) == '\\')
135 {
136
137 }
138 else if (buffer.remaining() >= 3
139 && buffer.get(position + 1) == COMMENT_TAG
140 && buffer.get(position + 2) == COMMENT_TAG
141 && buffer.get(position + 3) != COMMENT_TAG)
142 {
143
144
145 currentCommentLevel++;
146
147
148 if (!inComment())
149 {
150 commentBlock.append(";--");
151 buffer.position(position + 3);
152 buffer.mark();
153 continue;
154 }
155 }
156 else if (inComment()
157 && position >= 2
158 && buffer.get(position - 1) == COMMENT_TAG
159 && buffer.get(position - 2) == COMMENT_TAG)
160 {
161
162
163 currentCommentLevel--;
164
165 if (!inComment())
166 {
167 buffer.reset();
168
169
170
171
172
173
174
175
176
177 commentBlock.append(c);
178
179
180 buffer.position(position + 1);
181 buffer.compact();
182 buffer.flip();
183
184
185 continue;
186 }
187 }
188 else
189 {
190 if (!inComment())
191 {
192
193
194 while (buffer.hasRemaining())
195 {
196 lineCommentBuilder.append(buffer.get());
197 }
198 break;
199 }
200 else
201 {
202
203 }
204 }
205 }
206
207
208 if (inComment())
209 {
210 commentBlock.append(c);
211 }
212 else
213 {
214
215 processLineBuilder.append(c);
216 }
217 }
218
219 String processLineString;
220 String lineCommentString;
221 ConfigElement configElement;
222
223 processLineString = processLineBuilder.toString().trim();
224 lineCommentString = lineCommentBuilder.toString().trim();
225
226
227 if (processLineString.length() == 0)
228 {
229 if (lineCommentString.length() != 0)
230 {
231 commentBlock.append(";");
232 commentBlock.append(lineCommentString);
233 }
234 if (!inComment())
235 {
236 commentBlock.append("\n");
237 }
238 return null;
239 }
240
241 try
242 {
243 configElement = processTextLine(configfile, lineno, processLineString);
244 }
245 catch (ConfigParseException e)
246 {
247
248 if (WARNING_CLASSES.contains(e.getClass()))
249 {
250 warnings.add(e);
251 return null;
252 }
253 else
254 {
255 throw e;
256 }
257 }
258
259 if (lineCommentString.length() != 0)
260 {
261 configElement.setComment(lineCommentString);
262 }
263
264 if (commentBlock.length() != 0)
265 {
266 configElement.setPreComment(commentBlock.toString());
267 commentBlock.delete(0, commentBlock.length());
268 }
269
270 return configElement;
271 }
272
273 boolean inComment()
274 {
275 return currentCommentLevel != 0;
276 }
277
278 protected ConfigElement processTextLine(String configfile, int lineno, String line) throws ConfigParseException
279 {
280 ConfigElement configElement;
281
282 if (line.charAt(0) == '[')
283 {
284 configElement = parseCategoryHeader(configfile, lineno, line);
285 }
286 else if (line.charAt(0) == '#')
287 {
288 configElement = parseDirective(configfile, lineno, line);
289 }
290 else
291 {
292 if (currentCategory == null)
293 {
294 throw new ConfigParseException(configfile, lineno,
295 "parse error: No category context for line %d of %s", lineno, configfile);
296 }
297
298 configElement = parseVariable(configfile, lineno, line);
299 currentCategory.addElement(configElement);
300 }
301
302 return configElement;
303 }
304
305 protected Category parseCategoryHeader(String configfile, int lineno, String line) throws ConfigParseException
306 {
307 final Category category;
308 final String name;
309 final int nameEndPos;
310
311
312
313
314
315
316
317
318
319
320 nameEndPos = line.indexOf(']');
321 if (nameEndPos == -1)
322 {
323 throw new ConfigParseException(configfile, lineno,
324 "parse error: no closing ']', line %d of %s", lineno, configfile);
325 }
326 name = line.substring(1, nameEndPos);
327 category = new Category(configfile, lineno, name);
328
329
330 if (line.length() > nameEndPos + 1 && line.charAt(nameEndPos + 1) == '(')
331 {
332 final String[] options;
333 final String optionsString;
334 final int optionsEndPos;
335
336 optionsString = line.substring(nameEndPos + 1);
337 optionsEndPos = optionsString.indexOf(')');
338 if (optionsEndPos == -1)
339 {
340 throw new ConfigParseException(configfile, lineno,
341 "parse error: no closing ')', line %d of %s", lineno, configfile);
342 }
343
344 options = optionsString.substring(1, optionsEndPos).split(",");
345 for (String cur : options)
346 {
347 if ("!".equals(cur))
348 {
349 category.markAsTemplate();
350 }
351 else if ("+".equals(cur))
352 {
353 final Category categoryToAddTo;
354
355 categoryToAddTo = categories.get(name);
356 if (categoryToAddTo == null)
357 {
358 throw new ConfigParseException(configfile, lineno,
359 "Category addition requested, but category '%s' does not exist, line %d of %s",
360 name, lineno, configfile);
361 }
362
363
364
365 }
366 else
367 {
368 final Category baseCategory;
369
370 baseCategory = categories.get(cur);
371 if (baseCategory == null)
372 {
373 throw new ConfigParseException(configfile, lineno,
374 "Inheritance requested, but category '%s' does not exist, line %d of %s",
375 cur, lineno, configfile);
376 }
377 inheritCategory(category, baseCategory);
378 }
379 }
380 }
381
382 appendCategory(category);
383 return category;
384 }
385
386 ConfigDirective parseDirective(String configfile, int lineno, String line) throws ConfigParseException
387 {
388 ConfigDirective directive;
389 final StringBuilder nameBuilder;
390 final StringBuilder paramBuilder;
391 String name = null;
392 String param;
393
394 nameBuilder = new StringBuilder();
395 paramBuilder = new StringBuilder();
396 for (int i = 1; i < line.length(); i++)
397 {
398 final char c;
399
400 c = line.charAt(i);
401 if (name == null)
402 {
403 nameBuilder.append(c);
404 if (Character.isWhitespace(c) || i + 1 == line.length())
405 {
406 name = nameBuilder.toString().trim();
407 }
408 }
409 else
410 {
411 paramBuilder.append(c);
412 }
413 }
414
415 param = paramBuilder.toString().trim();
416
417 if (param.length() != 0 &&
418 (param.charAt(0) == '"' || param.charAt(0) == '<' || param.charAt(0) == '>'))
419 {
420 param = param.substring(1);
421 }
422
423 int endPos = param.length() - 1;
424 if (param.length() != 0 &&
425 (param.charAt(endPos) == '"' || param.charAt(endPos) == '<' || param.charAt(endPos) == '>'))
426 {
427 param = param.substring(0, endPos);
428 }
429
430 if ("exec".equalsIgnoreCase(name))
431 {
432 directive = new ExecDirective(configfile, lineno, param);
433 }
434 else if ("include".equalsIgnoreCase(name))
435 {
436 directive = new IncludeDirective(configfile, lineno, param);
437 }
438 else
439 {
440 throw new UnknownDirectiveException(configfile, lineno,
441 "Unknown directive '%s' at line %d of %s", name, lineno, configfile);
442 }
443
444 if (param.length() == 0)
445 {
446 throw new MissingDirectiveParameterException(configfile, lineno,
447 "Directive '#%s' needs an argument (%s) at line %d of %s",
448 name.toLowerCase(Locale.US),
449 "include".equalsIgnoreCase(name) ? "filename" : "/path/to/executable",
450 lineno,
451 configfile);
452 }
453
454 return directive;
455 }
456
457 protected ConfigVariable parseVariable(String configfile, int lineno, String line) throws ConfigParseException
458 {
459 int pos;
460 String name;
461 String value;
462
463 pos = line.indexOf('=');
464 if (pos == -1)
465 {
466 throw new MissingEqualSignException(configfile, lineno,
467 "No '=' (equal sign) in line %d of %s", lineno, configfile);
468 }
469
470 name = line.substring(0, pos).trim();
471
472
473 if (line.length() > pos + 1 && line.charAt(pos + 1) == '>')
474 {
475 pos++;
476 }
477
478 value = (line.length() > pos + 1) ? line.substring(pos + 1).trim() : "";
479 return new ConfigVariable(configfile, lineno, name, value);
480 }
481
482 void inheritCategory(Category category, Category baseCategory)
483 {
484 category.addBaseCategory(baseCategory);
485 }
486
487 void appendCategory(Category category)
488 {
489 categories.put(category.getName(), category);
490 currentCategory = category;
491 }
492 }