| /* |
| * Copyright (C) 2012-2014 DataStax Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.datastax.driver.core; |
| |
| import java.util.*; |
| |
| /** |
| * A very simple json parser. |
| * The only reason we need to read json in the driver is because for |
| * historical reason Cassandra encodes a few properties using json in |
| * the schema and we need to decode them. |
| * |
| * We however don't need a full-blown JSON library because: |
| * 1) we know we only need to decode string lists and string maps |
| * 2) we can basically assume the input is valid, we don't particularly |
| * have to bother about decoding exactly JSON as long as we at least |
| * decode what we need. |
| * 3) we don't really care much about performance, none of this is done |
| * in performance sensitive parts. |
| * |
| * So instead of pulling a new dependency, we roll out our own very dumb |
| * parser. We should obviously not expose this publicly. |
| */ |
| class SimpleJSONParser { |
| |
| private final String input; |
| private int idx; |
| |
| private SimpleJSONParser(String input) { |
| this.input = input; |
| } |
| |
| public static List<String> parseStringList(String input) { |
| if (input == null || input.isEmpty()) |
| return Collections.<String>emptyList(); |
| |
| List<String> output = new ArrayList<String>(); |
| SimpleJSONParser parser = new SimpleJSONParser(input); |
| if (parser.nextCharSkipSpaces() != '[') |
| throw new IllegalArgumentException("Not a JSON list: " + input); |
| |
| char c = parser.nextCharSkipSpaces(); |
| if (c == ']') |
| return output; |
| |
| while (true) { |
| assert c == '"'; |
| output.add(parser.nextString()); |
| c = parser.nextCharSkipSpaces(); |
| if (c == ']') |
| return output; |
| assert c == ','; |
| c = parser.nextCharSkipSpaces(); |
| } |
| } |
| |
| public static Map<String, String> parseStringMap(String input) { |
| if (input == null || input.isEmpty()) |
| return Collections.<String, String>emptyMap(); |
| |
| Map<String, String> output = new HashMap<String, String>(); |
| SimpleJSONParser parser = new SimpleJSONParser(input); |
| if (parser.nextCharSkipSpaces() != '{') |
| throw new IllegalArgumentException("Not a JSON map: " + input); |
| |
| char c = parser.nextCharSkipSpaces(); |
| if (c == '}') |
| return output; |
| |
| while (true) { |
| assert c == '"'; |
| String key = parser.nextString(); |
| c = parser.nextCharSkipSpaces(); |
| assert c == ':'; |
| c = parser.nextCharSkipSpaces(); |
| assert c == '"'; |
| String value = parser.nextString(); |
| output.put(key, value); |
| c = parser.nextCharSkipSpaces(); |
| if (c == '}') |
| return output; |
| assert c == ','; |
| c = parser.nextCharSkipSpaces(); |
| } |
| } |
| |
| /** |
| * Read the next char, the one at position idx, and advance ix. |
| */ |
| private char nextChar() { |
| if (idx >= input.length()) |
| throw new IllegalArgumentException("Invalid json input: " + input); |
| return input.charAt(idx++); |
| } |
| |
| /** |
| * Same as nextChar, except that it skips space characters (' ', '\t' and '\n'). |
| */ |
| private char nextCharSkipSpaces() { |
| char c = nextChar(); |
| while (c == ' ' || c == '\t' || c == '\n') |
| c = nextChar(); |
| return c; |
| } |
| |
| /** |
| * Reads a String, assuming idx is on the first character of the string (i.e. the |
| * one after the opening double-quote character). |
| * After the string has been read, idx will be on the first character after |
| * the closing double-quote. |
| */ |
| private String nextString() { |
| assert input.charAt(idx-1) == '"' : "Char is '" + input.charAt(idx-1) + '\''; |
| StringBuilder sb = new StringBuilder(); |
| while (true) { |
| char c = nextChar(); |
| switch (c) { |
| case '\n': |
| case '\r': |
| throw new IllegalArgumentException("Unterminated string"); |
| case '\\': |
| c = nextChar(); |
| switch (c) { |
| case 'b': |
| sb.append('\b'); |
| break; |
| case 't': |
| sb.append('\t'); |
| break; |
| case 'n': |
| sb.append('\n'); |
| break; |
| case 'f': |
| sb.append('\f'); |
| break; |
| case 'r': |
| sb.append('\r'); |
| break; |
| case 'u': |
| sb.append((char)Integer.parseInt(input.substring(idx, idx+4), 16)); |
| idx += 4; |
| break; |
| case '"': |
| case '\'': |
| case '\\': |
| case '/': |
| sb.append(c); |
| break; |
| default: |
| throw new IllegalArgumentException("Illegal escape"); |
| } |
| break; |
| default: |
| if (c == '"') |
| return sb.toString(); |
| sb.append(c); |
| } |
| } |
| } |
| } |