org.asteriskjava.config.ConfigFileReader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of asterisk-java Show documentation
Show all versions of asterisk-java Show documentation
The free Java library for Asterisk PBX integration.
package org.asteriskjava.config;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/**
* Reads and parses Asterisk configuration files.
* See Asterisk's main/config.c
.
* Client code is not supposed to use this class.
*
* @author srt
* @version $Id$
*/
public class ConfigFileReader
{
private static final int MAX_LINE_LENGTH = 8192;
private static char COMMENT_META = ';';
private static char COMMENT_TAG = '-';
private static final Set> WARNING_CLASSES;
static
{
WARNING_CLASSES = new HashSet<>();
WARNING_CLASSES.add(MissingEqualSignException.class);
WARNING_CLASSES.add(UnknownDirectiveException.class);
WARNING_CLASSES.add(MissingDirectiveParameterException.class);
}
private StringBuilder commentBlock;
protected final Map categories;
private final List warnings;
protected Category currentCategory;
private int currentCommentLevel = 0;
public ConfigFileReader()
{
this.commentBlock = new StringBuilder();
this.categories = new LinkedHashMap<>();
this.warnings = new ArrayList<>();
}
public ConfigFile readFile(String configfile)
{
final ConfigFile result;
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream(configfile), StandardCharsets.UTF_8)))
{
readFile(configfile, reader);
}
catch (Exception e)
{
// ignored
}
result = new ConfigFileImpl(configfile, new TreeMap<>(categories));
return result;
}
void reset()
{
commentBlock = new StringBuilder();
categories.clear();
warnings.clear();
currentCategory = null;
currentCommentLevel = 0;
}
Collection getCategories()
{
return categories.values();
}
public Collection getWarnings()
{
return new ArrayList<>(warnings);
}
void readFile(String configfile, BufferedReader reader) throws IOException, ConfigParseException
{
String line;
int lineno = 0;
CharBuffer buffer = CharBuffer.allocate(MAX_LINE_LENGTH);
reset();
while ((line = reader.readLine()) != null)
{
lineno++;
buffer.clear();
buffer.put(line);
buffer.put("\n");
buffer.flip();
processLine(configfile, lineno, buffer);
}
}
ConfigElement processLine(String configfile, int lineno, CharBuffer buffer) throws ConfigParseException
{
char c;
final StringBuilder processLineBuilder;
final StringBuilder lineCommentBuilder;
processLineBuilder = new StringBuilder(MAX_LINE_LENGTH);
lineCommentBuilder = new StringBuilder(MAX_LINE_LENGTH);
buffer.mark();
while (buffer.hasRemaining())
{
final int position;
position = buffer.position();
c = buffer.get();
// System.out.println(position + ": " + c);
if (c == COMMENT_META)
{
if (position >= 1 && buffer.get(position - 1) == '\\')
{
/* Escaped semicolons aren't comments. */
} // NOPMD
else if (buffer.remaining() >= 3 && buffer.get(position + 1) == COMMENT_TAG
&& buffer.get(position + 2) == COMMENT_TAG && buffer.get(position + 3) != COMMENT_TAG)
{
/* Meta-Comment start detected ";--" */
currentCommentLevel++;
// System.out.println("Comment start, new level: " +
// currentCommentLevel);
if (!inComment())
{
commentBlock.append(";--");
buffer.position(position + 3);
buffer.mark();
continue;
}
}
else if (inComment() && position >= 2 && buffer.get(position - 1) == COMMENT_TAG
&& buffer.get(position - 2) == COMMENT_TAG)
{
/* Meta-Comment end detected */
currentCommentLevel--;
if (!inComment())
{
buffer.reset();
// int commentLength = (position + 1) -
// buffer.position();
// buffer.reset();
// for (int i = 0; i < commentLength; i++)
// {
// commentBlock.append(buffer.get());
// }
commentBlock.append(c);
// System.out.println("Comment end at " + position + ":
// '" + commentBlock.toString() + "'");
buffer.position(position + 1);
buffer.compact();
buffer.flip();
// System.out.println("Buffer compacted");
continue;
}
}
else
{
if (!inComment())
{
/*
* If ; is found, and we are not nested in a comment, we
* immediately stop all comment processing
*/
// System.out.println("Found ; while not in comment");
while (buffer.hasRemaining())
{
lineCommentBuilder.append(buffer.get());
}
break;
}
/* Found ';' while in comment */
// NOPMD
}
}
if (inComment())
{
commentBlock.append(c);
}
else
{
// System.out.println("Added '" + c + "' to processLine");
processLineBuilder.append(c);
}
}
String processLineString;
String lineCommentString;
ConfigElement configElement;
processLineString = processLineBuilder.toString().trim();
lineCommentString = lineCommentBuilder.toString().trim();
// System.out.println("process line: '" + processLineString + "'");
if (processLineString.length() == 0)
{
if (lineCommentString.length() != 0)
{
commentBlock.append(";");
commentBlock.append(lineCommentString);
}
if (!inComment())
{
commentBlock.append("\n");
}
return null;
}
try
{
configElement = processTextLine(configfile, lineno, processLineString);
}
catch (ConfigParseException e)
{
// some parsing exceptions are treated as warnings by Asterisk, we
// mirror this behavior.
if (WARNING_CLASSES.contains(e.getClass()))
{
warnings.add(e);
return null;
}
throw e;
}
if (lineCommentString.length() != 0)
{
configElement.setComment(lineCommentString);
}
if (commentBlock.length() != 0)
{
configElement.setPreComment(commentBlock.toString());
commentBlock.delete(0, commentBlock.length());
}
return configElement;
}
boolean inComment()
{
return currentCommentLevel != 0;
}
protected ConfigElement processTextLine(String configfile, int lineno, String line) throws ConfigParseException
{
ConfigElement configElement;
if (line.charAt(0) == '[') // A category header
{
configElement = parseCategoryHeader(configfile, lineno, line);
}
else if (line.charAt(0) == '#') // a directive - #include or #exec
{
configElement = parseDirective(configfile, lineno, line);
}
else // just a line
{
if (currentCategory == null)
{
throw new ConfigParseException(configfile, lineno, "parse error: No category context for line %d of %s",
lineno, configfile);
}
configElement = parseVariable(configfile, lineno, line);
currentCategory.addElement(configElement);
}
return configElement;
}
protected Category parseCategoryHeader(String configfile, int lineno, String line) throws ConfigParseException
{
final Category category;
final String name;
final int nameEndPos;
/*
* format is one of the following: [foo] define a new category named
* 'foo' [foo](!) define a new template category named 'foo' [foo](+)
* append to category 'foo', error if foo does not exist. [foo](a)
* define a new category and inherit from template a. You can put a
* comma-separated list of templates and '!' and '+' between
* parentheses, with obvious meaning.
*/
nameEndPos = line.indexOf(']');
if (nameEndPos == -1)
{
throw new ConfigParseException(configfile, lineno, "parse error: no closing ']', line %d of %s", lineno,
configfile);
}
name = line.substring(1, nameEndPos);
category = new Category(configfile, lineno, name);
/* Handle options or categories to inherit from if available */
if (line.length() > nameEndPos + 1 && line.charAt(nameEndPos + 1) == '(')
{
final String[] options;
final String optionsString;
final int optionsEndPos;
optionsString = line.substring(nameEndPos + 1);
optionsEndPos = optionsString.indexOf(')');
if (optionsEndPos == -1)
{
throw new ConfigParseException(configfile, lineno, "parse error: no closing ')', line %d of %s", lineno,
configfile);
}
options = optionsString.substring(1, optionsEndPos).split(",");
for (String cur : options)
{
if ("!".equals(cur)) // category template
{
category.markAsTemplate();
}
else if ("+".equals(cur)) // category addition
{
final Category categoryToAddTo;
categoryToAddTo = categories.get(name);
if (categoryToAddTo == null)
{
throw new ConfigParseException(configfile, lineno,
"Category addition requested, but category '%s' does not exist, line %d of %s", name, lineno,
configfile);
}
// todo implement category addition
// category = categoryToAddTo;
}
else
{
final Category baseCategory;
baseCategory = categories.get(cur);
if (baseCategory == null)
{
throw new ConfigParseException(configfile, lineno,
"Inheritance requested, but category '%s' does not exist, line %d of %s", cur, lineno,
configfile);
}
inheritCategory(category, baseCategory);
}
}
}
appendCategory(category);
return category;
}
ConfigDirective parseDirective(String configfile, int lineno, String line) throws ConfigParseException
{
ConfigDirective directive;
final StringBuilder nameBuilder;
final StringBuilder paramBuilder;
String name = null;
String param;
nameBuilder = new StringBuilder();
paramBuilder = new StringBuilder();
for (int i = 1; i < line.length(); i++)
{
final char c;
c = line.charAt(i);
if (name == null)
{
nameBuilder.append(c);
if (Character.isWhitespace(c) || i + 1 == line.length())
{
name = nameBuilder.toString().trim();
}
}
else
{
paramBuilder.append(c);
}
}
param = paramBuilder.toString().trim();
if (param.length() != 0 && (param.charAt(0) == '"' || param.charAt(0) == '<' || param.charAt(0) == '>'))
{
param = param.substring(1);
}
int endPos = param.length() - 1;
if (param.length() != 0
&& (param.charAt(endPos) == '"' || param.charAt(endPos) == '<' || param.charAt(endPos) == '>'))
{
param = param.substring(0, endPos);
}
if ("exec".equalsIgnoreCase(name))
{
directive = new ExecDirective(configfile, lineno, param);
}
else if ("include".equalsIgnoreCase(name))
{
directive = new IncludeDirective(configfile, lineno, param);
}
else
{
throw new UnknownDirectiveException(configfile, lineno, "Unknown directive '%s' at line %d of %s", name, lineno,
configfile);
}
if (param.length() == 0)
{
if (name == null)
{
name = "";
}
throw new MissingDirectiveParameterException(configfile, lineno,
"Directive '#%s' needs an argument (%s) at line %d of %s", name.toLowerCase(Locale.US),
"include".equalsIgnoreCase(name) ? "filename" : "/path/to/executable", lineno, configfile);
}
return directive;
}
protected ConfigVariable parseVariable(String configfile, int lineno, String line) throws ConfigParseException
{
int pos;
String name;
String value;
pos = line.indexOf('=');
if (pos == -1)
{
throw new MissingEqualSignException(configfile, lineno, "No '=' (equal sign) in line %d of %s", lineno,
configfile);
}
name = line.substring(0, pos).trim();
// Ignore > in =>
if (line.length() > pos + 1 && line.charAt(pos + 1) == '>')
{
pos++;
}
value = (line.length() > pos + 1) ? line.substring(pos + 1).trim() : "";
return new ConfigVariable(configfile, lineno, name, value);
}
void inheritCategory(Category category, Category baseCategory)
{
category.addBaseCategory(baseCategory);
}
void appendCategory(Category category)
{
categories.put(category.getName(), category);
currentCategory = category;
}
}