/* * NOTE: This copyright does *not* cover user programs that use HQ * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2004, 2005, 2006], Hyperic, Inc. * This file is part of HQ. * * HQ is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. */ package org.hyperic.util.paramParser; import org.hyperic.util.TextIndenter; import java.io.PrintStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * Class which parses parameter strings based on a parameter * format. * * The format is a textual description with a few specific rules: * * - Literal strings are required literal strings. * e.g. '-value' means the string '-value' must exist * * - '|' indicates that a <> container should accept the first * value which matches any of the values. (I.e. <foo | bar>) * * - '#' indicates a class to be called to handle the associated value * * - Atoms must be surrounded by either '<>' or '[]' indicating * the atom is required or optional, respectively. * If the atom contains a '$String' at the beginning, and if a BlockHandler * is registered, it will be called after that block is processed. * * Example: * * <$setVal -value #Integer> [<-from #PastDate> <-to #FutureDate> * [-interval #com.mything.Interval]] */ public class ParamParser implements BlockHandler { private ContainerAtom format; private ParserRetriever retriever; private BlockHandler blockHandler; public ParamParser(String formatText){ this.init(new BasicRetriever(), formatText, this); } /** * Setup the ParamParser with the specified format and default * parser retriever. * * @param formatText Format to use when parsing parameters * @param blockHandler Handler to use to process data blocks * * @throws FormatException indicating there is an error in the * formatText */ public ParamParser(String formatText, BlockHandler blockHandler){ this.init(new BasicRetriever(), formatText, blockHandler); } /** * Setup the ParamParser with the specified format and the * user specified parser retriever * * @param formatText Format to use when parsing parameters * @param retriever Object to call when attempting to instantiate * ParserFormat objects to fulfill #class parameters * @param blockHandler Handler to use to process data blocks * * @throws FormatException indicating there is an error in the * formatText */ public ParamParser(String formatText, ParserRetriever retriever, BlockHandler blockHandler) { this.init(retriever, formatText, blockHandler); } private void init(ParserRetriever retriever, String formatText, BlockHandler blockHandler) { char[] formatChars; this.retriever = retriever; formatChars = formatText.toCharArray(); this.format = this.parseFormat(formatChars, 0, formatChars.length); this.blockHandler = blockHandler; this.format.setRequired(true); } /** * Parse a parameter string. The validation of the parameter string * will be performed, as well as calling of any #class based * format parsers. * * @param params Parameters to validate against the format * * @return An array of FormatParser[] objects which represent the * output from the parsing process. StringParser objects * are returned for every literal element in the parse string, * else the parser types are defined by the #class elements * in the format string * * @throws ParseException if there was an error parsing the parameters */ public ParseResult parseParams(String[] params) throws ParseException { ParseResult res; List processedElems; processedElems = new ArrayList(); res = new ParseResult("**MAIN**"); if(this.parseAtom(res, processedElems, this.format, params, 0) != params.length) { throw new ParseException("All parameters not converted"); } return res; } /** * Attempt to satisfy the conditions of an atom with the given * parameters. * * @param res ParseResult representing this block * @param procList Location to put resultant argument sent * @param atom Atom to satisfy * @param params Params to use to satisfy the atom * @param begIdx Offset into 'params' to begin parsing * * @return The # of parameters eaten from 'params' */ private int parseAtom(ParseResult res, List procList, FormatAtom atom, String[] params, int begIdx) throws ParseException { boolean isRequired = atom.isRequired(); if(atom instanceof ContainerAtom && ((ContainerAtom)atom).isORed() == false) { ContainerAtom cAtom = (ContainerAtom)atom; ArrayList addList = new ArrayList(); ParseResult contResult; int curIdx; contResult = new ParseResult(cAtom.getBlockName()); res.addChild(contResult); curIdx = begIdx; for(Iterator i=cAtom.getSubAtoms().iterator(); i.hasNext(); ){ FormatAtom subAtom = (FormatAtom)i.next(); ArrayList subList = new ArrayList(); int nEaten; try { nEaten = this.parseAtom(contResult, subList, subAtom, params, curIdx); } catch(ParseException exc){ if(isRequired){ res.removeChild(contResult); throw exc; } else { res.removeChild(contResult); return 0; } } curIdx += nEaten; addList.addAll(subList); } this.callBlockHandler(contResult, addList); procList.addAll(addList); return curIdx - begIdx; } else if(atom instanceof ContainerAtom && ((ContainerAtom)atom).isORed() == true) { ContainerAtom cAtom = (ContainerAtom)atom; ParseException lastException = null; ParseResult contResult; contResult = new ParseResult(cAtom.getBlockName()); res.addChild(contResult); for(Iterator i=cAtom.getSubAtoms().iterator(); i.hasNext(); ){ FormatAtom subAtom = (FormatAtom)i.next(); ArrayList subList = new ArrayList(); int nEaten; try { nEaten = this.parseAtom(contResult, subList, subAtom, params, begIdx); } catch(CriticalParseException exc){ throw exc; } catch(ParseException exc){ lastException = exc; continue; } this.callBlockHandler(contResult, subList); procList.addAll(subList); return nEaten; } res.removeChild(contResult); if(isRequired) throw new ParseException("| condition not met", lastException); else return 0; } else if(atom instanceof LiteralAtom){ LiteralAtom lAtom = (LiteralAtom)atom; String literal; literal = lAtom.getLiteral(); if(begIdx >= params.length){ throw new ParseException("No params left to handle literal '" + literal + "'"); } if(params[begIdx].equals(literal)){ procList.add(new StringParser(literal)); return 1; } else if(isRequired){ throw new ParseException("Required parameter '" + literal + "' not provided"); } else { return 0; } } else if(atom instanceof ClassAtom){ ClassAtom cAtom = (ClassAtom)atom; FormatParser parser; if(begIdx >= params.length){ throw new ParseException("No params left to handle class '" + cAtom.getParser().getClass().getName() + "'"); } parser = cAtom.getParser(); parser.parseValue(params[begIdx]); procList.add(parser); return 1; } else { throw new IllegalStateException("Unhandled atom type"); } } private void dumpAtom(TextIndenter tInd, FormatAtom atom, String txt){ boolean required = atom.isRequired(); tInd.append(required ? "<" : "["); tInd.append(txt); tInd.append(required ? ">\n" : "]\n"); } private void dumpFormat(TextIndenter tInd, ContainerAtom atom){ String containerStr; containerStr = atom.getBlockName(); if(containerStr != null) containerStr = "Container:" + containerStr; else containerStr = "Container"; this.dumpAtom(tInd, atom, containerStr); tInd.pushIndent(); for(Iterator i=atom.getSubAtoms().iterator(); i.hasNext(); ){ FormatAtom subAtom = (FormatAtom)i.next(); if(subAtom instanceof ContainerAtom){ dumpFormat(tInd, (ContainerAtom)subAtom); } else if(subAtom instanceof ClassAtom){ this.dumpAtom(tInd, subAtom, "class " + ((ClassAtom)subAtom).getParser().getClass().getName()); } else if(subAtom instanceof LiteralAtom){ this.dumpAtom(tInd, subAtom, "literal " + ((LiteralAtom)subAtom).getLiteral()); } } tInd.popIndent(); } /** * Dump the internal format of the textual format which was passed * when creating the ParamParser object * * @param os Stream to print the representation to */ public void dumpFormat(PrintStream os){ TextIndenter tInd; tInd = new TextIndenter(); this.dumpFormat(tInd, this.format); os.print(tInd.toString()); } /** * Returns the idx of last character which is part of the class name. */ private int findClassNameEnd(char[] chars, int beginIdx, int endIdx){ boolean doStart = true; for(int i=beginIdx; i<endIdx; i++){ if(doStart){ if(!Character.isJavaIdentifierStart(chars[i])){ throw new FormatException("Invalid class identifier at " + "index " + i); } doStart = false; continue; } if(!(Character.isJavaIdentifierPart(chars[i]) || chars[i] == '.')) { return i - 1; } } return endIdx - 1; } /** * Returns the idx of last character which is not whitespace */ private int findLiteralEnd(char[] chars, int beginIdx, int endIdx){ for(int i=beginIdx; i<endIdx; i++){ if(Character.isWhitespace(chars[i])){ return i - 1; } } return endIdx - 1; } private int findMatchingEnd(char[] chars, char startToken, char endToken, int beginIdx, int endIdx) { int depth = 0; for(int i=beginIdx; i<endIdx; i++){ if(chars[i] == startToken) depth++; else if(chars[i] == endToken) depth--; if(depth == 0) return i; } return -1; } private int findBlockNameEnd(char[] chars, int beginIdx, int endIdx){ for(int i=beginIdx; i<endIdx; i++){ if(!Character.isLetterOrDigit(chars[i])){ return i - 1; } } return endIdx - 1; } /** * Parse the characters in 'formatChars' from start to end - 1, and * return the Atom which the characters represent. */ private ContainerAtom parseFormat(char[] formatChars, int start, int end){ ContainerAtom res; int i; res = new ContainerAtom(); res.setBlockName("**MAIN**"); i = start; while(i < end){ int atomEnd; if(formatChars[i] == '<' || formatChars[i] == '['){ ContainerAtom newAtom; char begChar, endChar; String blockName; begChar = formatChars[i]; endChar = begChar == '<' ? '>' : ']'; atomEnd = this.findMatchingEnd(formatChars, begChar, endChar, i, end); if(atomEnd == -1){ throw new FormatException("Could not find closing " + endChar + " to match " + begChar + " at index " + i); } // Figure out the blockname if it has one blockName = null; if(i + 1 < end && formatChars[i + 1] == '$'){ int blockNameEnd; i++; blockNameEnd = this.findBlockNameEnd(formatChars, i + 1, atomEnd); if(blockNameEnd == -1){ throw new FormatException("Could not find end of " + "block name at index " + i); } blockName = new String(formatChars, i + 1, blockNameEnd - i); i = blockNameEnd + 1; } newAtom = parseFormat(formatChars, i + 1, atomEnd); newAtom.setRequired(begChar == '<'); if(blockName == null) blockName = "Block#" + System.identityHashCode(newAtom); newAtom.setBlockName(blockName); res.addSubAtom(newAtom); i = atomEnd + 1; } else if(Character.isWhitespace(formatChars[i])){ // Skipola i++; } else if(formatChars[i] == '#'){ ClassAtom newAtom; String className; atomEnd = this.findClassNameEnd(formatChars, i + 1, end); className = new String(formatChars, i + 1, atomEnd - i); newAtom = new ClassAtom(this.retriever.getParser(className)); newAtom.setRequired(true); res.addSubAtom(newAtom); i = atomEnd + 1; } else { LiteralAtom newAtom; String literal; atomEnd = this.findLiteralEnd(formatChars, i, end) + 1; literal = new String(formatChars, i, atomEnd - i); newAtom = new LiteralAtom(literal); newAtom.setRequired(true); res.addSubAtom(newAtom); i = atomEnd + 1; } } return res; } private void callBlockHandler(ParseResult result, List blockData){ this.blockHandler.handleBlock(result, (FormatParser[])blockData.toArray(new FormatParser[0])); } public void handleBlock(ParseResult resultBlock, FormatParser[] blockData){ System.out.println("Processing block '" + resultBlock.getBlockName() + "'"); if(resultBlock.getBlockName().equals("TimeRange")){ resultBlock.getRoot().setValue("Foo", "DoinTimeRange"); } for(int i=0; i<blockData.length; i++){ System.out.println(" " + blockData[i]); } } public static void main(String[] args) throws Exception { String format = "<<-foo #Integer> | <-bar #String>> " + "[$TimeRange <-from #PastDate> <-to #FutureDate> " + "[-interval #Integer]]"; ParamParser p = new ParamParser(format); p.dumpFormat(System.out); System.out.print(p.parseParams(args).toString()); } }