소스 검색

AMBARI-24741. Update simplejson to newest with speedup bindings to python2.7 and PyUnicodeUCS4/PyUnicodeUCS2 variations (dgrinenko) (#2479)

Dmytro Grinenko 7 년 전
부모
커밋
bf9b58a9a5
25개의 변경된 파일4850개의 추가작업 그리고 475개의 파일을 삭제
  1. 121 113
      ambari-agent/conf/unix/ambari-agent
  2. 52 13
      ambari-agent/src/main/python/ambari_agent/AmbariAgent.py
  3. 43 3
      ambari-common/src/main/python/ambari_simplejson/README.txt
  4. 373 94
      ambari-common/src/main/python/ambari_simplejson/__init__.py
  5. 3384 0
      ambari-common/src/main/python/ambari_simplejson/_speedups.c
  6. BIN
      ambari-common/src/main/python/ambari_simplejson/_speedups.so
  7. 0 0
      ambari-common/src/main/python/ambari_simplejson/_speedups/__init__.py
  8. 0 0
      ambari-common/src/main/python/ambari_simplejson/_speedups/posix/__init__.py
  9. 0 0
      ambari-common/src/main/python/ambari_simplejson/_speedups/posix/usc2/__init__.py
  10. BIN
      ambari-common/src/main/python/ambari_simplejson/_speedups/posix/usc2/_speedups.so
  11. 0 0
      ambari-common/src/main/python/ambari_simplejson/_speedups/posix/usc4/__init__.py
  12. BIN
      ambari-common/src/main/python/ambari_simplejson/_speedups/posix/usc4/_speedups.so
  13. 0 0
      ambari-common/src/main/python/ambari_simplejson/_speedups/ppc/__init__.py
  14. BIN
      ambari-common/src/main/python/ambari_simplejson/_speedups/ppc/_speedups.so
  15. 0 0
      ambari-common/src/main/python/ambari_simplejson/_speedups/win/__init__.py
  16. BIN
      ambari-common/src/main/python/ambari_simplejson/_speedups/win/_speedups.pyd
  17. 58 0
      ambari-common/src/main/python/ambari_simplejson/c_extension.py
  18. 34 0
      ambari-common/src/main/python/ambari_simplejson/compat.py
  19. 169 115
      ambari-common/src/main/python/ambari_simplejson/decoder.py
  20. 406 115
      ambari-common/src/main/python/ambari_simplejson/encoder.py
  21. 53 0
      ambari-common/src/main/python/ambari_simplejson/errors.py
  22. 103 0
      ambari-common/src/main/python/ambari_simplejson/ordered_dict.py
  23. 9 0
      ambari-common/src/main/python/ambari_simplejson/raw_json.py
  24. 39 9
      ambari-common/src/main/python/ambari_simplejson/scanner.py
  25. 6 13
      ambari-common/src/main/python/resource_management/core/resources/jcepolicyinfo.py

+ 121 - 113
ambari-agent/conf/unix/ambari-agent

@@ -21,14 +21,15 @@
 set -u # fail on unset variables
 VERSION="${ambariVersion}"
 HASH="${buildNumber}"
+_SCRIPT=$0
 
 case "${1:-}" in
   --version)
-        echo -e $VERSION
+        echo -e ${VERSION}
         exit 0
         ;;
   --hash)
-        echo -e $HASH
+        echo -e ${HASH}
         exit 0
         ;;
 esac
@@ -43,7 +44,7 @@ export CONFIG_FILE="$HOME_DIR/etc/ambari-agent/conf/ambari-agent.ini"
 
 get_agent_property() {
   property_name="$1"
-  value=$(awk -F "=" "/^$property_name/ {print \$2}" $CONFIG_FILE)
+  value=$(awk -F "=" "/^${property_name}/ {print \$2}" ${CONFIG_FILE})
   echo $value
 }
 
@@ -54,12 +55,12 @@ valid_path() {
     echo "path $1 not valid" 1>&2
     exit 1
   fi
-  echo $value
+  echo ${value}
 }
 
 
 export PATH=/usr/sbin:/sbin:/usr/lib/ambari-server/*:$PATH
-export AMBARI_CONF_DIR=$HOME_DIR/etc/ambari-server/conf:$PATH
+export AMBARI_CONF_DIR=${HOME_DIR}/etc/ambari-server/conf:$PATH
 
 # Because Ambari rpm unpacks modules here on all systems
 export PYTHONPATH=/usr/lib/ambari-agent/lib:${PYTHONPATH:-}
@@ -76,9 +77,9 @@ KEYSDIR="${KEYSDIR:?}"
 
 AMBARI_AGENT=ambari-agent
 PYTHON_WRAP=/usr/bin/ambari-python-wrap
-PIDFILE=$AMBARI_PID_DIR/$AMBARI_AGENT.pid
-OUTFILE=$AMBARI_AGENT_LOG_DIR/ambari-agent.out
-LOGFILE=$AMBARI_AGENT_LOG_DIR/ambari-agent.log
+PIDFILE=${AMBARI_PID_DIR}/${AMBARI_AGENT}.pid
+OUTFILE=${AMBARI_AGENT_LOG_DIR}/ambari-agent.out
+LOGFILE=${AMBARI_AGENT_LOG_DIR}/ambari-agent.log
 AGENT_SCRIPT=/usr/lib/ambari-agent/lib/ambari_agent/main.py
 AGENT_TMP_DIR=/var/lib/ambari-agent/tmp
 AGENT_WORKING_DIR=/var/lib/ambari-agent
@@ -100,30 +101,48 @@ if [ "$EUID" -ne 0 ] ; then
 fi
 
 # set reliable cwd for this and child processes.
-cd $AGENT_WORKING_DIR
+cd ${AGENT_WORKING_DIR}
+
+
+print_error(){
+  echo "ERROR: $1"
+}
+
+print_bold(){
+  tput bold
+  echo "$1"
+  tput sgr0
+}
+
+print_error_bold(){
+  tput bold
+  print_error "$1"
+  tput sgr0
+}
 
 change_files_permissions() {
-    if [ ! -z "$KEYSDIR" ]; then
-        ambari-sudo.sh chown -R $current_user "$KEYSDIR"
-    fi
-	ambari-sudo.sh mkdir -p "${AMBARI_PID_DIR:?}"
-	ambari-sudo.sh chown -R $current_user "${AMBARI_PID_DIR:?}/"
-	ambari-sudo.sh mkdir -p "${AMBARI_AGENT_LOG_DIR:?}"
-	ambari-sudo.sh chown -R $current_user:$current_group "${AMBARI_AGENT_LOG_DIR:?}/"
-	ambari-sudo.sh chown -R $current_user "/var/lib/ambari-agent/data/"
-	ambari-sudo.sh chown -R $current_user "/var/lib/ambari-agent/cache/"
-	ambari-sudo.sh chown 	$current_user "/usr/lib/ambari-agent/"
-	ambari-sudo.sh chown  $current_user "/var/lib/ambari-agent/cred"
+  if [ ! -z "${KEYSDIR}" ]; then
+    ambari-sudo.sh chown -R ${current_user} "${KEYSDIR}"
+  fi
+
+  ambari-sudo.sh mkdir -p "${AMBARI_PID_DIR:?}"
+  ambari-sudo.sh chown -R ${current_user} "${AMBARI_PID_DIR:?}/"
+  ambari-sudo.sh mkdir -p "${AMBARI_AGENT_LOG_DIR:?}"
+  ambari-sudo.sh chown -R ${current_user}:${current_group} "${AMBARI_AGENT_LOG_DIR:?}/"
+  ambari-sudo.sh chown -R ${current_user} "/var/lib/ambari-agent/data/"
+  ambari-sudo.sh chown -R ${current_user} "/var/lib/ambari-agent/cache/"
+  ambari-sudo.sh chown 	${current_user} "/usr/lib/ambari-agent/"
+  ambari-sudo.sh chown  ${current_user} "/var/lib/ambari-agent/cred"
 }
 
 
 if [ -z "${PYTHON:-}" ] ; then
-  export PYTHON=`readlink $PYTHON_WRAP`
+  export PYTHON=`readlink ${PYTHON_WRAP}`
 fi
 
 # Trying to read the passphrase from an environment
 if [ ! -z ${AMBARI_PASSPHRASE:-} ]; then
-  RESOLVED_AMBARI_PASSPHRASE=$AMBARI_PASSPHRASE
+  RESOLVED_AMBARI_PASSPHRASE=${AMBARI_PASSPHRASE}
 fi
 
 # Reading the environment file
@@ -132,8 +151,8 @@ if [ -a /var/lib/ambari-agent/ambari-env.sh ]; then
   . /var/lib/ambari-agent/ambari-env.sh
 fi
 
-if [ ! -z $AMBARI_AGENT_LOG_DIR ]; then
-  LOGFILE=$AMBARI_AGENT_LOG_DIR/ambari-agent.log
+if [ ! -z ${AMBARI_AGENT_LOG_DIR} ]; then
+  LOGFILE=${AMBARI_AGENT_LOG_DIR}/ambari-agent.log
 fi
 
 if [ ! -z ${AMBARI_AGENT_OUT_DIR:-} ]; then
@@ -148,36 +167,42 @@ elif [ -z ${RESOLVED_AMBARI_PASSPHRASE:-} ]; then
   RESOLVED_AMBARI_PASSPHRASE="DEV"
 fi
 
-export AMBARI_PASSPHRASE=$RESOLVED_AMBARI_PASSPHRASE
+export AMBARI_PASSPHRASE=${RESOLVED_AMBARI_PASSPHRASE}
 
-#echo $AMBARI_PASSPHRASE
 
-# check for version
+get_python_version(){
+ python - <<EOF
+import sys
+ver = tuple(sys.version_info)
+print("{}{}".format(ver[0], ver[1]))
+EOF
+}
+
 check_python_version ()
 {
   echo "Verifying Python version compatibility..."
-  majversion=`$PYTHON -V 2>&1 | awk '{print $2}' | cut -d'.' -f1`
-  minversion=`$PYTHON -V 2>&1 | awk '{print $2}' | cut -d'.' -f2`
-  numversion=$(( 10 * $majversion + $minversion))
-  if (( $numversion < 26 )); then
-    echo "ERROR: Found Python version $majversion.$minversion. Ambari Agent requires Python version > 2.6"
-    return $NOTOK
+  local min_py_ver=27
+  local curr_py_ver=$(get_python_version)
+
+  if [ ${curr_py_ver} -lt ${min_py_ver} ]; then
+    print_error "Found Python version ${curr_py_ver:0:1}.${curr_py_ver:1:1}. Ambari Agent requires Python version > ${min_py_ver:0:1}.${min_py_ver:1:1}"
+    return ${NOTOK}
   fi
-  echo "Using python " $PYTHON
-  return $OK
+  echo "Using python " ${PYTHON}
+  return ${OK}
 }
 
 check_ambari_common_dir ()
 {
   echo "Checking ambari-common dir..."
   # recursively compare all files except 'pyc' and 'pyo' in agent common dir and actual common dir to ensure they are up to date
-  diff -r $COMMON_DIR $COMMON_DIR_AGENT -x '*.py?'
+  diff -r ${COMMON_DIR} ${COMMON_DIR_AGENT} -x '*.py?'
   OUT=$?
-  if [ $OUT -ne 0 ];then
-    echo "ERROR: ambari_commons folder mismatch. $COMMON_DIR content should be the same as $COMMON_DIR_AGENT. Either ambari-agent is co-hosted with ambari-server and agent was upgraded without server or the link was broken."
-    return $NOTOK
+  if [ ${OUT} -ne 0 ];then
+    print_error "ambari_commons folder mismatch. ${COMMON_DIR} content should be the same as ${COMMON_DIR_AGENT}. Either ambari-agent is co-hosted with ambari-server and agent was upgraded without server or the link was broken."
+    return ${NOTOK}
   fi
-  return $OK
+  return ${OK}
 }
 
 retcode=0
@@ -185,19 +210,17 @@ retcode=0
 case "${1:-}" in
   start)
         check_python_version
-        if [ "$?" -eq "$NOTOK" ]; then
+        if [ "$?" -eq "${NOTOK}" ]; then
           exit -1
         fi
         echo "Checking for previously running Ambari Agent..."
-        if [ -f $PIDFILE ]; then
-          PID=`ambari-sudo.sh cat $PIDFILE`
-          if ! (ps -p $PID >/dev/null 2>/dev/null); then
+        if [ -f ${PIDFILE} ]; then
+          PID=`ambari-sudo.sh cat ${PIDFILE}`
+          if ! (ps -p ${PID} >/dev/null 2>/dev/null); then
             echo "$PIDFILE found with no process. Removing $PID..."
-            ambari-sudo.sh rm -f $PIDFILE
+            ambari-sudo.sh rm -f ${PIDFILE}
           else
-            tput bold
-            echo "ERROR: $AMBARI_AGENT already running"
-            tput sgr0
+            print_error_bold "$AMBARI_AGENT already running"
             echo "Check $PIDFILE for PID."
             exit -1
           fi
@@ -212,97 +235,88 @@ case "${1:-}" in
         echo "Starting ambari-agent"
 
         if [ "${AMBARI_AGENT_RUN_IN_FOREGROUND:-}" == true ] ; then
-          $PYTHON $AMBARI_AGENT_PY_SCRIPT "$@" > $OUTFILE 2>&1
+          ${PYTHON} ${AMBARI_AGENT_PY_SCRIPT} "$@" > ${OUTFILE} 2>&1
           exit $?
         fi
 
-        nohup $PYTHON $AMBARI_AGENT_PY_SCRIPT "$@" > $OUTFILE 2>&1 &
+        nohup ${PYTHON} ${AMBARI_AGENT_PY_SCRIPT} "$@" > ${OUTFILE} 2>&1 &
 
         sleep 2
         PID=$!
         echo "Verifying $AMBARI_AGENT process status..."
-        if ! (ps -p $PID >/dev/null 2>/dev/null); then
-          if [ -s $OUTFILE ]; then
-            echo "ERROR: $AMBARI_AGENT start failed. For more details, see $OUTFILE:"
+        if ! (ps -p ${PID} >/dev/null 2>/dev/null); then
+          if [ -s ${OUTFILE} ]; then
+            print_error "${AMBARI_AGENT} start failed. For more details, see ${OUTFILE}:"
             echo "===================="
-            tail -n 10 $OUTFILE
+            tail -n 10 ${OUTFILE}
             echo "===================="
           else
-            echo "ERROR: $AMBARI_AGENT start failed"
+            print_error "${AMBARI_AGENT} start failed"
           fi
-          echo "Agent out at: $OUTFILE"
-          echo "Agent log at: $LOGFILE"
+          echo "Agent out at: ${OUTFILE}"
+          echo "Agent log at: ${LOGFILE}"
           exit -1
         fi
-        tput bold
-        echo "Ambari Agent successfully started"
-        tput sgr0
-        echo "Agent PID at: $PIDFILE"
-        echo "Agent out at: $OUTFILE"
-        echo "Agent log at: $LOGFILE"
+
+        # print warnings from agent using perl-regex. \K is required to cut off "warning tag"
+        cat ${OUTFILE}|GREP_COLOR='01;33' grep --color=always -Po 'WARNING: \K(.*)'
+
+        print_bold "Ambari Agent successfully started"
+        echo "Agent PID at: ${PIDFILE}"
+        echo "Agent out at: ${OUTFILE}"
+        echo "Agent log at: ${LOGFILE}"
         ;;
   status)
-        if [ -f $PIDFILE ]; then
-          PID=`ambari-sudo.sh cat $PIDFILE`
-          echo "Found $AMBARI_AGENT PID: $PID"
+        if [ -f ${PIDFILE} ]; then
+          PID=`ambari-sudo.sh cat ${PIDFILE}`
+          echo "Found ${AMBARI_AGENT} PID: ${PID}"
           if ! (ps -p $PID >/dev/null 2>/dev/null); then
-            echo "$AMBARI_AGENT not running. Stale PID File at: $PIDFILE"
+            echo "${AMBARI_AGENT} not running. Stale PID File at: ${PIDFILE}"
             retcode=2
           else
-            tput bold
-            echo "$AMBARI_AGENT running."
-            tput sgr0
-            echo "Agent PID at: $PIDFILE"
-            echo "Agent out at: $OUTFILE"
-            echo "Agent log at: $LOGFILE"
+            print_bold "$AMBARI_AGENT running."
+            echo "Agent PID at: ${PIDFILE}"
+            echo "Agent out at: ${OUTFILE}"
+            echo "Agent log at: ${LOGFILE}"
           fi
         else
-          tput bold
-          echo "$AMBARI_AGENT currently not running"
-          tput sgr0
+          print_bold "${AMBARI_AGENT} currently not running"
           retcode=3
         fi
         ;;
   stop)
         check_python_version
-        if [ "$?" -eq "$NOTOK" ]; then
+        if [ "$?" -eq "${NOTOK}" ]; then
           exit -1
         fi
-        if [ -f $PIDFILE ]; then
-          PID=`ambari-sudo.sh cat $PIDFILE`
-          echo "Found $AMBARI_AGENT PID: $PID"
-          if ! (ps -p $PID >/dev/null 2>/dev/null); then
-            tput bold
-            echo "ERROR: $AMBARI_AGENT not running. Stale PID File at: $PIDFILE"
-            tput sgr0
+        if [ -f ${PIDFILE} ]; then
+          PID=`ambari-sudo.sh cat ${PIDFILE}`
+          echo "Found ${AMBARI_AGENT} PID: ${PID}"
+          if ! (ps -p ${PID} >/dev/null 2>/dev/null); then
+            print_error_bold "${AMBARI_AGENT} not running. Stale PID File at: ${PIDFILE}"
           else
-            echo "Stopping $AMBARI_AGENT"
+            echo "Stopping ${AMBARI_AGENT}"
             change_files_permissions
-            $PYTHON $AGENT_SCRIPT stop
+            ${PYTHON} ${AGENT_SCRIPT} stop
 
             status ambari-agent 2>/dev/null | grep start 1>/dev/null
             if [ "$?" -eq 0 ] ; then
-              echo "Stopping $AMBARI_AGENT upstart job"
+              echo "Stopping ${AMBARI_AGENT} upstart job"
               stop ambari-agent > /dev/null
             fi
           fi
-          echo "Removing PID file at $PIDFILE"
-          ambari-sudo.sh rm -f $PIDFILE
-          tput bold
-          echo "$AMBARI_AGENT successfully stopped"
-          tput sgr0
+          echo "Removing PID file at ${PIDFILE}"
+          ambari-sudo.sh rm -f ${PIDFILE}
+          print_bold "${AMBARI_AGENT} successfully stopped"
         else
-          tput bold
-          echo "$AMBARI_AGENT is not running. No PID found at $PIDFILE"
-          tput sgr0
+          print_bold "${AMBARI_AGENT} is not running. No PID found at ${PIDFILE}"
         fi
         ;;
   restart)
         echo -e "Restarting $AMBARI_AGENT"
-        scriptpath=$0
-        $scriptpath stop
+        ${_SCRIPT} stop
         shift
-        $scriptpath start "$@"
+        ${_SCRIPT} start "$@"
         retcode=$?
         ;;
   reset)
@@ -310,31 +324,25 @@ case "${1:-}" in
             echo "You must supply the hostname of the Ambari Server with the restore option. (e.g. ambari-agent reset c6401.ambari.apache.org)"
             exit 1
           fi
-          if [ -f $PIDFILE ]; then
-            echo "$AMBARI_AGENT is running. You must stop it before using reset."
+          if [ -f ${PIDFILE} ]; then
+            echo "${AMBARI_AGENT} is running. You must stop it before using reset."
             exit 1
           fi
-          echo -e "Resetting $AMBARI_AGENT"
+          echo -e "Resetting ${AMBARI_AGENT}"
           change_files_permissions
-          $PYTHON $AGENT_SCRIPT reset $2
+          ${PYTHON} ${AGENT_SCRIPT} reset $2
           retcode=$?
 
-          if [ $retcode -eq 0 ]; then
-            tput bold
-            echo "$AMBARI_AGENT has been reset successfully. Changed Ambari Server hostname to $2 and certificates were cleared."
-            tput sgr0
+          if [ ${retcode} -eq 0 ]; then
+            print_bold "${AMBARI_AGENT} has been reset successfully. Changed Ambari Server hostname to $2 and certificates were cleared."
           else
-            tput bold
-            echo "$AMBARI_AGENT could not be reset."
-            tput sgr0
+            print_bold "${AMBARI_AGENT} could not be reset."
           fi
           ;;
 
   *)
-        tput bold
-        echo "Usage: /usr/sbin/ambari-agent {start|stop|restart|status|reset <server_hostname>}"
-        tput sgr0
+        print_bold "Usage: ${_SCRIPT} {start|stop|restart|status|reset <server_hostname>}"
         retcode=1
 esac
 
-exit $retcode
+exit ${retcode}

+ 52 - 13
ambari-agent/src/main/python/ambari_agent/AmbariAgent.py

@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-'''
+"""
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
 distributed with this work for additional information
@@ -16,31 +16,68 @@ 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.
-'''
+"""
 
 import os
 import sys
 from ambari_commons import subprocess32
-import signal
 
 AGENT_AUTO_RESTART_EXIT_CODE = 77
 
-if os.environ.has_key("PYTHON_BIN"):
-  AGENT_SCRIPT = os.path.join(os.environ["PYTHON_BIN"],"site-packages/ambari_agent/main.py")
+
+def get_logger():
+  import logging
+
+  _logger = logging.getLogger(__name__)
+  _logger.handlers = []
+
+  # pattern used at ambari-agent#start to output messages from script. DO NOT change it without changing grep regex
+  formatter = logging.Formatter("%(levelname)s: %(message)s")
+  handler = logging.StreamHandler()
+  handler.setLevel(logging.WARNING)
+  handler.setFormatter(formatter)
+
+  _logger.addHandler(handler)
+
+  return _logger
+
+
+logger = get_logger()
+
+
+if "PYTHON_BIN" in os.environ:
+  AGENT_SCRIPT = os.path.join(os.environ["PYTHON_BIN"], "site-packages/ambari_agent/main.py")
 else:
   AGENT_SCRIPT = "/usr/lib/ambari-agent/lib/ambari_agent/main.py"
-if os.environ.has_key("AMBARI_PID_DIR"):
-  AGENT_PID_FILE = os.path.join(os.environ["AMBARI_PID_DIR"],"ambari-agent.pid")
+
+if "AMBARI_PID_DIR" in os.environ:
+  AGENT_PID_FILE = os.path.join(os.environ["AMBARI_PID_DIR"], "ambari-agent.pid")
 else:
   AGENT_PID_FILE = "/var/run/ambari-agent/ambari-agent.pid"
+
 # AGENT_AUTO_RESTART_EXIT_CODE = 77 is exit code which we return when restart_agent() is called
 status = AGENT_AUTO_RESTART_EXIT_CODE
 
+
+def check_native_libs_support():
+  not_loaded_extensions = []
+
+  from ambari_simplejson import c_extension
+  if not c_extension.is_loaded():
+    not_loaded_extensions.append("simplejson")
+
+  if subprocess32._posixsubprocess is None:
+    not_loaded_extensions.append("subprocess32")
+
+  if not_loaded_extensions:
+    logger.warning("Some native extensions not available for module(s): {}, it may affect execution performance".format(",".join(not_loaded_extensions)))
+
+
 def main():
   global status
 
-  if (os.environ.has_key("PYTHON")):
-    PYTHON = os.environ["PYTHON"]
+  if "PYTHON" in os.environ:
+    python = os.environ["PYTHON"]
   else:
     print("Key 'PYTHON' is not defined in environment variables")
     sys.exit(1)
@@ -48,14 +85,16 @@ def main():
   args = list(sys.argv)
   del args[0]
 
-  mergedArgs = [PYTHON, AGENT_SCRIPT] + args
+  merged_args = [python, AGENT_SCRIPT] + args
 
   while status == AGENT_AUTO_RESTART_EXIT_CODE:
-    mainProcess = subprocess32.Popen(mergedArgs)
-    mainProcess.communicate()
-    status = mainProcess.returncode
+    check_native_libs_support()
+    main_process = subprocess32.Popen(merged_args)
+    main_process.communicate()
+    status = main_process.returncode
     if os.path.isfile(AGENT_PID_FILE) and status == AGENT_AUTO_RESTART_EXIT_CODE:
       os.remove(AGENT_PID_FILE)
 
+
 if __name__ == "__main__":
     main()

+ 43 - 3
ambari-common/src/main/python/ambari_simplejson/README.txt

@@ -1,3 +1,43 @@
-Since json on Python 2.6 is extra slow (however 2.7 is fine). Ambari struggles much using it.
-This is ~40-100 times faster implementation of json module -- simplejson, with the same function set.
-For speedup it uses C library _speedups.so, without this library it works the same as Python 2.6 json module. 
+Simplejson 3.16.1
+-------------------
+
+Standard python json library with included speedups library for better performance. For speedup it uses
+ C library _speedups.so, without this library it works the same as bundled json module.
+
+
+_speedups folder content:
+/
+|-__init__.py
+|-win  # speedups for win x64 platform
+|  |-_speedups.pyd
+|  |-__init__.py
+|-linux
+|  |-usc2 # linux speedups for python compiled with PyUnicodeUCS2_* functions set
+|  |  |-_speedups.so
+|  |  |-__init__.py
+|  |-usc4 # linux speedups for python compiled with PyUnicodeUCS4_* functions set
+|     |-_speedups.so
+|     |-__init__.py
+|-ppc
+   |-_speedups.so
+   |-__init__.py
+
+USC2/USC4 Explanation:
+     - https://docs.python.org/2/faq/extending.html#when-importing-module-x-why-do-i-get-undefined-symbol-pyunicodeucs2
+
+How to build _speedups.so manually for custom distributive or architecture
+-----------------------------------------------------------------------
+ - Install development tools, for example on CentOS it could be done by command: yum group install "Development Tools"
+ - Install python-devel or python-dev package (depends on linux distribution)
+ - create setup.py file in the some folder as _speedups.c with content like below:
+
+ from distutils.core import setup, Extension
+ setup(name='simplejson', version='3.16.1',
+       ext_modules=[
+         Extension("ambari_commons._speedups", ["_speedups.c"])
+       ])
+
+ - Run python setup.py build
+ - Check build folder for compiled library
+ - Place resulting file to "ambari_simplejson/_speedups/<your platform>/" folder and create empty __init__.py at the same path
+ - Add newly added library path to c extension loader at ambari_simplejson/_import_paths.py#get -> _import_paths

+ 373 - 94
ambari-common/src/main/python/ambari_simplejson/__init__.py

@@ -5,24 +5,23 @@ interchange format.
 :mod:`simplejson` exposes an API familiar to users of the standard library
 :mod:`marshal` and :mod:`pickle` modules. It is the externally maintained
 version of the :mod:`json` library contained in Python 2.6, but maintains
-compatibility with Python 2.4 and Python 2.5 and (currently) has
-significant performance advantages, even without using the optional C
-extension for speedups.
+compatibility back to Python 2.5 and (currently) has significant performance
+advantages, even without using the optional C extension for speedups.
 
 Encoding basic Python object hierarchies::
 
     >>> import simplejson as json
     >>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
     '["foo", {"bar": ["baz", null, 1.0, 2]}]'
-    >>> print json.dumps("\"foo\bar")
+    >>> print(json.dumps("\"foo\bar"))
     "\"foo\bar"
-    >>> print json.dumps(u'\u1234')
+    >>> print(json.dumps(u'\u1234'))
     "\u1234"
-    >>> print json.dumps('\\')
+    >>> print(json.dumps('\\'))
     "\\"
-    >>> print json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True)
+    >>> print(json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True))
     {"a": 0, "b": 0, "c": 0}
-    >>> from StringIO import StringIO
+    >>> from simplejson.compat import StringIO
     >>> io = StringIO()
     >>> json.dump(['streaming API'], io)
     >>> io.getvalue()
@@ -31,14 +30,14 @@ Encoding basic Python object hierarchies::
 Compact encoding::
 
     >>> import simplejson as json
-    >>> json.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':'))
+    >>> obj = [1,2,3,{'4': 5, '6': 7}]
+    >>> json.dumps(obj, separators=(',',':'), sort_keys=True)
     '[1,2,3,{"4":5,"6":7}]'
 
 Pretty printing::
 
     >>> import simplejson as json
-    >>> s = json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=4)
-    >>> print '\n'.join([l.rstrip() for l in  s.splitlines()])
+    >>> print(json.dumps({'4': 5, '6': 7}, sort_keys=True, indent='    '))
     {
         "4": 5,
         "6": 7
@@ -52,7 +51,7 @@ Decoding JSON::
     True
     >>> json.loads('"\\"foo\\bar"') == u'"foo\x08ar'
     True
-    >>> from StringIO import StringIO
+    >>> from simplejson.compat import StringIO
     >>> io = StringIO('["streaming API"]')
     >>> json.load(io)[0] == 'streaming API'
     True
@@ -68,8 +67,8 @@ Specializing JSON object decoding::
     >>> json.loads('{"__complex__": true, "real": 1, "imag": 2}',
     ...     object_hook=as_complex)
     (1+2j)
-    >>> import decimal
-    >>> json.loads('1.1', parse_float=decimal.Decimal) == decimal.Decimal('1.1')
+    >>> from decimal import Decimal
+    >>> json.loads('1.1', parse_float=Decimal) == Decimal('1.1')
     True
 
 Specializing JSON object encoding::
@@ -78,7 +77,8 @@ Specializing JSON object encoding::
     >>> def encode_complex(obj):
     ...     if isinstance(obj, complex):
     ...         return [obj.real, obj.imag]
-    ...     raise TypeError(repr(o) + " is not JSON serializable")
+    ...     raise TypeError('Object of type %s is not JSON serializable' %
+    ...                     obj.__class__.__name__)
     ...
     >>> json.dumps(2 + 1j, default=encode_complex)
     '[2.0, 1.0]'
@@ -87,7 +87,6 @@ Specializing JSON object encoding::
     >>> ''.join(json.JSONEncoder(default=encode_complex).iterencode(2 + 1j))
     '[2.0, 1.0]'
 
-
 Using simplejson.tool from the shell to validate and pretty-print::
 
     $ echo '{"json":"obj"}' | python -m simplejson.tool
@@ -95,18 +94,60 @@ Using simplejson.tool from the shell to validate and pretty-print::
         "json": "obj"
     }
     $ echo '{ 1.2:3.4}' | python -m simplejson.tool
-    Expecting property name: line 1 column 2 (char 2)
+    Expecting property name: line 1 column 3 (char 2)
+
+Parsing multiple documents serialized as JSON lines (newline-delimited JSON)::
+
+    >>> import simplejson as json
+    >>> def loads_lines(docs):
+    ...     for doc in docs.splitlines():
+    ...         yield json.loads(doc)
+    ...
+    >>> sum(doc["count"] for doc in loads_lines('{"count":1}\n{"count":2}\n{"count":3}\n'))
+    6
+
+Serializing multiple objects to JSON lines (newline-delimited JSON)::
+
+    >>> import simplejson as json
+    >>> def dumps_lines(objs):
+    ...     for obj in objs:
+    ...         yield json.dumps(obj, separators=(',',':')) + '\n'
+    ...
+    >>> ''.join(dumps_lines([{'count': 1}, {'count': 2}, {'count': 3}]))
+    '{"count":1}\n{"count":2}\n{"count":3}\n'
+
 """
-__version__ = '2.0.9'
+from __future__ import absolute_import
+__version__ = '3.16.1'
 __all__ = [
     'dump', 'dumps', 'load', 'loads',
-    'JSONDecoder', 'JSONEncoder',
+    'JSONDecoder', 'JSONDecodeError', 'JSONEncoder',
+    'OrderedDict', 'simple_first', 'RawJSON', 'c_extension'
 ]
 
 __author__ = 'Bob Ippolito <bob@redivi.com>'
 
-from decoder import JSONDecoder
-from encoder import JSONEncoder
+from decimal import Decimal
+
+from .errors import JSONDecodeError
+from .raw_json import RawJSON
+from .decoder import JSONDecoder
+from .encoder import JSONEncoder, JSONEncoderForHTML
+def _import_OrderedDict():
+    import collections
+    try:
+        return collections.OrderedDict
+    except AttributeError:
+        from . import ordered_dict
+        return ordered_dict.OrderedDict
+OrderedDict = _import_OrderedDict()
+
+def _import_c_make_encoder():
+    try:
+        from ._speedups import make_encoder
+        return make_encoder
+    except ImportError:
+        return None
 
 _default_encoder = JSONEncoder(
     skipkeys=False,
@@ -117,56 +158,123 @@ _default_encoder = JSONEncoder(
     separators=None,
     encoding='utf-8',
     default=None,
+    use_decimal=True,
+    namedtuple_as_object=True,
+    tuple_as_array=True,
+    iterable_as_array=False,
+    bigint_as_string=False,
+    item_sort_key=None,
+    for_json=False,
+    ignore_nan=False,
+    int_as_string_bitcount=None,
 )
 
 def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
-        allow_nan=True, cls=None, indent=None, separators=None,
-        encoding='utf-8', default=None, **kw):
+         allow_nan=True, cls=None, indent=None, separators=None,
+         encoding='utf-8', default=None, use_decimal=True,
+         namedtuple_as_object=True, tuple_as_array=True,
+         bigint_as_string=False, sort_keys=False, item_sort_key=None,
+         for_json=False, ignore_nan=False, int_as_string_bitcount=None,
+         iterable_as_array=False, **kw):
     """Serialize ``obj`` as a JSON formatted stream to ``fp`` (a
     ``.write()``-supporting file-like object).
 
-    If ``skipkeys`` is true then ``dict`` keys that are not basic types
+    If *skipkeys* is true then ``dict`` keys that are not basic types
     (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
     will be skipped instead of raising a ``TypeError``.
 
-    If ``ensure_ascii`` is false, then the some chunks written to ``fp``
+    If *ensure_ascii* is false, then the some chunks written to ``fp``
     may be ``unicode`` instances, subject to normal Python ``str`` to
     ``unicode`` coercion rules. Unless ``fp.write()`` explicitly
     understands ``unicode`` (as in ``codecs.getwriter()``) this is likely
     to cause an error.
 
-    If ``check_circular`` is false, then the circular reference check
+    If *check_circular* is false, then the circular reference check
     for container types will be skipped and a circular reference will
     result in an ``OverflowError`` (or worse).
 
-    If ``allow_nan`` is false, then it will be a ``ValueError`` to
+    If *allow_nan* is false, then it will be a ``ValueError`` to
     serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``)
-    in strict compliance of the JSON specification, instead of using the
-    JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
+    in strict compliance of the original JSON specification, instead of using
+    the JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``). See
+    *ignore_nan* for ECMA-262 compliant behavior.
 
-    If ``indent`` is a non-negative integer, then JSON array elements and object
-    members will be pretty-printed with that indent level. An indent level
-    of 0 will only insert newlines. ``None`` is the most compact representation.
+    If *indent* is a string, then JSON array elements and object members
+    will be pretty-printed with a newline followed by that string repeated
+    for each level of nesting. ``None`` (the default) selects the most compact
+    representation without any newlines. For backwards compatibility with
+    versions of simplejson earlier than 2.1.0, an integer is also accepted
+    and is converted to a string with that many spaces.
 
-    If ``separators`` is an ``(item_separator, dict_separator)`` tuple
-    then it will be used instead of the default ``(', ', ': ')`` separators.
-    ``(',', ':')`` is the most compact JSON representation.
+    If specified, *separators* should be an
+    ``(item_separator, key_separator)`` tuple.  The default is ``(', ', ': ')``
+    if *indent* is ``None`` and ``(',', ': ')`` otherwise.  To get the most
+    compact JSON representation, you should specify ``(',', ':')`` to eliminate
+    whitespace.
 
-    ``encoding`` is the character encoding for str instances, default is UTF-8.
+    *encoding* is the character encoding for str instances, default is UTF-8.
 
-    ``default(obj)`` is a function that should return a serializable version
-    of obj or raise TypeError. The default simply raises TypeError.
+    *default(obj)* is a function that should return a serializable version
+    of obj or raise ``TypeError``. The default simply raises ``TypeError``.
+
+    If *use_decimal* is true (default: ``True``) then decimal.Decimal
+    will be natively serialized to JSON with full precision.
+
+    If *namedtuple_as_object* is true (default: ``True``),
+    :class:`tuple` subclasses with ``_asdict()`` methods will be encoded
+    as JSON objects.
+
+    If *tuple_as_array* is true (default: ``True``),
+    :class:`tuple` (and subclasses) will be encoded as JSON arrays.
+
+    If *iterable_as_array* is true (default: ``False``),
+    any object not in the above table that implements ``__iter__()``
+    will be encoded as a JSON array.
+
+    If *bigint_as_string* is true (default: ``False``), ints 2**53 and higher
+    or lower than -2**53 will be encoded as strings. This is to avoid the
+    rounding that happens in Javascript otherwise. Note that this is still a
+    lossy operation that will not round-trip correctly and should be used
+    sparingly.
+
+    If *int_as_string_bitcount* is a positive number (n), then int of size
+    greater than or equal to 2**n or lower than or equal to -2**n will be
+    encoded as strings.
+
+    If specified, *item_sort_key* is a callable used to sort the items in
+    each dictionary. This is useful if you want to sort items other than
+    in alphabetical order by key. This option takes precedence over
+    *sort_keys*.
+
+    If *sort_keys* is true (default: ``False``), the output of dictionaries
+    will be sorted by item.
+
+    If *for_json* is true (default: ``False``), objects with a ``for_json()``
+    method will use the return value of that method for encoding as JSON
+    instead of the object.
+
+    If *ignore_nan* is true (default: ``False``), then out of range
+    :class:`float` values (``nan``, ``inf``, ``-inf``) will be serialized as
+    ``null`` in compliance with the ECMA-262 specification. If true, this will
+    override *allow_nan*.
 
     To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
     ``.default()`` method to serialize additional types), specify it with
-    the ``cls`` kwarg.
+    the ``cls`` kwarg. NOTE: You should use *default* or *for_json* instead
+    of subclassing whenever possible.
 
     """
     # cached encoder
     if (not skipkeys and ensure_ascii and
         check_circular and allow_nan and
         cls is None and indent is None and separators is None and
-        encoding == 'utf-8' and default is None and not kw):
+        encoding == 'utf-8' and default is None and use_decimal
+        and namedtuple_as_object and tuple_as_array and not iterable_as_array
+        and not bigint_as_string and not sort_keys
+        and not item_sort_key and not for_json
+        and not ignore_nan and int_as_string_bitcount is None
+        and not kw
+    ):
         iterable = _default_encoder.iterencode(obj)
     else:
         if cls is None:
@@ -174,7 +282,17 @@ def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
         iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii,
             check_circular=check_circular, allow_nan=allow_nan, indent=indent,
             separators=separators, encoding=encoding,
-            default=default, **kw).iterencode(obj)
+            default=default, use_decimal=use_decimal,
+            namedtuple_as_object=namedtuple_as_object,
+            tuple_as_array=tuple_as_array,
+            iterable_as_array=iterable_as_array,
+            bigint_as_string=bigint_as_string,
+            sort_keys=sort_keys,
+            item_sort_key=item_sort_key,
+            for_json=for_json,
+            ignore_nan=ignore_nan,
+            int_as_string_bitcount=int_as_string_bitcount,
+            **kw).iterencode(obj)
     # could accelerate with writelines in some versions of Python, at
     # a debuggability cost
     for chunk in iterable:
@@ -182,8 +300,12 @@ def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
 
 
 def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
-        allow_nan=True, cls=None, indent=None, separators=None,
-        encoding='utf-8', default=None, **kw):
+          allow_nan=True, cls=None, indent=None, separators=None,
+          encoding='utf-8', default=None, use_decimal=True,
+          namedtuple_as_object=True, tuple_as_array=True,
+          bigint_as_string=False, sort_keys=False, item_sort_key=None,
+          for_json=False, ignore_nan=False, int_as_string_bitcount=None,
+          iterable_as_array=False, **kw):
     """Serialize ``obj`` to a JSON formatted ``str``.
 
     If ``skipkeys`` is false then ``dict`` keys that are not basic types
@@ -203,30 +325,80 @@ def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
     strict compliance of the JSON specification, instead of using the
     JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
 
-    If ``indent`` is a non-negative integer, then JSON array elements and
-    object members will be pretty-printed with that indent level. An indent
-    level of 0 will only insert newlines. ``None`` is the most compact
-    representation.
+    If ``indent`` is a string, then JSON array elements and object members
+    will be pretty-printed with a newline followed by that string repeated
+    for each level of nesting. ``None`` (the default) selects the most compact
+    representation without any newlines. For backwards compatibility with
+    versions of simplejson earlier than 2.1.0, an integer is also accepted
+    and is converted to a string with that many spaces.
 
-    If ``separators`` is an ``(item_separator, dict_separator)`` tuple
-    then it will be used instead of the default ``(', ', ': ')`` separators.
-    ``(',', ':')`` is the most compact JSON representation.
+    If specified, ``separators`` should be an
+    ``(item_separator, key_separator)`` tuple.  The default is ``(', ', ': ')``
+    if *indent* is ``None`` and ``(',', ': ')`` otherwise.  To get the most
+    compact JSON representation, you should specify ``(',', ':')`` to eliminate
+    whitespace.
 
     ``encoding`` is the character encoding for str instances, default is UTF-8.
 
     ``default(obj)`` is a function that should return a serializable version
     of obj or raise TypeError. The default simply raises TypeError.
 
+    If *use_decimal* is true (default: ``True``) then decimal.Decimal
+    will be natively serialized to JSON with full precision.
+
+    If *namedtuple_as_object* is true (default: ``True``),
+    :class:`tuple` subclasses with ``_asdict()`` methods will be encoded
+    as JSON objects.
+
+    If *tuple_as_array* is true (default: ``True``),
+    :class:`tuple` (and subclasses) will be encoded as JSON arrays.
+
+    If *iterable_as_array* is true (default: ``False``),
+    any object not in the above table that implements ``__iter__()``
+    will be encoded as a JSON array.
+
+    If *bigint_as_string* is true (not the default), ints 2**53 and higher
+    or lower than -2**53 will be encoded as strings. This is to avoid the
+    rounding that happens in Javascript otherwise.
+
+    If *int_as_string_bitcount* is a positive number (n), then int of size
+    greater than or equal to 2**n or lower than or equal to -2**n will be
+    encoded as strings.
+
+    If specified, *item_sort_key* is a callable used to sort the items in
+    each dictionary. This is useful if you want to sort items other than
+    in alphabetical order by key. This option takes precendence over
+    *sort_keys*.
+
+    If *sort_keys* is true (default: ``False``), the output of dictionaries
+    will be sorted by item.
+
+    If *for_json* is true (default: ``False``), objects with a ``for_json()``
+    method will use the return value of that method for encoding as JSON
+    instead of the object.
+
+    If *ignore_nan* is true (default: ``False``), then out of range
+    :class:`float` values (``nan``, ``inf``, ``-inf``) will be serialized as
+    ``null`` in compliance with the ECMA-262 specification. If true, this will
+    override *allow_nan*.
+
     To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
     ``.default()`` method to serialize additional types), specify it with
-    the ``cls`` kwarg.
+    the ``cls`` kwarg. NOTE: You should use *default* instead of subclassing
+    whenever possible.
 
     """
     # cached encoder
     if (not skipkeys and ensure_ascii and
         check_circular and allow_nan and
         cls is None and indent is None and separators is None and
-        encoding == 'utf-8' and default is None and not kw):
+        encoding == 'utf-8' and default is None and use_decimal
+        and namedtuple_as_object and tuple_as_array and not iterable_as_array
+        and not bigint_as_string and not sort_keys
+        and not item_sort_key and not for_json
+        and not ignore_nan and int_as_string_bitcount is None
+        and not kw
+    ):
         return _default_encoder.encode(obj)
     if cls is None:
         cls = JSONEncoder
@@ -234,85 +406,192 @@ def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
         skipkeys=skipkeys, ensure_ascii=ensure_ascii,
         check_circular=check_circular, allow_nan=allow_nan, indent=indent,
         separators=separators, encoding=encoding, default=default,
+        use_decimal=use_decimal,
+        namedtuple_as_object=namedtuple_as_object,
+        tuple_as_array=tuple_as_array,
+        iterable_as_array=iterable_as_array,
+        bigint_as_string=bigint_as_string,
+        sort_keys=sort_keys,
+        item_sort_key=item_sort_key,
+        for_json=for_json,
+        ignore_nan=ignore_nan,
+        int_as_string_bitcount=int_as_string_bitcount,
         **kw).encode(obj)
 
 
-_default_decoder = JSONDecoder(encoding=None, object_hook=None)
+_default_decoder = JSONDecoder(encoding=None, object_hook=None,
+                               object_pairs_hook=None)
 
 
 def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None,
-        parse_int=None, parse_constant=None, **kw):
+        parse_int=None, parse_constant=None, object_pairs_hook=None,
+        use_decimal=False, namedtuple_as_object=True, tuple_as_array=True,
+        **kw):
     """Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
     a JSON document) to a Python object.
 
-    If the contents of ``fp`` is encoded with an ASCII based encoding other
-    than utf-8 (e.g. latin-1), then an appropriate ``encoding`` name must
-    be specified. Encodings that are not ASCII based (such as UCS-2) are
-    not allowed, and should be wrapped with
-    ``codecs.getreader(fp)(encoding)``, or simply decoded to a ``unicode``
-    object and passed to ``loads()``
-
-    ``object_hook`` is an optional function that will be called with the
-    result of any object literal decode (a ``dict``). The return value of
-    ``object_hook`` will be used instead of the ``dict``. This feature
-    can be used to implement custom decoders (e.g. JSON-RPC class hinting).
+    *encoding* determines the encoding used to interpret any
+    :class:`str` objects decoded by this instance (``'utf-8'`` by
+    default).  It has no effect when decoding :class:`unicode` objects.
+
+    Note that currently only encodings that are a superset of ASCII work,
+    strings of other encodings should be passed in as :class:`unicode`.
+
+    *object_hook*, if specified, will be called with the result of every
+    JSON object decoded and its return value will be used in place of the
+    given :class:`dict`.  This can be used to provide custom
+    deserializations (e.g. to support JSON-RPC class hinting).
+
+    *object_pairs_hook* is an optional function that will be called with
+    the result of any object literal decode with an ordered list of pairs.
+    The return value of *object_pairs_hook* will be used instead of the
+    :class:`dict`.  This feature can be used to implement custom decoders
+    that rely on the order that the key and value pairs are decoded (for
+    example, :func:`collections.OrderedDict` will remember the order of
+    insertion). If *object_hook* is also defined, the *object_pairs_hook*
+    takes priority.
+
+    *parse_float*, if specified, will be called with the string of every
+    JSON float to be decoded.  By default, this is equivalent to
+    ``float(num_str)``. This can be used to use another datatype or parser
+    for JSON floats (e.g. :class:`decimal.Decimal`).
+
+    *parse_int*, if specified, will be called with the string of every
+    JSON int to be decoded.  By default, this is equivalent to
+    ``int(num_str)``.  This can be used to use another datatype or parser
+    for JSON integers (e.g. :class:`float`).
+
+    *parse_constant*, if specified, will be called with one of the
+    following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``.  This
+    can be used to raise an exception if invalid JSON numbers are
+    encountered.
+
+    If *use_decimal* is true (default: ``False``) then it implies
+    parse_float=decimal.Decimal for parity with ``dump``.
 
     To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
-    kwarg.
+    kwarg. NOTE: You should use *object_hook* or *object_pairs_hook* instead
+    of subclassing whenever possible.
 
     """
     return loads(fp.read(),
         encoding=encoding, cls=cls, object_hook=object_hook,
         parse_float=parse_float, parse_int=parse_int,
-        parse_constant=parse_constant, **kw)
+        parse_constant=parse_constant, object_pairs_hook=object_pairs_hook,
+        use_decimal=use_decimal, **kw)
 
 
 def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None,
-        parse_int=None, parse_constant=None, **kw):
+        parse_int=None, parse_constant=None, object_pairs_hook=None,
+        use_decimal=False, **kw):
     """Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON
     document) to a Python object.
 
-    If ``s`` is a ``str`` instance and is encoded with an ASCII based encoding
-    other than utf-8 (e.g. latin-1) then an appropriate ``encoding`` name
-    must be specified. Encodings that are not ASCII based (such as UCS-2)
-    are not allowed and should be decoded to ``unicode`` first.
-
-    ``object_hook`` is an optional function that will be called with the
-    result of any object literal decode (a ``dict``). The return value of
-    ``object_hook`` will be used instead of the ``dict``. This feature
-    can be used to implement custom decoders (e.g. JSON-RPC class hinting).
-
-    ``parse_float``, if specified, will be called with the string
-    of every JSON float to be decoded. By default this is equivalent to
-    float(num_str). This can be used to use another datatype or parser
-    for JSON floats (e.g. decimal.Decimal).
-
-    ``parse_int``, if specified, will be called with the string
-    of every JSON int to be decoded. By default this is equivalent to
-    int(num_str). This can be used to use another datatype or parser
-    for JSON integers (e.g. float).
-
-    ``parse_constant``, if specified, will be called with one of the
-    following strings: -Infinity, Infinity, NaN, null, true, false.
-    This can be used to raise an exception if invalid JSON numbers
-    are encountered.
+    *encoding* determines the encoding used to interpret any
+    :class:`str` objects decoded by this instance (``'utf-8'`` by
+    default).  It has no effect when decoding :class:`unicode` objects.
+
+    Note that currently only encodings that are a superset of ASCII work,
+    strings of other encodings should be passed in as :class:`unicode`.
+
+    *object_hook*, if specified, will be called with the result of every
+    JSON object decoded and its return value will be used in place of the
+    given :class:`dict`.  This can be used to provide custom
+    deserializations (e.g. to support JSON-RPC class hinting).
+
+    *object_pairs_hook* is an optional function that will be called with
+    the result of any object literal decode with an ordered list of pairs.
+    The return value of *object_pairs_hook* will be used instead of the
+    :class:`dict`.  This feature can be used to implement custom decoders
+    that rely on the order that the key and value pairs are decoded (for
+    example, :func:`collections.OrderedDict` will remember the order of
+    insertion). If *object_hook* is also defined, the *object_pairs_hook*
+    takes priority.
+
+    *parse_float*, if specified, will be called with the string of every
+    JSON float to be decoded.  By default, this is equivalent to
+    ``float(num_str)``. This can be used to use another datatype or parser
+    for JSON floats (e.g. :class:`decimal.Decimal`).
+
+    *parse_int*, if specified, will be called with the string of every
+    JSON int to be decoded.  By default, this is equivalent to
+    ``int(num_str)``.  This can be used to use another datatype or parser
+    for JSON integers (e.g. :class:`float`).
+
+    *parse_constant*, if specified, will be called with one of the
+    following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``.  This
+    can be used to raise an exception if invalid JSON numbers are
+    encountered.
+
+    If *use_decimal* is true (default: ``False``) then it implies
+    parse_float=decimal.Decimal for parity with ``dump``.
 
     To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
-    kwarg.
+    kwarg. NOTE: You should use *object_hook* or *object_pairs_hook* instead
+    of subclassing whenever possible.
 
     """
     if (cls is None and encoding is None and object_hook is None and
             parse_int is None and parse_float is None and
-            parse_constant is None and not kw):
+            parse_constant is None and object_pairs_hook is None
+            and not use_decimal and not kw):
         return _default_decoder.decode(s)
     if cls is None:
         cls = JSONDecoder
     if object_hook is not None:
         kw['object_hook'] = object_hook
+    if object_pairs_hook is not None:
+        kw['object_pairs_hook'] = object_pairs_hook
     if parse_float is not None:
         kw['parse_float'] = parse_float
     if parse_int is not None:
         kw['parse_int'] = parse_int
     if parse_constant is not None:
         kw['parse_constant'] = parse_constant
+    if use_decimal:
+        if parse_float is not None:
+            raise TypeError("use_decimal=True implies parse_float=Decimal")
+        kw['parse_float'] = Decimal
     return cls(encoding=encoding, **kw).decode(s)
+
+
+def _toggle_speedups(enabled):
+    from . import decoder as dec
+    from . import encoder as enc
+    from . import scanner as scan
+    c_make_encoder = _import_c_make_encoder()
+    if enabled:
+        dec.scanstring = dec.c_scanstring or dec.py_scanstring
+        enc.c_make_encoder = c_make_encoder
+        enc.encode_basestring_ascii = (enc.c_encode_basestring_ascii or
+            enc.py_encode_basestring_ascii)
+        scan.make_scanner = scan.c_make_scanner or scan.py_make_scanner
+    else:
+        dec.scanstring = dec.py_scanstring
+        enc.c_make_encoder = None
+        enc.encode_basestring_ascii = enc.py_encode_basestring_ascii
+        scan.make_scanner = scan.py_make_scanner
+    dec.make_scanner = scan.make_scanner
+    global _default_decoder
+    _default_decoder = JSONDecoder(
+        encoding=None,
+        object_hook=None,
+        object_pairs_hook=None,
+    )
+    global _default_encoder
+    _default_encoder = JSONEncoder(
+       skipkeys=False,
+       ensure_ascii=True,
+       check_circular=True,
+       allow_nan=True,
+       indent=None,
+       separators=None,
+       encoding='utf-8',
+       default=None,
+   )
+
+def simple_first(kv):
+    """Helper function to pass to item_sort_key to sort simple
+    elements to the top, then container elements.
+    """
+    return (isinstance(kv[1], (list, dict, tuple)), kv[0])

+ 3384 - 0
ambari-common/src/main/python/ambari_simplejson/_speedups.c

@@ -0,0 +1,3384 @@
+/* -*- mode: C; c-file-style: "python"; c-basic-offset: 4 -*- */
+#include "Python.h"
+#include "structmember.h"
+
+#if PY_MAJOR_VERSION >= 3
+#define PyInt_FromSsize_t PyLong_FromSsize_t
+#define PyInt_AsSsize_t PyLong_AsSsize_t
+#define PyInt_Check(obj) 0
+#define PyInt_CheckExact(obj) 0
+#define JSON_UNICHR Py_UCS4
+#define JSON_InternFromString PyUnicode_InternFromString
+#define PyString_GET_SIZE PyUnicode_GET_LENGTH
+#define PY2_UNUSED
+#define PY3_UNUSED UNUSED
+#else /* PY_MAJOR_VERSION >= 3 */
+#define PY2_UNUSED UNUSED
+#define PY3_UNUSED
+#define PyBytes_Check PyString_Check
+#define PyUnicode_READY(obj) 0
+#define PyUnicode_KIND(obj) (sizeof(Py_UNICODE))
+#define PyUnicode_DATA(obj) ((void *)(PyUnicode_AS_UNICODE(obj)))
+#define PyUnicode_READ(kind, data, index) ((JSON_UNICHR)((const Py_UNICODE *)(data))[(index)])
+#define PyUnicode_GET_LENGTH PyUnicode_GET_SIZE
+#define JSON_UNICHR Py_UNICODE
+#define JSON_InternFromString PyString_InternFromString
+#endif /* PY_MAJOR_VERSION < 3 */
+
+#if PY_VERSION_HEX < 0x02070000
+#if !defined(PyOS_string_to_double)
+#define PyOS_string_to_double json_PyOS_string_to_double
+static double
+json_PyOS_string_to_double(const char *s, char **endptr, PyObject *overflow_exception);
+static double
+json_PyOS_string_to_double(const char *s, char **endptr, PyObject *overflow_exception)
+{
+    double x;
+    assert(endptr == NULL);
+    assert(overflow_exception == NULL);
+    PyFPE_START_PROTECT("json_PyOS_string_to_double", return -1.0;)
+    x = PyOS_ascii_atof(s);
+    PyFPE_END_PROTECT(x)
+    return x;
+}
+#endif
+#endif /* PY_VERSION_HEX < 0x02070000 */
+
+#if PY_VERSION_HEX < 0x02060000
+#if !defined(Py_TYPE)
+#define Py_TYPE(ob)     (((PyObject*)(ob))->ob_type)
+#endif
+#if !defined(Py_SIZE)
+#define Py_SIZE(ob)     (((PyVarObject*)(ob))->ob_size)
+#endif
+#if !defined(PyVarObject_HEAD_INIT)
+#define PyVarObject_HEAD_INIT(type, size) PyObject_HEAD_INIT(type) size,
+#endif
+#endif /* PY_VERSION_HEX < 0x02060000 */
+
+#ifdef __GNUC__
+#define UNUSED __attribute__((__unused__))
+#else
+#define UNUSED
+#endif
+
+#define DEFAULT_ENCODING "utf-8"
+
+#define PyScanner_Check(op) PyObject_TypeCheck(op, &PyScannerType)
+#define PyScanner_CheckExact(op) (Py_TYPE(op) == &PyScannerType)
+#define PyEncoder_Check(op) PyObject_TypeCheck(op, &PyEncoderType)
+#define PyEncoder_CheckExact(op) (Py_TYPE(op) == &PyEncoderType)
+
+#define JSON_ALLOW_NAN 1
+#define JSON_IGNORE_NAN 2
+
+static PyObject *JSON_Infinity = NULL;
+static PyObject *JSON_NegInfinity = NULL;
+static PyObject *JSON_NaN = NULL;
+static PyObject *JSON_EmptyUnicode = NULL;
+#if PY_MAJOR_VERSION < 3
+static PyObject *JSON_EmptyStr = NULL;
+#endif
+
+static PyTypeObject PyScannerType;
+static PyTypeObject PyEncoderType;
+
+typedef struct {
+    PyObject *large_strings;  /* A list of previously accumulated large strings */
+    PyObject *small_strings;  /* Pending small strings */
+} JSON_Accu;
+
+static int
+JSON_Accu_Init(JSON_Accu *acc);
+static int
+JSON_Accu_Accumulate(JSON_Accu *acc, PyObject *unicode);
+static PyObject *
+JSON_Accu_FinishAsList(JSON_Accu *acc);
+static void
+JSON_Accu_Destroy(JSON_Accu *acc);
+
+#define ERR_EXPECTING_VALUE "Expecting value"
+#define ERR_ARRAY_DELIMITER "Expecting ',' delimiter or ']'"
+#define ERR_ARRAY_VALUE_FIRST "Expecting value or ']'"
+#define ERR_OBJECT_DELIMITER "Expecting ',' delimiter or '}'"
+#define ERR_OBJECT_PROPERTY "Expecting property name enclosed in double quotes"
+#define ERR_OBJECT_PROPERTY_FIRST "Expecting property name enclosed in double quotes or '}'"
+#define ERR_OBJECT_PROPERTY_DELIMITER "Expecting ':' delimiter"
+#define ERR_STRING_UNTERMINATED "Unterminated string starting at"
+#define ERR_STRING_CONTROL "Invalid control character %r at"
+#define ERR_STRING_ESC1 "Invalid \\X escape sequence %r"
+#define ERR_STRING_ESC4 "Invalid \\uXXXX escape sequence"
+
+typedef struct _PyScannerObject {
+    PyObject_HEAD
+    PyObject *encoding;
+    PyObject *strict_bool;
+    int strict;
+    PyObject *object_hook;
+    PyObject *pairs_hook;
+    PyObject *parse_float;
+    PyObject *parse_int;
+    PyObject *parse_constant;
+    PyObject *memo;
+} PyScannerObject;
+
+static PyMemberDef scanner_members[] = {
+    {"encoding", T_OBJECT, offsetof(PyScannerObject, encoding), READONLY, "encoding"},
+    {"strict", T_OBJECT, offsetof(PyScannerObject, strict_bool), READONLY, "strict"},
+    {"object_hook", T_OBJECT, offsetof(PyScannerObject, object_hook), READONLY, "object_hook"},
+    {"object_pairs_hook", T_OBJECT, offsetof(PyScannerObject, pairs_hook), READONLY, "object_pairs_hook"},
+    {"parse_float", T_OBJECT, offsetof(PyScannerObject, parse_float), READONLY, "parse_float"},
+    {"parse_int", T_OBJECT, offsetof(PyScannerObject, parse_int), READONLY, "parse_int"},
+    {"parse_constant", T_OBJECT, offsetof(PyScannerObject, parse_constant), READONLY, "parse_constant"},
+    {NULL}
+};
+
+typedef struct _PyEncoderObject {
+    PyObject_HEAD
+    PyObject *markers;
+    PyObject *defaultfn;
+    PyObject *encoder;
+    PyObject *indent;
+    PyObject *key_separator;
+    PyObject *item_separator;
+    PyObject *sort_keys;
+    PyObject *key_memo;
+    PyObject *encoding;
+    PyObject *Decimal;
+    PyObject *skipkeys_bool;
+    int skipkeys;
+    int fast_encode;
+    /* 0, JSON_ALLOW_NAN, JSON_IGNORE_NAN */
+    int allow_or_ignore_nan;
+    int use_decimal;
+    int namedtuple_as_object;
+    int tuple_as_array;
+    int iterable_as_array;
+    PyObject *max_long_size;
+    PyObject *min_long_size;
+    PyObject *item_sort_key;
+    PyObject *item_sort_kw;
+    int for_json;
+} PyEncoderObject;
+
+static PyMemberDef encoder_members[] = {
+    {"markers", T_OBJECT, offsetof(PyEncoderObject, markers), READONLY, "markers"},
+    {"default", T_OBJECT, offsetof(PyEncoderObject, defaultfn), READONLY, "default"},
+    {"encoder", T_OBJECT, offsetof(PyEncoderObject, encoder), READONLY, "encoder"},
+    {"encoding", T_OBJECT, offsetof(PyEncoderObject, encoder), READONLY, "encoding"},
+    {"indent", T_OBJECT, offsetof(PyEncoderObject, indent), READONLY, "indent"},
+    {"key_separator", T_OBJECT, offsetof(PyEncoderObject, key_separator), READONLY, "key_separator"},
+    {"item_separator", T_OBJECT, offsetof(PyEncoderObject, item_separator), READONLY, "item_separator"},
+    {"sort_keys", T_OBJECT, offsetof(PyEncoderObject, sort_keys), READONLY, "sort_keys"},
+    /* Python 2.5 does not support T_BOOl */
+    {"skipkeys", T_OBJECT, offsetof(PyEncoderObject, skipkeys_bool), READONLY, "skipkeys"},
+    {"key_memo", T_OBJECT, offsetof(PyEncoderObject, key_memo), READONLY, "key_memo"},
+    {"item_sort_key", T_OBJECT, offsetof(PyEncoderObject, item_sort_key), READONLY, "item_sort_key"},
+    {"max_long_size", T_OBJECT, offsetof(PyEncoderObject, max_long_size), READONLY, "max_long_size"},
+    {"min_long_size", T_OBJECT, offsetof(PyEncoderObject, min_long_size), READONLY, "min_long_size"},
+    {NULL}
+};
+
+static PyObject *
+join_list_unicode(PyObject *lst);
+static PyObject *
+JSON_ParseEncoding(PyObject *encoding);
+static PyObject *
+maybe_quote_bigint(PyEncoderObject* s, PyObject *encoded, PyObject *obj);
+static Py_ssize_t
+ascii_char_size(JSON_UNICHR c);
+static Py_ssize_t
+ascii_escape_char(JSON_UNICHR c, char *output, Py_ssize_t chars);
+static PyObject *
+ascii_escape_unicode(PyObject *pystr);
+static PyObject *
+ascii_escape_str(PyObject *pystr);
+static PyObject *
+py_encode_basestring_ascii(PyObject* self UNUSED, PyObject *pystr);
+#if PY_MAJOR_VERSION < 3
+static PyObject *
+join_list_string(PyObject *lst);
+static PyObject *
+scan_once_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr);
+static PyObject *
+scanstring_str(PyObject *pystr, Py_ssize_t end, char *encoding, int strict, Py_ssize_t *next_end_ptr);
+static PyObject *
+_parse_object_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr);
+#endif
+static PyObject *
+scanstring_unicode(PyObject *pystr, Py_ssize_t end, int strict, Py_ssize_t *next_end_ptr);
+static PyObject *
+scan_once_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr);
+static PyObject *
+_build_rval_index_tuple(PyObject *rval, Py_ssize_t idx);
+static PyObject *
+scanner_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
+static void
+scanner_dealloc(PyObject *self);
+static int
+scanner_clear(PyObject *self);
+static PyObject *
+encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
+static void
+encoder_dealloc(PyObject *self);
+static int
+encoder_clear(PyObject *self);
+static int
+is_raw_json(PyObject *obj);
+static PyObject *
+encoder_stringify_key(PyEncoderObject *s, PyObject *key);
+static int
+encoder_listencode_list(PyEncoderObject *s, JSON_Accu *rval, PyObject *seq, Py_ssize_t indent_level);
+static int
+encoder_listencode_obj(PyEncoderObject *s, JSON_Accu *rval, PyObject *obj, Py_ssize_t indent_level);
+static int
+encoder_listencode_dict(PyEncoderObject *s, JSON_Accu *rval, PyObject *dct, Py_ssize_t indent_level);
+static PyObject *
+_encoded_const(PyObject *obj);
+static void
+raise_errmsg(char *msg, PyObject *s, Py_ssize_t end);
+static PyObject *
+encoder_encode_string(PyEncoderObject *s, PyObject *obj);
+static int
+_convertPyInt_AsSsize_t(PyObject *o, Py_ssize_t *size_ptr);
+static PyObject *
+_convertPyInt_FromSsize_t(Py_ssize_t *size_ptr);
+static PyObject *
+encoder_encode_float(PyEncoderObject *s, PyObject *obj);
+static int
+_is_namedtuple(PyObject *obj);
+static int
+_has_for_json_hook(PyObject *obj);
+static PyObject *
+moduleinit(void);
+
+#define S_CHAR(c) (c >= ' ' && c <= '~' && c != '\\' && c != '"')
+#define IS_WHITESPACE(c) (((c) == ' ') || ((c) == '\t') || ((c) == '\n') || ((c) == '\r'))
+
+#define MIN_EXPANSION 6
+
+static PyObject* RawJSONType = NULL;
+static int
+is_raw_json(PyObject *obj)
+{
+    return PyObject_IsInstance(obj, RawJSONType) ? 1 : 0;
+}
+
+static int
+JSON_Accu_Init(JSON_Accu *acc)
+{
+    /* Lazily allocated */
+    acc->large_strings = NULL;
+    acc->small_strings = PyList_New(0);
+    if (acc->small_strings == NULL)
+        return -1;
+    return 0;
+}
+
+static int
+flush_accumulator(JSON_Accu *acc)
+{
+    Py_ssize_t nsmall = PyList_GET_SIZE(acc->small_strings);
+    if (nsmall) {
+        int ret;
+        PyObject *joined;
+        if (acc->large_strings == NULL) {
+            acc->large_strings = PyList_New(0);
+            if (acc->large_strings == NULL)
+                return -1;
+        }
+#if PY_MAJOR_VERSION >= 3
+        joined = join_list_unicode(acc->small_strings);
+#else /* PY_MAJOR_VERSION >= 3 */
+        joined = join_list_string(acc->small_strings);
+#endif /* PY_MAJOR_VERSION < 3 */
+        if (joined == NULL)
+            return -1;
+        if (PyList_SetSlice(acc->small_strings, 0, nsmall, NULL)) {
+            Py_DECREF(joined);
+            return -1;
+        }
+        ret = PyList_Append(acc->large_strings, joined);
+        Py_DECREF(joined);
+        return ret;
+    }
+    return 0;
+}
+
+static int
+JSON_Accu_Accumulate(JSON_Accu *acc, PyObject *unicode)
+{
+    Py_ssize_t nsmall;
+#if PY_MAJOR_VERSION >= 3
+    assert(PyUnicode_Check(unicode));
+#else /* PY_MAJOR_VERSION >= 3 */
+    assert(PyString_Check(unicode) || PyUnicode_Check(unicode));
+#endif /* PY_MAJOR_VERSION < 3 */
+
+    if (PyList_Append(acc->small_strings, unicode))
+        return -1;
+    nsmall = PyList_GET_SIZE(acc->small_strings);
+    /* Each item in a list of unicode objects has an overhead (in 64-bit
+     * builds) of:
+     *   - 8 bytes for the list slot
+     *   - 56 bytes for the header of the unicode object
+     * that is, 64 bytes.  100000 such objects waste more than 6MB
+     * compared to a single concatenated string.
+     */
+    if (nsmall < 100000)
+        return 0;
+    return flush_accumulator(acc);
+}
+
+static PyObject *
+JSON_Accu_FinishAsList(JSON_Accu *acc)
+{
+    int ret;
+    PyObject *res;
+
+    ret = flush_accumulator(acc);
+    Py_CLEAR(acc->small_strings);
+    if (ret) {
+        Py_CLEAR(acc->large_strings);
+        return NULL;
+    }
+    res = acc->large_strings;
+    acc->large_strings = NULL;
+    if (res == NULL)
+        return PyList_New(0);
+    return res;
+}
+
+static void
+JSON_Accu_Destroy(JSON_Accu *acc)
+{
+    Py_CLEAR(acc->small_strings);
+    Py_CLEAR(acc->large_strings);
+}
+
+static int
+IS_DIGIT(JSON_UNICHR c)
+{
+    return c >= '0' && c <= '9';
+}
+
+static PyObject *
+maybe_quote_bigint(PyEncoderObject* s, PyObject *encoded, PyObject *obj)
+{
+    if (s->max_long_size != Py_None && s->min_long_size != Py_None) {
+        if (PyObject_RichCompareBool(obj, s->max_long_size, Py_GE) ||
+            PyObject_RichCompareBool(obj, s->min_long_size, Py_LE)) {
+#if PY_MAJOR_VERSION >= 3
+            PyObject* quoted = PyUnicode_FromFormat("\"%U\"", encoded);
+#else
+            PyObject* quoted = PyString_FromFormat("\"%s\"",
+                                                   PyString_AsString(encoded));
+#endif
+            Py_DECREF(encoded);
+            encoded = quoted;
+        }
+    }
+
+    return encoded;
+}
+
+static int
+_is_namedtuple(PyObject *obj)
+{
+    int rval = 0;
+    PyObject *_asdict = PyObject_GetAttrString(obj, "_asdict");
+    if (_asdict == NULL) {
+        PyErr_Clear();
+        return 0;
+    }
+    rval = PyCallable_Check(_asdict);
+    Py_DECREF(_asdict);
+    return rval;
+}
+
+static int
+_has_for_json_hook(PyObject *obj)
+{
+    int rval = 0;
+    PyObject *for_json = PyObject_GetAttrString(obj, "for_json");
+    if (for_json == NULL) {
+        PyErr_Clear();
+        return 0;
+    }
+    rval = PyCallable_Check(for_json);
+    Py_DECREF(for_json);
+    return rval;
+}
+
+static int
+_convertPyInt_AsSsize_t(PyObject *o, Py_ssize_t *size_ptr)
+{
+    /* PyObject to Py_ssize_t converter */
+    *size_ptr = PyInt_AsSsize_t(o);
+    if (*size_ptr == -1 && PyErr_Occurred())
+        return 0;
+    return 1;
+}
+
+static PyObject *
+_convertPyInt_FromSsize_t(Py_ssize_t *size_ptr)
+{
+    /* Py_ssize_t to PyObject converter */
+    return PyInt_FromSsize_t(*size_ptr);
+}
+
+static Py_ssize_t
+ascii_escape_char(JSON_UNICHR c, char *output, Py_ssize_t chars)
+{
+    /* Escape unicode code point c to ASCII escape sequences
+    in char *output. output must have at least 12 bytes unused to
+    accommodate an escaped surrogate pair "\uXXXX\uXXXX" */
+    if (S_CHAR(c)) {
+        output[chars++] = (char)c;
+    }
+    else {
+        output[chars++] = '\\';
+        switch (c) {
+            case '\\': output[chars++] = (char)c; break;
+            case '"': output[chars++] = (char)c; break;
+            case '\b': output[chars++] = 'b'; break;
+            case '\f': output[chars++] = 'f'; break;
+            case '\n': output[chars++] = 'n'; break;
+            case '\r': output[chars++] = 'r'; break;
+            case '\t': output[chars++] = 't'; break;
+            default:
+#if PY_MAJOR_VERSION >= 3 || defined(Py_UNICODE_WIDE)
+                if (c >= 0x10000) {
+                    /* UTF-16 surrogate pair */
+                    JSON_UNICHR v = c - 0x10000;
+                    c = 0xd800 | ((v >> 10) & 0x3ff);
+                    output[chars++] = 'u';
+                    output[chars++] = "0123456789abcdef"[(c >> 12) & 0xf];
+                    output[chars++] = "0123456789abcdef"[(c >>  8) & 0xf];
+                    output[chars++] = "0123456789abcdef"[(c >>  4) & 0xf];
+                    output[chars++] = "0123456789abcdef"[(c      ) & 0xf];
+                    c = 0xdc00 | (v & 0x3ff);
+                    output[chars++] = '\\';
+                }
+#endif
+                output[chars++] = 'u';
+                output[chars++] = "0123456789abcdef"[(c >> 12) & 0xf];
+                output[chars++] = "0123456789abcdef"[(c >>  8) & 0xf];
+                output[chars++] = "0123456789abcdef"[(c >>  4) & 0xf];
+                output[chars++] = "0123456789abcdef"[(c      ) & 0xf];
+        }
+    }
+    return chars;
+}
+
+static Py_ssize_t
+ascii_char_size(JSON_UNICHR c)
+{
+    if (S_CHAR(c)) {
+        return 1;
+    }
+    else if (c == '\\' ||
+               c == '"'  ||
+               c == '\b' ||
+               c == '\f' ||
+               c == '\n' ||
+               c == '\r' ||
+               c == '\t') {
+        return 2;
+    }
+#if PY_MAJOR_VERSION >= 3 || defined(Py_UNICODE_WIDE)
+    else if (c >= 0x10000U) {
+        return 2 * MIN_EXPANSION;
+    }
+#endif
+    else {
+        return MIN_EXPANSION;
+    }
+}
+
+static PyObject *
+ascii_escape_unicode(PyObject *pystr)
+{
+    /* Take a PyUnicode pystr and return a new ASCII-only escaped PyString */
+    Py_ssize_t i;
+    Py_ssize_t input_chars = PyUnicode_GET_LENGTH(pystr);
+    Py_ssize_t output_size = 2;
+    Py_ssize_t chars;
+    PY2_UNUSED int kind = PyUnicode_KIND(pystr);
+    void *data = PyUnicode_DATA(pystr);
+    PyObject *rval;
+    char *output;
+
+    output_size = 2;
+    for (i = 0; i < input_chars; i++) {
+        output_size += ascii_char_size(PyUnicode_READ(kind, data, i));
+    }
+#if PY_MAJOR_VERSION >= 3
+    rval = PyUnicode_New(output_size, 127);
+    if (rval == NULL) {
+        return NULL;
+    }
+    assert(PyUnicode_KIND(rval) == PyUnicode_1BYTE_KIND);
+    output = (char *)PyUnicode_DATA(rval);
+#else
+    rval = PyString_FromStringAndSize(NULL, output_size);
+    if (rval == NULL) {
+        return NULL;
+    }
+    output = PyString_AS_STRING(rval);
+#endif
+    chars = 0;
+    output[chars++] = '"';
+    for (i = 0; i < input_chars; i++) {
+        chars = ascii_escape_char(PyUnicode_READ(kind, data, i), output, chars);
+    }
+    output[chars++] = '"';
+    assert(chars == output_size);
+    return rval;
+}
+
+#if PY_MAJOR_VERSION >= 3
+
+static PyObject *
+ascii_escape_str(PyObject *pystr)
+{
+    PyObject *rval;
+    PyObject *input = PyUnicode_DecodeUTF8(PyBytes_AS_STRING(pystr), PyBytes_GET_SIZE(pystr), NULL);
+    if (input == NULL)
+        return NULL;
+    rval = ascii_escape_unicode(input);
+    Py_DECREF(input);
+    return rval;
+}
+
+#else /* PY_MAJOR_VERSION >= 3 */
+
+static PyObject *
+ascii_escape_str(PyObject *pystr)
+{
+    /* Take a PyString pystr and return a new ASCII-only escaped PyString */
+    Py_ssize_t i;
+    Py_ssize_t input_chars;
+    Py_ssize_t output_size;
+    Py_ssize_t chars;
+    PyObject *rval;
+    char *output;
+    char *input_str;
+
+    input_chars = PyString_GET_SIZE(pystr);
+    input_str = PyString_AS_STRING(pystr);
+    output_size = 2;
+
+    /* Fast path for a string that's already ASCII */
+    for (i = 0; i < input_chars; i++) {
+        JSON_UNICHR c = (JSON_UNICHR)input_str[i];
+        if (c > 0x7f) {
+            /* We hit a non-ASCII character, bail to unicode mode */
+            PyObject *uni;
+            uni = PyUnicode_DecodeUTF8(input_str, input_chars, "strict");
+            if (uni == NULL) {
+                return NULL;
+            }
+            rval = ascii_escape_unicode(uni);
+            Py_DECREF(uni);
+            return rval;
+        }
+        output_size += ascii_char_size(c);
+    }
+
+    rval = PyString_FromStringAndSize(NULL, output_size);
+    if (rval == NULL) {
+        return NULL;
+    }
+    chars = 0;
+    output = PyString_AS_STRING(rval);
+    output[chars++] = '"';
+    for (i = 0; i < input_chars; i++) {
+        chars = ascii_escape_char((JSON_UNICHR)input_str[i], output, chars);
+    }
+    output[chars++] = '"';
+    assert(chars == output_size);
+    return rval;
+}
+#endif /* PY_MAJOR_VERSION < 3 */
+
+static PyObject *
+encoder_stringify_key(PyEncoderObject *s, PyObject *key)
+{
+    if (PyUnicode_Check(key)) {
+        Py_INCREF(key);
+        return key;
+    }
+#if PY_MAJOR_VERSION >= 3
+    else if (PyBytes_Check(key) && s->encoding != NULL) {
+        const char *encoding = PyUnicode_AsUTF8(s->encoding);
+        if (encoding == NULL)
+            return NULL;
+        return PyUnicode_Decode(
+            PyBytes_AS_STRING(key),
+            PyBytes_GET_SIZE(key),
+            encoding,
+            NULL);
+    }
+#else /* PY_MAJOR_VERSION >= 3 */
+    else if (PyString_Check(key)) {
+        Py_INCREF(key);
+        return key;
+    }
+#endif /* PY_MAJOR_VERSION < 3 */
+    else if (PyFloat_Check(key)) {
+        return encoder_encode_float(s, key);
+    }
+    else if (key == Py_True || key == Py_False || key == Py_None) {
+        /* This must come before the PyInt_Check because
+           True and False are also 1 and 0.*/
+        return _encoded_const(key);
+    }
+    else if (PyInt_Check(key) || PyLong_Check(key)) {
+        if (!(PyInt_CheckExact(key) || PyLong_CheckExact(key))) {
+            /* See #118, do not trust custom str/repr */
+            PyObject *res;
+            PyObject *tmp = PyObject_CallFunctionObjArgs((PyObject *)&PyLong_Type, key, NULL);
+            if (tmp == NULL) {
+                return NULL;
+            }
+            res = PyObject_Str(tmp);
+            Py_DECREF(tmp);
+            return res;
+        }
+        else {
+            return PyObject_Str(key);
+        }
+    }
+    else if (s->use_decimal && PyObject_TypeCheck(key, (PyTypeObject *)s->Decimal)) {
+        return PyObject_Str(key);
+    }
+    if (s->skipkeys) {
+        Py_INCREF(Py_None);
+        return Py_None;
+    }
+    PyErr_Format(PyExc_TypeError,
+                 "keys must be str, int, float, bool or None, "
+                 "not %.100s", key->ob_type->tp_name);
+    return NULL;
+}
+
+static PyObject *
+encoder_dict_iteritems(PyEncoderObject *s, PyObject *dct)
+{
+    PyObject *items;
+    PyObject *iter = NULL;
+    PyObject *lst = NULL;
+    PyObject *item = NULL;
+    PyObject *kstr = NULL;
+    PyObject *sortfun = NULL;
+    PyObject *sortres;
+    static PyObject *sortargs = NULL;
+
+    if (sortargs == NULL) {
+        sortargs = PyTuple_New(0);
+        if (sortargs == NULL)
+            return NULL;
+    }
+
+    if (PyDict_CheckExact(dct))
+        items = PyDict_Items(dct);
+    else
+        items = PyMapping_Items(dct);
+    if (items == NULL)
+        return NULL;
+    iter = PyObject_GetIter(items);
+    Py_DECREF(items);
+    if (iter == NULL)
+        return NULL;
+    if (s->item_sort_kw == Py_None)
+        return iter;
+    lst = PyList_New(0);
+    if (lst == NULL)
+        goto bail;
+    while ((item = PyIter_Next(iter))) {
+        PyObject *key, *value;
+        if (!PyTuple_Check(item) || Py_SIZE(item) != 2) {
+            PyErr_SetString(PyExc_ValueError, "items must return 2-tuples");
+            goto bail;
+        }
+        key = PyTuple_GET_ITEM(item, 0);
+        if (key == NULL)
+            goto bail;
+#if PY_MAJOR_VERSION < 3
+        else if (PyString_Check(key)) {
+            /* item can be added as-is */
+        }
+#endif /* PY_MAJOR_VERSION < 3 */
+        else if (PyUnicode_Check(key)) {
+            /* item can be added as-is */
+        }
+        else {
+            PyObject *tpl;
+            kstr = encoder_stringify_key(s, key);
+            if (kstr == NULL)
+                goto bail;
+            else if (kstr == Py_None) {
+                /* skipkeys */
+                Py_DECREF(kstr);
+                continue;
+            }
+            value = PyTuple_GET_ITEM(item, 1);
+            if (value == NULL)
+                goto bail;
+            tpl = PyTuple_Pack(2, kstr, value);
+            if (tpl == NULL)
+                goto bail;
+            Py_CLEAR(kstr);
+            Py_DECREF(item);
+            item = tpl;
+        }
+        if (PyList_Append(lst, item))
+            goto bail;
+        Py_DECREF(item);
+    }
+    Py_CLEAR(iter);
+    if (PyErr_Occurred())
+        goto bail;
+    sortfun = PyObject_GetAttrString(lst, "sort");
+    if (sortfun == NULL)
+        goto bail;
+    sortres = PyObject_Call(sortfun, sortargs, s->item_sort_kw);
+    if (!sortres)
+        goto bail;
+    Py_DECREF(sortres);
+    Py_CLEAR(sortfun);
+    iter = PyObject_GetIter(lst);
+    Py_CLEAR(lst);
+    return iter;
+bail:
+    Py_XDECREF(sortfun);
+    Py_XDECREF(kstr);
+    Py_XDECREF(item);
+    Py_XDECREF(lst);
+    Py_XDECREF(iter);
+    return NULL;
+}
+
+/* Use JSONDecodeError exception to raise a nice looking ValueError subclass */
+static PyObject *JSONDecodeError = NULL;
+static void
+raise_errmsg(char *msg, PyObject *s, Py_ssize_t end)
+{
+    PyObject *exc = PyObject_CallFunction(JSONDecodeError, "(zOO&)", msg, s, _convertPyInt_FromSsize_t, &end);
+    if (exc) {
+        PyErr_SetObject(JSONDecodeError, exc);
+        Py_DECREF(exc);
+    }
+}
+
+static PyObject *
+join_list_unicode(PyObject *lst)
+{
+    /* return u''.join(lst) */
+    return PyUnicode_Join(JSON_EmptyUnicode, lst);
+}
+
+#if PY_MAJOR_VERSION >= 3
+#define join_list_string join_list_unicode
+#else /* PY_MAJOR_VERSION >= 3 */
+static PyObject *
+join_list_string(PyObject *lst)
+{
+    /* return ''.join(lst) */
+    static PyObject *joinfn = NULL;
+    if (joinfn == NULL) {
+        joinfn = PyObject_GetAttrString(JSON_EmptyStr, "join");
+        if (joinfn == NULL)
+            return NULL;
+    }
+    return PyObject_CallFunctionObjArgs(joinfn, lst, NULL);
+}
+#endif /* PY_MAJOR_VERSION < 3 */
+
+static PyObject *
+_build_rval_index_tuple(PyObject *rval, Py_ssize_t idx)
+{
+    /* return (rval, idx) tuple, stealing reference to rval */
+    PyObject *tpl;
+    PyObject *pyidx;
+    /*
+    steal a reference to rval, returns (rval, idx)
+    */
+    if (rval == NULL) {
+        assert(PyErr_Occurred());
+        return NULL;
+    }
+    pyidx = PyInt_FromSsize_t(idx);
+    if (pyidx == NULL) {
+        Py_DECREF(rval);
+        return NULL;
+    }
+    tpl = PyTuple_New(2);
+    if (tpl == NULL) {
+        Py_DECREF(pyidx);
+        Py_DECREF(rval);
+        return NULL;
+    }
+    PyTuple_SET_ITEM(tpl, 0, rval);
+    PyTuple_SET_ITEM(tpl, 1, pyidx);
+    return tpl;
+}
+
+#define APPEND_OLD_CHUNK \
+    if (chunk != NULL) { \
+        if (chunks == NULL) { \
+            chunks = PyList_New(0); \
+            if (chunks == NULL) { \
+                goto bail; \
+            } \
+        } \
+        if (PyList_Append(chunks, chunk)) { \
+            goto bail; \
+        } \
+        Py_CLEAR(chunk); \
+    }
+
+#if PY_MAJOR_VERSION < 3
+static PyObject *
+scanstring_str(PyObject *pystr, Py_ssize_t end, char *encoding, int strict, Py_ssize_t *next_end_ptr)
+{
+    /* Read the JSON string from PyString pystr.
+    end is the index of the first character after the quote.
+    encoding is the encoding of pystr (must be an ASCII superset)
+    if strict is zero then literal control characters are allowed
+    *next_end_ptr is a return-by-reference index of the character
+        after the end quote
+
+    Return value is a new PyString (if ASCII-only) or PyUnicode
+    */
+    PyObject *rval;
+    Py_ssize_t len = PyString_GET_SIZE(pystr);
+    Py_ssize_t begin = end - 1;
+    Py_ssize_t next = begin;
+    int has_unicode = 0;
+    char *buf = PyString_AS_STRING(pystr);
+    PyObject *chunks = NULL;
+    PyObject *chunk = NULL;
+    PyObject *strchunk = NULL;
+
+    if (len == end) {
+        raise_errmsg(ERR_STRING_UNTERMINATED, pystr, begin);
+        goto bail;
+    }
+    else if (end < 0 || len < end) {
+        PyErr_SetString(PyExc_ValueError, "end is out of bounds");
+        goto bail;
+    }
+    while (1) {
+        /* Find the end of the string or the next escape */
+        Py_UNICODE c = 0;
+        for (next = end; next < len; next++) {
+            c = (unsigned char)buf[next];
+            if (c == '"' || c == '\\') {
+                break;
+            }
+            else if (strict && c <= 0x1f) {
+                raise_errmsg(ERR_STRING_CONTROL, pystr, next);
+                goto bail;
+            }
+            else if (c > 0x7f) {
+                has_unicode = 1;
+            }
+        }
+        if (!(c == '"' || c == '\\')) {
+            raise_errmsg(ERR_STRING_UNTERMINATED, pystr, begin);
+            goto bail;
+        }
+        /* Pick up this chunk if it's not zero length */
+        if (next != end) {
+            APPEND_OLD_CHUNK
+            strchunk = PyString_FromStringAndSize(&buf[end], next - end);
+            if (strchunk == NULL) {
+                goto bail;
+            }
+            if (has_unicode) {
+                chunk = PyUnicode_FromEncodedObject(strchunk, encoding, NULL);
+                Py_DECREF(strchunk);
+                if (chunk == NULL) {
+                    goto bail;
+                }
+            }
+            else {
+                chunk = strchunk;
+            }
+        }
+        next++;
+        if (c == '"') {
+            end = next;
+            break;
+        }
+        if (next == len) {
+            raise_errmsg(ERR_STRING_UNTERMINATED, pystr, begin);
+            goto bail;
+        }
+        c = buf[next];
+        if (c != 'u') {
+            /* Non-unicode backslash escapes */
+            end = next + 1;
+            switch (c) {
+                case '"': break;
+                case '\\': break;
+                case '/': break;
+                case 'b': c = '\b'; break;
+                case 'f': c = '\f'; break;
+                case 'n': c = '\n'; break;
+                case 'r': c = '\r'; break;
+                case 't': c = '\t'; break;
+                default: c = 0;
+            }
+            if (c == 0) {
+                raise_errmsg(ERR_STRING_ESC1, pystr, end - 2);
+                goto bail;
+            }
+        }
+        else {
+            c = 0;
+            next++;
+            end = next + 4;
+            if (end >= len) {
+                raise_errmsg(ERR_STRING_ESC4, pystr, next - 1);
+                goto bail;
+            }
+            /* Decode 4 hex digits */
+            for (; next < end; next++) {
+                JSON_UNICHR digit = (JSON_UNICHR)buf[next];
+                c <<= 4;
+                switch (digit) {
+                    case '0': case '1': case '2': case '3': case '4':
+                    case '5': case '6': case '7': case '8': case '9':
+                        c |= (digit - '0'); break;
+                    case 'a': case 'b': case 'c': case 'd': case 'e':
+                    case 'f':
+                        c |= (digit - 'a' + 10); break;
+                    case 'A': case 'B': case 'C': case 'D': case 'E':
+                    case 'F':
+                        c |= (digit - 'A' + 10); break;
+                    default:
+                        raise_errmsg(ERR_STRING_ESC4, pystr, end - 5);
+                        goto bail;
+                }
+            }
+#if defined(Py_UNICODE_WIDE)
+            /* Surrogate pair */
+            if ((c & 0xfc00) == 0xd800) {
+                if (end + 6 < len && buf[next] == '\\' && buf[next+1] == 'u') {
+                    JSON_UNICHR c2 = 0;
+                    end += 6;
+                    /* Decode 4 hex digits */
+                    for (next += 2; next < end; next++) {
+                        c2 <<= 4;
+                        JSON_UNICHR digit = buf[next];
+                        switch (digit) {
+                        case '0': case '1': case '2': case '3': case '4':
+                        case '5': case '6': case '7': case '8': case '9':
+                            c2 |= (digit - '0'); break;
+                        case 'a': case 'b': case 'c': case 'd': case 'e':
+                        case 'f':
+                            c2 |= (digit - 'a' + 10); break;
+                        case 'A': case 'B': case 'C': case 'D': case 'E':
+                        case 'F':
+                            c2 |= (digit - 'A' + 10); break;
+                        default:
+                            raise_errmsg(ERR_STRING_ESC4, pystr, end - 5);
+                            goto bail;
+                        }
+                    }
+                    if ((c2 & 0xfc00) != 0xdc00) {
+                        /* not a low surrogate, rewind */
+                        end -= 6;
+                        next = end;
+                    }
+                    else {
+                        c = 0x10000 + (((c - 0xd800) << 10) | (c2 - 0xdc00));
+                    }
+                }
+            }
+#endif /* Py_UNICODE_WIDE */
+        }
+        if (c > 0x7f) {
+            has_unicode = 1;
+        }
+        APPEND_OLD_CHUNK
+        if (has_unicode) {
+            chunk = PyUnicode_FromOrdinal(c);
+            if (chunk == NULL) {
+                goto bail;
+            }
+        }
+        else {
+            char c_char = Py_CHARMASK(c);
+            chunk = PyString_FromStringAndSize(&c_char, 1);
+            if (chunk == NULL) {
+                goto bail;
+            }
+        }
+    }
+
+    if (chunks == NULL) {
+        if (chunk != NULL)
+            rval = chunk;
+        else {
+            rval = JSON_EmptyStr;
+            Py_INCREF(rval);
+        }
+    }
+    else {
+        APPEND_OLD_CHUNK
+        rval = join_list_string(chunks);
+        if (rval == NULL) {
+            goto bail;
+        }
+        Py_CLEAR(chunks);
+    }
+
+    *next_end_ptr = end;
+    return rval;
+bail:
+    *next_end_ptr = -1;
+    Py_XDECREF(chunk);
+    Py_XDECREF(chunks);
+    return NULL;
+}
+#endif /* PY_MAJOR_VERSION < 3 */
+
+static PyObject *
+scanstring_unicode(PyObject *pystr, Py_ssize_t end, int strict, Py_ssize_t *next_end_ptr)
+{
+    /* Read the JSON string from PyUnicode pystr.
+    end is the index of the first character after the quote.
+    if strict is zero then literal control characters are allowed
+    *next_end_ptr is a return-by-reference index of the character
+        after the end quote
+
+    Return value is a new PyUnicode
+    */
+    PyObject *rval;
+    Py_ssize_t begin = end - 1;
+    Py_ssize_t next = begin;
+    PY2_UNUSED int kind = PyUnicode_KIND(pystr);
+    Py_ssize_t len = PyUnicode_GET_LENGTH(pystr);
+    void *buf = PyUnicode_DATA(pystr);
+    PyObject *chunks = NULL;
+    PyObject *chunk = NULL;
+
+    if (len == end) {
+        raise_errmsg(ERR_STRING_UNTERMINATED, pystr, begin);
+        goto bail;
+    }
+    else if (end < 0 || len < end) {
+        PyErr_SetString(PyExc_ValueError, "end is out of bounds");
+        goto bail;
+    }
+    while (1) {
+        /* Find the end of the string or the next escape */
+        JSON_UNICHR c = 0;
+        for (next = end; next < len; next++) {
+            c = PyUnicode_READ(kind, buf, next);
+            if (c == '"' || c == '\\') {
+                break;
+            }
+            else if (strict && c <= 0x1f) {
+                raise_errmsg(ERR_STRING_CONTROL, pystr, next);
+                goto bail;
+            }
+        }
+        if (!(c == '"' || c == '\\')) {
+            raise_errmsg(ERR_STRING_UNTERMINATED, pystr, begin);
+            goto bail;
+        }
+        /* Pick up this chunk if it's not zero length */
+        if (next != end) {
+            APPEND_OLD_CHUNK
+#if PY_MAJOR_VERSION < 3
+            chunk = PyUnicode_FromUnicode(&((const Py_UNICODE *)buf)[end], next - end);
+#else
+            chunk = PyUnicode_Substring(pystr, end, next);
+#endif
+            if (chunk == NULL) {
+                goto bail;
+            }
+        }
+        next++;
+        if (c == '"') {
+            end = next;
+            break;
+        }
+        if (next == len) {
+            raise_errmsg(ERR_STRING_UNTERMINATED, pystr, begin);
+            goto bail;
+        }
+        c = PyUnicode_READ(kind, buf, next);
+        if (c != 'u') {
+            /* Non-unicode backslash escapes */
+            end = next + 1;
+            switch (c) {
+                case '"': break;
+                case '\\': break;
+                case '/': break;
+                case 'b': c = '\b'; break;
+                case 'f': c = '\f'; break;
+                case 'n': c = '\n'; break;
+                case 'r': c = '\r'; break;
+                case 't': c = '\t'; break;
+                default: c = 0;
+            }
+            if (c == 0) {
+                raise_errmsg(ERR_STRING_ESC1, pystr, end - 2);
+                goto bail;
+            }
+        }
+        else {
+            c = 0;
+            next++;
+            end = next + 4;
+            if (end >= len) {
+                raise_errmsg(ERR_STRING_ESC4, pystr, next - 1);
+                goto bail;
+            }
+            /* Decode 4 hex digits */
+            for (; next < end; next++) {
+                JSON_UNICHR digit = PyUnicode_READ(kind, buf, next);
+                c <<= 4;
+                switch (digit) {
+                    case '0': case '1': case '2': case '3': case '4':
+                    case '5': case '6': case '7': case '8': case '9':
+                        c |= (digit - '0'); break;
+                    case 'a': case 'b': case 'c': case 'd': case 'e':
+                    case 'f':
+                        c |= (digit - 'a' + 10); break;
+                    case 'A': case 'B': case 'C': case 'D': case 'E':
+                    case 'F':
+                        c |= (digit - 'A' + 10); break;
+                    default:
+                        raise_errmsg(ERR_STRING_ESC4, pystr, end - 5);
+                        goto bail;
+                }
+            }
+#if PY_MAJOR_VERSION >= 3 || defined(Py_UNICODE_WIDE)
+            /* Surrogate pair */
+            if ((c & 0xfc00) == 0xd800) {
+                JSON_UNICHR c2 = 0;
+                if (end + 6 < len &&
+                    PyUnicode_READ(kind, buf, next) == '\\' &&
+                    PyUnicode_READ(kind, buf, next + 1) == 'u') {
+                    end += 6;
+                    /* Decode 4 hex digits */
+                    for (next += 2; next < end; next++) {
+                        JSON_UNICHR digit = PyUnicode_READ(kind, buf, next);
+                        c2 <<= 4;
+                        switch (digit) {
+                        case '0': case '1': case '2': case '3': case '4':
+                        case '5': case '6': case '7': case '8': case '9':
+                            c2 |= (digit - '0'); break;
+                        case 'a': case 'b': case 'c': case 'd': case 'e':
+                        case 'f':
+                            c2 |= (digit - 'a' + 10); break;
+                        case 'A': case 'B': case 'C': case 'D': case 'E':
+                        case 'F':
+                            c2 |= (digit - 'A' + 10); break;
+                        default:
+                            raise_errmsg(ERR_STRING_ESC4, pystr, end - 5);
+                            goto bail;
+                        }
+                    }
+                    if ((c2 & 0xfc00) != 0xdc00) {
+                        /* not a low surrogate, rewind */
+                        end -= 6;
+                        next = end;
+                    }
+                    else {
+                        c = 0x10000 + (((c - 0xd800) << 10) | (c2 - 0xdc00));
+                    }
+                }
+            }
+#endif
+        }
+        APPEND_OLD_CHUNK
+        chunk = PyUnicode_FromOrdinal(c);
+        if (chunk == NULL) {
+            goto bail;
+        }
+    }
+
+    if (chunks == NULL) {
+        if (chunk != NULL)
+            rval = chunk;
+        else {
+            rval = JSON_EmptyUnicode;
+            Py_INCREF(rval);
+        }
+    }
+    else {
+        APPEND_OLD_CHUNK
+        rval = join_list_unicode(chunks);
+        if (rval == NULL) {
+            goto bail;
+        }
+        Py_CLEAR(chunks);
+    }
+    *next_end_ptr = end;
+    return rval;
+bail:
+    *next_end_ptr = -1;
+    Py_XDECREF(chunk);
+    Py_XDECREF(chunks);
+    return NULL;
+}
+
+PyDoc_STRVAR(pydoc_scanstring,
+    "scanstring(basestring, end, encoding, strict=True) -> (str, end)\n"
+    "\n"
+    "Scan the string s for a JSON string. End is the index of the\n"
+    "character in s after the quote that started the JSON string.\n"
+    "Unescapes all valid JSON string escape sequences and raises ValueError\n"
+    "on attempt to decode an invalid string. If strict is False then literal\n"
+    "control characters are allowed in the string.\n"
+    "\n"
+    "Returns a tuple of the decoded string and the index of the character in s\n"
+    "after the end quote."
+);
+
+static PyObject *
+py_scanstring(PyObject* self UNUSED, PyObject *args)
+{
+    PyObject *pystr;
+    PyObject *rval;
+    Py_ssize_t end;
+    Py_ssize_t next_end = -1;
+    char *encoding = NULL;
+    int strict = 1;
+    if (!PyArg_ParseTuple(args, "OO&|zi:scanstring", &pystr, _convertPyInt_AsSsize_t, &end, &encoding, &strict)) {
+        return NULL;
+    }
+    if (encoding == NULL) {
+        encoding = DEFAULT_ENCODING;
+    }
+    if (PyUnicode_Check(pystr)) {
+        if (PyUnicode_READY(pystr))
+            return NULL;
+        rval = scanstring_unicode(pystr, end, strict, &next_end);
+    }
+#if PY_MAJOR_VERSION < 3
+    /* Using a bytes input is unsupported for scanning in Python 3.
+       It is coerced to str in the decoder before it gets here. */
+    else if (PyString_Check(pystr)) {
+        rval = scanstring_str(pystr, end, encoding, strict, &next_end);
+    }
+#endif
+    else {
+        PyErr_Format(PyExc_TypeError,
+                     "first argument must be a string, not %.80s",
+                     Py_TYPE(pystr)->tp_name);
+        return NULL;
+    }
+    return _build_rval_index_tuple(rval, next_end);
+}
+
+PyDoc_STRVAR(pydoc_encode_basestring_ascii,
+    "encode_basestring_ascii(basestring) -> str\n"
+    "\n"
+    "Return an ASCII-only JSON representation of a Python string"
+);
+
+static PyObject *
+py_encode_basestring_ascii(PyObject* self UNUSED, PyObject *pystr)
+{
+    /* Return an ASCII-only JSON representation of a Python string */
+    /* METH_O */
+    if (PyBytes_Check(pystr)) {
+        return ascii_escape_str(pystr);
+    }
+    else if (PyUnicode_Check(pystr)) {
+        if (PyUnicode_READY(pystr))
+            return NULL;
+        return ascii_escape_unicode(pystr);
+    }
+    else {
+        PyErr_Format(PyExc_TypeError,
+                     "first argument must be a string, not %.80s",
+                     Py_TYPE(pystr)->tp_name);
+        return NULL;
+    }
+}
+
+static void
+scanner_dealloc(PyObject *self)
+{
+    /* bpo-31095: UnTrack is needed before calling any callbacks */
+    PyObject_GC_UnTrack(self);
+    scanner_clear(self);
+    Py_TYPE(self)->tp_free(self);
+}
+
+static int
+scanner_traverse(PyObject *self, visitproc visit, void *arg)
+{
+    PyScannerObject *s;
+    assert(PyScanner_Check(self));
+    s = (PyScannerObject *)self;
+    Py_VISIT(s->encoding);
+    Py_VISIT(s->strict_bool);
+    Py_VISIT(s->object_hook);
+    Py_VISIT(s->pairs_hook);
+    Py_VISIT(s->parse_float);
+    Py_VISIT(s->parse_int);
+    Py_VISIT(s->parse_constant);
+    Py_VISIT(s->memo);
+    return 0;
+}
+
+static int
+scanner_clear(PyObject *self)
+{
+    PyScannerObject *s;
+    assert(PyScanner_Check(self));
+    s = (PyScannerObject *)self;
+    Py_CLEAR(s->encoding);
+    Py_CLEAR(s->strict_bool);
+    Py_CLEAR(s->object_hook);
+    Py_CLEAR(s->pairs_hook);
+    Py_CLEAR(s->parse_float);
+    Py_CLEAR(s->parse_int);
+    Py_CLEAR(s->parse_constant);
+    Py_CLEAR(s->memo);
+    return 0;
+}
+
+#if PY_MAJOR_VERSION < 3
+static PyObject *
+_parse_object_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr)
+{
+    /* Read a JSON object from PyString pystr.
+    idx is the index of the first character after the opening curly brace.
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the closing curly brace.
+
+    Returns a new PyObject (usually a dict, but object_hook or
+    object_pairs_hook can change that)
+    */
+    char *str = PyString_AS_STRING(pystr);
+    Py_ssize_t end_idx = PyString_GET_SIZE(pystr) - 1;
+    PyObject *rval = NULL;
+    PyObject *pairs = NULL;
+    PyObject *item;
+    PyObject *key = NULL;
+    PyObject *val = NULL;
+    char *encoding = PyString_AS_STRING(s->encoding);
+    int has_pairs_hook = (s->pairs_hook != Py_None);
+    int did_parse = 0;
+    Py_ssize_t next_idx;
+    if (has_pairs_hook) {
+        pairs = PyList_New(0);
+        if (pairs == NULL)
+            return NULL;
+    }
+    else {
+        rval = PyDict_New();
+        if (rval == NULL)
+            return NULL;
+    }
+
+    /* skip whitespace after { */
+    while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+    /* only loop if the object is non-empty */
+    if (idx <= end_idx && str[idx] != '}') {
+        int trailing_delimiter = 0;
+        while (idx <= end_idx) {
+            PyObject *memokey;
+            trailing_delimiter = 0;
+
+            /* read key */
+            if (str[idx] != '"') {
+                raise_errmsg(ERR_OBJECT_PROPERTY, pystr, idx);
+                goto bail;
+            }
+            key = scanstring_str(pystr, idx + 1, encoding, s->strict, &next_idx);
+            if (key == NULL)
+                goto bail;
+            memokey = PyDict_GetItem(s->memo, key);
+            if (memokey != NULL) {
+                Py_INCREF(memokey);
+                Py_DECREF(key);
+                key = memokey;
+            }
+            else {
+                if (PyDict_SetItem(s->memo, key, key) < 0)
+                    goto bail;
+            }
+            idx = next_idx;
+
+            /* skip whitespace between key and : delimiter, read :, skip whitespace */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+            if (idx > end_idx || str[idx] != ':') {
+                raise_errmsg(ERR_OBJECT_PROPERTY_DELIMITER, pystr, idx);
+                goto bail;
+            }
+            idx++;
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+            /* read any JSON data type */
+            val = scan_once_str(s, pystr, idx, &next_idx);
+            if (val == NULL)
+                goto bail;
+
+            if (has_pairs_hook) {
+                item = PyTuple_Pack(2, key, val);
+                if (item == NULL)
+                    goto bail;
+                Py_CLEAR(key);
+                Py_CLEAR(val);
+                if (PyList_Append(pairs, item) == -1) {
+                    Py_DECREF(item);
+                    goto bail;
+                }
+                Py_DECREF(item);
+            }
+            else {
+                if (PyDict_SetItem(rval, key, val) < 0)
+                    goto bail;
+                Py_CLEAR(key);
+                Py_CLEAR(val);
+            }
+            idx = next_idx;
+
+            /* skip whitespace before } or , */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+            /* bail if the object is closed or we didn't get the , delimiter */
+            did_parse = 1;
+            if (idx > end_idx) break;
+            if (str[idx] == '}') {
+                break;
+            }
+            else if (str[idx] != ',') {
+                raise_errmsg(ERR_OBJECT_DELIMITER, pystr, idx);
+                goto bail;
+            }
+            idx++;
+
+            /* skip whitespace after , delimiter */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+            trailing_delimiter = 1;
+        }
+        if (trailing_delimiter) {
+            raise_errmsg(ERR_OBJECT_PROPERTY, pystr, idx);
+            goto bail;
+        }
+    }
+    /* verify that idx < end_idx, str[idx] should be '}' */
+    if (idx > end_idx || str[idx] != '}') {
+        if (did_parse) {
+            raise_errmsg(ERR_OBJECT_DELIMITER, pystr, idx);
+        } else {
+            raise_errmsg(ERR_OBJECT_PROPERTY_FIRST, pystr, idx);
+        }
+        goto bail;
+    }
+
+    /* if pairs_hook is not None: rval = object_pairs_hook(pairs) */
+    if (s->pairs_hook != Py_None) {
+        val = PyObject_CallFunctionObjArgs(s->pairs_hook, pairs, NULL);
+        if (val == NULL)
+            goto bail;
+        Py_DECREF(pairs);
+        *next_idx_ptr = idx + 1;
+        return val;
+    }
+
+    /* if object_hook is not None: rval = object_hook(rval) */
+    if (s->object_hook != Py_None) {
+        val = PyObject_CallFunctionObjArgs(s->object_hook, rval, NULL);
+        if (val == NULL)
+            goto bail;
+        Py_DECREF(rval);
+        rval = val;
+        val = NULL;
+    }
+    *next_idx_ptr = idx + 1;
+    return rval;
+bail:
+    Py_XDECREF(rval);
+    Py_XDECREF(key);
+    Py_XDECREF(val);
+    Py_XDECREF(pairs);
+    return NULL;
+}
+#endif /* PY_MAJOR_VERSION < 3 */
+
+static PyObject *
+_parse_object_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr)
+{
+    /* Read a JSON object from PyUnicode pystr.
+    idx is the index of the first character after the opening curly brace.
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the closing curly brace.
+
+    Returns a new PyObject (usually a dict, but object_hook can change that)
+    */
+    void *str = PyUnicode_DATA(pystr);
+    Py_ssize_t end_idx = PyUnicode_GET_LENGTH(pystr) - 1;
+    PY2_UNUSED int kind = PyUnicode_KIND(pystr);
+    PyObject *rval = NULL;
+    PyObject *pairs = NULL;
+    PyObject *item;
+    PyObject *key = NULL;
+    PyObject *val = NULL;
+    int has_pairs_hook = (s->pairs_hook != Py_None);
+    int did_parse = 0;
+    Py_ssize_t next_idx;
+
+    if (has_pairs_hook) {
+        pairs = PyList_New(0);
+        if (pairs == NULL)
+            return NULL;
+    }
+    else {
+        rval = PyDict_New();
+        if (rval == NULL)
+            return NULL;
+    }
+
+    /* skip whitespace after { */
+    while (idx <= end_idx && IS_WHITESPACE(PyUnicode_READ(kind, str, idx))) idx++;
+
+    /* only loop if the object is non-empty */
+    if (idx <= end_idx && PyUnicode_READ(kind, str, idx) != '}') {
+        int trailing_delimiter = 0;
+        while (idx <= end_idx) {
+            PyObject *memokey;
+            trailing_delimiter = 0;
+
+            /* read key */
+            if (PyUnicode_READ(kind, str, idx) != '"') {
+                raise_errmsg(ERR_OBJECT_PROPERTY, pystr, idx);
+                goto bail;
+            }
+            key = scanstring_unicode(pystr, idx + 1, s->strict, &next_idx);
+            if (key == NULL)
+                goto bail;
+            memokey = PyDict_GetItem(s->memo, key);
+            if (memokey != NULL) {
+                Py_INCREF(memokey);
+                Py_DECREF(key);
+                key = memokey;
+            }
+            else {
+                if (PyDict_SetItem(s->memo, key, key) < 0)
+                    goto bail;
+            }
+            idx = next_idx;
+
+            /* skip whitespace between key and : delimiter, read :, skip
+               whitespace */
+            while (idx <= end_idx && IS_WHITESPACE(PyUnicode_READ(kind, str, idx))) idx++;
+            if (idx > end_idx || PyUnicode_READ(kind, str, idx) != ':') {
+                raise_errmsg(ERR_OBJECT_PROPERTY_DELIMITER, pystr, idx);
+                goto bail;
+            }
+            idx++;
+            while (idx <= end_idx && IS_WHITESPACE(PyUnicode_READ(kind, str, idx))) idx++;
+
+            /* read any JSON term */
+            val = scan_once_unicode(s, pystr, idx, &next_idx);
+            if (val == NULL)
+                goto bail;
+
+            if (has_pairs_hook) {
+                item = PyTuple_Pack(2, key, val);
+                if (item == NULL)
+                    goto bail;
+                Py_CLEAR(key);
+                Py_CLEAR(val);
+                if (PyList_Append(pairs, item) == -1) {
+                    Py_DECREF(item);
+                    goto bail;
+                }
+                Py_DECREF(item);
+            }
+            else {
+                if (PyDict_SetItem(rval, key, val) < 0)
+                    goto bail;
+                Py_CLEAR(key);
+                Py_CLEAR(val);
+            }
+            idx = next_idx;
+
+            /* skip whitespace before } or , */
+            while (idx <= end_idx && IS_WHITESPACE(PyUnicode_READ(kind, str, idx))) idx++;
+
+            /* bail if the object is closed or we didn't get the ,
+               delimiter */
+            did_parse = 1;
+            if (idx > end_idx) break;
+            if (PyUnicode_READ(kind, str, idx) == '}') {
+                break;
+            }
+            else if (PyUnicode_READ(kind, str, idx) != ',') {
+                raise_errmsg(ERR_OBJECT_DELIMITER, pystr, idx);
+                goto bail;
+            }
+            idx++;
+
+            /* skip whitespace after , delimiter */
+            while (idx <= end_idx && IS_WHITESPACE(PyUnicode_READ(kind, str, idx))) idx++;
+            trailing_delimiter = 1;
+        }
+        if (trailing_delimiter) {
+            raise_errmsg(ERR_OBJECT_PROPERTY, pystr, idx);
+            goto bail;
+        }
+    }
+
+    /* verify that idx < end_idx, str[idx] should be '}' */
+    if (idx > end_idx || PyUnicode_READ(kind, str, idx) != '}') {
+        if (did_parse) {
+            raise_errmsg(ERR_OBJECT_DELIMITER, pystr, idx);
+        } else {
+            raise_errmsg(ERR_OBJECT_PROPERTY_FIRST, pystr, idx);
+        }
+        goto bail;
+    }
+
+    /* if pairs_hook is not None: rval = object_pairs_hook(pairs) */
+    if (s->pairs_hook != Py_None) {
+        val = PyObject_CallFunctionObjArgs(s->pairs_hook, pairs, NULL);
+        if (val == NULL)
+            goto bail;
+        Py_DECREF(pairs);
+        *next_idx_ptr = idx + 1;
+        return val;
+    }
+
+    /* if object_hook is not None: rval = object_hook(rval) */
+    if (s->object_hook != Py_None) {
+        val = PyObject_CallFunctionObjArgs(s->object_hook, rval, NULL);
+        if (val == NULL)
+            goto bail;
+        Py_DECREF(rval);
+        rval = val;
+        val = NULL;
+    }
+    *next_idx_ptr = idx + 1;
+    return rval;
+bail:
+    Py_XDECREF(rval);
+    Py_XDECREF(key);
+    Py_XDECREF(val);
+    Py_XDECREF(pairs);
+    return NULL;
+}
+
+#if PY_MAJOR_VERSION < 3
+static PyObject *
+_parse_array_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr)
+{
+    /* Read a JSON array from PyString pystr.
+    idx is the index of the first character after the opening brace.
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the closing brace.
+
+    Returns a new PyList
+    */
+    char *str = PyString_AS_STRING(pystr);
+    Py_ssize_t end_idx = PyString_GET_SIZE(pystr) - 1;
+    PyObject *val = NULL;
+    PyObject *rval = PyList_New(0);
+    Py_ssize_t next_idx;
+    if (rval == NULL)
+        return NULL;
+
+    /* skip whitespace after [ */
+    while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+    /* only loop if the array is non-empty */
+    if (idx <= end_idx && str[idx] != ']') {
+        int trailing_delimiter = 0;
+        while (idx <= end_idx) {
+            trailing_delimiter = 0;
+            /* read any JSON term and de-tuplefy the (rval, idx) */
+            val = scan_once_str(s, pystr, idx, &next_idx);
+            if (val == NULL) {
+                goto bail;
+            }
+
+            if (PyList_Append(rval, val) == -1)
+                goto bail;
+
+            Py_CLEAR(val);
+            idx = next_idx;
+
+            /* skip whitespace between term and , */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+            /* bail if the array is closed or we didn't get the , delimiter */
+            if (idx > end_idx) break;
+            if (str[idx] == ']') {
+                break;
+            }
+            else if (str[idx] != ',') {
+                raise_errmsg(ERR_ARRAY_DELIMITER, pystr, idx);
+                goto bail;
+            }
+            idx++;
+
+            /* skip whitespace after , */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+            trailing_delimiter = 1;
+        }
+        if (trailing_delimiter) {
+            raise_errmsg(ERR_EXPECTING_VALUE, pystr, idx);
+            goto bail;
+        }
+    }
+
+    /* verify that idx < end_idx, str[idx] should be ']' */
+    if (idx > end_idx || str[idx] != ']') {
+        if (PyList_GET_SIZE(rval)) {
+            raise_errmsg(ERR_ARRAY_DELIMITER, pystr, idx);
+        } else {
+            raise_errmsg(ERR_ARRAY_VALUE_FIRST, pystr, idx);
+        }
+        goto bail;
+    }
+    *next_idx_ptr = idx + 1;
+    return rval;
+bail:
+    Py_XDECREF(val);
+    Py_DECREF(rval);
+    return NULL;
+}
+#endif /* PY_MAJOR_VERSION < 3 */
+
+static PyObject *
+_parse_array_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr)
+{
+    /* Read a JSON array from PyString pystr.
+    idx is the index of the first character after the opening brace.
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the closing brace.
+
+    Returns a new PyList
+    */
+    PY2_UNUSED int kind = PyUnicode_KIND(pystr);
+    void *str = PyUnicode_DATA(pystr);
+    Py_ssize_t end_idx = PyUnicode_GET_LENGTH(pystr) - 1;
+    PyObject *val = NULL;
+    PyObject *rval = PyList_New(0);
+    Py_ssize_t next_idx;
+    if (rval == NULL)
+        return NULL;
+
+    /* skip whitespace after [ */
+    while (idx <= end_idx && IS_WHITESPACE(PyUnicode_READ(kind, str, idx))) idx++;
+
+    /* only loop if the array is non-empty */
+    if (idx <= end_idx && PyUnicode_READ(kind, str, idx) != ']') {
+        int trailing_delimiter = 0;
+        while (idx <= end_idx) {
+            trailing_delimiter = 0;
+            /* read any JSON term  */
+            val = scan_once_unicode(s, pystr, idx, &next_idx);
+            if (val == NULL) {
+                goto bail;
+            }
+
+            if (PyList_Append(rval, val) == -1)
+                goto bail;
+
+            Py_CLEAR(val);
+            idx = next_idx;
+
+            /* skip whitespace between term and , */
+            while (idx <= end_idx && IS_WHITESPACE(PyUnicode_READ(kind, str, idx))) idx++;
+
+            /* bail if the array is closed or we didn't get the , delimiter */
+            if (idx > end_idx) break;
+            if (PyUnicode_READ(kind, str, idx) == ']') {
+                break;
+            }
+            else if (PyUnicode_READ(kind, str, idx) != ',') {
+                raise_errmsg(ERR_ARRAY_DELIMITER, pystr, idx);
+                goto bail;
+            }
+            idx++;
+
+            /* skip whitespace after , */
+            while (idx <= end_idx && IS_WHITESPACE(PyUnicode_READ(kind, str, idx))) idx++;
+            trailing_delimiter = 1;
+        }
+        if (trailing_delimiter) {
+            raise_errmsg(ERR_EXPECTING_VALUE, pystr, idx);
+            goto bail;
+        }
+    }
+
+    /* verify that idx < end_idx, str[idx] should be ']' */
+    if (idx > end_idx || PyUnicode_READ(kind, str, idx) != ']') {
+        if (PyList_GET_SIZE(rval)) {
+            raise_errmsg(ERR_ARRAY_DELIMITER, pystr, idx);
+        } else {
+            raise_errmsg(ERR_ARRAY_VALUE_FIRST, pystr, idx);
+        }
+        goto bail;
+    }
+    *next_idx_ptr = idx + 1;
+    return rval;
+bail:
+    Py_XDECREF(val);
+    Py_DECREF(rval);
+    return NULL;
+}
+
+static PyObject *
+_parse_constant(PyScannerObject *s, PyObject *constant, Py_ssize_t idx, Py_ssize_t *next_idx_ptr)
+{
+    /* Read a JSON constant from PyString pystr.
+    constant is the Python string that was found
+        ("NaN", "Infinity", "-Infinity").
+    idx is the index of the first character of the constant
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the constant.
+
+    Returns the result of parse_constant
+    */
+    PyObject *rval;
+
+    /* rval = parse_constant(constant) */
+    rval = PyObject_CallFunctionObjArgs(s->parse_constant, constant, NULL);
+    idx += PyString_GET_SIZE(constant);
+    *next_idx_ptr = idx;
+    return rval;
+}
+
+#if PY_MAJOR_VERSION < 3
+static PyObject *
+_match_number_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_ssize_t *next_idx_ptr)
+{
+    /* Read a JSON number from PyString pystr.
+    idx is the index of the first character of the number
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the number.
+
+    Returns a new PyObject representation of that number:
+        PyInt, PyLong, or PyFloat.
+        May return other types if parse_int or parse_float are set
+    */
+    char *str = PyString_AS_STRING(pystr);
+    Py_ssize_t end_idx = PyString_GET_SIZE(pystr) - 1;
+    Py_ssize_t idx = start;
+    int is_float = 0;
+    PyObject *rval;
+    PyObject *numstr;
+
+    /* read a sign if it's there, make sure it's not the end of the string */
+    if (str[idx] == '-') {
+        if (idx >= end_idx) {
+            raise_errmsg(ERR_EXPECTING_VALUE, pystr, idx);
+            return NULL;
+        }
+        idx++;
+    }
+
+    /* read as many integer digits as we find as long as it doesn't start with 0 */
+    if (str[idx] >= '1' && str[idx] <= '9') {
+        idx++;
+        while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++;
+    }
+    /* if it starts with 0 we only expect one integer digit */
+    else if (str[idx] == '0') {
+        idx++;
+    }
+    /* no integer digits, error */
+    else {
+        raise_errmsg(ERR_EXPECTING_VALUE, pystr, idx);
+        return NULL;
+    }
+
+    /* if the next char is '.' followed by a digit then read all float digits */
+    if (idx < end_idx && str[idx] == '.' && str[idx + 1] >= '0' && str[idx + 1] <= '9') {
+        is_float = 1;
+        idx += 2;
+        while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++;
+    }
+
+    /* if the next char is 'e' or 'E' then maybe read the exponent (or backtrack) */
+    if (idx < end_idx && (str[idx] == 'e' || str[idx] == 'E')) {
+
+        /* save the index of the 'e' or 'E' just in case we need to backtrack */
+        Py_ssize_t e_start = idx;
+        idx++;
+
+        /* read an exponent sign if present */
+        if (idx < end_idx && (str[idx] == '-' || str[idx] == '+')) idx++;
+
+        /* read all digits */
+        while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++;
+
+        /* if we got a digit, then parse as float. if not, backtrack */
+        if (str[idx - 1] >= '0' && str[idx - 1] <= '9') {
+            is_float = 1;
+        }
+        else {
+            idx = e_start;
+        }
+    }
+
+    /* copy the section we determined to be a number */
+    numstr = PyString_FromStringAndSize(&str[start], idx - start);
+    if (numstr == NULL)
+        return NULL;
+    if (is_float) {
+        /* parse as a float using a fast path if available, otherwise call user defined method */
+        if (s->parse_float != (PyObject *)&PyFloat_Type) {
+            rval = PyObject_CallFunctionObjArgs(s->parse_float, numstr, NULL);
+        }
+        else {
+            /* rval = PyFloat_FromDouble(PyOS_ascii_atof(PyString_AS_STRING(numstr))); */
+            double d = PyOS_string_to_double(PyString_AS_STRING(numstr),
+                                             NULL, NULL);
+            if (d == -1.0 && PyErr_Occurred())
+                return NULL;
+            rval = PyFloat_FromDouble(d);
+        }
+    }
+    else {
+        /* parse as an int using a fast path if available, otherwise call user defined method */
+        if (s->parse_int != (PyObject *)&PyInt_Type) {
+            rval = PyObject_CallFunctionObjArgs(s->parse_int, numstr, NULL);
+        }
+        else {
+            rval = PyInt_FromString(PyString_AS_STRING(numstr), NULL, 10);
+        }
+    }
+    Py_DECREF(numstr);
+    *next_idx_ptr = idx;
+    return rval;
+}
+#endif /* PY_MAJOR_VERSION < 3 */
+
+static PyObject *
+_match_number_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_ssize_t *next_idx_ptr)
+{
+    /* Read a JSON number from PyUnicode pystr.
+    idx is the index of the first character of the number
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the number.
+
+    Returns a new PyObject representation of that number:
+        PyInt, PyLong, or PyFloat.
+        May return other types if parse_int or parse_float are set
+    */
+    PY2_UNUSED int kind = PyUnicode_KIND(pystr);
+    void *str = PyUnicode_DATA(pystr);
+    Py_ssize_t end_idx = PyUnicode_GET_LENGTH(pystr) - 1;
+    Py_ssize_t idx = start;
+    int is_float = 0;
+    JSON_UNICHR c;
+    PyObject *rval;
+    PyObject *numstr;
+
+    /* read a sign if it's there, make sure it's not the end of the string */
+    if (PyUnicode_READ(kind, str, idx) == '-') {
+        if (idx >= end_idx) {
+            raise_errmsg(ERR_EXPECTING_VALUE, pystr, idx);
+            return NULL;
+        }
+        idx++;
+    }
+
+    /* read as many integer digits as we find as long as it doesn't start with 0 */
+    c = PyUnicode_READ(kind, str, idx);
+    if (c == '0') {
+        /* if it starts with 0 we only expect one integer digit */
+        idx++;
+    }
+    else if (IS_DIGIT(c)) {
+        idx++;
+        while (idx <= end_idx && IS_DIGIT(PyUnicode_READ(kind, str, idx))) {
+            idx++;
+        }
+    }
+    else {
+        /* no integer digits, error */
+        raise_errmsg(ERR_EXPECTING_VALUE, pystr, idx);
+        return NULL;
+    }
+
+    /* if the next char is '.' followed by a digit then read all float digits */
+    if (idx < end_idx &&
+        PyUnicode_READ(kind, str, idx) == '.' &&
+        IS_DIGIT(PyUnicode_READ(kind, str, idx + 1))) {
+        is_float = 1;
+        idx += 2;
+        while (idx <= end_idx && IS_DIGIT(PyUnicode_READ(kind, str, idx))) idx++;
+    }
+
+    /* if the next char is 'e' or 'E' then maybe read the exponent (or backtrack) */
+    if (idx < end_idx &&
+        (PyUnicode_READ(kind, str, idx) == 'e' ||
+            PyUnicode_READ(kind, str, idx) == 'E')) {
+        Py_ssize_t e_start = idx;
+        idx++;
+
+        /* read an exponent sign if present */
+        if (idx < end_idx &&
+            (PyUnicode_READ(kind, str, idx) == '-' ||
+                PyUnicode_READ(kind, str, idx) == '+')) idx++;
+
+        /* read all digits */
+        while (idx <= end_idx && IS_DIGIT(PyUnicode_READ(kind, str, idx))) idx++;
+
+        /* if we got a digit, then parse as float. if not, backtrack */
+        if (IS_DIGIT(PyUnicode_READ(kind, str, idx - 1))) {
+            is_float = 1;
+        }
+        else {
+            idx = e_start;
+        }
+    }
+
+    /* copy the section we determined to be a number */
+#if PY_MAJOR_VERSION >= 3
+    numstr = PyUnicode_Substring(pystr, start, idx);
+#else
+    numstr = PyUnicode_FromUnicode(&((Py_UNICODE *)str)[start], idx - start);
+#endif
+    if (numstr == NULL)
+        return NULL;
+    if (is_float) {
+        /* parse as a float using a fast path if available, otherwise call user defined method */
+        if (s->parse_float != (PyObject *)&PyFloat_Type) {
+            rval = PyObject_CallFunctionObjArgs(s->parse_float, numstr, NULL);
+        }
+        else {
+#if PY_MAJOR_VERSION >= 3
+            rval = PyFloat_FromString(numstr);
+#else
+            rval = PyFloat_FromString(numstr, NULL);
+#endif
+        }
+    }
+    else {
+        /* no fast path for unicode -> int, just call */
+        rval = PyObject_CallFunctionObjArgs(s->parse_int, numstr, NULL);
+    }
+    Py_DECREF(numstr);
+    *next_idx_ptr = idx;
+    return rval;
+}
+
+#if PY_MAJOR_VERSION < 3
+static PyObject *
+scan_once_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr)
+{
+    /* Read one JSON term (of any kind) from PyString pystr.
+    idx is the index of the first character of the term
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the number.
+
+    Returns a new PyObject representation of the term.
+    */
+    char *str = PyString_AS_STRING(pystr);
+    Py_ssize_t length = PyString_GET_SIZE(pystr);
+    PyObject *rval = NULL;
+    int fallthrough = 0;
+    if (idx < 0 || idx >= length) {
+        raise_errmsg(ERR_EXPECTING_VALUE, pystr, idx);
+        return NULL;
+    }
+    switch (str[idx]) {
+        case '"':
+            /* string */
+            rval = scanstring_str(pystr, idx + 1,
+                PyString_AS_STRING(s->encoding),
+                s->strict,
+                next_idx_ptr);
+            break;
+        case '{':
+            /* object */
+            if (Py_EnterRecursiveCall(" while decoding a JSON object "
+                                      "from a string"))
+                return NULL;
+            rval = _parse_object_str(s, pystr, idx + 1, next_idx_ptr);
+            Py_LeaveRecursiveCall();
+            break;
+        case '[':
+            /* array */
+            if (Py_EnterRecursiveCall(" while decoding a JSON array "
+                                      "from a string"))
+                return NULL;
+            rval = _parse_array_str(s, pystr, idx + 1, next_idx_ptr);
+            Py_LeaveRecursiveCall();
+            break;
+        case 'n':
+            /* null */
+            if ((idx + 3 < length) && str[idx + 1] == 'u' && str[idx + 2] == 'l' && str[idx + 3] == 'l') {
+                Py_INCREF(Py_None);
+                *next_idx_ptr = idx + 4;
+                rval = Py_None;
+            }
+            else
+                fallthrough = 1;
+            break;
+        case 't':
+            /* true */
+            if ((idx + 3 < length) && str[idx + 1] == 'r' && str[idx + 2] == 'u' && str[idx + 3] == 'e') {
+                Py_INCREF(Py_True);
+                *next_idx_ptr = idx + 4;
+                rval = Py_True;
+            }
+            else
+                fallthrough = 1;
+            break;
+        case 'f':
+            /* false */
+            if ((idx + 4 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'l' && str[idx + 3] == 's' && str[idx + 4] == 'e') {
+                Py_INCREF(Py_False);
+                *next_idx_ptr = idx + 5;
+                rval = Py_False;
+            }
+            else
+                fallthrough = 1;
+            break;
+        case 'N':
+            /* NaN */
+            if ((idx + 2 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'N') {
+                rval = _parse_constant(s, JSON_NaN, idx, next_idx_ptr);
+            }
+            else
+                fallthrough = 1;
+            break;
+        case 'I':
+            /* Infinity */
+            if ((idx + 7 < length) && str[idx + 1] == 'n' && str[idx + 2] == 'f' && str[idx + 3] == 'i' && str[idx + 4] == 'n' && str[idx + 5] == 'i' && str[idx + 6] == 't' && str[idx + 7] == 'y') {
+                rval = _parse_constant(s, JSON_Infinity, idx, next_idx_ptr);
+            }
+            else
+                fallthrough = 1;
+            break;
+        case '-':
+            /* -Infinity */
+            if ((idx + 8 < length) && str[idx + 1] == 'I' && str[idx + 2] == 'n' && str[idx + 3] == 'f' && str[idx + 4] == 'i' && str[idx + 5] == 'n' && str[idx + 6] == 'i' && str[idx + 7] == 't' && str[idx + 8] == 'y') {
+                rval = _parse_constant(s, JSON_NegInfinity, idx, next_idx_ptr);
+            }
+            else
+                fallthrough = 1;
+            break;
+        default:
+            fallthrough = 1;
+    }
+    /* Didn't find a string, object, array, or named constant. Look for a number. */
+    if (fallthrough)
+        rval = _match_number_str(s, pystr, idx, next_idx_ptr);
+    return rval;
+}
+#endif /* PY_MAJOR_VERSION < 3 */
+
+
+static PyObject *
+scan_once_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr)
+{
+    /* Read one JSON term (of any kind) from PyUnicode pystr.
+    idx is the index of the first character of the term
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the number.
+
+    Returns a new PyObject representation of the term.
+    */
+    PY2_UNUSED int kind = PyUnicode_KIND(pystr);
+    void *str = PyUnicode_DATA(pystr);
+    Py_ssize_t length = PyUnicode_GET_LENGTH(pystr);
+    PyObject *rval = NULL;
+    int fallthrough = 0;
+    if (idx < 0 || idx >= length) {
+        raise_errmsg(ERR_EXPECTING_VALUE, pystr, idx);
+        return NULL;
+    }
+    switch (PyUnicode_READ(kind, str, idx)) {
+        case '"':
+            /* string */
+            rval = scanstring_unicode(pystr, idx + 1,
+                s->strict,
+                next_idx_ptr);
+            break;
+        case '{':
+            /* object */
+            if (Py_EnterRecursiveCall(" while decoding a JSON object "
+                                      "from a unicode string"))
+                return NULL;
+            rval = _parse_object_unicode(s, pystr, idx + 1, next_idx_ptr);
+            Py_LeaveRecursiveCall();
+            break;
+        case '[':
+            /* array */
+            if (Py_EnterRecursiveCall(" while decoding a JSON array "
+                                      "from a unicode string"))
+                return NULL;
+            rval = _parse_array_unicode(s, pystr, idx + 1, next_idx_ptr);
+            Py_LeaveRecursiveCall();
+            break;
+        case 'n':
+            /* null */
+            if ((idx + 3 < length) &&
+                PyUnicode_READ(kind, str, idx + 1) == 'u' &&
+                PyUnicode_READ(kind, str, idx + 2) == 'l' &&
+                PyUnicode_READ(kind, str, idx + 3) == 'l') {
+                Py_INCREF(Py_None);
+                *next_idx_ptr = idx + 4;
+                rval = Py_None;
+            }
+            else
+                fallthrough = 1;
+            break;
+        case 't':
+            /* true */
+            if ((idx + 3 < length) &&
+                PyUnicode_READ(kind, str, idx + 1) == 'r' &&
+                PyUnicode_READ(kind, str, idx + 2) == 'u' &&
+                PyUnicode_READ(kind, str, idx + 3) == 'e') {
+                Py_INCREF(Py_True);
+                *next_idx_ptr = idx + 4;
+                rval = Py_True;
+            }
+            else
+                fallthrough = 1;
+            break;
+        case 'f':
+            /* false */
+            if ((idx + 4 < length) &&
+                PyUnicode_READ(kind, str, idx + 1) == 'a' &&
+                PyUnicode_READ(kind, str, idx + 2) == 'l' &&
+                PyUnicode_READ(kind, str, idx + 3) == 's' &&
+                PyUnicode_READ(kind, str, idx + 4) == 'e') {
+                Py_INCREF(Py_False);
+                *next_idx_ptr = idx + 5;
+                rval = Py_False;
+            }
+            else
+                fallthrough = 1;
+            break;
+        case 'N':
+            /* NaN */
+            if ((idx + 2 < length) &&
+                PyUnicode_READ(kind, str, idx + 1) == 'a' &&
+                PyUnicode_READ(kind, str, idx + 2) == 'N') {
+                rval = _parse_constant(s, JSON_NaN, idx, next_idx_ptr);
+            }
+            else
+                fallthrough = 1;
+            break;
+        case 'I':
+            /* Infinity */
+            if ((idx + 7 < length) &&
+                PyUnicode_READ(kind, str, idx + 1) == 'n' &&
+                PyUnicode_READ(kind, str, idx + 2) == 'f' &&
+                PyUnicode_READ(kind, str, idx + 3) == 'i' &&
+                PyUnicode_READ(kind, str, idx + 4) == 'n' &&
+                PyUnicode_READ(kind, str, idx + 5) == 'i' &&
+                PyUnicode_READ(kind, str, idx + 6) == 't' &&
+                PyUnicode_READ(kind, str, idx + 7) == 'y') {
+                rval = _parse_constant(s, JSON_Infinity, idx, next_idx_ptr);
+            }
+            else
+                fallthrough = 1;
+            break;
+        case '-':
+            /* -Infinity */
+            if ((idx + 8 < length) &&
+                PyUnicode_READ(kind, str, idx + 1) == 'I' &&
+                PyUnicode_READ(kind, str, idx + 2) == 'n' &&
+                PyUnicode_READ(kind, str, idx + 3) == 'f' &&
+                PyUnicode_READ(kind, str, idx + 4) == 'i' &&
+                PyUnicode_READ(kind, str, idx + 5) == 'n' &&
+                PyUnicode_READ(kind, str, idx + 6) == 'i' &&
+                PyUnicode_READ(kind, str, idx + 7) == 't' &&
+                PyUnicode_READ(kind, str, idx + 8) == 'y') {
+                rval = _parse_constant(s, JSON_NegInfinity, idx, next_idx_ptr);
+            }
+            else
+                fallthrough = 1;
+            break;
+        default:
+            fallthrough = 1;
+    }
+    /* Didn't find a string, object, array, or named constant. Look for a number. */
+    if (fallthrough)
+        rval = _match_number_unicode(s, pystr, idx, next_idx_ptr);
+    return rval;
+}
+
+static PyObject *
+scanner_call(PyObject *self, PyObject *args, PyObject *kwds)
+{
+    /* Python callable interface to scan_once_{str,unicode} */
+    PyObject *pystr;
+    PyObject *rval;
+    Py_ssize_t idx;
+    Py_ssize_t next_idx = -1;
+    static char *kwlist[] = {"string", "idx", NULL};
+    PyScannerObject *s;
+    assert(PyScanner_Check(self));
+    s = (PyScannerObject *)self;
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO&:scan_once", kwlist, &pystr, _convertPyInt_AsSsize_t, &idx))
+        return NULL;
+
+    if (PyUnicode_Check(pystr)) {
+        if (PyUnicode_READY(pystr))
+            return NULL;
+        rval = scan_once_unicode(s, pystr, idx, &next_idx);
+    }
+#if PY_MAJOR_VERSION < 3
+    else if (PyString_Check(pystr)) {
+        rval = scan_once_str(s, pystr, idx, &next_idx);
+    }
+#endif /* PY_MAJOR_VERSION < 3 */
+    else {
+        PyErr_Format(PyExc_TypeError,
+                 "first argument must be a string, not %.80s",
+                 Py_TYPE(pystr)->tp_name);
+        return NULL;
+    }
+    PyDict_Clear(s->memo);
+    return _build_rval_index_tuple(rval, next_idx);
+}
+
+static PyObject *
+JSON_ParseEncoding(PyObject *encoding)
+{
+    if (encoding == Py_None)
+        return JSON_InternFromString(DEFAULT_ENCODING);
+#if PY_MAJOR_VERSION >= 3
+    if (PyUnicode_Check(encoding)) {
+        if (PyUnicode_AsUTF8(encoding) == NULL) {
+            return NULL;
+        }
+        Py_INCREF(encoding);
+        return encoding;
+    }
+#else /* PY_MAJOR_VERSION >= 3 */
+    if (PyString_Check(encoding)) {
+        Py_INCREF(encoding);
+        return encoding;
+    }
+    if (PyUnicode_Check(encoding))
+        return PyUnicode_AsEncodedString(encoding, NULL, NULL);
+#endif /* PY_MAJOR_VERSION >= 3 */
+    PyErr_SetString(PyExc_TypeError, "encoding must be a string");
+    return NULL;
+}
+
+static PyObject *
+scanner_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+    /* Initialize Scanner object */
+    PyObject *ctx;
+    static char *kwlist[] = {"context", NULL};
+    PyScannerObject *s;
+    PyObject *encoding;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:make_scanner", kwlist, &ctx))
+        return NULL;
+
+    s = (PyScannerObject *)type->tp_alloc(type, 0);
+    if (s == NULL)
+        return NULL;
+
+    if (s->memo == NULL) {
+        s->memo = PyDict_New();
+        if (s->memo == NULL)
+            goto bail;
+    }
+
+    encoding = PyObject_GetAttrString(ctx, "encoding");
+    if (encoding == NULL)
+        goto bail;
+    s->encoding = JSON_ParseEncoding(encoding);
+    Py_XDECREF(encoding);
+    if (s->encoding == NULL)
+        goto bail;
+
+    /* All of these will fail "gracefully" so we don't need to verify them */
+    s->strict_bool = PyObject_GetAttrString(ctx, "strict");
+    if (s->strict_bool == NULL)
+        goto bail;
+    s->strict = PyObject_IsTrue(s->strict_bool);
+    if (s->strict < 0)
+        goto bail;
+    s->object_hook = PyObject_GetAttrString(ctx, "object_hook");
+    if (s->object_hook == NULL)
+        goto bail;
+    s->pairs_hook = PyObject_GetAttrString(ctx, "object_pairs_hook");
+    if (s->pairs_hook == NULL)
+        goto bail;
+    s->parse_float = PyObject_GetAttrString(ctx, "parse_float");
+    if (s->parse_float == NULL)
+        goto bail;
+    s->parse_int = PyObject_GetAttrString(ctx, "parse_int");
+    if (s->parse_int == NULL)
+        goto bail;
+    s->parse_constant = PyObject_GetAttrString(ctx, "parse_constant");
+    if (s->parse_constant == NULL)
+        goto bail;
+
+    return (PyObject *)s;
+
+bail:
+    Py_DECREF(s);
+    return NULL;
+}
+
+PyDoc_STRVAR(scanner_doc, "JSON scanner object");
+
+static
+PyTypeObject PyScannerType = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "simplejson._speedups.Scanner",       /* tp_name */
+    sizeof(PyScannerObject), /* tp_basicsize */
+    0,                    /* tp_itemsize */
+    scanner_dealloc, /* tp_dealloc */
+    0,                    /* tp_print */
+    0,                    /* tp_getattr */
+    0,                    /* tp_setattr */
+    0,                    /* tp_compare */
+    0,                    /* tp_repr */
+    0,                    /* tp_as_number */
+    0,                    /* tp_as_sequence */
+    0,                    /* tp_as_mapping */
+    0,                    /* tp_hash */
+    scanner_call,         /* tp_call */
+    0,                    /* tp_str */
+    0,/* PyObject_GenericGetAttr, */                    /* tp_getattro */
+    0,/* PyObject_GenericSetAttr, */                    /* tp_setattro */
+    0,                    /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,   /* tp_flags */
+    scanner_doc,          /* tp_doc */
+    scanner_traverse,                    /* tp_traverse */
+    scanner_clear,                    /* tp_clear */
+    0,                    /* tp_richcompare */
+    0,                    /* tp_weaklistoffset */
+    0,                    /* tp_iter */
+    0,                    /* tp_iternext */
+    0,                    /* tp_methods */
+    scanner_members,                    /* tp_members */
+    0,                    /* tp_getset */
+    0,                    /* tp_base */
+    0,                    /* tp_dict */
+    0,                    /* tp_descr_get */
+    0,                    /* tp_descr_set */
+    0,                    /* tp_dictoffset */
+    0,                    /* tp_init */
+    0,/* PyType_GenericAlloc, */        /* tp_alloc */
+    scanner_new,          /* tp_new */
+    0,/* PyObject_GC_Del, */              /* tp_free */
+};
+
+static PyObject *
+encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+    static char *kwlist[] = {
+        "markers",
+        "default",
+        "encoder",
+        "indent",
+        "key_separator",
+        "item_separator",
+        "sort_keys",
+        "skipkeys",
+        "allow_nan",
+        "key_memo",
+        "use_decimal",
+        "namedtuple_as_object",
+        "tuple_as_array",
+        "int_as_string_bitcount",
+        "item_sort_key",
+        "encoding",
+        "for_json",
+        "ignore_nan",
+        "Decimal",
+        "iterable_as_array",
+        NULL};
+
+    PyEncoderObject *s;
+    PyObject *markers, *defaultfn, *encoder, *indent, *key_separator;
+    PyObject *item_separator, *sort_keys, *skipkeys, *allow_nan, *key_memo;
+    PyObject *use_decimal, *namedtuple_as_object, *tuple_as_array, *iterable_as_array;
+    PyObject *int_as_string_bitcount, *item_sort_key, *encoding, *for_json;
+    PyObject *ignore_nan, *Decimal;
+    int is_true;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOOOOOOOOOOOOOOOOOOO:make_encoder", kwlist,
+        &markers, &defaultfn, &encoder, &indent, &key_separator, &item_separator,
+        &sort_keys, &skipkeys, &allow_nan, &key_memo, &use_decimal,
+        &namedtuple_as_object, &tuple_as_array,
+        &int_as_string_bitcount, &item_sort_key, &encoding, &for_json,
+        &ignore_nan, &Decimal, &iterable_as_array))
+        return NULL;
+
+    s = (PyEncoderObject *)type->tp_alloc(type, 0);
+    if (s == NULL)
+        return NULL;
+
+    Py_INCREF(markers);
+    s->markers = markers;
+    Py_INCREF(defaultfn);
+    s->defaultfn = defaultfn;
+    Py_INCREF(encoder);
+    s->encoder = encoder;
+#if PY_MAJOR_VERSION >= 3
+    if (encoding == Py_None) {
+        s->encoding = NULL;
+    }
+    else
+#endif /* PY_MAJOR_VERSION >= 3 */
+    {
+        s->encoding = JSON_ParseEncoding(encoding);
+        if (s->encoding == NULL)
+            goto bail;
+    }
+    Py_INCREF(indent);
+    s->indent = indent;
+    Py_INCREF(key_separator);
+    s->key_separator = key_separator;
+    Py_INCREF(item_separator);
+    s->item_separator = item_separator;
+    Py_INCREF(skipkeys);
+    s->skipkeys_bool = skipkeys;
+    s->skipkeys = PyObject_IsTrue(skipkeys);
+    if (s->skipkeys < 0)
+        goto bail;
+    Py_INCREF(key_memo);
+    s->key_memo = key_memo;
+    s->fast_encode = (PyCFunction_Check(s->encoder) && PyCFunction_GetFunction(s->encoder) == (PyCFunction)py_encode_basestring_ascii);
+    is_true = PyObject_IsTrue(ignore_nan);
+    if (is_true < 0)
+        goto bail;
+    s->allow_or_ignore_nan = is_true ? JSON_IGNORE_NAN : 0;
+    is_true = PyObject_IsTrue(allow_nan);
+    if (is_true < 0)
+        goto bail;
+    s->allow_or_ignore_nan |= is_true ? JSON_ALLOW_NAN : 0;
+    s->use_decimal = PyObject_IsTrue(use_decimal);
+    if (s->use_decimal < 0)
+        goto bail;
+    s->namedtuple_as_object = PyObject_IsTrue(namedtuple_as_object);
+    if (s->namedtuple_as_object < 0)
+        goto bail;
+    s->tuple_as_array = PyObject_IsTrue(tuple_as_array);
+    if (s->tuple_as_array < 0)
+        goto bail;
+    s->iterable_as_array = PyObject_IsTrue(iterable_as_array);
+    if (s->iterable_as_array < 0)
+        goto bail;
+    if (PyInt_Check(int_as_string_bitcount) || PyLong_Check(int_as_string_bitcount)) {
+        static const unsigned long long_long_bitsize = SIZEOF_LONG_LONG * 8;
+        long int_as_string_bitcount_val = PyLong_AsLong(int_as_string_bitcount);
+        if (int_as_string_bitcount_val > 0 && int_as_string_bitcount_val < (long)long_long_bitsize) {
+            s->max_long_size = PyLong_FromUnsignedLongLong(1ULL << (int)int_as_string_bitcount_val);
+            s->min_long_size = PyLong_FromLongLong(-1LL << (int)int_as_string_bitcount_val);
+            if (s->min_long_size == NULL || s->max_long_size == NULL) {
+                goto bail;
+            }
+        }
+        else {
+            PyErr_Format(PyExc_TypeError,
+                         "int_as_string_bitcount (%ld) must be greater than 0 and less than the number of bits of a `long long` type (%lu bits)",
+                         int_as_string_bitcount_val, long_long_bitsize);
+            goto bail;
+        }
+    }
+    else if (int_as_string_bitcount == Py_None) {
+        Py_INCREF(Py_None);
+        s->max_long_size = Py_None;
+        Py_INCREF(Py_None);
+        s->min_long_size = Py_None;
+    }
+    else {
+        PyErr_SetString(PyExc_TypeError, "int_as_string_bitcount must be None or an integer");
+        goto bail;
+    }
+    if (item_sort_key != Py_None) {
+        if (!PyCallable_Check(item_sort_key)) {
+            PyErr_SetString(PyExc_TypeError, "item_sort_key must be None or callable");
+            goto bail;
+        }
+    }
+    else {
+        is_true = PyObject_IsTrue(sort_keys);
+        if (is_true < 0)
+            goto bail;
+        if (is_true) {
+            static PyObject *itemgetter0 = NULL;
+            if (!itemgetter0) {
+                PyObject *operator = PyImport_ImportModule("operator");
+                if (!operator)
+                    goto bail;
+                itemgetter0 = PyObject_CallMethod(operator, "itemgetter", "i", 0);
+                Py_DECREF(operator);
+            }
+            item_sort_key = itemgetter0;
+            if (!item_sort_key)
+                goto bail;
+        }
+    }
+    if (item_sort_key == Py_None) {
+        Py_INCREF(Py_None);
+        s->item_sort_kw = Py_None;
+    }
+    else {
+        s->item_sort_kw = PyDict_New();
+        if (s->item_sort_kw == NULL)
+            goto bail;
+        if (PyDict_SetItemString(s->item_sort_kw, "key", item_sort_key))
+            goto bail;
+    }
+    Py_INCREF(sort_keys);
+    s->sort_keys = sort_keys;
+    Py_INCREF(item_sort_key);
+    s->item_sort_key = item_sort_key;
+    Py_INCREF(Decimal);
+    s->Decimal = Decimal;
+    s->for_json = PyObject_IsTrue(for_json);
+    if (s->for_json < 0)
+        goto bail;
+
+    return (PyObject *)s;
+
+bail:
+    Py_DECREF(s);
+    return NULL;
+}
+
+static PyObject *
+encoder_call(PyObject *self, PyObject *args, PyObject *kwds)
+{
+    /* Python callable interface to encode_listencode_obj */
+    static char *kwlist[] = {"obj", "_current_indent_level", NULL};
+    PyObject *obj;
+    Py_ssize_t indent_level;
+    PyEncoderObject *s;
+    JSON_Accu rval;
+    assert(PyEncoder_Check(self));
+    s = (PyEncoderObject *)self;
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO&:_iterencode", kwlist,
+        &obj, _convertPyInt_AsSsize_t, &indent_level))
+        return NULL;
+    if (JSON_Accu_Init(&rval))
+        return NULL;
+    if (encoder_listencode_obj(s, &rval, obj, indent_level)) {
+        JSON_Accu_Destroy(&rval);
+        return NULL;
+    }
+    return JSON_Accu_FinishAsList(&rval);
+}
+
+static PyObject *
+_encoded_const(PyObject *obj)
+{
+    /* Return the JSON string representation of None, True, False */
+    if (obj == Py_None) {
+        static PyObject *s_null = NULL;
+        if (s_null == NULL) {
+            s_null = JSON_InternFromString("null");
+        }
+        Py_INCREF(s_null);
+        return s_null;
+    }
+    else if (obj == Py_True) {
+        static PyObject *s_true = NULL;
+        if (s_true == NULL) {
+            s_true = JSON_InternFromString("true");
+        }
+        Py_INCREF(s_true);
+        return s_true;
+    }
+    else if (obj == Py_False) {
+        static PyObject *s_false = NULL;
+        if (s_false == NULL) {
+            s_false = JSON_InternFromString("false");
+        }
+        Py_INCREF(s_false);
+        return s_false;
+    }
+    else {
+        PyErr_SetString(PyExc_ValueError, "not a const");
+        return NULL;
+    }
+}
+
+static PyObject *
+encoder_encode_float(PyEncoderObject *s, PyObject *obj)
+{
+    /* Return the JSON representation of a PyFloat */
+    double i = PyFloat_AS_DOUBLE(obj);
+    if (!Py_IS_FINITE(i)) {
+        if (!s->allow_or_ignore_nan) {
+            PyErr_SetString(PyExc_ValueError, "Out of range float values are not JSON compliant");
+            return NULL;
+        }
+        if (s->allow_or_ignore_nan & JSON_IGNORE_NAN) {
+            return _encoded_const(Py_None);
+        }
+        /* JSON_ALLOW_NAN is set */
+        else if (i > 0) {
+            Py_INCREF(JSON_Infinity);
+            return JSON_Infinity;
+        }
+        else if (i < 0) {
+            Py_INCREF(JSON_NegInfinity);
+            return JSON_NegInfinity;
+        }
+        else {
+            Py_INCREF(JSON_NaN);
+            return JSON_NaN;
+        }
+    }
+    /* Use a better float format here? */
+    if (PyFloat_CheckExact(obj)) {
+        return PyObject_Repr(obj);
+    }
+    else {
+        /* See #118, do not trust custom str/repr */
+        PyObject *res;
+        PyObject *tmp = PyObject_CallFunctionObjArgs((PyObject *)&PyFloat_Type, obj, NULL);
+        if (tmp == NULL) {
+            return NULL;
+        }
+        res = PyObject_Repr(tmp);
+        Py_DECREF(tmp);
+        return res;
+    }
+}
+
+static PyObject *
+encoder_encode_string(PyEncoderObject *s, PyObject *obj)
+{
+    /* Return the JSON representation of a string */
+    PyObject *encoded;
+
+    if (s->fast_encode) {
+        return py_encode_basestring_ascii(NULL, obj);
+    }
+    encoded = PyObject_CallFunctionObjArgs(s->encoder, obj, NULL);
+    if (encoded != NULL &&
+#if PY_MAJOR_VERSION < 3
+        !PyString_Check(encoded) &&
+#endif /* PY_MAJOR_VERSION < 3 */
+        !PyUnicode_Check(encoded))
+    {
+        PyErr_Format(PyExc_TypeError,
+                     "encoder() must return a string, not %.80s",
+                     Py_TYPE(encoded)->tp_name);
+        Py_DECREF(encoded);
+        return NULL;
+    }
+    return encoded;
+}
+
+static int
+_steal_accumulate(JSON_Accu *accu, PyObject *stolen)
+{
+    /* Append stolen and then decrement its reference count */
+    int rval = JSON_Accu_Accumulate(accu, stolen);
+    Py_DECREF(stolen);
+    return rval;
+}
+
+static int
+encoder_listencode_obj(PyEncoderObject *s, JSON_Accu *rval, PyObject *obj, Py_ssize_t indent_level)
+{
+    /* Encode Python object obj to a JSON term, rval is a PyList */
+    int rv = -1;
+    do {
+        if (obj == Py_None || obj == Py_True || obj == Py_False) {
+            PyObject *cstr = _encoded_const(obj);
+            if (cstr != NULL)
+                rv = _steal_accumulate(rval, cstr);
+        }
+        else if ((PyBytes_Check(obj) && s->encoding != NULL) ||
+                 PyUnicode_Check(obj))
+        {
+            PyObject *encoded = encoder_encode_string(s, obj);
+            if (encoded != NULL)
+                rv = _steal_accumulate(rval, encoded);
+        }
+        else if (PyInt_Check(obj) || PyLong_Check(obj)) {
+            PyObject *encoded;
+            if (PyInt_CheckExact(obj) || PyLong_CheckExact(obj)) {
+                encoded = PyObject_Str(obj);
+            }
+            else {
+                /* See #118, do not trust custom str/repr */
+                PyObject *tmp = PyObject_CallFunctionObjArgs((PyObject *)&PyLong_Type, obj, NULL);
+                if (tmp == NULL) {
+                    encoded = NULL;
+                }
+                else {
+                    encoded = PyObject_Str(tmp);
+                    Py_DECREF(tmp);
+                }
+            }
+            if (encoded != NULL) {
+                encoded = maybe_quote_bigint(s, encoded, obj);
+                if (encoded == NULL)
+                    break;
+                rv = _steal_accumulate(rval, encoded);
+            }
+        }
+        else if (PyFloat_Check(obj)) {
+            PyObject *encoded = encoder_encode_float(s, obj);
+            if (encoded != NULL)
+                rv = _steal_accumulate(rval, encoded);
+        }
+        else if (s->for_json && _has_for_json_hook(obj)) {
+            PyObject *newobj;
+            if (Py_EnterRecursiveCall(" while encoding a JSON object"))
+                return rv;
+            newobj = PyObject_CallMethod(obj, "for_json", NULL);
+            if (newobj != NULL) {
+                rv = encoder_listencode_obj(s, rval, newobj, indent_level);
+                Py_DECREF(newobj);
+            }
+            Py_LeaveRecursiveCall();
+        }
+        else if (s->namedtuple_as_object && _is_namedtuple(obj)) {
+            PyObject *newobj;
+            if (Py_EnterRecursiveCall(" while encoding a JSON object"))
+                return rv;
+            newobj = PyObject_CallMethod(obj, "_asdict", NULL);
+            if (newobj != NULL) {
+                rv = encoder_listencode_dict(s, rval, newobj, indent_level);
+                Py_DECREF(newobj);
+            }
+            Py_LeaveRecursiveCall();
+        }
+        else if (PyList_Check(obj) || (s->tuple_as_array && PyTuple_Check(obj))) {
+            if (Py_EnterRecursiveCall(" while encoding a JSON object"))
+                return rv;
+            rv = encoder_listencode_list(s, rval, obj, indent_level);
+            Py_LeaveRecursiveCall();
+        }
+        else if (PyDict_Check(obj)) {
+            if (Py_EnterRecursiveCall(" while encoding a JSON object"))
+                return rv;
+            rv = encoder_listencode_dict(s, rval, obj, indent_level);
+            Py_LeaveRecursiveCall();
+        }
+        else if (s->use_decimal && PyObject_TypeCheck(obj, (PyTypeObject *)s->Decimal)) {
+            PyObject *encoded = PyObject_Str(obj);
+            if (encoded != NULL)
+                rv = _steal_accumulate(rval, encoded);
+        }
+        else if (is_raw_json(obj))
+        {
+            PyObject *encoded = PyObject_GetAttrString(obj, "encoded_json");
+            if (encoded != NULL)
+                rv = _steal_accumulate(rval, encoded);
+        }
+        else {
+            PyObject *ident = NULL;
+            PyObject *newobj;
+            if (s->iterable_as_array) {
+                newobj = PyObject_GetIter(obj);
+                if (newobj == NULL)
+                    PyErr_Clear();
+                else {
+                    rv = encoder_listencode_list(s, rval, newobj, indent_level);
+                    Py_DECREF(newobj);
+                    break;
+                }
+            }
+            if (s->markers != Py_None) {
+                int has_key;
+                ident = PyLong_FromVoidPtr(obj);
+                if (ident == NULL)
+                    break;
+                has_key = PyDict_Contains(s->markers, ident);
+                if (has_key) {
+                    if (has_key != -1)
+                        PyErr_SetString(PyExc_ValueError, "Circular reference detected");
+                    Py_DECREF(ident);
+                    break;
+                }
+                if (PyDict_SetItem(s->markers, ident, obj)) {
+                    Py_DECREF(ident);
+                    break;
+                }
+            }
+            if (Py_EnterRecursiveCall(" while encoding a JSON object"))
+                return rv;
+            newobj = PyObject_CallFunctionObjArgs(s->defaultfn, obj, NULL);
+            if (newobj == NULL) {
+                Py_XDECREF(ident);
+                Py_LeaveRecursiveCall();
+                break;
+            }
+            rv = encoder_listencode_obj(s, rval, newobj, indent_level);
+            Py_LeaveRecursiveCall();
+            Py_DECREF(newobj);
+            if (rv) {
+                Py_XDECREF(ident);
+                rv = -1;
+            }
+            else if (ident != NULL) {
+                if (PyDict_DelItem(s->markers, ident)) {
+                    Py_XDECREF(ident);
+                    rv = -1;
+                }
+                Py_XDECREF(ident);
+            }
+        }
+    } while (0);
+    return rv;
+}
+
+static int
+encoder_listencode_dict(PyEncoderObject *s, JSON_Accu *rval, PyObject *dct, Py_ssize_t indent_level)
+{
+    /* Encode Python dict dct a JSON term */
+    static PyObject *open_dict = NULL;
+    static PyObject *close_dict = NULL;
+    static PyObject *empty_dict = NULL;
+    PyObject *kstr = NULL;
+    PyObject *ident = NULL;
+    PyObject *iter = NULL;
+    PyObject *item = NULL;
+    PyObject *items = NULL;
+    PyObject *encoded = NULL;
+    Py_ssize_t idx;
+
+    if (open_dict == NULL || close_dict == NULL || empty_dict == NULL) {
+        open_dict = JSON_InternFromString("{");
+        close_dict = JSON_InternFromString("}");
+        empty_dict = JSON_InternFromString("{}");
+        if (open_dict == NULL || close_dict == NULL || empty_dict == NULL)
+            return -1;
+    }
+    if (PyDict_Size(dct) == 0)
+        return JSON_Accu_Accumulate(rval, empty_dict);
+
+    if (s->markers != Py_None) {
+        int has_key;
+        ident = PyLong_FromVoidPtr(dct);
+        if (ident == NULL)
+            goto bail;
+        has_key = PyDict_Contains(s->markers, ident);
+        if (has_key) {
+            if (has_key != -1)
+                PyErr_SetString(PyExc_ValueError, "Circular reference detected");
+            goto bail;
+        }
+        if (PyDict_SetItem(s->markers, ident, dct)) {
+            goto bail;
+        }
+    }
+
+    if (JSON_Accu_Accumulate(rval, open_dict))
+        goto bail;
+
+    if (s->indent != Py_None) {
+        /* TODO: DOES NOT RUN */
+        indent_level += 1;
+        /*
+            newline_indent = '\n' + (_indent * _current_indent_level)
+            separator = _item_separator + newline_indent
+            buf += newline_indent
+        */
+    }
+
+    iter = encoder_dict_iteritems(s, dct);
+    if (iter == NULL)
+        goto bail;
+
+    idx = 0;
+    while ((item = PyIter_Next(iter))) {
+        PyObject *encoded, *key, *value;
+        if (!PyTuple_Check(item) || Py_SIZE(item) != 2) {
+            PyErr_SetString(PyExc_ValueError, "items must return 2-tuples");
+            goto bail;
+        }
+        key = PyTuple_GET_ITEM(item, 0);
+        if (key == NULL)
+            goto bail;
+        value = PyTuple_GET_ITEM(item, 1);
+        if (value == NULL)
+            goto bail;
+
+        encoded = PyDict_GetItem(s->key_memo, key);
+        if (encoded != NULL) {
+            Py_INCREF(encoded);
+        } else {
+            kstr = encoder_stringify_key(s, key);
+            if (kstr == NULL)
+                goto bail;
+            else if (kstr == Py_None) {
+                /* skipkeys */
+                Py_DECREF(item);
+                Py_DECREF(kstr);
+                continue;
+            }
+        }
+        if (idx) {
+            if (JSON_Accu_Accumulate(rval, s->item_separator))
+                goto bail;
+        }
+        if (encoded == NULL) {
+            encoded = encoder_encode_string(s, kstr);
+            Py_CLEAR(kstr);
+            if (encoded == NULL)
+                goto bail;
+            if (PyDict_SetItem(s->key_memo, key, encoded))
+                goto bail;
+        }
+        if (JSON_Accu_Accumulate(rval, encoded)) {
+            goto bail;
+        }
+        Py_CLEAR(encoded);
+        if (JSON_Accu_Accumulate(rval, s->key_separator))
+            goto bail;
+        if (encoder_listencode_obj(s, rval, value, indent_level))
+            goto bail;
+        Py_CLEAR(item);
+        idx += 1;
+    }
+    Py_CLEAR(iter);
+    if (PyErr_Occurred())
+        goto bail;
+    if (ident != NULL) {
+        if (PyDict_DelItem(s->markers, ident))
+            goto bail;
+        Py_CLEAR(ident);
+    }
+    if (s->indent != Py_None) {
+        /* TODO: DOES NOT RUN */
+        indent_level -= 1;
+        /*
+            yield '\n' + (_indent * _current_indent_level)
+        */
+    }
+    if (JSON_Accu_Accumulate(rval, close_dict))
+        goto bail;
+    return 0;
+
+bail:
+    Py_XDECREF(encoded);
+    Py_XDECREF(items);
+    Py_XDECREF(item);
+    Py_XDECREF(iter);
+    Py_XDECREF(kstr);
+    Py_XDECREF(ident);
+    return -1;
+}
+
+
+static int
+encoder_listencode_list(PyEncoderObject *s, JSON_Accu *rval, PyObject *seq, Py_ssize_t indent_level)
+{
+    /* Encode Python list seq to a JSON term */
+    static PyObject *open_array = NULL;
+    static PyObject *close_array = NULL;
+    static PyObject *empty_array = NULL;
+    PyObject *ident = NULL;
+    PyObject *iter = NULL;
+    PyObject *obj = NULL;
+    int is_true;
+    int i = 0;
+
+    if (open_array == NULL || close_array == NULL || empty_array == NULL) {
+        open_array = JSON_InternFromString("[");
+        close_array = JSON_InternFromString("]");
+        empty_array = JSON_InternFromString("[]");
+        if (open_array == NULL || close_array == NULL || empty_array == NULL)
+            return -1;
+    }
+    ident = NULL;
+    is_true = PyObject_IsTrue(seq);
+    if (is_true == -1)
+        return -1;
+    else if (is_true == 0)
+        return JSON_Accu_Accumulate(rval, empty_array);
+
+    if (s->markers != Py_None) {
+        int has_key;
+        ident = PyLong_FromVoidPtr(seq);
+        if (ident == NULL)
+            goto bail;
+        has_key = PyDict_Contains(s->markers, ident);
+        if (has_key) {
+            if (has_key != -1)
+                PyErr_SetString(PyExc_ValueError, "Circular reference detected");
+            goto bail;
+        }
+        if (PyDict_SetItem(s->markers, ident, seq)) {
+            goto bail;
+        }
+    }
+
+    iter = PyObject_GetIter(seq);
+    if (iter == NULL)
+        goto bail;
+
+    if (JSON_Accu_Accumulate(rval, open_array))
+        goto bail;
+    if (s->indent != Py_None) {
+        /* TODO: DOES NOT RUN */
+        indent_level += 1;
+        /*
+            newline_indent = '\n' + (_indent * _current_indent_level)
+            separator = _item_separator + newline_indent
+            buf += newline_indent
+        */
+    }
+    while ((obj = PyIter_Next(iter))) {
+        if (i) {
+            if (JSON_Accu_Accumulate(rval, s->item_separator))
+                goto bail;
+        }
+        if (encoder_listencode_obj(s, rval, obj, indent_level))
+            goto bail;
+        i++;
+        Py_CLEAR(obj);
+    }
+    Py_CLEAR(iter);
+    if (PyErr_Occurred())
+        goto bail;
+    if (ident != NULL) {
+        if (PyDict_DelItem(s->markers, ident))
+            goto bail;
+        Py_CLEAR(ident);
+    }
+    if (s->indent != Py_None) {
+        /* TODO: DOES NOT RUN */
+        indent_level -= 1;
+        /*
+            yield '\n' + (_indent * _current_indent_level)
+        */
+    }
+    if (JSON_Accu_Accumulate(rval, close_array))
+        goto bail;
+    return 0;
+
+bail:
+    Py_XDECREF(obj);
+    Py_XDECREF(iter);
+    Py_XDECREF(ident);
+    return -1;
+}
+
+static void
+encoder_dealloc(PyObject *self)
+{
+    /* bpo-31095: UnTrack is needed before calling any callbacks */
+    PyObject_GC_UnTrack(self);
+    encoder_clear(self);
+    Py_TYPE(self)->tp_free(self);
+}
+
+static int
+encoder_traverse(PyObject *self, visitproc visit, void *arg)
+{
+    PyEncoderObject *s;
+    assert(PyEncoder_Check(self));
+    s = (PyEncoderObject *)self;
+    Py_VISIT(s->markers);
+    Py_VISIT(s->defaultfn);
+    Py_VISIT(s->encoder);
+    Py_VISIT(s->encoding);
+    Py_VISIT(s->indent);
+    Py_VISIT(s->key_separator);
+    Py_VISIT(s->item_separator);
+    Py_VISIT(s->key_memo);
+    Py_VISIT(s->sort_keys);
+    Py_VISIT(s->item_sort_kw);
+    Py_VISIT(s->item_sort_key);
+    Py_VISIT(s->max_long_size);
+    Py_VISIT(s->min_long_size);
+    Py_VISIT(s->Decimal);
+    return 0;
+}
+
+static int
+encoder_clear(PyObject *self)
+{
+    /* Deallocate Encoder */
+    PyEncoderObject *s;
+    assert(PyEncoder_Check(self));
+    s = (PyEncoderObject *)self;
+    Py_CLEAR(s->markers);
+    Py_CLEAR(s->defaultfn);
+    Py_CLEAR(s->encoder);
+    Py_CLEAR(s->encoding);
+    Py_CLEAR(s->indent);
+    Py_CLEAR(s->key_separator);
+    Py_CLEAR(s->item_separator);
+    Py_CLEAR(s->key_memo);
+    Py_CLEAR(s->skipkeys_bool);
+    Py_CLEAR(s->sort_keys);
+    Py_CLEAR(s->item_sort_kw);
+    Py_CLEAR(s->item_sort_key);
+    Py_CLEAR(s->max_long_size);
+    Py_CLEAR(s->min_long_size);
+    Py_CLEAR(s->Decimal);
+    return 0;
+}
+
+PyDoc_STRVAR(encoder_doc, "_iterencode(obj, _current_indent_level) -> iterable");
+
+static
+PyTypeObject PyEncoderType = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "simplejson._speedups.Encoder",       /* tp_name */
+    sizeof(PyEncoderObject), /* tp_basicsize */
+    0,                    /* tp_itemsize */
+    encoder_dealloc, /* tp_dealloc */
+    0,                    /* tp_print */
+    0,                    /* tp_getattr */
+    0,                    /* tp_setattr */
+    0,                    /* tp_compare */
+    0,                    /* tp_repr */
+    0,                    /* tp_as_number */
+    0,                    /* tp_as_sequence */
+    0,                    /* tp_as_mapping */
+    0,                    /* tp_hash */
+    encoder_call,         /* tp_call */
+    0,                    /* tp_str */
+    0,                    /* tp_getattro */
+    0,                    /* tp_setattro */
+    0,                    /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,   /* tp_flags */
+    encoder_doc,          /* tp_doc */
+    encoder_traverse,     /* tp_traverse */
+    encoder_clear,        /* tp_clear */
+    0,                    /* tp_richcompare */
+    0,                    /* tp_weaklistoffset */
+    0,                    /* tp_iter */
+    0,                    /* tp_iternext */
+    0,                    /* tp_methods */
+    encoder_members,      /* tp_members */
+    0,                    /* tp_getset */
+    0,                    /* tp_base */
+    0,                    /* tp_dict */
+    0,                    /* tp_descr_get */
+    0,                    /* tp_descr_set */
+    0,                    /* tp_dictoffset */
+    0,                    /* tp_init */
+    0,                    /* tp_alloc */
+    encoder_new,          /* tp_new */
+    0,                    /* tp_free */
+};
+
+static PyMethodDef speedups_methods[] = {
+    {"encode_basestring_ascii",
+        (PyCFunction)py_encode_basestring_ascii,
+        METH_O,
+        pydoc_encode_basestring_ascii},
+    {"scanstring",
+        (PyCFunction)py_scanstring,
+        METH_VARARGS,
+        pydoc_scanstring},
+    {NULL, NULL, 0, NULL}
+};
+
+PyDoc_STRVAR(module_doc,
+"simplejson speedups\n");
+
+#if PY_MAJOR_VERSION >= 3
+static struct PyModuleDef moduledef = {
+    PyModuleDef_HEAD_INIT,
+    "_speedups",        /* m_name */
+    module_doc,         /* m_doc */
+    -1,                 /* m_size */
+    speedups_methods,   /* m_methods */
+    NULL,               /* m_reload */
+    NULL,               /* m_traverse */
+    NULL,               /* m_clear*/
+    NULL,               /* m_free */
+};
+#endif
+
+PyObject *
+import_dependency(char *module_name, char *attr_name)
+{
+    PyObject *rval;
+    PyObject *module = PyImport_ImportModule(module_name);
+    if (module == NULL)
+        return NULL;
+    rval = PyObject_GetAttrString(module, attr_name);
+    Py_DECREF(module);
+    return rval;
+}
+
+static int
+init_constants(void)
+{
+    JSON_NaN = JSON_InternFromString("NaN");
+    if (JSON_NaN == NULL)
+        return 0;
+    JSON_Infinity = JSON_InternFromString("Infinity");
+    if (JSON_Infinity == NULL)
+        return 0;
+    JSON_NegInfinity = JSON_InternFromString("-Infinity");
+    if (JSON_NegInfinity == NULL)
+        return 0;
+#if PY_MAJOR_VERSION >= 3
+    JSON_EmptyUnicode = PyUnicode_New(0, 127);
+#else /* PY_MAJOR_VERSION >= 3 */
+    JSON_EmptyStr = PyString_FromString("");
+    if (JSON_EmptyStr == NULL)
+        return 0;
+    JSON_EmptyUnicode = PyUnicode_FromUnicode(NULL, 0);
+#endif /* PY_MAJOR_VERSION >= 3 */
+    if (JSON_EmptyUnicode == NULL)
+        return 0;
+
+    return 1;
+}
+
+static PyObject *
+moduleinit(void)
+{
+    PyObject *m;
+    if (PyType_Ready(&PyScannerType) < 0)
+        return NULL;
+    if (PyType_Ready(&PyEncoderType) < 0)
+        return NULL;
+    if (!init_constants())
+        return NULL;
+
+#if PY_MAJOR_VERSION >= 3
+    m = PyModule_Create(&moduledef);
+#else
+    m = Py_InitModule3("_speedups", speedups_methods, module_doc);
+#endif
+    Py_INCREF((PyObject*)&PyScannerType);
+    PyModule_AddObject(m, "make_scanner", (PyObject*)&PyScannerType);
+    Py_INCREF((PyObject*)&PyEncoderType);
+    PyModule_AddObject(m, "make_encoder", (PyObject*)&PyEncoderType);
+    RawJSONType = import_dependency("simplejson.raw_json", "RawJSON");
+    if (RawJSONType == NULL)
+        return NULL;
+    JSONDecodeError = import_dependency("simplejson.errors", "JSONDecodeError");
+    if (JSONDecodeError == NULL)
+        return NULL;
+    return m;
+}
+
+#if PY_MAJOR_VERSION >= 3
+PyMODINIT_FUNC
+PyInit__speedups(void)
+{
+    return moduleinit();
+}
+#else
+void
+init_speedups(void)
+{
+    moduleinit();
+}
+#endif

BIN
ambari-common/src/main/python/ambari_simplejson/_speedups.so


+ 0 - 0
ambari-common/src/main/python/ambari_simplejson/_speedups/__init__.py


+ 0 - 0
ambari-common/src/main/python/ambari_simplejson/_speedups/posix/__init__.py


+ 0 - 0
ambari-common/src/main/python/ambari_simplejson/_speedups/posix/usc2/__init__.py


BIN
ambari-common/src/main/python/ambari_simplejson/_speedups/posix/usc2/_speedups.so


+ 0 - 0
ambari-common/src/main/python/ambari_simplejson/_speedups/posix/usc4/__init__.py


BIN
ambari-common/src/main/python/ambari_simplejson/_speedups/posix/usc4/_speedups.so


+ 0 - 0
ambari-common/src/main/python/ambari_simplejson/_speedups/ppc/__init__.py


BIN
ambari-common/src/main/python/ambari_simplejson/_speedups/ppc/_speedups.so


+ 0 - 0
ambari-common/src/main/python/ambari_simplejson/_speedups/win/__init__.py


BIN
ambari-common/src/main/python/ambari_simplejson/_speedups/win/_speedups.pyd


+ 58 - 0
ambari-common/src/main/python/ambari_simplejson/c_extension.py

@@ -0,0 +1,58 @@
+"""
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+"""
+
+"""
+The C extension loader for various platforms.
+
+NOTE: this module should be on top level
+
+Example of usage:
+
+def _import_c_scanstring():
+    from . import c_extension
+    _speedups = c_extension.get()
+
+    if _speedups:
+        try:
+            return _speedups.scanstring     #  import here required functions
+        except AttributeError:
+            pass
+
+    return None
+"""
+
+
+def get():
+  _path = ".".join(__name__.split(".")[:-1])
+  # trying to load extension from available platforms
+  _import_paths = ["_speedups.posix.usc2", "_speedups.posix.usc4", "_speedups.ppc", "_speedups.win"]
+
+  for _import_path in _import_paths:
+    try:
+      return __import__("{}.{}".format(_path, _import_path), fromlist=["_speedups"])._speedups
+    except (ImportError, AttributeError):
+      pass
+
+  return None
+
+
+def is_loaded():
+  """
+  :rtype bool
+  """
+  return get() is not None

+ 34 - 0
ambari-common/src/main/python/ambari_simplejson/compat.py

@@ -0,0 +1,34 @@
+"""Python 3 compatibility shims
+"""
+import sys
+if sys.version_info[0] < 3:
+    PY3 = False
+    def b(s):
+        return s
+    try:
+        from cStringIO import StringIO
+    except ImportError:
+        from StringIO import StringIO
+    BytesIO = StringIO
+    text_type = unicode
+    binary_type = str
+    string_types = (basestring,)
+    integer_types = (int, long)
+    unichr = unichr
+    reload_module = reload
+else:
+    PY3 = True
+    if sys.version_info[:2] >= (3, 4):
+        from importlib import reload as reload_module
+    else:
+        from imp import reload as reload_module
+    def b(s):
+        return bytes(s, 'latin1')
+    from io import StringIO, BytesIO
+    text_type = str
+    binary_type = bytes
+    string_types = (str,)
+    integer_types = (int,)
+    unichr = chr
+
+long_type = integer_types[-1]

+ 169 - 115
ambari-common/src/main/python/ambari_simplejson/decoder.py

@@ -1,52 +1,44 @@
 """Implementation of JSONDecoder
 """
+from __future__ import absolute_import
 import re
 import sys
 import struct
+from .compat import PY3, unichr
+from .scanner import make_scanner, JSONDecodeError
 
-from ambari_simplejson.scanner import make_scanner
-try:
-    from ambari_simplejson._speedups import scanstring as c_scanstring
-except ImportError:
-    c_scanstring = None
 
-__all__ = ['JSONDecoder']
+def _import_c_scanstring():
+    from . import c_extension
+    _speedups = c_extension.get()
 
-FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
+    if _speedups:
+        try:
+            return _speedups.scanstring
+        except AttributeError:
+            pass
 
-def _floatconstants():
-    _BYTES = '7FF80000000000007FF0000000000000'.decode('hex')
-    if sys.byteorder != 'big':
-        _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1]
-    nan, inf = struct.unpack('dd', _BYTES)
-    return nan, inf, -inf
+    return None
 
-NaN, PosInf, NegInf = _floatconstants()
 
+c_scanstring = _import_c_scanstring()
 
-def linecol(doc, pos):
-    lineno = doc.count('\n', 0, pos) + 1
-    if lineno == 1:
-        colno = pos
-    else:
-        colno = pos - doc.rindex('\n', 0, pos)
-    return lineno, colno
+# NOTE (3.1.0): JSONDecodeError may still be imported from this module for
+# compatibility, but it was never in the __all__
+__all__ = ['JSONDecoder']
 
+FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
 
-def errmsg(msg, doc, pos, end=None):
-    # Note that this function is called from _speedups
-    lineno, colno = linecol(doc, pos)
-    if end is None:
-        #fmt = '{0}: line {1} column {2} (char {3})'
-        #return fmt.format(msg, lineno, colno, pos)
-        fmt = '%s: line %d column %d (char %d)'
-        return fmt % (msg, lineno, colno, pos)
-    endlineno, endcolno = linecol(doc, end)
-    #fmt = '{0}: line {1} column {2} - line {3} column {4} (char {5} - {6})'
-    #return fmt.format(msg, lineno, colno, endlineno, endcolno, pos, end)
-    fmt = '%s: line %d column %d - line %d column %d (char %d - %d)'
-    return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end)
+def _floatconstants():
+    if sys.version_info < (2, 6):
+        _BYTES = '7FF80000000000007FF0000000000000'.decode('hex')
+        nan, inf = struct.unpack('>dd', _BYTES)
+    else:
+        nan = float('nan')
+        inf = float('inf')
+    return nan, inf, -inf
 
+NaN, PosInf, NegInf = _floatconstants()
 
 _CONSTANTS = {
     '-Infinity': NegInf,
@@ -62,13 +54,15 @@ BACKSLASH = {
 
 DEFAULT_ENCODING = "utf-8"
 
-def py_scanstring(s, end, encoding=None, strict=True, _b=BACKSLASH, _m=STRINGCHUNK.match):
+def py_scanstring(s, end, encoding=None, strict=True,
+        _b=BACKSLASH, _m=STRINGCHUNK.match, _join=u''.join,
+        _PY3=PY3, _maxunicode=sys.maxunicode):
     """Scan the string s for a JSON string. End is the index of the
     character in s after the quote that started the JSON string.
     Unescapes all valid JSON string escape sequences and raises ValueError
     on attempt to decode an invalid string. If strict is False then literal
     control characters are allowed in the string.
-    
+
     Returns a tuple of the decoded string and the index of the character in s
     after the end quote."""
     if encoding is None:
@@ -79,13 +73,13 @@ def py_scanstring(s, end, encoding=None, strict=True, _b=BACKSLASH, _m=STRINGCHU
     while 1:
         chunk = _m(s, end)
         if chunk is None:
-            raise ValueError(
-                errmsg("Unterminated string starting at", s, begin))
+            raise JSONDecodeError(
+                "Unterminated string starting at", s, begin)
         end = chunk.end()
         content, terminator = chunk.groups()
         # Content is contains zero or more unescaped string characters
         if content:
-            if not isinstance(content, unicode):
+            if not _PY3 and not isinstance(content, unicode):
                 content = unicode(content, encoding)
             _append(content)
         # Terminator is the end of string, a literal control character,
@@ -94,49 +88,57 @@ def py_scanstring(s, end, encoding=None, strict=True, _b=BACKSLASH, _m=STRINGCHU
             break
         elif terminator != '\\':
             if strict:
-                msg = "Invalid control character %r at" % (terminator,)
-                #msg = "Invalid control character {0!r} at".format(terminator)
-                raise ValueError(errmsg(msg, s, end))
+                msg = "Invalid control character %r at"
+                raise JSONDecodeError(msg, s, end)
             else:
                 _append(terminator)
                 continue
         try:
             esc = s[end]
         except IndexError:
-            raise ValueError(
-                errmsg("Unterminated string starting at", s, begin))
+            raise JSONDecodeError(
+                "Unterminated string starting at", s, begin)
         # If not a unicode escape sequence, must be in the lookup table
         if esc != 'u':
             try:
                 char = _b[esc]
             except KeyError:
-                msg = "Invalid \\escape: " + repr(esc)
-                raise ValueError(errmsg(msg, s, end))
+                msg = "Invalid \\X escape sequence %r"
+                raise JSONDecodeError(msg, s, end)
             end += 1
         else:
             # Unicode escape sequence
+            msg = "Invalid \\uXXXX escape sequence"
             esc = s[end + 1:end + 5]
-            next_end = end + 5
-            if len(esc) != 4:
-                msg = "Invalid \\uXXXX escape"
-                raise ValueError(errmsg(msg, s, end))
-            uni = int(esc, 16)
+            escX = esc[1:2]
+            if len(esc) != 4 or escX == 'x' or escX == 'X':
+                raise JSONDecodeError(msg, s, end - 1)
+            try:
+                uni = int(esc, 16)
+            except ValueError:
+                raise JSONDecodeError(msg, s, end - 1)
+            end += 5
             # Check for surrogate pair on UCS-4 systems
-            if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535:
-                msg = "Invalid \\uXXXX\\uXXXX surrogate pair"
-                if not s[end + 5:end + 7] == '\\u':
-                    raise ValueError(errmsg(msg, s, end))
-                esc2 = s[end + 7:end + 11]
-                if len(esc2) != 4:
-                    raise ValueError(errmsg(msg, s, end))
-                uni2 = int(esc2, 16)
-                uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00))
-                next_end += 6
+            # Note that this will join high/low surrogate pairs
+            # but will also pass unpaired surrogates through
+            if (_maxunicode > 65535 and
+                uni & 0xfc00 == 0xd800 and
+                s[end:end + 2] == '\\u'):
+                esc2 = s[end + 2:end + 6]
+                escX = esc2[1:2]
+                if len(esc2) == 4 and not (escX == 'x' or escX == 'X'):
+                    try:
+                        uni2 = int(esc2, 16)
+                    except ValueError:
+                        raise JSONDecodeError(msg, s, end)
+                    if uni2 & 0xfc00 == 0xdc00:
+                        uni = 0x10000 + (((uni - 0xd800) << 10) |
+                                         (uni2 - 0xdc00))
+                        end += 6
             char = unichr(uni)
-            end = next_end
         # Append the unescaped character
         _append(char)
-    return u''.join(chunks), end
+    return _join(chunks), end
 
 
 # Use speedup if available
@@ -145,8 +147,15 @@ scanstring = c_scanstring or py_scanstring
 WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)
 WHITESPACE_STR = ' \t\n\r'
 
-def JSONObject((s, end), encoding, strict, scan_once, object_hook, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
-    pairs = {}
+def JSONObject(state, encoding, strict, scan_once, object_hook,
+        object_pairs_hook, memo=None,
+        _w=WHITESPACE.match, _ws=WHITESPACE_STR):
+    (s, end) = state
+    # Backwards compatibility
+    if memo is None:
+        memo = {}
+    memo_get = memo.setdefault
+    pairs = []
     # Use a slice to prevent IndexError from being raised, the following
     # check will raise a more specific ValueError if the string is empty
     nextchar = s[end:end + 1]
@@ -157,19 +166,28 @@ def JSONObject((s, end), encoding, strict, scan_once, object_hook, _w=WHITESPACE
             nextchar = s[end:end + 1]
         # Trivial empty object
         if nextchar == '}':
+            if object_pairs_hook is not None:
+                result = object_pairs_hook(pairs)
+                return result, end + 1
+            pairs = {}
+            if object_hook is not None:
+                pairs = object_hook(pairs)
             return pairs, end + 1
         elif nextchar != '"':
-            raise ValueError(errmsg("Expecting property name", s, end))
+            raise JSONDecodeError(
+                "Expecting property name enclosed in double quotes",
+                s, end)
     end += 1
     while True:
         key, end = scanstring(s, end, encoding, strict)
+        key = memo_get(key, key)
 
         # To skip some function call overhead we optimize the fast paths where
         # the JSON key separator is ": " or just ":".
         if s[end:end + 1] != ':':
             end = _w(s, end).end()
             if s[end:end + 1] != ':':
-                raise ValueError(errmsg("Expecting : delimiter", s, end))
+                raise JSONDecodeError("Expecting ':' delimiter", s, end)
 
         end += 1
 
@@ -181,11 +199,8 @@ def JSONObject((s, end), encoding, strict, scan_once, object_hook, _w=WHITESPACE
         except IndexError:
             pass
 
-        try:
-            value, end = scan_once(s, end)
-        except StopIteration:
-            raise ValueError(errmsg("Expecting object", s, end))
-        pairs[key] = value
+        value, end = scan_once(s, end)
+        pairs.append((key, value))
 
         try:
             nextchar = s[end]
@@ -199,7 +214,7 @@ def JSONObject((s, end), encoding, strict, scan_once, object_hook, _w=WHITESPACE
         if nextchar == '}':
             break
         elif nextchar != ',':
-            raise ValueError(errmsg("Expecting , delimiter", s, end - 1))
+            raise JSONDecodeError("Expecting ',' delimiter or '}'", s, end - 1)
 
         try:
             nextchar = s[end]
@@ -214,13 +229,20 @@ def JSONObject((s, end), encoding, strict, scan_once, object_hook, _w=WHITESPACE
 
         end += 1
         if nextchar != '"':
-            raise ValueError(errmsg("Expecting property name", s, end - 1))
-
+            raise JSONDecodeError(
+                "Expecting property name enclosed in double quotes",
+                s, end - 1)
+
+    if object_pairs_hook is not None:
+        result = object_pairs_hook(pairs)
+        return result, end
+    pairs = dict(pairs)
     if object_hook is not None:
         pairs = object_hook(pairs)
     return pairs, end
 
-def JSONArray((s, end), scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
+def JSONArray(state, scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
+    (s, end) = state
     values = []
     nextchar = s[end:end + 1]
     if nextchar in _ws:
@@ -229,12 +251,11 @@ def JSONArray((s, end), scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
     # Look-ahead for trivial empty array
     if nextchar == ']':
         return values, end + 1
+    elif nextchar == '':
+        raise JSONDecodeError("Expecting value or ']'", s, end)
     _append = values.append
     while True:
-        try:
-            value, end = scan_once(s, end)
-        except StopIteration:
-            raise ValueError(errmsg("Expecting object", s, end))
+        value, end = scan_once(s, end)
         _append(value)
         nextchar = s[end:end + 1]
         if nextchar in _ws:
@@ -244,7 +265,7 @@ def JSONArray((s, end), scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
         if nextchar == ']':
             break
         elif nextchar != ',':
-            raise ValueError(errmsg("Expecting , delimiter", s, end))
+            raise JSONDecodeError("Expecting ',' delimiter or ']'", s, end - 1)
 
         try:
             if s[end] in _ws:
@@ -268,7 +289,7 @@ class JSONDecoder(object):
     +---------------+-------------------+
     | array         | list              |
     +---------------+-------------------+
-    | string        | unicode           |
+    | string        | str, unicode      |
     +---------------+-------------------+
     | number (int)  | int, long         |
     +---------------+-------------------+
@@ -287,37 +308,56 @@ class JSONDecoder(object):
     """
 
     def __init__(self, encoding=None, object_hook=None, parse_float=None,
-            parse_int=None, parse_constant=None, strict=True):
-        """``encoding`` determines the encoding used to interpret any ``str``
-        objects decoded by this instance (utf-8 by default).  It has no
-        effect when decoding ``unicode`` objects.
+            parse_int=None, parse_constant=None, strict=True,
+            object_pairs_hook=None):
+        """
+        *encoding* determines the encoding used to interpret any
+        :class:`str` objects decoded by this instance (``'utf-8'`` by
+        default).  It has no effect when decoding :class:`unicode` objects.
 
         Note that currently only encodings that are a superset of ASCII work,
-        strings of other encodings should be passed in as ``unicode``.
+        strings of other encodings should be passed in as :class:`unicode`.
 
-        ``object_hook``, if specified, will be called with the result
-        of every JSON object decoded and its return value will be used in
-        place of the given ``dict``.  This can be used to provide custom
+        *object_hook*, if specified, will be called with the result of every
+        JSON object decoded and its return value will be used in place of the
+        given :class:`dict`.  This can be used to provide custom
         deserializations (e.g. to support JSON-RPC class hinting).
 
-        ``parse_float``, if specified, will be called with the string
-        of every JSON float to be decoded. By default this is equivalent to
-        float(num_str). This can be used to use another datatype or parser
-        for JSON floats (e.g. decimal.Decimal).
-
-        ``parse_int``, if specified, will be called with the string
-        of every JSON int to be decoded. By default this is equivalent to
-        int(num_str). This can be used to use another datatype or parser
-        for JSON integers (e.g. float).
-
-        ``parse_constant``, if specified, will be called with one of the
-        following strings: -Infinity, Infinity, NaN.
-        This can be used to raise an exception if invalid JSON numbers
-        are encountered.
+        *object_pairs_hook* is an optional function that will be called with
+        the result of any object literal decode with an ordered list of pairs.
+        The return value of *object_pairs_hook* will be used instead of the
+        :class:`dict`.  This feature can be used to implement custom decoders
+        that rely on the order that the key and value pairs are decoded (for
+        example, :func:`collections.OrderedDict` will remember the order of
+        insertion). If *object_hook* is also defined, the *object_pairs_hook*
+        takes priority.
+
+        *parse_float*, if specified, will be called with the string of every
+        JSON float to be decoded.  By default, this is equivalent to
+        ``float(num_str)``. This can be used to use another datatype or parser
+        for JSON floats (e.g. :class:`decimal.Decimal`).
+
+        *parse_int*, if specified, will be called with the string of every
+        JSON int to be decoded.  By default, this is equivalent to
+        ``int(num_str)``.  This can be used to use another datatype or parser
+        for JSON integers (e.g. :class:`float`).
+
+        *parse_constant*, if specified, will be called with one of the
+        following strings: ``'-Infinity'``, ``'Infinity'``, ``'NaN'``.  This
+        can be used to raise an exception if invalid JSON numbers are
+        encountered.
+
+        *strict* controls the parser's behavior when it encounters an
+        invalid control character in a string. The default setting of
+        ``True`` means that unescaped control characters are parse errors, if
+        ``False`` then control characters will be allowed in strings.
 
         """
+        if encoding is None:
+            encoding = DEFAULT_ENCODING
         self.encoding = encoding
         self.object_hook = object_hook
+        self.object_pairs_hook = object_pairs_hook
         self.parse_float = parse_float or float
         self.parse_int = parse_int or int
         self.parse_constant = parse_constant or _CONSTANTS.__getitem__
@@ -325,30 +365,44 @@ class JSONDecoder(object):
         self.parse_object = JSONObject
         self.parse_array = JSONArray
         self.parse_string = scanstring
+        self.memo = {}
         self.scan_once = make_scanner(self)
 
-    def decode(self, s, _w=WHITESPACE.match):
+    def decode(self, s, _w=WHITESPACE.match, _PY3=PY3):
         """Return the Python representation of ``s`` (a ``str`` or ``unicode``
         instance containing a JSON document)
 
         """
-        obj, end = self.raw_decode(s, idx=_w(s, 0).end())
+        if _PY3 and isinstance(s, bytes):
+            s = str(s, self.encoding)
+        obj, end = self.raw_decode(s)
         end = _w(s, end).end()
         if end != len(s):
-            raise ValueError(errmsg("Extra data", s, end, len(s)))
+            raise JSONDecodeError("Extra data", s, end, len(s))
         return obj
 
-    def raw_decode(self, s, idx=0):
-        """Decode a JSON document from ``s`` (a ``str`` or ``unicode`` beginning
-        with a JSON document) and return a 2-tuple of the Python
+    def raw_decode(self, s, idx=0, _w=WHITESPACE.match, _PY3=PY3):
+        """Decode a JSON document from ``s`` (a ``str`` or ``unicode``
+        beginning with a JSON document) and return a 2-tuple of the Python
         representation and the index in ``s`` where the document ended.
+        Optionally, ``idx`` can be used to specify an offset in ``s`` where
+        the JSON document begins.
 
         This can be used to decode a JSON document from a string that may
         have extraneous data at the end.
 
         """
-        try:
-            obj, end = self.scan_once(s, idx)
-        except StopIteration:
-            raise ValueError("No JSON object could be decoded")
-        return obj, end
+        if idx < 0:
+            # Ensure that raw_decode bails on negative indexes, the regex
+            # would otherwise mask this behavior. #98
+            raise JSONDecodeError('Expecting value', s, idx)
+        if _PY3 and not isinstance(s, str):
+            raise TypeError("Input string must be text, not bytes")
+        # strip UTF-8 bom
+        if len(s) > idx:
+            ord0 = ord(s[idx])
+            if ord0 == 0xfeff:
+                idx += 1
+            elif ord0 == 0xef and s[idx:idx + 3] == '\xef\xbb\xbf':
+                idx += 3
+        return self.scan_once(s, idx=_w(s, idx).end())

+ 406 - 115
ambari-common/src/main/python/ambari_simplejson/encoder.py

@@ -1,17 +1,32 @@
 """Implementation of JSONEncoder
 """
+from __future__ import absolute_import
 import re
+from operator import itemgetter
+# Do not import Decimal directly to avoid reload issues
+import decimal
+from .compat import unichr, binary_type, text_type, string_types, integer_types, PY3
 
-try:
-    from ambari_simplejson._speedups import encode_basestring_ascii as c_encode_basestring_ascii
-except ImportError:
-    c_encode_basestring_ascii = None
-try:
-    from ambari_simplejson._speedups import make_encoder as c_make_encoder
-except ImportError:
-    c_make_encoder = None
 
-ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]')
+def _import_speedups():
+    from . import c_extension
+    _speedups = c_extension.get()
+
+    if _speedups:
+        try:
+          return _speedups.encode_basestring_ascii, _speedups.make_encoder
+        except AttributeError:
+            pass
+
+    return None, None
+
+
+c_encode_basestring_ascii, c_make_encoder = _import_speedups()
+
+from .decoder import PosInf
+from .raw_json import RawJSON
+
+ESCAPE = re.compile(r'[\x00-\x1f\\"]')
 ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')
 HAS_UTF8 = re.compile(r'[\x80-\xff]')
 ESCAPE_DCT = {
@@ -27,25 +42,57 @@ for i in range(0x20):
     #ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i))
     ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))
 
-# Assume this produces an infinity on all machines (probably not guaranteed)
-INFINITY = float('1e66666')
 FLOAT_REPR = repr
 
-def encode_basestring(s):
+def encode_basestring(s, _PY3=PY3, _q=u'"'):
     """Return a JSON representation of a Python string
 
     """
+    if _PY3:
+        if isinstance(s, bytes):
+            s = str(s, 'utf-8')
+        elif type(s) is not str:
+            # convert an str subclass instance to exact str
+            # raise a TypeError otherwise
+            s = str.__str__(s)
+    else:
+        if isinstance(s, str) and HAS_UTF8.search(s) is not None:
+            s = unicode(s, 'utf-8')
+        elif type(s) not in (str, unicode):
+            # convert an str subclass instance to exact str
+            # convert a unicode subclass instance to exact unicode
+            # raise a TypeError otherwise
+            if isinstance(s, str):
+                s = str.__str__(s)
+            else:
+                s = unicode.__getnewargs__(s)[0]
     def replace(match):
         return ESCAPE_DCT[match.group(0)]
-    return '"' + ESCAPE.sub(replace, s) + '"'
+    return _q + ESCAPE.sub(replace, s) + _q
 
 
-def py_encode_basestring_ascii(s):
+def py_encode_basestring_ascii(s, _PY3=PY3):
     """Return an ASCII-only JSON representation of a Python string
 
     """
-    if isinstance(s, str) and HAS_UTF8.search(s) is not None:
-        s = s.decode('utf-8')
+    if _PY3:
+        if isinstance(s, bytes):
+            s = str(s, 'utf-8')
+        elif type(s) is not str:
+            # convert an str subclass instance to exact str
+            # raise a TypeError otherwise
+            s = str.__str__(s)
+    else:
+        if isinstance(s, str) and HAS_UTF8.search(s) is not None:
+            s = unicode(s, 'utf-8')
+        elif type(s) not in (str, unicode):
+            # convert an str subclass instance to exact str
+            # convert a unicode subclass instance to exact unicode
+            # raise a TypeError otherwise
+            if isinstance(s, str):
+                s = str.__str__(s)
+            else:
+                s = unicode.__getnewargs__(s)[0]
     def replace(match):
         s = match.group(0)
         try:
@@ -65,7 +112,8 @@ def py_encode_basestring_ascii(s):
     return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"'
 
 
-encode_basestring_ascii = c_encode_basestring_ascii or py_encode_basestring_ascii
+encode_basestring_ascii = (
+    c_encode_basestring_ascii or py_encode_basestring_ascii)
 
 class JSONEncoder(object):
     """Extensible JSON <http://json.org> encoder for Python data structures.
@@ -75,7 +123,7 @@ class JSONEncoder(object):
     +-------------------+---------------+
     | Python            | JSON          |
     +===================+===============+
-    | dict              | object        |
+    | dict, namedtuple  | object        |
     +-------------------+---------------+
     | list, tuple       | array         |
     +-------------------+---------------+
@@ -98,9 +146,14 @@ class JSONEncoder(object):
     """
     item_separator = ', '
     key_separator = ': '
+
     def __init__(self, skipkeys=False, ensure_ascii=True,
-            check_circular=True, allow_nan=True, sort_keys=False,
-            indent=None, separators=None, encoding='utf-8', default=None):
+                 check_circular=True, allow_nan=True, sort_keys=False,
+                 indent=None, separators=None, encoding='utf-8', default=None,
+                 use_decimal=True, namedtuple_as_object=True,
+                 tuple_as_array=True, bigint_as_string=False,
+                 item_sort_key=None, for_json=False, ignore_nan=False,
+                 int_as_string_bitcount=None, iterable_as_array=False):
         """Constructor for JSONEncoder, with sensible defaults.
 
         If skipkeys is false, then it is a TypeError to attempt
@@ -125,14 +178,17 @@ class JSONEncoder(object):
         sorted by key; this is useful for regression tests to ensure
         that JSON serializations can be compared on a day-to-day basis.
 
-        If indent is a non-negative integer, then JSON array
-        elements and object members will be pretty-printed with that
-        indent level.  An indent level of 0 will only insert newlines.
-        None is the most compact representation.
+        If indent is a string, then JSON array elements and object members
+        will be pretty-printed with a newline followed by that string repeated
+        for each level of nesting. ``None`` (the default) selects the most compact
+        representation without any newlines. For backwards compatibility with
+        versions of simplejson earlier than 2.1.0, an integer is also accepted
+        and is converted to a string with that many spaces.
 
-        If specified, separators should be a (item_separator, key_separator)
-        tuple.  The default is (', ', ': ').  To get the most compact JSON
-        representation you should specify (',', ':') to eliminate whitespace.
+        If specified, separators should be an (item_separator, key_separator)
+        tuple.  The default is (', ', ': ') if *indent* is ``None`` and
+        (',', ': ') otherwise.  To get the most compact JSON representation,
+        you should specify (',', ':') to eliminate whitespace.
 
         If specified, default is a function that gets called for objects
         that can't otherwise be serialized.  It should return a JSON encodable
@@ -142,6 +198,41 @@ class JSONEncoder(object):
         transformed into unicode using that encoding prior to JSON-encoding.
         The default is UTF-8.
 
+        If use_decimal is true (default: ``True``), ``decimal.Decimal`` will
+        be supported directly by the encoder. For the inverse, decode JSON
+        with ``parse_float=decimal.Decimal``.
+
+        If namedtuple_as_object is true (the default), objects with
+        ``_asdict()`` methods will be encoded as JSON objects.
+
+        If tuple_as_array is true (the default), tuple (and subclasses) will
+        be encoded as JSON arrays.
+
+        If *iterable_as_array* is true (default: ``False``),
+        any object not in the above table that implements ``__iter__()``
+        will be encoded as a JSON array.
+
+        If bigint_as_string is true (not the default), ints 2**53 and higher
+        or lower than -2**53 will be encoded as strings. This is to avoid the
+        rounding that happens in Javascript otherwise.
+
+        If int_as_string_bitcount is a positive number (n), then int of size
+        greater than or equal to 2**n or lower than or equal to -2**n will be
+        encoded as strings.
+
+        If specified, item_sort_key is a callable used to sort the items in
+        each dictionary. This is useful if you want to sort items other than
+        in alphabetical order by key.
+
+        If for_json is true (not the default), objects with a ``for_json()``
+        method will use the return value of that method for encoding as JSON
+        instead of the object.
+
+        If *ignore_nan* is true (default: ``False``), then out of range
+        :class:`float` values (``nan``, ``inf``, ``-inf``) will be serialized
+        as ``null`` in compliance with the ECMA-262 specification. If true,
+        this will override *allow_nan*.
+
         """
 
         self.skipkeys = skipkeys
@@ -149,9 +240,22 @@ class JSONEncoder(object):
         self.check_circular = check_circular
         self.allow_nan = allow_nan
         self.sort_keys = sort_keys
+        self.use_decimal = use_decimal
+        self.namedtuple_as_object = namedtuple_as_object
+        self.tuple_as_array = tuple_as_array
+        self.iterable_as_array = iterable_as_array
+        self.bigint_as_string = bigint_as_string
+        self.item_sort_key = item_sort_key
+        self.for_json = for_json
+        self.ignore_nan = ignore_nan
+        self.int_as_string_bitcount = int_as_string_bitcount
+        if indent is not None and not isinstance(indent, string_types):
+            indent = indent * ' '
         self.indent = indent
         if separators is not None:
             self.item_separator, self.key_separator = separators
+        elif indent is not None:
+            self.item_separator = ','
         if default is not None:
             self.default = default
         self.encoding = encoding
@@ -174,22 +278,23 @@ class JSONEncoder(object):
                 return JSONEncoder.default(self, o)
 
         """
-        raise TypeError(repr(o) + " is not JSON serializable")
+        raise TypeError('Object of type %s is not JSON serializable' %
+                        o.__class__.__name__)
 
     def encode(self, o):
         """Return a JSON string representation of a Python data structure.
 
+        >>> from simplejson import JSONEncoder
         >>> JSONEncoder().encode({"foo": ["bar", "baz"]})
         '{"foo": ["bar", "baz"]}'
 
         """
         # This is for extremely simple cases and benchmarks.
-        if isinstance(o, basestring):
-            if isinstance(o, str):
-                _encoding = self.encoding
-                if (_encoding is not None
-                        and not (_encoding == 'utf-8')):
-                    o = o.decode(_encoding)
+        if isinstance(o, binary_type):
+            _encoding = self.encoding
+            if (_encoding is not None and not (_encoding == 'utf-8')):
+                o = text_type(o, _encoding)
+        if isinstance(o, string_types):
             if self.ensure_ascii:
                 return encode_basestring_ascii(o)
             else:
@@ -200,7 +305,10 @@ class JSONEncoder(object):
         chunks = self.iterencode(o, _one_shot=True)
         if not isinstance(chunks, (list, tuple)):
             chunks = list(chunks)
-        return ''.join(chunks)
+        if self.ensure_ascii:
+            return ''.join(chunks)
+        else:
+            return u''.join(chunks)
 
     def iterencode(self, o, _one_shot=False):
         """Encode the given object and yield each string
@@ -220,15 +328,17 @@ class JSONEncoder(object):
             _encoder = encode_basestring_ascii
         else:
             _encoder = encode_basestring
-        if self.encoding != 'utf-8':
+        if self.encoding != 'utf-8' and self.encoding is not None:
             def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding):
-                if isinstance(o, str):
-                    o = o.decode(_encoding)
+                if isinstance(o, binary_type):
+                    o = text_type(o, _encoding)
                 return _orig_encoder(o)
 
-        def floatstr(o, allow_nan=self.allow_nan, _repr=FLOAT_REPR, _inf=INFINITY, _neginf=-INFINITY):
-            # Check for specials.  Note that this type of test is processor- and/or
-            # platform-specific, so do tests which don't depend on the internals.
+        def floatstr(o, allow_nan=self.allow_nan, ignore_nan=self.ignore_nan,
+                _repr=FLOAT_REPR, _inf=PosInf, _neginf=-PosInf):
+            # Check for specials. Note that this type of test is processor
+            # and/or platform-specific, so do tests which don't depend on
+            # the internals.
 
             if o != o:
                 text = 'NaN'
@@ -237,44 +347,135 @@ class JSONEncoder(object):
             elif o == _neginf:
                 text = '-Infinity'
             else:
+                if type(o) != float:
+                    # See #118, do not trust custom str/repr
+                    o = float(o)
                 return _repr(o)
 
-            if not allow_nan:
+            if ignore_nan:
+                text = 'null'
+            elif not allow_nan:
                 raise ValueError(
                     "Out of range float values are not JSON compliant: " +
                     repr(o))
 
             return text
 
-
-        if _one_shot and c_make_encoder is not None and not self.indent and not self.sort_keys:
+        key_memo = {}
+        int_as_string_bitcount = (
+            53 if self.bigint_as_string else self.int_as_string_bitcount)
+        if (_one_shot and c_make_encoder is not None
+                and self.indent is None):
             _iterencode = c_make_encoder(
                 markers, self.default, _encoder, self.indent,
                 self.key_separator, self.item_separator, self.sort_keys,
-                self.skipkeys, self.allow_nan)
+                self.skipkeys, self.allow_nan, key_memo, self.use_decimal,
+                self.namedtuple_as_object, self.tuple_as_array,
+                int_as_string_bitcount,
+                self.item_sort_key, self.encoding, self.for_json,
+                self.ignore_nan, decimal.Decimal, self.iterable_as_array)
         else:
             _iterencode = _make_iterencode(
                 markers, self.default, _encoder, self.indent, floatstr,
                 self.key_separator, self.item_separator, self.sort_keys,
-                self.skipkeys, _one_shot)
-        return _iterencode(o, 0)
+                self.skipkeys, _one_shot, self.use_decimal,
+                self.namedtuple_as_object, self.tuple_as_array,
+                int_as_string_bitcount,
+                self.item_sort_key, self.encoding, self.for_json,
+                self.iterable_as_array, Decimal=decimal.Decimal)
+        try:
+            return _iterencode(o, 0)
+        finally:
+            key_memo.clear()
+
+
+class JSONEncoderForHTML(JSONEncoder):
+    """An encoder that produces JSON safe to embed in HTML.
+
+    To embed JSON content in, say, a script tag on a web page, the
+    characters &, < and > should be escaped. They cannot be escaped
+    with the usual entities (e.g. &amp;) because they are not expanded
+    within <script> tags.
+
+    This class also escapes the line separator and paragraph separator
+    characters U+2028 and U+2029, irrespective of the ensure_ascii setting,
+    as these characters are not valid in JavaScript strings (see
+    http://timelessrepo.com/json-isnt-a-javascript-subset).
+    """
 
-def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, _key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
+    def encode(self, o):
+        # Override JSONEncoder.encode because it has hacks for
+        # performance that make things more complicated.
+        chunks = self.iterencode(o, True)
+        if self.ensure_ascii:
+            return ''.join(chunks)
+        else:
+            return u''.join(chunks)
+
+    def iterencode(self, o, _one_shot=False):
+        chunks = super(JSONEncoderForHTML, self).iterencode(o, _one_shot)
+        for chunk in chunks:
+            chunk = chunk.replace('&', '\\u0026')
+            chunk = chunk.replace('<', '\\u003c')
+            chunk = chunk.replace('>', '\\u003e')
+
+            if not self.ensure_ascii:
+                chunk = chunk.replace(u'\u2028', '\\u2028')
+                chunk = chunk.replace(u'\u2029', '\\u2029')
+
+            yield chunk
+
+
+def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
+        _key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
+        _use_decimal, _namedtuple_as_object, _tuple_as_array,
+        _int_as_string_bitcount, _item_sort_key,
+        _encoding,_for_json,
+        _iterable_as_array,
         ## HACK: hand-optimized bytecode; turn globals into locals
-        False=False,
-        True=True,
+        _PY3=PY3,
         ValueError=ValueError,
-        basestring=basestring,
+        string_types=string_types,
+        Decimal=None,
         dict=dict,
         float=float,
         id=id,
-        int=int,
+        integer_types=integer_types,
         isinstance=isinstance,
         list=list,
-        long=long,
         str=str,
         tuple=tuple,
+        iter=iter,
     ):
+    if _use_decimal and Decimal is None:
+        Decimal = decimal.Decimal
+    if _item_sort_key and not callable(_item_sort_key):
+        raise TypeError("item_sort_key must be None or callable")
+    elif _sort_keys and not _item_sort_key:
+        _item_sort_key = itemgetter(0)
+
+    if (_int_as_string_bitcount is not None and
+        (_int_as_string_bitcount <= 0 or
+         not isinstance(_int_as_string_bitcount, integer_types))):
+        raise TypeError("int_as_string_bitcount must be a positive integer")
+
+    def _encode_int(value):
+        skip_quoting = (
+            _int_as_string_bitcount is None
+            or
+            _int_as_string_bitcount < 1
+        )
+        if type(value) not in integer_types:
+            # See #118, do not trust custom str/repr
+            value = int(value)
+        if (
+            skip_quoting or
+            (-1 << _int_as_string_bitcount)
+            < value <
+            (1 << _int_as_string_bitcount)
+        ):
+            return str(value)
+        return '"' + str(value) + '"'
 
     def _iterencode_list(lst, _current_indent_level):
         if not lst:
@@ -288,7 +489,7 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, _key_separ
         buf = '['
         if _indent is not None:
             _current_indent_level += 1
-            newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
+            newline_indent = '\n' + (_indent * _current_indent_level)
             separator = _item_separator + newline_indent
             buf += newline_indent
         else:
@@ -300,35 +501,82 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, _key_separ
                 first = False
             else:
                 buf = separator
-            if isinstance(value, basestring):
+            if isinstance(value, string_types):
                 yield buf + _encoder(value)
+            elif _PY3 and isinstance(value, bytes) and _encoding is not None:
+                yield buf + _encoder(value)
+            elif isinstance(value, RawJSON):
+                yield buf + value.encoded_json
             elif value is None:
                 yield buf + 'null'
             elif value is True:
                 yield buf + 'true'
             elif value is False:
                 yield buf + 'false'
-            elif isinstance(value, (int, long)):
-                yield buf + str(value)
+            elif isinstance(value, integer_types):
+                yield buf + _encode_int(value)
             elif isinstance(value, float):
                 yield buf + _floatstr(value)
+            elif _use_decimal and isinstance(value, Decimal):
+                yield buf + str(value)
             else:
                 yield buf
-                if isinstance(value, (list, tuple)):
+                for_json = _for_json and getattr(value, 'for_json', None)
+                if for_json and callable(for_json):
+                    chunks = _iterencode(for_json(), _current_indent_level)
+                elif isinstance(value, list):
                     chunks = _iterencode_list(value, _current_indent_level)
-                elif isinstance(value, dict):
-                    chunks = _iterencode_dict(value, _current_indent_level)
                 else:
-                    chunks = _iterencode(value, _current_indent_level)
+                    _asdict = _namedtuple_as_object and getattr(value, '_asdict', None)
+                    if _asdict and callable(_asdict):
+                        chunks = _iterencode_dict(_asdict(),
+                                                  _current_indent_level)
+                    elif _tuple_as_array and isinstance(value, tuple):
+                        chunks = _iterencode_list(value, _current_indent_level)
+                    elif isinstance(value, dict):
+                        chunks = _iterencode_dict(value, _current_indent_level)
+                    else:
+                        chunks = _iterencode(value, _current_indent_level)
                 for chunk in chunks:
                     yield chunk
-        if newline_indent is not None:
-            _current_indent_level -= 1
-            yield '\n' + (' ' * (_indent * _current_indent_level))
-        yield ']'
+        if first:
+            # iterable_as_array misses the fast path at the top
+            yield '[]'
+        else:
+            if newline_indent is not None:
+                _current_indent_level -= 1
+                yield '\n' + (_indent * _current_indent_level)
+            yield ']'
         if markers is not None:
             del markers[markerid]
 
+    def _stringify_key(key):
+        if isinstance(key, string_types): # pragma: no cover
+            pass
+        elif _PY3 and isinstance(key, bytes) and _encoding is not None:
+            key = str(key, _encoding)
+        elif isinstance(key, float):
+            key = _floatstr(key)
+        elif key is True:
+            key = 'true'
+        elif key is False:
+            key = 'false'
+        elif key is None:
+            key = 'null'
+        elif isinstance(key, integer_types):
+            if type(key) not in integer_types:
+                # See #118, do not trust custom str/repr
+                key = int(key)
+            key = str(key)
+        elif _use_decimal and isinstance(key, Decimal):
+            key = str(key)
+        elif _skipkeys:
+            key = None
+        else:
+            raise TypeError('keys must be str, int, float, bool or None, '
+                            'not %s' % key.__class__.__name__)
+        return key
+
     def _iterencode_dict(dct, _current_indent_level):
         if not dct:
             yield '{}'
@@ -341,100 +589,143 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, _key_separ
         yield '{'
         if _indent is not None:
             _current_indent_level += 1
-            newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
+            newline_indent = '\n' + (_indent * _current_indent_level)
             item_separator = _item_separator + newline_indent
             yield newline_indent
         else:
             newline_indent = None
             item_separator = _item_separator
         first = True
-        if _sort_keys:
-            items = dct.items()
-            items.sort(key=lambda kv: kv[0])
+        if _PY3:
+            iteritems = dct.items()
         else:
-            items = dct.iteritems()
+            iteritems = dct.iteritems()
+        if _item_sort_key:
+            items = []
+            for k, v in dct.items():
+                if not isinstance(k, string_types):
+                    k = _stringify_key(k)
+                    if k is None:
+                        continue
+                items.append((k, v))
+            items.sort(key=_item_sort_key)
+        else:
+            items = iteritems
         for key, value in items:
-            if isinstance(key, basestring):
-                pass
-            # JavaScript is weakly typed for these, so it makes sense to
-            # also allow them.  Many encoders seem to do something like this.
-            elif isinstance(key, float):
-                key = _floatstr(key)
-            elif key is True:
-                key = 'true'
-            elif key is False:
-                key = 'false'
-            elif key is None:
-                key = 'null'
-            elif isinstance(key, (int, long)):
-                key = str(key)
-            elif _skipkeys:
-                continue
-            else:
-                raise TypeError("key " + repr(key) + " is not a string")
+            if not (_item_sort_key or isinstance(key, string_types)):
+                key = _stringify_key(key)
+                if key is None:
+                    # _skipkeys must be True
+                    continue
             if first:
                 first = False
             else:
                 yield item_separator
             yield _encoder(key)
             yield _key_separator
-            if isinstance(value, basestring):
+            if isinstance(value, string_types):
+                yield _encoder(value)
+            elif _PY3 and isinstance(value, bytes) and _encoding is not None:
                 yield _encoder(value)
+            elif isinstance(value, RawJSON):
+                yield value.encoded_json
             elif value is None:
                 yield 'null'
             elif value is True:
                 yield 'true'
             elif value is False:
                 yield 'false'
-            elif isinstance(value, (int, long)):
-                yield str(value)
+            elif isinstance(value, integer_types):
+                yield _encode_int(value)
             elif isinstance(value, float):
                 yield _floatstr(value)
+            elif _use_decimal and isinstance(value, Decimal):
+                yield str(value)
             else:
-                if isinstance(value, (list, tuple)):
+                for_json = _for_json and getattr(value, 'for_json', None)
+                if for_json and callable(for_json):
+                    chunks = _iterencode(for_json(), _current_indent_level)
+                elif isinstance(value, list):
                     chunks = _iterencode_list(value, _current_indent_level)
-                elif isinstance(value, dict):
-                    chunks = _iterencode_dict(value, _current_indent_level)
                 else:
-                    chunks = _iterencode(value, _current_indent_level)
+                    _asdict = _namedtuple_as_object and getattr(value, '_asdict', None)
+                    if _asdict and callable(_asdict):
+                        chunks = _iterencode_dict(_asdict(),
+                                                  _current_indent_level)
+                    elif _tuple_as_array and isinstance(value, tuple):
+                        chunks = _iterencode_list(value, _current_indent_level)
+                    elif isinstance(value, dict):
+                        chunks = _iterencode_dict(value, _current_indent_level)
+                    else:
+                        chunks = _iterencode(value, _current_indent_level)
                 for chunk in chunks:
                     yield chunk
         if newline_indent is not None:
             _current_indent_level -= 1
-            yield '\n' + (' ' * (_indent * _current_indent_level))
+            yield '\n' + (_indent * _current_indent_level)
         yield '}'
         if markers is not None:
             del markers[markerid]
 
     def _iterencode(o, _current_indent_level):
-        if isinstance(o, basestring):
+        if isinstance(o, string_types):
             yield _encoder(o)
+        elif _PY3 and isinstance(o, bytes) and _encoding is not None:
+            yield _encoder(o)
+        elif isinstance(o, RawJSON):
+            yield o.encoded_json
         elif o is None:
             yield 'null'
         elif o is True:
             yield 'true'
         elif o is False:
             yield 'false'
-        elif isinstance(o, (int, long)):
-            yield str(o)
+        elif isinstance(o, integer_types):
+            yield _encode_int(o)
         elif isinstance(o, float):
             yield _floatstr(o)
-        elif isinstance(o, (list, tuple)):
-            for chunk in _iterencode_list(o, _current_indent_level):
-                yield chunk
-        elif isinstance(o, dict):
-            for chunk in _iterencode_dict(o, _current_indent_level):
-                yield chunk
         else:
-            if markers is not None:
-                markerid = id(o)
-                if markerid in markers:
-                    raise ValueError("Circular reference detected")
-                markers[markerid] = o
-            o = _default(o)
-            for chunk in _iterencode(o, _current_indent_level):
-                yield chunk
-            if markers is not None:
-                del markers[markerid]
+            for_json = _for_json and getattr(o, 'for_json', None)
+            if for_json and callable(for_json):
+                for chunk in _iterencode(for_json(), _current_indent_level):
+                    yield chunk
+            elif isinstance(o, list):
+                for chunk in _iterencode_list(o, _current_indent_level):
+                    yield chunk
+            else:
+                _asdict = _namedtuple_as_object and getattr(o, '_asdict', None)
+                if _asdict and callable(_asdict):
+                    for chunk in _iterencode_dict(_asdict(),
+                            _current_indent_level):
+                        yield chunk
+                elif (_tuple_as_array and isinstance(o, tuple)):
+                    for chunk in _iterencode_list(o, _current_indent_level):
+                        yield chunk
+                elif isinstance(o, dict):
+                    for chunk in _iterencode_dict(o, _current_indent_level):
+                        yield chunk
+                elif _use_decimal and isinstance(o, Decimal):
+                    yield str(o)
+                else:
+                    while _iterable_as_array:
+                        # Markers are not checked here because it is valid for
+                        # an iterable to return self.
+                        try:
+                            o = iter(o)
+                        except TypeError:
+                            break
+                        for chunk in _iterencode_list(o, _current_indent_level):
+                            yield chunk
+                        return
+                    if markers is not None:
+                        markerid = id(o)
+                        if markerid in markers:
+                            raise ValueError("Circular reference detected")
+                        markers[markerid] = o
+                    o = _default(o)
+                    for chunk in _iterencode(o, _current_indent_level):
+                        yield chunk
+                    if markers is not None:
+                        del markers[markerid]
 
     return _iterencode

+ 53 - 0
ambari-common/src/main/python/ambari_simplejson/errors.py

@@ -0,0 +1,53 @@
+"""Error classes used by simplejson
+"""
+__all__ = ['JSONDecodeError']
+
+
+def linecol(doc, pos):
+    lineno = doc.count('\n', 0, pos) + 1
+    if lineno == 1:
+        colno = pos + 1
+    else:
+        colno = pos - doc.rindex('\n', 0, pos)
+    return lineno, colno
+
+
+def errmsg(msg, doc, pos, end=None):
+    lineno, colno = linecol(doc, pos)
+    msg = msg.replace('%r', repr(doc[pos:pos + 1]))
+    if end is None:
+        fmt = '%s: line %d column %d (char %d)'
+        return fmt % (msg, lineno, colno, pos)
+    endlineno, endcolno = linecol(doc, end)
+    fmt = '%s: line %d column %d - line %d column %d (char %d - %d)'
+    return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end)
+
+
+class JSONDecodeError(ValueError):
+    """Subclass of ValueError with the following additional properties:
+
+    msg: The unformatted error message
+    doc: The JSON document being parsed
+    pos: The start index of doc where parsing failed
+    end: The end index of doc where parsing failed (may be None)
+    lineno: The line corresponding to pos
+    colno: The column corresponding to pos
+    endlineno: The line corresponding to end (may be None)
+    endcolno: The column corresponding to end (may be None)
+
+    """
+    # Note that this exception is used from _speedups
+    def __init__(self, msg, doc, pos, end=None):
+        ValueError.__init__(self, errmsg(msg, doc, pos, end=end))
+        self.msg = msg
+        self.doc = doc
+        self.pos = pos
+        self.end = end
+        self.lineno, self.colno = linecol(doc, pos)
+        if end is not None:
+            self.endlineno, self.endcolno = linecol(doc, end)
+        else:
+            self.endlineno, self.endcolno = None, None
+
+    def __reduce__(self):
+        return self.__class__, (self.msg, self.doc, self.pos, self.end)

+ 103 - 0
ambari-common/src/main/python/ambari_simplejson/ordered_dict.py

@@ -0,0 +1,103 @@
+"""Drop-in replacement for collections.OrderedDict by Raymond Hettinger
+
+http://code.activestate.com/recipes/576693/
+
+"""
+from UserDict import DictMixin
+
+class OrderedDict(dict, DictMixin):
+
+    def __init__(self, *args, **kwds):
+        if len(args) > 1:
+            raise TypeError('expected at most 1 arguments, got %d' % len(args))
+        try:
+            self.__end
+        except AttributeError:
+            self.clear()
+        self.update(*args, **kwds)
+
+    def clear(self):
+        self.__end = end = []
+        end += [None, end, end]         # sentinel node for doubly linked list
+        self.__map = {}                 # key --> [key, prev, next]
+        dict.clear(self)
+
+    def __setitem__(self, key, value):
+        if key not in self:
+            end = self.__end
+            curr = end[1]
+            curr[2] = end[1] = self.__map[key] = [key, curr, end]
+        dict.__setitem__(self, key, value)
+
+    def __delitem__(self, key):
+        dict.__delitem__(self, key)
+        key, prev, next = self.__map.pop(key)
+        prev[2] = next
+        next[1] = prev
+
+    def __iter__(self):
+        end = self.__end
+        curr = end[2]
+        while curr is not end:
+            yield curr[0]
+            curr = curr[2]
+
+    def __reversed__(self):
+        end = self.__end
+        curr = end[1]
+        while curr is not end:
+            yield curr[0]
+            curr = curr[1]
+
+    def popitem(self, last=True):
+        if not self:
+            raise KeyError('dictionary is empty')
+        key = reversed(self).next() if last else iter(self).next()
+        value = self.pop(key)
+        return key, value
+
+    def __reduce__(self):
+        items = [[k, self[k]] for k in self]
+        tmp = self.__map, self.__end
+        del self.__map, self.__end
+        inst_dict = vars(self).copy()
+        self.__map, self.__end = tmp
+        if inst_dict:
+            return (self.__class__, (items,), inst_dict)
+        return self.__class__, (items,)
+
+    def keys(self):
+        return list(self)
+
+    setdefault = DictMixin.setdefault
+    update = DictMixin.update
+    pop = DictMixin.pop
+    values = DictMixin.values
+    items = DictMixin.items
+    iterkeys = DictMixin.iterkeys
+    itervalues = DictMixin.itervalues
+    iteritems = DictMixin.iteritems
+
+    def __repr__(self):
+        if not self:
+            return '%s()' % (self.__class__.__name__,)
+        return '%s(%r)' % (self.__class__.__name__, self.items())
+
+    def copy(self):
+        return self.__class__(self)
+
+    @classmethod
+    def fromkeys(cls, iterable, value=None):
+        d = cls()
+        for key in iterable:
+            d[key] = value
+        return d
+
+    def __eq__(self, other):
+        if isinstance(other, OrderedDict):
+            return len(self)==len(other) and \
+                   all(p==q for p, q in  zip(self.items(), other.items()))
+        return dict.__eq__(self, other)
+
+    def __ne__(self, other):
+        return not self == other

+ 9 - 0
ambari-common/src/main/python/ambari_simplejson/raw_json.py

@@ -0,0 +1,9 @@
+"""Implementation of RawJSON
+"""
+
+class RawJSON(object):
+    """Wrap an encoded JSON document for direct embedding in the output
+
+    """
+    def __init__(self, encoded_json):
+        self.encoded_json = encoded_json

+ 39 - 9
ambari-common/src/main/python/ambari_simplejson/scanner.py

@@ -1,17 +1,31 @@
 """JSON token scanner
 """
 import re
-try:
-    from ambari_simplejson._speedups import make_scanner as c_make_scanner
-except ImportError:
-    c_make_scanner = None
+from .errors import JSONDecodeError
 
-__all__ = ['make_scanner']
+
+def _import_c_make_scanner():
+    from . import c_extension
+    _speedups = c_extension.get()
+
+    if _speedups:
+        try:
+            return _speedups.make_scanner
+        except AttributeError:
+            pass
+
+    return None
+
+
+c_make_scanner = _import_c_make_scanner()
+
+__all__ = ['make_scanner', 'JSONDecodeError']
 
 NUMBER_RE = re.compile(
     r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
     (re.VERBOSE | re.MULTILINE | re.DOTALL))
 
+
 def py_make_scanner(context):
     parse_object = context.parse_object
     parse_array = context.parse_array
@@ -23,17 +37,21 @@ def py_make_scanner(context):
     parse_int = context.parse_int
     parse_constant = context.parse_constant
     object_hook = context.object_hook
+    object_pairs_hook = context.object_pairs_hook
+    memo = context.memo
 
     def _scan_once(string, idx):
+        errmsg = 'Expecting value'
         try:
             nextchar = string[idx]
         except IndexError:
-            raise StopIteration
+            raise JSONDecodeError(errmsg, string, idx)
 
         if nextchar == '"':
             return parse_string(string, idx + 1, encoding, strict)
         elif nextchar == '{':
-            return parse_object((string, idx + 1), encoding, strict, _scan_once, object_hook)
+            return parse_object((string, idx + 1), encoding, strict,
+                _scan_once, object_hook, object_pairs_hook, memo)
         elif nextchar == '[':
             return parse_array((string, idx + 1), _scan_once)
         elif nextchar == 'n' and string[idx:idx + 4] == 'null':
@@ -58,8 +76,20 @@ def py_make_scanner(context):
         elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
             return parse_constant('-Infinity'), idx + 9
         else:
-            raise StopIteration
+            raise JSONDecodeError(errmsg, string, idx)
+
+    def scan_once(string, idx):
+        if idx < 0:
+            # Ensure the same behavior as the C speedup, otherwise
+            # this would work for *some* negative string indices due
+            # to the behavior of __getitem__ for strings. #98
+            raise JSONDecodeError('Expecting value', string, idx)
+        try:
+            return _scan_once(string, idx)
+        finally:
+            memo.clear()
+
+    return scan_once
 
-    return _scan_once
 
 make_scanner = c_make_scanner or py_make_scanner

+ 6 - 13
ambari-common/src/main/python/resource_management/core/resources/jcepolicyinfo.py

@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 """
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
@@ -20,23 +19,17 @@ Ambari Agent
 
 """
 
-from resource_management.core.logger import Logger
 from resource_management.core import shell
 from ambari_commons import subprocess32
 
+
 class JcePolicyInfo:
   def __init__(self, java_home):
-    self.java_home = java_home
+    # ToDo: change hardcoded path to resolved one
+    self.command_format = java_home + "/bin/java -jar /var/lib/ambari-agent/tools/jcepolicyinfo.jar {}"
     self.jar = "/var/lib/ambari-agent/tools/jcepolicyinfo.jar"
 
   def is_unlimited_key_jce_policy(self):
-    Logger.info("Testing the JVM's JCE policy to see it if supports an unlimited key length.")
-    return shell.call(
-      self._command('-tu'),
-      stdout = subprocess32.PIPE,
-      stderr = subprocess32.PIPE,
-      timeout = 5,
-      quiet = True)[0] == 0
-
-  def _command(self, options):
-    return '{0}/bin/java -jar /var/lib/ambari-agent/tools/jcepolicyinfo.jar {1}'.format(self.java_home, options)
+    ret = shell.call(
+      self.command_format.format('-tu'), stdout=subprocess32.PIPE, stderr=subprocess32.PIPE, timeout=5, quiet=True)[0]
+    return ret == 0