# @GPL_HEADER_START
# Use of the RAVE and related source code is subject to the terms
# of the following licenses:
# 
# GNU Public License (GPL) Rights pursuant to Version 2, June 1991
# Government Purpose License Rights (GPLR) pursuant to DFARS 252.225-7013
# 
# NO WARRANTY
# 
# ANY INFORMATION, MATERIALS, SERVICES, INTELLECTUAL PROPERTY OR OTHER
# PROPERTY OR RIGHTS GRANTED OR PROVIDED BY CARNEGIE MELLON UNIVERSITY
# PURSUANT TO THIS LICENSE (HEREINAFTER THE "DELIVERABLES") ARE ON AN
# "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY
# KIND, EITHER EXPRESS OR IMPLIED AS TO ANY MATTER INCLUDING, BUT NOT
# LIMITED TO, WARRANTY OF FITNESS FOR A PARTICULAR PURPOSE,
# MERCHANTABILITY, INFORMATIONAL CONTENT, NONINFRINGEMENT, OR ERROR-FREE
# OPERATION. CARNEGIE MELLON UNIVERSITY SHALL NOT BE LIABLE FOR INDIRECT,
# SPECIAL OR CONSEQUENTIAL DAMAGES, SUCH AS LOSS OF PROFITS OR INABILITY
# TO USE SAID INTELLECTUAL PROPERTY, UNDER THIS LICENSE, REGARDLESS OF
# WHETHER SUCH PARTY WAS AWARE OF THE POSSIBILITY OF SUCH DAMAGES.
# LICENSEE AGREES THAT IT WILL NOT MAKE ANY WARRANTY ON BEHALF OF
# CARNEGIE MELLON UNIVERSITY, EXPRESS OR IMPLIED, TO ANY PERSON
# CONCERNING THE APPLICATION OF OR THE RESULTS TO BE OBTAINED WITH THE
# DELIVERABLES UNDER THIS LICENSE.
# 
# Licensee hereby agrees to defend, indemnify, and hold harmless Carnegie
# Mellon University, its trustees, officers, employees, and agents from
# all claims or demands made against them (and any related losses,
# expenses, or attorney's fees) arising out of, or relating to Licensee's
# and/or its sub licensees' negligent use or willful misuse of or
# negligent conduct or willful misconduct regarding the Software,
# facilities, or other rights or assistance granted by Carnegie Mellon
# University under this License, including, but not limited to, any
# claims of product liability, personal injury, death, damage to
# property, or violation of any laws or regulations.
# 
# Carnegie Mellon University Software Engineering Institute authored
# documents are sponsored by the U.S. Department of Defense under
# Contract F19628-00-C-0003. Carnegie Mellon University retains
# copyrights in all material produced under this contract. The U.S.
# Government retains a non-exclusive, royalty-free license to publish or
# reproduce these documents, or allow others to do so, for U.S.
# Government purposes only pursuant to the copyright license under the
# contract clause at 252.227.7013.
# 
# @GPL_HEADER_END
"""
Simple JSON parsing and unparsing code, including the opportunity to
register specialized encoders/decoders for certain classes, and for
objects to say how they should be encoded and decoded.
"""

import codecs, cPickle

class MalformedJSONException(Exception):
    def __init__(self, msg):
        self.msg = msg
    def __str__(self):
        return self.msg

class JSONParserStream(object):
    def __init__(self):
        self.buffer = ''
        self.buffer_pos = 0
    def fill(self):
        if self.buffer_pos == len(self.buffer):
            return False
        return True
    def read(self):
        if self.fill():
            self.buffer_pos = self.buffer_pos + 1
            return self.buffer[self.buffer_pos - 1]
        else:
            return ''
    def peek(self):
        if self.fill():
            return self.buffer[self.buffer_pos]
        else:
            return ''

class JSONParserFile(JSONParserStream):
    def __init__(self, file, buffer_size=1024):
        super(JSONParserFile, self).__init__()
        if not isinstance(file, codecs.StreamReader):
            file = codecs.getreader('utf_8')(file)
        self.file = file
        self.buffer_size = buffer_size
    def fill(self):
        if not super(JSONParserFile, self).fill():
            try:
                # We're out of data, read more.
                self.buffer = self.file.read(self.buffer_size)
                if self.buffer == '':
                    return False
                self.buffer_pos = 0
            except:
                raise
                # If anything happens, reset to base state, and re-raise
                self.buffer = ''
                self.buffer_pos = 0
                raise
        return True

class JSONParserString(JSONParserStream):
    def __init__(self, input):
        super(JSONParserString, self).__init__()
        self.buffer = input

class JSONParser(object):
    def __init__(self, input, handlers=None, allow_further_input=None):
        self.allow_further_input = True
        if isinstance(input, (str,unicode)):
            # Strip whitespace
            input = JSONParserString(input)
            # Don't allow further input by default
            self.allow_further_input = False
        elif isinstance(input, file):
            input = JSONParserFile(input)
        else:
            # Something we don't handle?
            raise input
        if allow_further_input <> None:
            self.allow_further_input = allow_further_input
        self.input = input
        self.handlers = handlers
    def read_json_value(self):
        self.skip_whitespace()
        v = self.parse_value()
        self.skip_whitespace()
        if not self.allow_further_input:
            if self.input.peek() <> '':
                raise MalformedJSONException('Data found after end of input ' +
                                             'in JSON value')
        return v
    def parse_value(self):
        c = self.input.peek()
        if c == '{':
            return self.parse_object()
        elif c == '[':
            return self.parse_array()
        elif c == '"':
            return self.parse_string()
        elif (c >= '0' and c <= '9') or c == '-':
            return self.parse_number()
        elif c == 't':
            self.accept_string('true')
            return True
        elif c == 'f':
            self.accept_string('false')
            return False
        elif c == 'n':
            self.accept_string('null')
            return None
        else:
            c = self.input.read()
            self.raise_unexpected(c)
    def skip_whitespace(self):
        c = self.input.peek()
        while c.isspace():
            self.input.read()
            c = self.input.peek()
    def accept_string(self, str):
        for i in xrange(len(str)):
            c = self.input.peek()
            if c <> str[i]:
                self.raise_unexpected(c)
            self.input.read()   # Consume character
    def accept_four_hex(self):
        result = ''
        for i in xrange(4):
            c = self.input.read()
            if not ((c >= '0' and c <= '9') or (c >= 'A' and c <='F') or
                    (c >= 'a' and c <= 'f')):
                self.raise_unexpected(c)
            result = result + c
        return result
    def parse_object(self):
        result = {}
        self.accept_string('{')
        self.skip_whitespace()
        c = self.input.peek()
        while c <> '}':
            k = self.parse_string()
            self.skip_whitespace()
            self.accept_string(':')
            self.skip_whitespace()
            v = self.parse_value()
            result[k] = v
            self.skip_whitespace()
            c = self.input.peek()
            if c == ',':
                self.input.read() # Consume ','
                self.skip_whitespace()
            elif c <> '}':
                self.raise_unexpected(c)
        self.input.read()       # Consume '}'
        # Check for ultimate fallback encoding
        if result.keys() == ['--python-pickle--']:
            try:
                result = cPickle.loads(str(result['--python-pickle--']))
            except cPickle.UnpicklingError:
                raise MalformedJSONException('Unable to unpickle ultimate ' +
                                             'fallback pickle')
        return result
    def parse_array(self):
        result = []
        self.accept_string('[')
        self.skip_whitespace()
        c = self.input.peek()
        while c <> ']':
            v = self.parse_value()
            result.append(v)
            self.skip_whitespace()
            c = self.input.peek()
            if c == ',':
                self.input.read() # Consume ','
                self.skip_whitespace()
            elif c <> ']':
                self.raise_unexpected(c)
        self.input.read()       # Consume ']'
        return result
    def parse_string(self):
        result = []
        self.accept_string('"')
        while True:
            c = self.input.read()
            if c == '"':
                return u''.join(result)
            elif c == '\\':
                c = self.input.read()
                if c == '"':
                    result.append('"')
                elif c == '\\':
                    result.append('\\')
                elif c == '/':
                    result.append('/')
                elif c == 'b':
                    result.append('\b')
                elif c == 'f':
                    result.append('\f')
                elif c == 'n':
                    result.append('\n')
                elif c == 'r':
                    result.append('\r')
                elif c == 't':
                    result.append('\t')
                elif c == 'u':
                    digits = self.accept_four_hex()
                    result.append(unichr(int(digits, 16)))
                else:
                    self.raise_unexpected(c)
            else:
                result.append(c)
    def parse_number(self):
        result = []
        c = self.input.peek()
        # Optional negative sign
        if c == '-':
            self.input.read()   # Consume sign
            result.append(c)
            c = self.input.peek()
        # Required initial digit
        if (c >= '0' and c <= '9'):
            self.input.read()   # Consume sign
            result.append(c)
            c = self.input.peek()
        else:
            # Required digit not found
            self.raise_unexpected(c)
        # Value before decimal point or exponent, after initial digit
        while (c >= '0' and c <= '9'):
            self.input.read()   # Consume it
            result.append(c)
            c = self.input.peek()
        # Optional fractional amount
        if c == '.':
            self.input.read()   # Consume decimal point
            result.append(c)
            c = self.input.peek()
            while (c >= '0' and c <= '9'):
                self.input.read() # Consume it
                result.append(c)
                c = self.input.peek()
        # Optional exponent
        if c == 'e' or c == 'E':
            self.input.read()   # Consume 'e'
            result.append(c)
            c = self.input.peek()
            # Optional sign
            if c == '-' or c == '+':
                self.input.read() # Consume it
                result.append(c)
                c = self.input.peek()
            # Exponent digits
            while (c >= '0' and c <= '9'):
                self.input.read() # Consume it
                result.append(c)
                c = self.input.peek()
        # Done
        result = ''.join(result)
        try:
            # Try as an integer first
            result = int(result)
        except:
            try:
                # Okay, try a float
                result = float(result)
            except:
                raise
        return result
    def raise_unexpected(self, c):
        if c == '':
            raise MalformedJSONException('Unexpected EOF in JSON value')
        else:
            raise MalformedJSONException('Unexpected character "' + c + '" ' +
                                         'in JSON value')

class JSONParserFactory(object):
    def __init__(self):
        super(JSONParserFactory, self).__init__()
    def get_parser(self, input):
        return JSONParser(input)
    def parse(self, input):
        return JSONParser(input).read_json_value()

class JSONUnparserString(object):
    def __init__(self):
        super(JSONUnparserString, self).__init__()
        self.buffer = []
    def write(self, str):
        self.buffer.append(str)
    def get_value(self):
        return u''.join(self.buffer)

class JSONUnparser(object):
    def __init__(self, output=None):
        super(JSONUnparser, self).__init__()
        self.output = output
    def write_json_value(self, v):
    #   Note that 1 == True and 0 == False, so we can't use == in boolean
    #   comparisons lest we incorrectly transcode  1 and 0. However, is
    #   works because it compares the memory locations to each other, and
    #   True and False (and None) are singletons.
        if v is True:
            self.output.write('true')
        elif v is False:
            self.output.write('false')
        elif v is None:
            self.output.write('null')
        elif isinstance(v, dict):
            l = v.keys()
            l.sort()
            self.output.write('{')
            continuing = False
            for k in l:
                if not isinstance(k, (str, unicode)):
                    raise MalformedJSONException('Non-string key in dict')
                if continuing:
                    self.output.write(",")
                self.write_json_value(k)
                self.output.write(':')
                self.write_json_value(v[k])
                continuing = True
            self.output.write('}')
        elif isinstance(v, (list, tuple)):
            self.output.write('[')
            continuing = False
            for x in v:
                if continuing:
                    self.output.write(",")
                self.write_json_value(x)
                continuing = True
            self.output.write(']')
        elif isinstance(v, (str, unicode)):
            v = v.replace('\\', '\\\\')
            v = v.replace('"', '\\"')
            v = v.replace('\b', '\\b')
            v = v.replace('\f', '\\f')
            v = v.replace('\n', '\\n')
            v = v.replace('\r', '\\r')
            v = v.replace('\t', '\\t')
            self.output.write('"')
            self.output.write(v)
            self.output.write('"')
        elif isinstance(v, float):
        #   The precision of floats converted via str() is
        #   pretty low (13 characters total). We get better
        #   results with a format string.
            self.output.write("%.10f" % v)
        elif isinstance(v, (int, long)):
            self.output.write(str(v))
        else:
            # Try encoding this using a call to the object:
            try:
                self.write_json_value(v.to_json_value())
            except AttributeError:
                # Try encoding this using the ultimate fallback plan
                try:
                    self.write_json_value({'--python-pickle--':
                                               cPickle.dumps(v)})
                except cPickle.PicklingError:
                    raise MalformedJSONException('Unhandled type encountered ' +
                                                 'while writing JSON')

class JSONUnparserFactory(object):
    def __init__(self):
        super(JSONUnparserFactory, self).__init__()
    def get_unparser(self, output):
        return JSONUnparser(output)
    def unparse(self, v, output=None):
        if output == None:
            output = JSONUnparserString()
            JSONUnparser(output).write_json_value(v)
            return output.get_value()
        else:
            JSONUnparser(output).write_json_value(v)

default_parser_factory = JSONParserFactory()
default_unparser_factory = JSONUnparserFactory()

def parse(input):
    return default_parser_factory.parse(input)

def unparse(value, output=None):
    return default_unparser_factory.unparse(value, output)

__all__ = ['MalformedJSONException',
           'JSONParser',
           'JSONParserFactory',
           'JSONUnparser',
           'JSONUnparserFactory',
           'default_parser_factory',
           'default_unparser_factory',
           'parse',
           'unparse']
