Mercurial > hg > blitz_condensed
view src/com/go/trove/util/PropertyParser.java @ 34:6f68e94c1fb8 default tip
Add CondensedStats monitoring utility, equivalent to vmstat
author | Dominic Cleal <dominic-cleal@cdo2.com> |
---|---|
date | Thu, 05 Aug 2010 11:07:25 +0100 |
parents | 3dc0c5604566 |
children |
line wrap: on
line source
/* ==================================================================== * Trove - Copyright (c) 1997-2000 Walt Disney Internet Group * ==================================================================== * The Tea Software License, Version 1.1 * * Copyright (c) 2000 Walt Disney Internet Group. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Walt Disney Internet Group (http://opensource.go.com/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Tea", "TeaServlet", "Kettle", "Trove" and "BeanDoc" must * not be used to endorse or promote products derived from this * software without prior written permission. For written * permission, please contact opensource@dig.com. * * 5. Products derived from this software may not be called "Tea", * "TeaServlet", "Kettle" or "Trove", nor may "Tea", "TeaServlet", * "Kettle", "Trove" or "BeanDoc" appear in their name, without prior * written permission of the Walt Disney Internet Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE WALT DISNEY INTERNET GROUP OR ITS * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== * * For more information about Tea, please see http://opensource.go.com/. */ package com.go.trove.util; import java.util.*; import java.io.InputStream; import java.io.Reader; import java.io.IOException; import java.io.Serializable; import com.go.trove.io.SourceInfo; import com.go.trove.io.SourceReader; /****************************************************************************** * Parses a properties file similar to how {@link java.util.Properties} does, * except: * * <ul> * <li>Values have trailing whitespace trimmed. * <li>Quotation marks ( " or ' ) can be used to define keys and values that * have embedded spaces. * <li>Quotation marks can also be used to define multi-line keys and values * without having to use continuation characters. * <li>Properties may be nested using braces '{' and '}'. * </ul> * * Just like Properties, comment lines start with optional whitespace followed * by a '#' or '!'. Keys and values may have an optional '=' or ':' as a * separator, unicode escapes are supported as well as other common escapes. * A line may end in a backslash so that it continues to the next line. * Escapes for brace characters '{' and '}' are also supported. * * Example: * * <pre> * # Properties file * * foo = bar * foo.sub = blink * empty * * block { * inner { * foo = bar * item * } * next.item = "true" * } * * section = test { * level = 4 * message = "Message: " * } * </pre> * * is equivalent to * * <pre> * # Properties file * * foo = bar * foo.sub = blink * empty * * block.inner.foo = bar * block.inner.item * block.next.item = true * * section = test * section.level = 4 * section.message = Message: * </pre> * * @author Brian S O'Neill * @version * <!--$$Revision: 1.1 $-->, <!--$$JustDate:--> 12/11/00 <!-- $--> */ public class PropertyParser { // Parsed grammer (EBNF) is: // // Properties ::= { PropertyList } // PropertyList ::= { Property | COMMENT } // Property ::= KEY [ VALUE ] [ Block ] // Block ::= LBRACE PropertyList RBRACE private Map mMap; private Vector mListeners = new Vector(1); private int mErrorCount = 0; private Scanner mScanner; /** * @param map Map to receive properties */ public PropertyParser(Map map) { mMap = map; } public void addErrorListener(ErrorListener listener) { mListeners.addElement(listener); } public void removeErrorListener(ErrorListener listener) { mListeners.removeElement(listener); } private void dispatchParseError(ErrorEvent e) { mErrorCount++; synchronized (mListeners) { for (int i = 0; i < mListeners.size(); i++) { ((ErrorListener)mListeners.elementAt(i)).parseError(e); } } } private void error(String str, SourceInfo info) { dispatchParseError(new ErrorEvent(this, str, info)); } private void error(String str, Token token) { error(str, token.getSourceInfo()); } /** * Parses properties from the given reader and stores them in the Map. To * capture any parsing errors, call addErrorListener prior to parsing. */ public void parse(Reader reader) throws IOException { mScanner = new Scanner(reader); mScanner.addErrorListener(new ErrorListener() { public void parseError(ErrorEvent e) { dispatchParseError(e); } }); try { parseProperties(); } finally { mScanner.close(); } } private void parseProperties() throws IOException { Token token; while ((token = peek()).getId() != Token.EOF) { switch (token.getId()) { case Token.KEY: case Token.LBRACE: case Token.COMMENT: parsePropertyList(null); break; case Token.RBRACE: token = read(); error("No matching left brace", token); break; default: token = read(); error("Unexpected token: " + token.getValue(), token); break; } } } private void parsePropertyList(String keyPrefix) throws IOException { Token token; loop: while ((token = peek()).getId() != Token.EOF) { switch (token.getId()) { case Token.KEY: token = read(); parseProperty(keyPrefix, token); break; case Token.COMMENT: read(); break; case Token.LBRACE: read(); error("Nested properties must have a base name", token); parseBlock(keyPrefix); break; default: break loop; } } } private void parseProperty(String keyPrefix, Token token) throws IOException { String key = token.getValue(); if (keyPrefix != null) { key = keyPrefix + key; } String value = null; if (peek().getId() == Token.VALUE) { token = read(); value = token.getValue(); } if (peek().getId() == Token.LBRACE) { read(); parseBlock(key + '.'); } else if (value == null) { value = ""; } if (value != null) { putProperty(key, value, token); } } // When this is called, the LBRACE token has already been read. private void parseBlock(String keyPrefix) throws IOException { parsePropertyList(keyPrefix); Token token; if ((token = peek()).getId() == Token.RBRACE) { read(); } else { error("Right brace expected", token); } } private void putProperty(String key, String value, Token token) { if (mMap.containsKey(key)) { error("Property \"" + key + "\" already defined", token); } mMap.put(key, value); } /** * Total number of errors accumulated by this PropertyParser instance. */ public int getErrorCount() { return mErrorCount; } private Token read() throws IOException { return mScanner.readToken(); } private Token peek() throws IOException { return mScanner.peekToken(); } private void unread(Token token) throws IOException { mScanner.unreadToken(token); } /************************************************************************** * * @author Brian S O'Neill * @version * <!--$$Revision: 1.1 $-->, <!--$$JustDate:--> 12/11/00 <!-- $--> */ public static interface ErrorListener extends java.util.EventListener { public void parseError(ErrorEvent e); } /************************************************************************** * * @author Brian S O'Neill * @version * <!--$$Revision: 1.1 $-->, <!--$$JustDate:--> 12/11/00 <!-- $--> */ public static class ErrorEvent extends java.util.EventObject { private String mErrorMsg; private SourceInfo mInfo; ErrorEvent(Object source, String errorMsg, SourceInfo info) { super(source); mErrorMsg = errorMsg; mInfo = info; } public String getErrorMessage() { return mErrorMsg; } /** * Returns the error message prepended with source file information. */ public String getDetailedErrorMessage() { String prepend = getSourceInfoMessage(); if (prepend == null || prepend.length() == 0) { return mErrorMsg; } else { return prepend + ": " + mErrorMsg; } } public String getSourceInfoMessage() { if (mInfo == null) { return ""; } else { return String.valueOf(mInfo.getLine()); } } /** * This method reports on where in the source code an error was found. * * @return Source information on this error or null if not known. */ public SourceInfo getSourceInfo() { return mInfo; } } /************************************************************************** * * @author Brian S O'Neill * @version * <!--$$Revision: 1.1 $-->, <!--$$JustDate:--> 12/11/00 <!-- $--> */ private static class Token implements java.io.Serializable { public final static int UNKNOWN = 0; public final static int EOF = 1; public final static int COMMENT = 2; public final static int KEY = 3; public final static int VALUE = 4; public final static int LBRACE = 5; public final static int RBRACE = 6; private final static int LAST_ID = 6; private int mTokenId; private String mValue; private SourceInfo mInfo; Token(int sourceLine, int sourceStartPos, int sourceEndPos, int tokenId, String value) { mTokenId = tokenId; mValue = value; if (tokenId > LAST_ID) { throw new IllegalArgumentException("Token Id out of range: " + tokenId); } mInfo = new SourceInfo(sourceLine, sourceStartPos, sourceEndPos); if (sourceStartPos > sourceEndPos) { // This is an internal error. throw new IllegalArgumentException ("Token start position greater than " + "end position at line: " + sourceLine); } } public Token(SourceInfo info, int tokenId, String value) { mTokenId = tokenId; if (tokenId > LAST_ID) { throw new IllegalArgumentException("Token Id out of range: " + tokenId); } mInfo = info; } public final int getId() { return mTokenId; } /** * Token code is non-null, and is exactly the same as the name for * its Id. */ public String getCode() { return Code.TOKEN_CODES[mTokenId]; } public final SourceInfo getSourceInfo() { return mInfo; } public String getValue() { return mValue; } public String toString() { StringBuffer buf = new StringBuffer(10); String image = getCode(); if (image != null) { buf.append(image); } String str = getValue(); if (str != null) { if (image != null) { buf.append(' '); } buf.append('"'); buf.append(str); buf.append('"'); } return buf.toString(); } private static class Code { public static final String[] TOKEN_CODES = { "UNKNOWN", "EOF", "COMMENT", "KEY", "VALUE", "LBRACE", "RBRACE", }; } } /************************************************************************** * * @author Brian S O'Neill * @version * <!--$$Revision: 1.1 $-->, <!--$$JustDate:--> 12/11/00 <!-- $--> */ private static class Scanner { private SourceReader mSource; /** The scanner supports any amount of lookahead. */ private Stack mLookahead = new Stack(); private boolean mScanKey = true; private Token mEOFToken; private Vector mListeners = new Vector(1); private int mErrorCount = 0; public Scanner(Reader in) { mSource = new SourceReader(in, null, null); } public void addErrorListener(ErrorListener listener) { mListeners.addElement(listener); } public void removeErrorListener(ErrorListener listener) { mListeners.removeElement(listener); } private void dispatchParseError(ErrorEvent e) { mErrorCount++; synchronized (mListeners) { for (int i = 0; i < mListeners.size(); i++) { ((ErrorListener)mListeners.elementAt(i)).parseError(e); } } } private void error(String str, SourceInfo info) { dispatchParseError(new ErrorEvent(this, str, info)); } private void error(String str) { error(str, new SourceInfo(mSource.getLineNumber(), mSource.getStartPosition(), mSource.getEndPosition())); } /** * Returns EOF as the last token. */ public synchronized Token readToken() throws IOException { if (mLookahead.empty()) { return scanToken(); } else { return (Token)mLookahead.pop(); } } /** * Returns EOF as the last token. */ public synchronized Token peekToken() throws IOException { if (mLookahead.empty()) { return (Token)mLookahead.push(scanToken()); } else { return (Token)mLookahead.peek(); } } public synchronized void unreadToken(Token token) throws IOException { mLookahead.push(token); } public void close() throws IOException { mSource.close(); } public int getErrorCount() { return mErrorCount; } private Token scanToken() throws IOException { if (mSource.isClosed()) { if (mEOFToken == null) { mEOFToken = makeToken(Token.EOF, null); } return mEOFToken; } int c; int peek; int startPos; while ( (c = mSource.read()) != -1 ) { switch (c) { case SourceReader.ENTER_CODE: case SourceReader.ENTER_TEXT: continue; case '#': case '!': mScanKey = true; return scanComment(); case '{': mScanKey = true; return makeToken(Token.LBRACE, "{"); case '}': mScanKey = true; return makeToken(Token.RBRACE, "}"); case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case '.': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': mSource.unread(); return scanKeyOrValue(); case '\n': mScanKey = true; // fall through case ' ': case '\0': case '\t': continue; default: if (Character.isWhitespace((char)c)) { continue; } else { mSource.unread(); return scanKeyOrValue(); } } } if (mEOFToken == null) { mEOFToken = makeToken(Token.EOF, null); } return mEOFToken; } private Token scanKeyOrValue() throws IOException { StringBuffer buf = new StringBuffer(40); boolean trim = true; int startLine = mSource.getLineNumber(); int startPos = mSource.getStartPosition(); int endPos = mSource.getEndPosition(); boolean skipWhitespace = true; boolean skipSeparator = true; int c; loop: while ( (c = mSource.read()) != -1 ) { switch (c) { case '\n': mSource.unread(); break loop; case '\\': int next = mSource.read(); if (next == -1 || next == '\n') { // line continuation skipWhitespace = true; continue; } c = processEscape(c, next); skipWhitespace = false; break; case '{': case '}': mSource.unread(); break loop; case '=': case ':': if (mScanKey) { mSource.unread(); break loop; } else if (skipSeparator) { skipSeparator = false; continue; } skipWhitespace = false; break; case '\'': case '"': if (buf.length() == 0) { scanStringLiteral(c, buf); endPos = mSource.getEndPosition(); trim = false; break loop; } // fall through case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case '.': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': skipWhitespace = false; break; case ' ': case '\0': case '\t': if (skipWhitespace) { continue; } if (mScanKey) { break loop; } break; default: if (Character.isWhitespace((char)c)) { if (skipWhitespace) { continue; } if (mScanKey) { break loop; } } else { skipWhitespace = false; } break; } buf.append((char)c); endPos = mSource.getEndPosition(); skipSeparator = false; } int tokenId; if (mScanKey) { tokenId = Token.KEY; mScanKey = false; } else { tokenId = Token.VALUE; mScanKey = true; } String value = buf.toString(); if (trim) { value = value.trim(); } return new Token(startLine, startPos, endPos, tokenId, value); } private Token scanComment() throws IOException { StringBuffer buf = new StringBuffer(40); int startLine = mSource.getLineNumber(); int startPos = mSource.getStartPosition(); int endPos = mSource.getEndPosition(); int c; while ( (c = mSource.peek()) != -1 ) { if (c == '\n') { break; } mSource.read(); buf.append((char)c); endPos = mSource.getEndPosition(); } return new Token(startLine, startPos, endPos, Token.COMMENT, buf.toString()); } private void scanStringLiteral(int quote, StringBuffer buf) throws IOException { int c; while ( (c = mSource.read()) != -1 ) { if (c == quote) { return; } if (c == '\\') { int next = mSource.read(); if (next == -1 || next == '\n') { // line continuation continue; } c = processEscape(c, next); } buf.append((char)c); } } private int processEscape(int c, int next) { switch (next) { case '0': return '\0'; case 't': return '\t'; case 'n': return '\n'; case 'f': return '\f'; case 'r': return '\r'; case '\\': case '\'': case '\"': case '=': case ':': case '{': case '}': return next; default: error("Invalid escape code: \\" + (char)next); return next; } } private Token makeToken(int Id, String value) { return new Token(mSource.getLineNumber(), mSource.getStartPosition(), mSource.getEndPosition(), Id, value); } } }