com.eva.properties.PropertiesParser Maven / Gradle / Ivy
/*
* $Id: PropertiesParser.java 109 2007-03-24 14:55:03Z max $
*
* Copyright (c) 2006-2007 Maximilian Antoni. All rights reserved.
*
* This software is licensed as described in the file LICENSE.txt, which you
* should have received as part of this distribution. The terms are also
* available at http://www.maxantoni.de/projects/eva-properties/license.txt.
*/
package com.eva.properties;
import java.io.IOException;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* parses property files.
*
* @author Max Antoni
* @version $Revision: 109 $
*/
class PropertiesParser {
/**
* the underlying stream tokenizer.
*/
private StreamTokenizer st;
/**
* the current properties object being parsed. This properties object is
* used as the parent for nested properties.
*/
private Properties current;
/**
* creates a properties parser with the given reader.
*
* @param inReader the reader.
*/
private PropertiesParser(Reader inReader) {
super();
if(inReader == null) {
throw new NullPointerException();
}
st = new StreamTokenizer(inReader);
st.resetSyntax();
st.wordChars('a', 'z');
st.wordChars('A', 'Z');
st.parseNumbers();
st.wordChars(128 + 32, 255);
st.wordChars('_', '_');
st.whitespaceChars(0, ' ');
st.quoteChar('"');
st.quoteChar('\'');
st.ordinaryChar('.');
st.eolIsSignificant(true);
st.slashSlashComments(true);
st.slashStarComments(true);
}
/**
* reads an object from a string.
*
* @param inString the string.
* @return the object.
* @throws PropertiesException if the string cannot be parsed.
*/
static Object readObject(String inString) throws PropertiesException {
try {
return readObject(new StringReader(inString));
}
catch(IOException e) {
throw new PropertiesException(e);
}
}
/**
* reads a properties object from a data source. The type of property
* returned depends on the file structure.
*
* @param inDataSource the data source.
* @return the properties object.
* @throws IOException
* @throws PropertiesException
*/
static Properties read(DataSource inDataSource) throws IOException,
PropertiesException {
return read(null, inDataSource);
}
/**
* reads a properties object from a data source using the provided properties
* object as the parent properties. The type of property returned depends on
* the file structure.
*
* @param inParent the parent properties.
* @param inDataSource the data source.
* @return the properties object.
* @throws IOException
* @throws PropertiesException
*/
static Properties read(Properties inParent, DataSource inDataSource)
throws IOException, PropertiesException {
Properties properties = new PropertiesParser(inDataSource.getReader())
.mapOrList(inParent);
if(properties instanceof MapProperties) {
initMap((MapProperties) properties, inDataSource);
}
else {
initList((ListProperties) properties, inDataSource);
}
return properties;
}
/**
* reads a map properties object from a data source. The given map
* properties will be filled with the parsed keys and values and
* {@link #initMap(MapProperties, DataSource)} is used to initialize the
* map.
*
* @param inoutMap the map properties to be filled by the parser.
* @param inDataSource the data source.
* @throws PropertiesException
*/
static void readMap(MapProperties inoutMap, DataSource inDataSource)
throws PropertiesException {
PropertiesParser parser = new PropertiesParser(inDataSource.getReader());
parser.current = inoutMap;
try {
parser.map(inoutMap, StreamTokenizer.TT_EOF);
}
catch(IOException e) {
throw new PropertiesException(e);
}
initMap(inoutMap, inDataSource);
}
/**
* reads a list properties object from a data source. The given list
* properties will be filled with the parsed values and
* {@link #initList(ListProperties, DataSource)} is used to initialize the
* list.
*
* @param inoutList the list properties to be filled by the parser.
* @param inDataSource the data source.
* @throws PropertiesException
*/
static void readList(ListProperties inoutList, DataSource inDataSource)
throws PropertiesException {
PropertiesParser parser = new PropertiesParser(inDataSource.getReader());
parser.current = inoutList;
try {
parser.list(inoutList, StreamTokenizer.TT_EOF);
}
catch(IOException e) {
throw new PropertiesException(e);
}
initList(inoutList, inDataSource);
}
/**
* initializes a list properties object with the given data source. The list
* is checked for maps to initialize.
*
* @param inoutList the list properties.
* @param inDataSource the data source.
*/
private static void initList(ListProperties inoutList,
DataSource inDataSource) {
for(Iterator i = inoutList.iterator(); i.hasNext();) {
Object o = i.next();
if(o instanceof MapProperties) {
initMap((MapProperties) o, inDataSource);
}
else if(o instanceof ListProperties) {
// Recurse down list properties:
initList((ListProperties) o, inDataSource);
}
}
}
/**
*
* initializes a map properties object with the given data source.
*
*
* If there is not datasource-base
property in the map, it is
* set with the value provided by {@link DataSource#getDelegateBase()}.
*
*
* If the data source provided a class laoder, the class loader is stored in
* the map properties under the key classloader
.
*
*
* @param inoutMap the map properties.
* @param inDataSource the data source.
*/
private static void initMap(MapProperties inoutMap, DataSource inDataSource) {
if(!inoutMap.containsKeyInternal(Properties.DATASOURCE_BASE)) {
inoutMap.putInternal(Properties.DATASOURCE_BASE,
inDataSource.getDelegateBase());
}
ClassLoader classLoader = inDataSource.getClassLoader();
if(classLoader != null) {
inoutMap.put("classloader", classLoader);
}
}
/**
* reads an object from a reader.
*
* @param inReader the reader.
* @return the object.
* @throws IOException
* @throws PropertiesException
*/
static Object readObject(Reader inReader) throws IOException,
PropertiesException {
try {
return new PropertiesParser(inReader).primary(true);
}
catch(ParserException e) {
throw new PropertiesException(e.getMessage());
}
}
/**
* reads a map properties object from a reader. The given map
* properties will be filled with the parsed keys and values.
*
* @param inoutProperties the map properties to be filled by the parser.
* @param inReader the reader.
* @throws PropertiesException
*/
static void readMap(MapProperties inoutProperties, Reader inReader)
throws PropertiesException {
PropertiesParser parser = new PropertiesParser(inReader);
parser.current = inoutProperties;
try {
parser.map(inoutProperties, StreamTokenizer.TT_EOF);
}
catch(IOException e) {
throw new PropertiesException(e.getMessage());
}
}
/**
* reads a list properties object from a reader. The given list
* properties will be filled with the parsed values.
*
* @param inoutProperties the list properties to be filled by the parser.
* @param inReader the reader.
* @throws PropertiesException
*/
static void readList(ListProperties inoutProperties, Reader inReader)
throws PropertiesException {
PropertiesParser parser = new PropertiesParser(inReader);
parser.current = inoutProperties;
try {
parser.list(inoutProperties, StreamTokenizer.TT_EOF);
}
catch(IOException e) {
throw new PropertiesException(e);
}
}
/**
* reads a map or a list.
*
* @param inParent the parent properties to use. Can be null
.
* @return the parsed properties.
* @throws IOException
* @throws PropertiesException
*/
private Properties mapOrList(Properties inParent) throws IOException,
PropertiesException {
Object primary;
try {
primary = primary(true);
}
catch(ParserException e) {
// Handle non-primary types:
switch(st.ttype) {
case '*':
primary = "*";
st.nextToken();
break;
case ':':
if(!"*".equals(e.getMessage())) {
throw new PropertiesException(e.getMessage());
}
primary = "*";
break;
default:
throw new PropertiesException(e.getMessage());
}
}
if(st.ttype == ':' || st.ttype == '=') {
if(!(primary instanceof String)) {
throw new PropertiesException("String expected, "
+ primary.getClass().getName());
}
MapProperties map = new MapProperties(inParent);
current = map;
try {
map.putInternal((String) primary, primary(true));
}
catch(ParserException e) {
throw new PropertiesException(e.getMessage());
}
map(map, StreamTokenizer.TT_EOF);
return map;
}
ListProperties list = new ListProperties(inParent);
current = list;
if(primary instanceof Properties) {
((Properties) primary).setParent(list);
}
list.add(primary);
if(st.ttype == StreamTokenizer.TT_EOF) {
return current;
}
list(list, StreamTokenizer.TT_EOF);
return list;
}
/**
* returns a string representation of the given token. Used to generate
* exception messages.
*
* @param inToken the token.
* @return the string representation of the token.
*/
private String token(int inToken) {
switch (inToken) {
case StreamTokenizer.TT_WORD:
return st.sval;
case '\'':
return '\'' + st.sval + '\'';
case '"':
return '"' + st.sval + '"';
case StreamTokenizer.TT_NUMBER:
return String.valueOf(st.nval);
case StreamTokenizer.TT_EOF:
return "EOF";
case StreamTokenizer.TT_EOL:
return "EOL";
default:
return "" + (char) inToken;
}
}
/**
* returns an error message containing the current token and line number.
*
* @return the error message.
*/
private String error() {
return token(st.ttype) + " line " + st.lineno();
}
/**
* converts the given double to either a Long
or
* Double
object.
*
* @param inNumber the double.
* @return the number.
* @throws IOException
*/
private Number number(double inNumber) throws IOException {
if(st.nextToken() == '.') {
if(st.nextToken() != StreamTokenizer.TT_NUMBER) {
throw new IllegalStateException("Number expected, " + error());
}
st.nextToken();
return Double.valueOf(inNumber + "." + st.nval);
}
if(((long) inNumber) == inNumber) {
return new Long((long) inNumber);
}
return new Double(inNumber);
}
/**
* reads a primary value from the tokenizer.
*
* @param inGet specifies whether to initially read a token from the
* tokenizer.
* @return the primary object.
* @throws IOException
* @throws ParserException
*/
private Object primary(boolean inGet) throws IOException, ParserException {
int token = inGet ? st.nextToken() : st.ttype;
while(token == StreamTokenizer.TT_EOL) {
token = st.nextToken();
}
switch(token) {
case StreamTokenizer.TT_WORD:
String s = st.sval;
st.nextToken();
if("null".equals(s) || "nil".equals(s)) {
return Null.INSTANCE;
}
if("true".equals(s) || "yes".equals(s) || "on".equals(s)) {
return Boolean.TRUE;
}
if("false".equals(s) || "no".equals(s) || "off".equals(s)) {
return Boolean.FALSE;
}
return s;
case StreamTokenizer.TT_NUMBER:
return number(st.nval);
case '\'':
s = st.sval;
st.nextToken();
if(s.length() == 1) {
return new Character(s.charAt(0));
}
return s.toCharArray();
case '"':
s = st.sval;
st.nextToken();
return s;
case '[':
ListProperties list = new ListProperties(current);
current = list;
list(list, ']');
current = list.getParent();
return list;
case '{':
MapProperties map = new MapProperties(current);
current = map;
map(map, '}');
current = map.getParent();
return map;
case '$':
return reference();
case '.':
return doubleNumber();
case '(':
return switcher();
case '&':
return proxy();
case '*':
return factory();
default:
throw new PropertiesException("Unexpected token, " + error());
}
}
/**
* creates a factory.
*
* @return the factory.
* @throws IOException
* @throws ParserException
*/
private Factory factory() throws IOException, ParserException {
List arguments = null;
StringBuffer className = new StringBuffer();
while(true) {
switch (st.nextToken()) {
case StreamTokenizer.TT_WORD:
className.append(st.sval);
break;
case '.':
className.append('.');
break;
case '$':
className.append('$');
break;
case '{':
className.append('{');
break;
case '}':
className.append('}');
break;
case '(':
arguments = list(')');
return new Factory(className.toString(), arguments.toArray());
case ':':
if(className.length() == 0) {
/*
* Happens when the first token in a map properties file is
* a "*" that is meant to be a joker. The exception will be
* catched by mapOrList(...).
*/
throw new ParserException("*");
}
default:
return new Factory(className.toString(), null);
}
}
}
/**
* creates a proxy.
*
* @return the proxy.
* @throws IOException
* @throws PropertiesException
*/
private Proxy proxy() throws IOException, PropertiesException {
boolean proxyParent = st.nextToken() != '!';
Object object;
try {
object = primary(!proxyParent); // get next token if '!' was there.
}
catch(ParserException e) {
throw new PropertiesException(e.getMessage());
}
if(object instanceof String) {
return new Proxy(proxyParent ? current : null, (String) object);
}
if(object instanceof Replaceable) {
return new Proxy(proxyParent ? current : null, (Replaceable) object);
}
throw new PropertiesException("String or Replaceable expected, "
+ object.getClass().getName());
}
/**
* creates a switch.
*
* @return the switch.
* @throws IOException
* @throws PropertiesException
*/
private Switch switcher() throws IOException, PropertiesException {
st.nextToken();
Switch s = new Switch();
while(st.ttype != ')') {
try {
s.add(primary(false));
}
catch(ParserException e) {
throw new PropertiesException(e.getMessage());
}
if(st.ttype != ',' && st.ttype != StreamTokenizer.TT_EOL) {
if(st.ttype == ')') {
break;
}
throw new PropertiesException(") or , or EOL expected, "
+ error());
}
overreadEmptyLines();
}
st.nextToken();
return s;
}
/**
* skips empty lines.
*
* @throws IOException
*/
private void overreadEmptyLines() throws IOException {
do {
st.nextToken();
}
while(st.ttype == StreamTokenizer.TT_EOL);
}
/**
* creates a double.
*
* @return the double.
* @throws IOException
* @throws PropertiesException if the next token is not a number.
*/
private Double doubleNumber() throws IOException, PropertiesException {
if(st.nextToken() != StreamTokenizer.TT_NUMBER) {
throw new PropertiesException("Number expected, " + error());
}
return new Double(st.nval);
}
/**
* creates a reference.
*
* @return the reference.
* @throws IOException
* @throws PropertiesException
*/
private Reference reference() throws IOException, PropertiesException {
if(st.nextToken() != '{') {
throw new PropertiesException("{ expected, " + error());
}
StringBuffer reference = new StringBuffer();
while(st.nextToken() != '}') {
switch(st.ttype) {
case StreamTokenizer.TT_WORD:
reference.append(st.sval);
break;
case StreamTokenizer.TT_NUMBER:
if(((long) st.nval) == st.nval) {
reference.append((long) st.nval);
}
else {
reference.append(st.nval);
}
break;
case '.':
reference.append('.');
break;
case '*':
reference.append('*');
break;
default:
throw new PropertiesException("} expected, " + error());
}
}
st.nextToken();
return new Reference(reference.toString());
}
/**
* fills a map.
*
* @param inoutMap the map to fill.
* @param inToToken the token that marks the end of the map.
* @throws IOException
* @throws PropertiesException
*/
private void map(MapProperties inoutMap, int inToToken) throws IOException,
PropertiesException {
overreadEmptyLines();
while(st.ttype != inToToken) {
String key = key();
st.nextToken();
if(st.ttype != ':' && st.ttype != '=') {
throw new PropertiesException(": or = expected, " + error());
}
try {
inoutMap.putInternal(key, primary(true));
}
catch(ParserException e) {
throw new PropertiesException(e.getMessage());
}
if(st.ttype != ',' && st.ttype != StreamTokenizer.TT_EOL) {
if(st.ttype == inToToken) {
break;
}
throw new PropertiesException(token(inToToken) + " expected, "
+ error());
}
overreadEmptyLines();
}
st.nextToken();
}
/**
* creates a list.
*
* @param inToToken the token that marks the end of the list.
* @return the list.
* @throws IOException
* @throws PropertiesException
*/
private List list(int inToToken) throws IOException, PropertiesException {
List list = new ArrayList();
list(list, inToToken);
return list;
}
/**
* fills a list.
*
* @param inoutList the list to fill.
* @param inToToken the token that marks the end of the list.
* @throws IOException
* @throws PropertiesException
*/
private void list(List inoutList, int inToToken) throws IOException,
PropertiesException {
overreadEmptyLines();
while(st.ttype != inToToken) {
try {
inoutList.add(primary(false));
}
catch(ParserException e) {
throw new PropertiesException(e.getMessage());
}
if(st.ttype != ',' && st.ttype != StreamTokenizer.TT_EOL) {
if(st.ttype == inToToken) {
break;
}
throw new PropertiesException(token(inToToken)
+ " expected, " + error());
}
overreadEmptyLines();
}
st.nextToken();
}
/**
* creates a key for a map.
*
* @return the key.
* @throws IOException
*/
private String key() throws IOException {
while(st.ttype == StreamTokenizer.TT_EOL) {
st.nextToken();
}
switch (st.ttype) {
case StreamTokenizer.TT_NUMBER:
if(((int) st.nval) == st.nval) {
return String.valueOf((long) st.nval);
}
return String.valueOf(st.nval);
case StreamTokenizer.TT_WORD:
case '"':
case '\'':
return st.sval;
case '*':
return "*";
default:
throw new PropertiesException("Unexpected token, " + error());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy