/* * jlscp - a java LinuxSampler control protocol API * * Copyright (C) 2005-2008 Grigor Iliev * * This file is part of jlscp. * * jlscp is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. * * jlscp 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 jlscp; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ package org.linuxsampler.lscp; import java.io.IOException; import java.util.Vector; /** * This class contains only helper functions that are used from the other classes in this library. * @author Grigor Iliev */ public final class Parser { /** Forbits the instantiatrion of this class */ private Parser() { } /** * Parses an integer value. * @param s The integer value to be parsed. * @throws LscpException If the string does not contain valid integer value. */ protected static int parseInt(String s) throws LscpException { try { return Integer.parseInt(s); } catch(NumberFormatException x) { throw new LscpException(LscpI18n.getLogMsg("Parser.notInt!", s), x); } } /** * Parses a float value. * @param s The float value to be parsed. * @throws LscpException If the string does not contain valid float value. */ protected static float parseFloat(String s) throws LscpException { try { return Float.parseFloat(s); } catch(NumberFormatException x) { throw new LscpException(LscpI18n.getLogMsg("Parser.notFloat!", s)); } } /** * Parses a comma separated list. * * @param list The comma separated list. * @return A String array containing all items in the list. */ public static String[] parseList(String list) { return parseList(list, ','); } /** * Parses a list. * @param list The list to parse. * @param separator Provides the character used as separator. * @return A String array containing all items in the list. */ public static String[] parseList(String list, char separator) { if(list == null || list.length() == 0) return new String[0]; int pos = 0; int idx; Vector v = new Vector(); while((idx = list.indexOf(separator, pos)) > 0) { v.add(list.substring(pos, idx)); pos = idx + 1; } if(pos < list.length()) v.add(list.substring(pos)); return v.toArray(new String[v.size()]); } /** * Parses a comma separated list with boolean values. * * @param list The comma separated list with boolean values. * @return A Boolean array containing all items in the list. */ public static Boolean[] parseBoolList(String list) { String[] ar = parseList(list); Boolean[] bar = new Boolean[ar.length]; for(int i = 0; i < ar.length; i++) { bar[i] = Boolean.parseBoolean(ar[i]); } return bar; } /** * Parses a comma separated list with integer values. * * @param list The comma separated list with integer values. * @return A Integer array containing all items in the list. * * @throws LscpException if the list contains value(s) from different type. */ public static Integer[] parseIntList(String list) throws LscpException { return parseIntList(list, ','); } /** * Parses a list of integer values. * * @param list The list of integer values. * @param separator Provides the character used as separator. * @return A Integer array containing all items in the list. * * @throws LscpException if the list contains value(s) from different type. */ public static Integer[] parseIntList(String list, char separator) throws LscpException { String[] ar = parseList(list, separator); Integer[] iar = new Integer[ar.length]; for(int i = 0; i < ar.length; i++) iar[i] = parseInt(ar[i]); return iar; } /** * Parses a comma separated list with float values. * * @param list The comma separated list with float values. * @return A Float array containing all items in the list. * * @throws LscpException if the list contains value(s) from different type. */ public static Float[] parseFloatList(String list) throws LscpException { String[] ar = parseList(list); Float[] far = new Float[ar.length]; for(int i = 0; i < ar.length; i++) far[i] = parseFloat(ar[i]); return far; } /** * Parses a comma separated string list, which elements contains escaped sequences. * @param list The list to parse. * @return A String array containing all items in the list. */ public static String[] parseEscapedStringList(String list) throws LscpException { return parseEscapedStringList(list, ','); } /** * Parses a string list, which elements contains escaped sequences. * @param list The list to parse. * @param separator Provides the character used as separator. * @return A String array containing all items in the list. */ public static String[] parseEscapedStringList(String list, char separator) throws LscpException { if(list == null || list.length() == 0) return new String[0]; int q1 = 0, q2 = 0; Vector v = new Vector(); for(;;) { if(list.charAt(q1) != '\'') throw new LscpException(LscpI18n.getLogMsg("Parser.brokenList!")); q2 = findApostrophe(list, q1 + 1); if(q2 == -1) throw new LscpException(LscpI18n.getLogMsg("Parser.EOL!")); v.add(list.substring(q1 + 1, q2)); if(q2 + 1 >= list.length()) break; if(list.charAt(q2 + 1) != separator) throw new LscpException(LscpI18n.getLogMsg("Parser.brokenList!")); q1 = q2 + 2; if(q1 >= list.length()) throw new LscpException(LscpI18n.getLogMsg("Parser.EOL!")); } return v.toArray(new String[v.size()]); } /** * Returns the index of the first occurrence of a non-escaped apostrophe * in the specified string, starting at index, or -1 if nothing is found. */ private static int findApostrophe(String s, int index) { return findNonEscapedChar(s, index, '\''); } /** * Parses a comma separated list whose items are encapsulated into apostrophes. * @param list The comma separated list. * @return A String array containing all items in the list. * @throws LscpException if the list is broken. */ public static String[] parseStringList(String list) throws LscpException { return parseStringList(list, ','); } /** * Parses a list whose items are encapsulated into apostrophes. * @param list The list of strings. * @param separator Provides the character used as separator. * @return A String array containing all items in the list. * @throws LscpException if the list is broken. */ public static String[] parseStringList(String list, char separator) throws LscpException { if(list == null || list.length() == 0) return new String[0]; int q1 = 0, q2 = 0; Vector v = new Vector(); for(;;) { if(list.charAt(q1) != '\'') throw new LscpException(LscpI18n.getLogMsg("Parser.brokenList!")); q2 = list.indexOf('\'', q1 + 1); if(q2 == -1) throw new LscpException(LscpI18n.getLogMsg("Parser.EOL!")); v.add(list.substring(q1 + 1, q2)); if(q2 + 1 >= list.length()) break; if(list.charAt(q2 + 1) != separator) throw new LscpException(LscpI18n.getLogMsg("Parser.brokenList!")); q1 = q2 + 2; if(q1 >= list.length()) throw new LscpException(LscpI18n.getLogMsg("Parser.EOL!")); } return v.toArray(new String[v.size()]); } protected static String[][] parseListOfStringLists(String list) throws LscpException { if(list.length() == 0) return new String[0][0]; String[][] s2S; if(!list.startsWith("''") && !list.startsWith("\"\"")) { s2S = new String[1][]; s2S[0] = parseStringList(list); return s2S; } int i = 0, i2 = 0; Vector v = new Vector(); for(;;) { i2 = getEndListIndex(i, list); v.add(list.substring(i + 1, i2)); if(i2 == list.length() - 1) break; if(list.charAt(i2 + 1) != ',') throw new LscpException(LscpI18n.getLogMsg("Parser.brokenList!")); i = i2 + 2; } s2S = new String[v.size()][]; for(i = 0; i < v.size(); i++) s2S[i] = parseStringList(v.get(i)); return s2S; } /** * Parses a comma separated list whose items are encapsulated into curly braces. * * @param list The comma separated list. * @return A String array containing all items in the list. * * @throws LscpException if the list is broken. */ public static String[] parseArray(String list) throws LscpException { if(list == null || list.length() == 0) return new String[0]; int q1 = 0, q2 = 0; Vector v = new Vector(); for(;;) { if(list.charAt(q1) != '{') throw new LscpException(LscpI18n.getLogMsg("Parser.brokenList!")); q2 = list.indexOf('}', q1 + 1); if(q2 == -1) throw new LscpException(LscpI18n.getLogMsg("Parser.EOL!")); v.add(list.substring(q1 + 1, q2)); if(q2 + 1 >= list.length()) break; if(list.charAt(q2 + 1) != ',') throw new LscpException(LscpI18n.getLogMsg("Parser.brokenList!")); q1 = q2 + 2; if(q1 >= list.length()) throw new LscpException(LscpI18n.getLogMsg("Parser.EOL!")); } return v.toArray(new String[v.size()]); } /** Helper function used by parseListOfStringLists. */ private static int getEndListIndex(int start, String list) throws LscpException { int i = start + 1; char q = list.charAt(0); // quote symbol if(list.charAt(start) != q) throw new LscpException(LscpI18n.getLogMsg("Parser.brokenList!")); if(list.charAt(i) == '\'') { // Check for empty list if(i == list.length() - 1 || list.charAt(i + 1) == ',') return i; } for(;;) { if(list.charAt(i) != q) throw new LscpException(LscpI18n.getLogMsg("Parser.brokenList!")); i = list.indexOf(q, i + 1); if(i == -1 || i == list.length() - 1) throw new LscpException(LscpI18n.getLogMsg("Parser.brokenList!")); if(list.charAt(i + 1) == q) return i + 1; if(list.charAt(i + 1) != ',') throw new LscpException(LscpI18n.getLogMsg("Parser.brokenList!")); i += 2; } } /** * Gets the type of the parameter represented by the specified result set. * @param resultSet A String array containing the information categories * of a multi-line result set. * @return The type of the parameter represented by the specified result set or * null if the specified result set does not contain * TYPE category. */ protected static ParameterType parseType(String[] resultSet) { if(resultSet == null || resultSet.length == 0) return null; for(String s : resultSet) { if(s.startsWith("TYPE: ")) { String type = s.substring("TYPE: ".length(), s.length()); if(type.equals("BOOL")) return ParameterType.BOOL; if(type.equals("INT")) return ParameterType.INT; if(type.equals("FOAT")) return ParameterType.FLOAT; if(type.equals("STRING")) return ParameterType.STRING; } } return null; } /** * Determines whether the parameter represented by the specified result set allows * only one value or a list of values. * @param resultSet A String array containing the information categories * of a multi-line result set. * @return false if the parameter represented by the specified result set * allows only one value and true if allows a list of values. */ protected static Boolean parseMultiplicity(String[] resultSet) { if(resultSet == null || resultSet.length == 0) return null; for(String s : resultSet) { if(s.startsWith("MULTIPLICITY: ")) return Boolean.parseBoolean ( s.substring("MULTIPLICITY: ".length(), s.length()) ); } return null; } /** * Parses an empty result set and returns an appropriate ResultSet object. * Notice that the result set may be of type warning or error. * @param ln A String representing the single line result set to be parsed. * @return A ResultSet object. * @throws LscpException If LSCP protocol error occurs. * @throws LSException If the LinuxSampler instance returns error message. */ protected static ResultSet parseEmptyResultSet(String ln) throws LscpException, LSException { ResultSet rs = new ResultSet(); if(ln.equals("OK")) { return rs; } else if(ln.startsWith("OK[") && ln.endsWith("]")) { ln = ln.substring("OK[".length(), ln.length() - 1); try { rs.setIndex(Integer.parseInt(ln)); return rs; } catch(NumberFormatException x) { throw new LscpException(LscpI18n.getLogMsg("CommandFailed!"), x); } } else if(ln.startsWith("WRN")) { parseWarning(ln, rs); Client.getLogger().warning(rs.getMessage()); return rs; } else if(ln.startsWith("ERR:")) { parseError(ln, rs); throw new LSException(rs.getCode(), rs.getMessage()); } throw new LscpException(LscpI18n.getLogMsg("CommandFailed!") ); } /** * Parses warning message. * @param ln The warning message to be parsed. * @param rs A ResultSet instance where the warning must be stored. * @throws LscpException If LSCP protocol corruption occurs. */ protected static void parseWarning(String ln, ResultSet rs) throws LscpException { if(!ln.startsWith("WRN")) throw new LscpException(LscpI18n.getLogMsg("CommandFailed!")); int i, j; rs.setWarning(true); if(ln.charAt(3) == '[') { i = ln.indexOf(']'); if(i == -1) throw new LscpException(LscpI18n.getLogMsg("CommandFailed!")); try { j = Integer.parseInt(ln.substring("WRN[".length(), i)); rs.setIndex(j); } catch(NumberFormatException x) { throw new LscpException(LscpI18n.getLogMsg("CommandFailed!"), x); } if(ln.charAt(i + 1) != ':') throw new LscpException(LscpI18n.getLogMsg("CommandFailed!")); } i = ln.indexOf(':'); if(i == -1) throw new LscpException(LscpI18n.getLogMsg("CommandFailed!")); j = ln.indexOf(':', i + 1); if(j == -1) throw new LscpException(LscpI18n.getLogMsg("CommandFailed!")); try { rs.setCode(Integer.parseInt(ln.substring(i + 1, j))); } catch(NumberFormatException x) { throw new LscpException(LscpI18n.getLogMsg("CommandFailed!"), x); } rs.setMessage(ln.substring(j + 1)); } /** * Parses error message. * @param ln The error message to be parsed. * @param rs A ResultSet instance where the error must be stored. * @throws LscpException If LSCP protocol corruption occurs. */ protected static void parseError(String ln, ResultSet rs) throws LscpException { if(!ln.startsWith("ERR:")) throw new LscpException(LscpI18n.getLogMsg("CommandFailed!")); int i = ln.indexOf(':', "ERR:".length()); if(i == -1) throw new LscpException(LscpI18n.getLogMsg("CommandFailed!")); try { rs.setCode(Integer.parseInt(ln.substring("ERR:".length(), i))); } catch(NumberFormatException x) { throw new LscpException(LscpI18n.getLogMsg("CommandFailed!"), x); } rs.setMessage(ln.substring(i + 1)); } /** * Gets the info character string to the specified information category. * @param resultSet A String array containing the information categories * of a multi-line result set. * @param category Specifies the category whose info character string to be returned. * @return The info character string to the specified information category or * null if the specified result set does not contain that category. */ protected static String getCategoryInfo(String[] resultSet, String category) { String c = category + ": "; for(String s : resultSet) if(s.startsWith(c)) return s.substring(c.length(), s.length()); return null; } /** * Eliminates the quotation marks if the string is quoted. * @return New string without quotation marks if the string is quoted; else * the same string is returned. */ public static String removeQuotation(String s) { if(s == null || s.length() < 2) return s; char q = s.charAt(0); char q2 = s.charAt(s.length() - 1); if((q == '\'' && q2 == '\'') || (q == '"' && q2 == '"')) return s.substring(1, s.length() - 1); return s; } /** * Returns the provided string with added escape sequences where necessary. */ public static String toEscapedString(Object obj) { String s = obj.toString(); StringBuffer sb = new StringBuffer(); for(int i = 0; i < s.length(); i++) { switch(s.charAt(i)) { case '\n': sb.append("\\n"); break; case '\r': sb.append("\\r"); break; case '\f': sb.append("\\f"); break; case '\t': sb.append("\\t"); break; case 0x0B: sb.append("\\v"); break; case '\'': sb.append("\\'"); break; case '\"': sb.append("\\\""); break; case '\\': sb.append("\\\\"); break; default : sb.append(s.charAt(i)); } } return sb.toString(); } /** * Returns the provided file name with added escape sequences where necessary. */ public static String toEscapedFileName(Object obj) { String s = obj.toString(); StringBuffer sb = new StringBuffer(); for(int i = 0; i < s.length(); i++) { switch(s.charAt(i)) { case '/' : sb.append("\\x2f"); break; case '\n': sb.append("\\n"); break; case '\r': sb.append("\\r"); break; case '\f': sb.append("\\f"); break; case '\t': sb.append("\\t"); break; case 0x0B: sb.append("\\v"); break; case '\'': sb.append("\\'"); break; case '\"': sb.append("\\\""); break; case '\\': sb.append("\\\\"); break; default : sb.append(s.charAt(i)); } } return sb.toString(); } /** * Returns more aggressively escaped sequence of the provided escaped string. * @param escapedString Already escaped string, which should be escaped more aggressively. */ public static String toExtendedEscapeSequence(String escapedString) { StringBuffer sb = new StringBuffer(); for(int i = 0; i < escapedString.length(); i++) { char c = escapedString.charAt(i); if(c == '`') sb.append("\\x60"); else if(c < ' ' || c > '~') sb.append(toEscapeString(c)); else sb.append(c); /*if(c == '/' || c == '\\' || c == '\'' || c == '"') sb.append(c); else if(c >= '0' && c <='9') sb.append(c); else if((c >= 'A' && c <='Z') || (c >= 'a' && c <='z')) sb.append(c); else { }*/ } return sb.toString(); } private static String toEscapeString(char c) { String s = Integer.toHexString((int)c); if(s.length() % 2 != 0) s = "0" + s; StringBuffer sb = new StringBuffer(); for(int i = 0; i < s.length();) { sb.append("\\x").append(s.charAt(i)).append(s.charAt(i + 1)); i += 2; } return sb.toString(); } /** * Removes the escape sequences from the specified file name * @return The provided file name with removed escape sequences. */ public static String toNonEscapedFileName(Object obj) { return toNonEscapedString(obj); } /** * Removes the escape sequences from the string obj.toString(). * @return The provided text with removed escape sequences. */ public static String toNonEscapedString(Object obj) { String s = obj.toString(); StringBuffer sb = new StringBuffer(); for(int i = 0; i < s.length(); i++) { char c = s.charAt(i); if(c == '\\') { if(i >= s.length()) { Client.getLogger().info("Broken escape sequence!"); break; } char c2 = s.charAt(++i); if(c2 == '\'') sb.append('\''); else if(c2 == '"') sb.append('"'); else if(c2 == '\\') sb.append('\\'); else if(c2 == 'r') sb.append('\r'); else if(c2 == 'n') sb.append('\n'); else if(c2 == 'f') sb.append('\f'); else if(c2 == 't') sb.append('\t'); else if(c2 == 'v') sb.append((char)0x0B); else if(c2 == 'x') { Character ch = getHexEscapeSequence(s, i + 1); if(ch != null) sb.append(ch.charValue()); i += 2; } else if(c2 >= '0' && c2 <= '9') { Character ch = getOctEscapeSequence(s, i); if(ch != null) sb.append(ch.charValue()); i += 2; } else Client.getLogger().info("Unknown escape sequence \\" + c2); } else { sb.append(c); } } return sb.toString(); } private static Character getHexEscapeSequence(String s, int index) { Character c = null; if(index + 1 >= s.length()) { Client.getLogger().info("Broken escape sequence"); return c; } try { c = (char)Integer.parseInt(s.substring(index, index + 2), 16); } catch(Exception x) { Client.getLogger().info("Broken escape sequence!"); } return c; } private static Character getOctEscapeSequence(String s, int index) { Character c = null; if(index + 2 >= s.length()) { Client.getLogger().info("Broken escape sequence"); return c; } try { c = (char)Integer.parseInt(s.substring(index, index + 3), 8); } catch(Exception x) { Client.getLogger().info("Broken escape sequence!"); } return c; } /** * Determines whether the character at the specified position * is escaped with backslash. */ public static boolean isEscaped(String s, int index) { if (index < 0 || index >= s.length()) return false; int count = 0; for (int i = index - 1; i >= 0; i--) { if (s.charAt(i) != '\\') break; count++; } return count % 2 != 0; } /** * Returns the index of the first occurrence of the specified non-escaped character * in the specified string, starting at index, or -1 if nothing is found. */ private static int findNonEscapedChar(String s, int index, char c) { if(s == null) return -1; int pos = index; if (pos < 0 || pos >= s.length()) return -1; for(;;) { int i = s.indexOf(c, pos); if (i == -1) break; if (!isEscaped(s, i)) return i; pos = i + 1; if (pos >= s.length()) break; } return -1; } /** * Returns the index of the first occurrence of a file separator * in the specified escaped path, starting at index, or -1 if nothing is found. */ private static int findFileSeparator(String path, int index) { return findNonEscapedChar(path, index, '/'); } /** * Gets the position of the last file separator in the specified * escaped path, or -1 if failed. */ private static int getLastFileSeparator(String path) { if(path == null || path.length() == 0) return -1; int pos = path.length() - 1; for(;;) { pos = path.lastIndexOf('/', pos); if(pos == -1) return -1; if(!isEscaped(path, pos)) return pos; pos--; } } /** * Determines whether the specified escaped path ends with a file separator. */ public static boolean hasEndingFileSeparator(String path) { if(path == null || path.length() < 2) return false; int last = path.length() - 1; if(path.charAt(last) == '/' && !isEscaped(path, last)) return true; return false; } /** * If the specified escaped path ends with a file separator, * a new string is returned with the ending file separator removed. */ public static String removeEndingFileSeparator(String path) { if(path == null || path.length() < 2) return path; int last = path.length() - 1; if(path.charAt(last) == '/' && !isEscaped(path, last)) { path = path.substring(0, path.length() - 1); } return path; } /** * Gets the parent directory of the specified escaped path. */ public static String getParentDirectory(String path) { if(path == null || path.length() == 0) return null; if(path.charAt(0) != '/') return null; if(path.length() == 1) return null; path = removeEndingFileSeparator(path); int i = getLastFileSeparator(path); if(i == 0) return "/"; return path.substring(0, i); } /** * Extracts the file name from the specified escaped path. * If the path does not ends with a file name, null is returned. */ public static String getFileName(String path) { if(path == null || path.length() < 2) return null; int i = getLastFileSeparator(path); if(i == -1) return null; if(i == path.length() - 1) return null; return path.substring(i + 1); } /** * Returns an array containing all directories in the specified escaped path. */ public static String[] getDirectoryList(String path) { if(path == null || path.length() == 0) return null; if(path.charAt(0) != '/') return null; Vector v = new Vector(); v.add("/"); if(path.length() == 1) return v.toArray(new String[v.size()]); if(!hasEndingFileSeparator(path)) path += "/"; int i = 1; int j = findFileSeparator(path, i); while(j != -1) { v.add(path.substring(i, j)); i = j + 1; if(i >= path.length()) return v.toArray(new String[v.size()]); j = findFileSeparator(path, i); } return null; } }