All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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