// Copyright (C) 2011-2012 CRS4.
//
// This file is part of Seal.
//
// Seal is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// Seal is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
// for more details.
//
// You should have received a copy of the GNU General Public License along
// with Seal. If not, see <http://www.gnu.org/licenses/>.
package it.crs4.seal.common;
import java.io.IOException;
import java.io.File;
import java.io.LineNumberReader;
import java.io.Reader;
import java.util.regex.*;
import java.util.Properties;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Collection;
public class ConfigFileParser
{
// Line patterns. Lines are trimmed of spaces before being matched.
// Section name
private static final Pattern PSectionTitle = Pattern.compile("^\\[ *(\\w+) *\\] *$");
// key-value. Key is delimited by : or =. Spaces before the value are removed. Since
// the line is trimmed, spaces at the end of the value will also be removed.
// Except for this latest point, this should match the behaviour of the Python
// ConfigParser module.
private static final Pattern PKvLine = Pattern.compile("^ *([^:=]+)[:=] *(.*) *$");
// Settings in the default section are inherited by all other sections.
// It must be named DEFAULT for compatibility.
private static final String DefaultSectionName = "DEFAULT";
private Properties defaultProperties;
private HashMap<String, Properties> sections;
private enum TokenType {
SectionName,
KeyValue
}
public static class KvPair
{
private String key;
private String value;
public KvPair(String k, String v) {
key = k;
value = v;
}
public String getKey() { return key; }
public String getValue() { return value; }
public boolean equals(Object other) {
if (other instanceof KvPair)
{
KvPair otherPair = (KvPair)other;
if (key == null && value == null)
return otherPair.key == null && otherPair.value == null;
else if (key == null && value != null)
return (key == otherPair.key) && value.equals(otherPair.value);
else if (value == null && key != null)
return (value == otherPair.value) && key.equals(otherPair.key);
else
return key.equals(otherPair.key) && value.equals(otherPair.value);
}
else
return false;
}
public int hashCode() {
int k = (key == null) ? 0 : key.hashCode();
int v = (value == null) ? 0 : value.hashCode();
return k ^ v;
}
public String toString() {
return ((key == null) ? "''" : key) + " => " + ((value == null) ? "''" : value);
}
}
public static class KvIterator implements Iterator<KvPair>
{
private Iterator<String> namesIt;
private Properties properties;
public KvIterator(Properties p)
{
properties = p;
namesIt = p.stringPropertyNames().iterator();
}
public boolean hasNext()
{
return namesIt.hasNext();
}
public KvPair next()
{
String propName = namesIt.next();
return new KvPair(propName, properties.getProperty(propName));
}
public void remove()
{
throw new UnsupportedOperationException();
}
}
// in here we maintain the current scanner state and token
private LineNumberReader input;
private TokenType currentToken;
private String currentSectionName;
private KvPair tokenKV;
public ConfigFileParser()
{
defaultProperties = new Properties();
sections = new HashMap<String, Properties>();
currentSectionName = null;;
}
/**
* Reads the next high-level token from the input file.
* Advances the reader to the next high-level token, either a section title or
* a key-value assignment.
*
* @return true if the next token was read; false if the token was not advanced because there's no more to read.
* @exception IOException Something bad happened while reading the file.
* @exception FormatException An invalid line was read from the config file.
*/
private boolean advanceToken() throws IOException, FormatException
{
String line = null;
boolean done = false;
do
{
line = input.readLine();
if (line != null) // readLine returns null on EOF
{
line = line.trim();
if (line.isEmpty() || line.charAt(0) == '#' || line.charAt(0) == ';')
{
// comment line. Skip
}
else
{
Matcher titleMatcher = PSectionTitle.matcher(line);
Matcher kvMatcher = PKvLine.matcher(line);
if (titleMatcher.matches())
{
currentToken = TokenType.SectionName;
currentSectionName = titleMatcher.group(1);
done = true;
}
else if (kvMatcher.matches())
{
currentToken = TokenType.KeyValue;
tokenKV = new KvPair(kvMatcher.group(1).trim(), kvMatcher.group(2).trim());
done = true;
}
else
throw new FormatException("Invalid config line (" + input.getLineNumber() + "): " + line);
}
}
else
done = true; // EOF
}
while (!done);
return line != null;
}
public void load(Reader cfgFile) throws IOException, FormatException
{
input = new LineNumberReader(cfgFile);
// re-initialize data structures to support reloading
defaultProperties = new Properties();
sections = new HashMap<String, Properties>();
currentSectionName = null;
while (advanceToken())
{
if (TokenType.KeyValue == currentToken)
{
Properties p;
if (currentSectionName == null || currentSectionName.equalsIgnoreCase(DefaultSectionName))
p = defaultProperties;
else
p = sections.get(currentSectionName);
p.put(tokenKV.getKey(), tokenKV.getValue());
}
else if (TokenType.SectionName == currentToken)
{
sections.put(currentSectionName, new Properties(defaultProperties));
}
else
throw new RuntimeException("Unknown token type " + currentToken + ". Please file a bug report");
}
}
public Collection<String> getSectionNames()
{
return sections.keySet();
}
public boolean hasSection(String sectionName)
{
if (sectionName != null && sectionName.equalsIgnoreCase(DefaultSectionName))
return true;
else
return sections.containsKey(sectionName);
}
public Iterator<KvPair> getSectionIterator(String sectionName)
{
Properties p = sections.get(sectionName);
if (p == null)
p = new Properties(defaultProperties);
return new KvIterator(p);
}
public String getValue(String sectionName, String key)
{
Properties section = sections.get(sectionName);
if (section != null)
return section.getProperty(key);
else
return defaultProperties.getProperty(key);
}
}