import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Stack; /** * A super simplified PCD parser. * * An overview of this code is as follows: * * main - the main method for demonstrating the parser's * capabilities this method parses input from stdin * and outputs the corresponding PCD object tree * * getScope - an auxiliary function for parsePCD. * This function counts the number of spaces * preceding a line of text and divides it by 4. * * parsePCD - the main body for the PCD parser. * This function parses PCD input from a Java * reader object, and outputs a PCD object tree. * The object tree specification is shown in * function's javadoc documentation (the comments * preceding the method declaration) with examples. * * printPCD - prints any PCD object tree. * This function was created to debug the parsePCD * function. * - see parsePCD for object specifications */ public class PCD { public static void main (String[] args) { try { Object table = parsePCD(new InputStreamReader(System.in)); System.out.println("--- START OUTPUT ---"); printPCD(table, 0); } catch (IOException ioe) { ioe.printStackTrace(); } } /** * Determine the scope of the current line. * * This method counts the number of spaces preceding * the text on the current line and divides that number * by four (4) and returns it. * * Therefore, for every four spaces of indent for a given * line of text, the scope is incremented by 1. * * For example, the line of text (in-between the quotation marks): * * " abc" * * has a scope value of 2 (because "abc" is indented by 8 spaces). ** * @param line a line of PCD to parse * @param lineNumber the current line number * being parsed (for error messages) * @return the scope level of the current line * (-1 if the current line is a comment) */ private static int getScope(String line, int lineNumber) { int spacesRead = 0; // used to count the // number of spaces read boolean commentLine = false; // used for determining whether // the current line is a comment // (i.e. whether it can be skipped) char[] array = line.toCharArray(); // convert the current line to a // character array for easier // and faster parsing (for loop) // iterate through each character on the current line and count // the number of spaces before a first non-space character for (int count = 0; count < array.length; count++) { if (array[count] == ' ') { // if the current character is space, count it spacesRead++; } else if (array[count] == '#') { // if the current line is a comment, // then we can ignore its scope commentLine = true; spacesRead = -4; break; } else { // it the current character is not a space, break! break; } } // ensure that the number of spaces counted // was a multiple of four (4) if (spacesRead % 4 != 0 && ! commentLine) { System.err.println("INVALID SPACING ON LINE #" + lineNumber + ": " + line); System.exit(3); } return (spacesRead / 4); } /** * This function parses input from a reader * object 'inputReader' into a tree structure * composed of Hashtables, ArrayLists, and Strings. * * Hashtables are used for branching in PCD - for example: * * field1 * value1 * field2 * value2 * * ArrayLists are used for PCD lists (multiple * lines with the same scope) - example: * * listItem1 * listItem2 * listItem3 * * Strings are used to represent everything else. * * An example parse would be: * * name * Lysozyme * sequence * dna * CAAGTTCAGCTTAAAGAA... * protein * QVQLKESGPGLVAPSQSL... * substrates * peptidoglycan * N,N',N''-triacetylchitotrioside * * * Which would yield the following structure: * * new Hashtable() { * put ("name", "Lysozyme"); * put ("sequnece", new Hashtable() { * put("dna", "CAAGTTCAGCTTAAAGAA..."); * put("protein", "QVQLKESGPGLVAPSQSL..."); * }); * put ("substrates", new ArrayList() { * add("peptidoglycan"); * add("N,N',N''-triacetylchitotrioside"); * }); * } ** * @param inputReader the reader object to parse input from * @return the object representing the PCD parsed * @throws IOException any i/o exceptions from reading the input */ private static Object parsePCD(Reader inputReader) throws IOException { int scope = 0; int nextScope = 0; int lineNumber = 1; int prevScope = 0; String line = ""; String lastline = ""; String originalLine = ""; BufferedReader reader = new BufferedReader(inputReader); Object current = null; Stack previous = new Stack(); Stack tags = new Stack(); // iterate through every line from standard input do { originalLine = reader.readLine(); // avoid parsing past the end of file // originalLine will only be null if we are at // the end of the current file or stream if (originalLine != null) { nextScope = getScope(originalLine, lineNumber); } // skip comments if (nextScope >= 0 || originalLine == null) { // handle the end of the file parsing if (originalLine != null) { scope = nextScope; line = originalLine.trim(); } else { line = null; scope = -2; } // skip blank lines if (line == null || line.length() > 0) { if (current == null) { current = line; } else if (originalLine == null || scope < prevScope) { // build the hashtable going backwards // go back if the scope is not right for (int count = scope; count < prevScope && !previous.empty(); count++) { Object temp = previous.pop(); // if the current object is a map if (temp instanceof Map) { // pop the last tag because its // value should be the same as current ((Map)temp).put(tags.pop(), current); current = temp; } else { // if the current object is not a map // we must convert it to a hashtable (map) Map newMap = new LinkedHashMap(); // if the current object is a list if (temp instanceof List) { List listtemp = (List)temp; // iterate through the list and copy // each item to the new hashtable for (int countlist = 0; countlist < listtemp.size(); countlist++) { newMap.put(listtemp.get(countlist), Boolean.TRUE); } // pop the last tag because its // value should be the same as current newMap.put(listtemp.get( listtemp.size() - 1), current); } else { // if the current object is a string // just add it to a new map object newMap.put(temp, current); } current = newMap; } } } else if (scope == prevScope + 1) { // if the current scope is incremented // push relevant information onto the stack if (current instanceof Map) { tags.push(lastline); } previous.push(current); current = line; } else if (scope == prevScope) { // handle lists // if the scope hasn't changed, then we must // either append the current line to a list object // or handle appropriately if (current instanceof List) { // if the current object is a list, // add the current line to the list ((List)current).add(line); } else if (current instanceof Map) { // if the current object is a map, add the // current line as a key in the map with a // boolean value of TRUE ((Map)current).put(lastline, Boolean.TRUE); } else { // if the current object is a string // make a new list containing both // the current line and current object List newCurrentList = new ArrayList(); newCurrentList.add(current); newCurrentList.add(line); current = newCurrentList; } } else { // if the next scope level is non-sensical, // return with an error message System.err.println("INVALID SCOPE ON LINE #" + lineNumber + ": " + originalLine); System.exit(2); } // update the last line and prevScope (previous scope) // values to the current line and scope values prevScope = scope; lastline = line; } } // prepare for the next line of the file. lineNumber++; } while (originalLine != null); return current; } /** * Print PCD is a simple function. * * It accepts two arguments: object and scope. * * The parameter 'object' is printed as follows: * 1. if 'object' is a map, evey key is printed along * with its values (which are indented by 1) * 2. if 'object' is a list, every object in the list is printed * 3. if 'object' is a string, it is printed as is ** * @param object is the current object to print. * @param scope defines the number of spaces (in multiples of 4) * to indent the line being printed. */ private static void printPCD(Object object, int scope) { if (object == null) { // if the current object is null, which // it should never be, print "NULL" System.out.println("NULL"); } else if (object instanceof Map) { // if 'object' is a map, evey key is printed // along with its values (which are indented by 1) Object[] entries = ((Map)object).entrySet().toArray(); Map.Entry current; for (int counte = 0; counte < entries.length; counte++) { current = ((Map.Entry)entries[counte]); printPCD(current.getKey(), scope); printPCD(current.getValue(), scope + 1); } } else if (object instanceof List) { // if 'object' is a list, print every object in // the list, recursively call printPCD for (int countlist = 0; countlist < ((List)object).size(); countlist++) { printPCD(((List)object).get(countlist), scope); } } else { // if 'object' is a string, print it with the proper scope for (int counts = 0; counts < scope; counts++) { System.out.print(" "); } System.out.print(object); System.out.print("\n"); System.out.flush(); } } }